aboutsummaryrefslogtreecommitdiff
path: root/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
blob: 8a19a8b7be20a6e3760ccceb6a6963a8452e3ead (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
using System;
using System.IO;
using System.Xml;
using System.Text;

namespace Google.ProtocolBuffers.Serialization
{
    /// <summary>
    /// Extensions and helpers to abstract the reading/writing of messages by a client-specified content type.
    /// </summary>
    public static class MessageFormatFactory
    {
        /// <summary>
        /// Constructs an ICodedInputStream from the input stream based on the contentType provided
        /// </summary>
        /// <param name="options">Options specific to reading this message and/or content type</param>
        /// <param name="contentType">The mime type of the input stream content</param>
        /// <param name="input">The stream to read the message from</param>
        /// <returns>The ICodedInputStream that can be given to the IBuilder.MergeFrom(...) method</returns>
        public static ICodedInputStream CreateInputStream(MessageFormatOptions options, string contentType, Stream input)
        {
            FormatType inputType = ContentTypeToFormat(contentType, options.DefaultContentType);

            ICodedInputStream codedInput;
            if (inputType == FormatType.ProtoBuffer)
            {
                codedInput = CodedInputStream.CreateInstance(input);
            }
            else if (inputType == FormatType.Json)
            {
                JsonFormatReader reader = JsonFormatReader.CreateInstance(input);
                codedInput = reader.ReadStartMessage();
            }
            else if (inputType == FormatType.Xml)
            {
                XmlFormatReader reader = XmlFormatReader.CreateInstance(input);
                reader.RootElementName = options.XmlReaderRootElementName;
                reader.Options = options.XmlReaderOptions;
                codedInput = reader.ReadStartMessage();
            }
            else
                throw new NotSupportedException();

            return codedInput;
        }

        /// <summary>
        /// Merges the message from the input stream based on the contentType provided
        /// </summary>
        /// <typeparam name="TBuilder">A type derived from IBuilderLite</typeparam>
        /// <param name="builder">An instance of a message builder</param>
        /// <param name="options">Options specific to reading this message and/or content type</param>
        /// <param name="contentType">The mime type of the input stream content</param>
        /// <param name="input">The stream to read the message from</param>
        /// <returns>The same builder instance that was supplied in the builder parameter</returns>
        public static TBuilder MergeFrom<TBuilder>(this TBuilder builder, MessageFormatOptions options, string contentType, Stream input) where TBuilder : IBuilderLite
        {
            ICodedInputStream codedInput = CreateInputStream(options, contentType, input);
            return (TBuilder)builder.WeakMergeFrom(codedInput, options.ExtensionRegistry);
        }
        
        /// <summary>
        /// Writes the message instance to the stream using the content type provided
        /// </summary>
        /// <param name="options">Options specific to writing this message and/or content type</param>
        /// <param name="contentType">The mime type of the content to be written</param>
        /// <param name="output">The stream to write the message to</param>
        /// <remarks> If you do not dispose of ICodedOutputStream some formats may yield incomplete output </remarks>
        public static ICodedOutputStream CreateOutputStream(MessageFormatOptions options, string contentType, Stream output)
        {
            FormatType outputType = ContentTypeToFormat(contentType, options.DefaultContentType);

            ICodedOutputStream codedOutput;
            if (outputType == FormatType.ProtoBuffer)
            {
                codedOutput = CodedOutputStream.CreateInstance(output);
            }
            else if (outputType == FormatType.Json)
            {
                JsonFormatWriter writer = JsonFormatWriter.CreateInstance(output);
                if (options.FormattedOutput)
                {
                    writer.Formatted();
                }
                writer.StartMessage();
                codedOutput = writer;
            }
            else if (outputType == FormatType.Xml)
            {
                XmlFormatWriter writer;
                if (options.FormattedOutput)
                {
                    writer = XmlFormatWriter.CreateInstance(output);
                }
                else
                {
                    XmlWriterSettings settings = new XmlWriterSettings()
                                                     {
                                                         CheckCharacters = false,
                                                         NewLineHandling = NewLineHandling.Entitize,
                                                         OmitXmlDeclaration = true,
                                                         Encoding = Encoding.UTF8,
                                                         Indent = true,
                                                         IndentChars = "  ",
                                                         NewLineChars = Environment.NewLine,
                                                     };
                    writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));
                }
                writer.RootElementName = options.XmlWriterRootElementName;
                writer.Options = options.XmlWriterOptions;
                writer.StartMessage();
                codedOutput = writer;
            }
            else
                throw new NotSupportedException();

            return codedOutput;
        }

        /// <summary>
        /// Writes the message instance to the stream using the content type provided
        /// </summary>
        /// <param name="message">An instance of a message</param>
        /// <param name="options">Options specific to writing this message and/or content type</param>
        /// <param name="contentType">The mime type of the content to be written</param>
        /// <param name="output">The stream to write the message to</param>
        public static void WriteTo(this IMessageLite message, MessageFormatOptions options, string contentType, Stream output)
        {
            using (ICodedOutputStream codedOutput = CreateOutputStream(options, contentType, output))
            {
                message.WriteTo(codedOutput);

                // This is effectivly done by Dispose(); however, if you need to finalize a message
                // without disposing the underlying stream, this is the only way to do it.
                if (codedOutput is AbstractWriter)
                    ((AbstractWriter)codedOutput).EndMessage();

                codedOutput.Flush();
            }
        }


        enum FormatType { ProtoBuffer, Json, Xml };

        private static FormatType ContentTypeToFormat(string contentType, string defaultType)
        {
            switch ((contentType ?? String.Empty).Split(';')[0].Trim().ToLower())
            {
                case "application/json":
                case "application/x-json":
                case "application/x-javascript":
                case "text/javascript":
                case "text/x-javascript":
                case "text/x-json":
                case "text/json":
                    {
                        return FormatType.Json;
                    }

                case "text/xml":
                case "application/xml":
                    {
                        return FormatType.Xml;
                    }

                case "application/binary":
                case "application/x-protobuf":
                case "application/vnd.google.protobuf":
                    {
                        return FormatType.ProtoBuffer;
                    }

                case "":
                case null:
                    if (!String.IsNullOrEmpty(defaultType))
                    {
                        return ContentTypeToFormat(defaultType, null);
                    }
                    break;
            }

            throw new ArgumentOutOfRangeException("contentType");
        }
    }
}