first pass at adding required changes
diff --git a/src/ProtocolBuffers.Serialization/AbstractReader.cs b/src/ProtocolBuffers.Serialization/AbstractReader.cs
index f54c270..538af38 100644
--- a/src/ProtocolBuffers.Serialization/AbstractReader.cs
+++ b/src/ProtocolBuffers.Serialization/AbstractReader.cs
@@ -112,6 +112,16 @@
/// Merges the input stream into the provided IBuilderLite
/// </summary>
protected abstract bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry);
+
+ /// <summary>
+ /// Reads the root-message preamble specific to this formatter
+ /// </summary>
+ public abstract AbstractReader ReadStartMessage();
+
+ /// <summary>
+ /// Reads the root-message close specific to this formatter
+ /// </summary>
+ public abstract void ReadEndMessage();
/// <summary>
/// Merges the input stream into the provided IBuilderLite
diff --git a/src/ProtocolBuffers.Serialization/AbstractWriter.cs b/src/ProtocolBuffers.Serialization/AbstractWriter.cs
index 6592c1d..50dfe67 100644
--- a/src/ProtocolBuffers.Serialization/AbstractWriter.cs
+++ b/src/ProtocolBuffers.Serialization/AbstractWriter.cs
@@ -44,6 +44,26 @@
public abstract void WriteMessage(IMessageLite message);
/// <summary>
+ /// Used to write any nessary root-message preamble. After this call you can call
+ /// IMessageLite.MergeTo(...) and complete the message with a call to EndMessage().
+ /// These three calls are identical to just calling WriteMessage(message);
+ /// </summary>
+ /// <example>
+ /// AbstractWriter writer;
+ /// writer.StartMessage();
+ /// message.WriteTo(writer);
+ /// writer.EndMessage();
+ /// // ... or, but not both ...
+ /// writer.WriteMessage(message);
+ /// </example>
+ public abstract void StartMessage();
+
+ /// <summary>
+ /// Used to complete a root-message previously started with a call to StartMessage()
+ /// </summary>
+ public abstract void EndMessage();
+
+ /// <summary>
/// Writes a Boolean value
/// </summary>
protected abstract void Write(string field, Boolean value);
diff --git a/src/ProtocolBuffers.Serialization/DictionaryReader.cs b/src/ProtocolBuffers.Serialization/DictionaryReader.cs
index cc5c680..f606bc9 100644
--- a/src/ProtocolBuffers.Serialization/DictionaryReader.cs
+++ b/src/ProtocolBuffers.Serialization/DictionaryReader.cs
@@ -23,6 +23,21 @@
}
/// <summary>
+ /// No-op
+ /// </summary>
+ public override AbstractReader ReadStartMessage()
+ {
+ return this;
+ }
+
+ /// <summary>
+ /// No-op
+ /// </summary>
+ public override void ReadEndMessage()
+ {
+ }
+
+ /// <summary>
/// Merges the contents of stream into the provided message builder
/// </summary>
public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
diff --git a/src/ProtocolBuffers.Serialization/DictionaryWriter.cs b/src/ProtocolBuffers.Serialization/DictionaryWriter.cs
index 96175a7..4c3b011 100644
--- a/src/ProtocolBuffers.Serialization/DictionaryWriter.cs
+++ b/src/ProtocolBuffers.Serialization/DictionaryWriter.cs
@@ -53,6 +53,19 @@
message.WriteTo(this);
}
+
+ /// <summary>
+ /// No-op
+ /// </summary>
+ public override void StartMessage()
+ { }
+
+ /// <summary>
+ /// No-op
+ /// </summary>
+ public override void EndMessage()
+ { }
+
/// <summary>
/// Writes a Boolean value
/// </summary>
diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
new file mode 100644
index 0000000..7173d47
--- /dev/null
+++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
@@ -0,0 +1,168 @@
+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="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)
+ {
+ 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();
+
+ message.WriteTo(codedOutput);
+
+ 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");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
new file mode 100644
index 0000000..5b88ac9
--- /dev/null
+++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
@@ -0,0 +1,82 @@
+using System;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+ /// <summary>
+ /// Defines control information for the various formatting used with HTTP services
+ /// </summary>
+ public struct MessageFormatOptions
+ {
+ /// <summary>The mime type for xml content</summary>
+ /// <remarks>Other valid xml mime types include: application/binary, application/x-protobuf</remarks>
+ public const string ContentTypeProtoBuffer = "application/vnd.google.protobuf";
+
+ /// <summary>The mime type for xml content</summary>
+ /// <remarks>Other valid xml mime types include: text/xml</remarks>
+ public const string ContentTypeXml = "application/xml";
+
+ /// <summary>The mime type for json content</summary>
+ /// <remarks>
+ /// Other valid json mime types include: application/json, application/x-json,
+ /// application/x-javascript, text/javascript, text/x-javascript, text/x-json, text/json
+ /// </remarks>
+ public const string ContentTypeJson = "application/json";
+
+ private string _defaultContentType;
+ private string _xmlReaderRootElementName;
+ private string _xmlWriterRootElementName;
+ private ExtensionRegistry _extensionRegistry;
+
+ /// <summary>
+ /// The default content type to use if the input type is null or empty. If this
+ /// value is not supplied an ArgumentOutOfRangeException exception will be raised.
+ /// </summary>
+ public string DefaultContentType
+ {
+ get { return _defaultContentType ?? String.Empty; }
+ set { _defaultContentType = value; }
+ }
+
+ /// <summary>
+ /// The extension registry to use when reading messages
+ /// </summary>
+ public ExtensionRegistry ExtensionRegistry
+ {
+ get { return _extensionRegistry ?? ExtensionRegistry.Empty; }
+ set { _extensionRegistry = value; }
+ }
+
+ /// <summary>
+ /// The name of the xml root element when reading messages
+ /// </summary>
+ public string XmlReaderRootElementName
+ {
+ get { return _xmlReaderRootElementName ?? XmlFormatReader.DefaultRootElementName; }
+ set { _xmlReaderRootElementName = value; }
+ }
+
+ /// <summary>
+ /// Xml reader options
+ /// </summary>
+ public XmlReaderOptions XmlReaderOptions { get; set; }
+
+ /// <summary>
+ /// True to use formatted output including new-lines and default indentation
+ /// </summary>
+ public bool FormattedOutput { get; set; }
+
+ /// <summary>
+ /// The name of the xml root element when writing messages
+ /// </summary>
+ public string XmlWriterRootElementName
+ {
+ get { return _xmlWriterRootElementName ?? XmlFormatWriter.DefaultRootElementName; }
+ set { _xmlWriterRootElementName = value; }
+ }
+
+ /// <summary>
+ /// Xml writer options
+ /// </summary>
+ public XmlWriterOptions XmlWriterOptions { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs b/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs
new file mode 100644
index 0000000..3ca9964
--- /dev/null
+++ b/src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers;
+using System.IO;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+ /// <summary>
+ /// Extensions for the IRpcServerStub
+ /// </summary>
+ public static class ServiceExtensions
+ {
+ /// <summary>
+ /// Used to implement a service endpoint on an HTTP server. This works with services generated with the
+ /// service_generator_type option set to IRPCDISPATCH.
+ /// </summary>
+ /// <param name="stub">The service execution stub</param>
+ /// <param name="methodName">The name of the method being invoked</param>
+ /// <param name="options">optional arguments for the format reader/writer</param>
+ /// <param name="contentType">The mime type for the input stream</param>
+ /// <param name="input">The input stream</param>
+ /// <param name="responseType">The mime type for the output stream</param>
+ /// <param name="output">The output stream</param>
+ public static void HttpCallMethod(this IRpcServerStub stub, string methodName, MessageFormatOptions options,
+ string contentType, Stream input, string responseType, Stream output)
+ {
+ ICodedInputStream codedInput = MessageFormatFactory.CreateInputStream(options, contentType, input);
+ IMessageLite response = stub.CallMethod(methodName, codedInput, options.ExtensionRegistry);
+ response.WriteTo(options, responseType, output);
+ }
+ }
+}
diff --git a/src/ProtocolBuffers.Serialization/JsonFormatReader.cs b/src/ProtocolBuffers.Serialization/JsonFormatReader.cs
index d4505d7..4033678 100644
--- a/src/ProtocolBuffers.Serialization/JsonFormatReader.cs
+++ b/src/ProtocolBuffers.Serialization/JsonFormatReader.cs
@@ -101,17 +101,34 @@
}
/// <summary>
- /// Merges the contents of stream into the provided message builder
+ /// Reads the root-message preamble specific to this formatter
/// </summary>
- public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+ public override AbstractReader ReadStartMessage()
{
_input.Consume('{');
_stopChar.Push('}');
_state = ReaderState.BeginObject;
- builder.WeakMergeFrom(this, registry);
- _input.Consume((char) _stopChar.Pop());
+ return this;
+ }
+
+ /// <summary>
+ /// Reads the root-message close specific to this formatter
+ /// </summary>
+ public override void ReadEndMessage()
+ {
+ _input.Consume((char)_stopChar.Pop());
_state = ReaderState.EndValue;
+ }
+
+ /// <summary>
+ /// Merges the contents of stream into the provided message builder
+ /// </summary>
+ public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
+ {
+ AbstractReader rdr = ReadStartMessage();
+ builder.WeakMergeFrom(rdr, registry);
+ rdr.ReadEndMessage();
return builder;
}
diff --git a/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs b/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
index d54507c..12d180d 100644
--- a/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
+++ b/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
@@ -445,13 +445,31 @@
/// </summary>
public override void WriteMessage(IMessageLite message)
{
+ StartMessage();
+ message.WriteTo(this);
+ EndMessage();
+ }
+
+ /// <summary>
+ /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
+ /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
+ /// a call to EndMessage().
+ /// </summary>
+ public override void StartMessage()
+ {
if (_isArray)
{
Seperator();
}
WriteToOutput("{");
_counter.Add(0);
- message.WriteTo(this);
+ }
+
+ /// <summary>
+ /// Used to complete a root-message previously started with a call to StartMessage()
+ /// </summary>
+ public override void EndMessage()
+ {
_counter.RemoveAt(_counter.Count - 1);
WriteLine("}");
Flush();
diff --git a/src/ProtocolBuffers.Serialization/Properties/AssemblyInfo.cs b/src/ProtocolBuffers.Serialization/Properties/AssemblyInfo.cs
index fdae52b..b6581aa 100644
--- a/src/ProtocolBuffers.Serialization/Properties/AssemblyInfo.cs
+++ b/src/ProtocolBuffers.Serialization/Properties/AssemblyInfo.cs
@@ -65,12 +65,12 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
-// [assembly: AssemblyVersion("2.3.0.277")]
+// [assembly: AssemblyVersion("2.3.0.369")]
-[assembly: AssemblyVersion("2.3.0.277")]
+[assembly: AssemblyVersion("2.3.0.369")]
#if !COMPACT_FRAMEWORK_35
-[assembly: AssemblyFileVersion("2.3.0.277")]
+[assembly: AssemblyFileVersion("2.3.0.369")]
#endif
[assembly: CLSCompliant(true)]
\ No newline at end of file
diff --git a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
index 0c53577..fdbbe50 100644
--- a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
+++ b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
@@ -98,6 +98,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Extensions.cs" />
+ <Compile Include="Http\MessageFormatFactory.cs" />
+ <Compile Include="Http\MessageFormatOptions.cs" />
+ <Compile Include="Http\ServiceExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="AbstractReader.cs" />
<Compile Include="AbstractTextReader.cs" />
diff --git a/src/ProtocolBuffers.Serialization/XmlFormatReader.cs b/src/ProtocolBuffers.Serialization/XmlFormatReader.cs
index cb2cb2e..0d3bca6 100644
--- a/src/ProtocolBuffers.Serialization/XmlFormatReader.cs
+++ b/src/ProtocolBuffers.Serialization/XmlFormatReader.cs
@@ -136,6 +136,41 @@
}
/// <summary>
+ /// Reads the root-message preamble specific to this formatter
+ /// </summary>
+ public override AbstractReader ReadStartMessage()
+ {
+ return ReadStartMessage(_rootElementName);
+ }
+
+ public AbstractReader ReadStartMessage(string element)
+ {
+ string field;
+ Assert(PeekNext(out field) && field == element);
+
+ XmlReader child = _input.ReadSubtree();
+ while (!child.IsStartElement() && child.Read())
+ {
+ continue;
+ }
+ child.Read();
+ return CloneWith(child);
+ }
+
+ /// <summary>
+ /// Reads the root-message close specific to this formatter, MUST be called
+ /// on the reader obtained from ReadStartMessage(string element).
+ /// </summary>
+ public override void ReadEndMessage()
+ {
+ Assert(0 == _input.Depth);
+ if(_input.NodeType == XmlNodeType.EndElement)
+ {
+ _input.Read();
+ }
+ }
+
+ /// <summary>
/// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
/// </summary>
public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
diff --git a/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs b/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs
index fd36c1d..97fc6b2 100644
--- a/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs
+++ b/src/ProtocolBuffers.Serialization/XmlFormatWriter.cs
@@ -112,6 +112,43 @@
}
/// <summary>
+ /// Used to write the root-message preamble, in xml this is open element for RootElementName,
+ /// by default "<root>". After this call you can call IMessageLite.MergeTo(...) and
+ /// complete the message with a call to EndMessage().
+ /// </summary>
+ public override void StartMessage()
+ {
+ StartMessage(_rootElementName);
+ }
+
+ /// <summary>
+ /// Used to write the root-message preamble, in xml this is open element for elementName.
+ /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
+ /// a call to EndMessage().
+ /// </summary>
+ public void StartMessage(string elementName)
+ {
+ if (TestOption(XmlWriterOptions.OutputJsonTypes))
+ {
+ _output.WriteStartElement("root"); // json requires this is the root-element
+ _output.WriteAttributeString("type", "object");
+ }
+ else
+ {
+ _output.WriteStartElement(elementName);
+ }
+ }
+
+ /// <summary>
+ /// Used to complete a root-message previously started with a call to StartMessage()
+ /// </summary>
+ public override void EndMessage()
+ {
+ _output.WriteEndElement();
+ _output.Flush();
+ }
+
+ /// <summary>
/// Writes a message as an element using the name defined in <see cref="RootElementName"/>
/// </summary>
public override void WriteMessage(IMessageLite message)
@@ -124,19 +161,9 @@
/// </summary>
public void WriteMessage(string elementName, IMessageLite message)
{
- if (TestOption(XmlWriterOptions.OutputJsonTypes))
- {
- _output.WriteStartElement("root"); // json requires this is the root-element
- _output.WriteAttributeString("type", "object");
- }
- else
- {
- _output.WriteStartElement(elementName);
- }
-
+ StartMessage(elementName);
message.WriteTo(this);
- _output.WriteEndElement();
- _output.Flush();
+ EndMessage();
}
/// <summary>