Merge pull request #1304 from slluis/mac-proxy-autoconfig
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Dispatcher / BaseMessagesFormatter.cs
1 //
2 // DefaultMessageOperationFormatter.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //      Eyal Alaluf <eyala@mainsoft.com>
7 //
8 // Copyright (C) 2005-2010 Novell, Inc.  http://www.novell.com
9 // Copyright (C) 2008 Mainsoft Co. http://www.mainsoft.com
10 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 using System;
32 using System.Collections.Generic;
33 using System.IO;
34 using System.Linq;
35 using System.Reflection;
36 using System.Runtime.Serialization;
37 using System.ServiceModel;
38 using System.ServiceModel.Channels;
39 using System.ServiceModel.Description;
40 using System.Text;
41 using System.Xml;
42 using System.Xml.Serialization;
43
44 namespace System.ServiceModel.Dispatcher
45 {
46         // This type is introduced for moonlight compatibility.
47         internal class OperationFormatter
48                 : IDispatchMessageFormatter, IClientMessageFormatter
49         {
50                 BaseMessagesFormatter impl;
51                 string operation_name;
52
53                 public OperationFormatter (OperationDescription od, bool isRpc, bool isEncoded)
54                 {
55                         Validate (od, isRpc, isEncoded);
56
57                         impl = BaseMessagesFormatter.Create (od);
58
59                         operation_name = od.Name;
60                 }
61
62                 public string OperationName {
63                         get { return operation_name; }
64                 }
65
66                 internal static bool IsValidReturnValue (MessagePartDescription part)
67                 {
68                         return part != null && part.Type != typeof (void);
69                 }
70
71                 internal static void Validate (OperationDescription od, bool isRpc, bool isEncoded)
72                 {
73                         bool hasParameter = false, hasVoid = false;
74                         foreach (var md in od.Messages) {
75                                 if (md.IsTypedMessage || md.IsUntypedMessage) {
76                                         if (isRpc && !isEncoded)
77                                                 throw new InvalidOperationException ("Message with action {0} is either strongly-typed or untyped, but defined as RPC and encoded.");
78                                         if (hasParameter && !md.IsVoid)
79                                                 throw new InvalidOperationException (String.Format ("Operation '{0}' contains a message with parameters. Strongly-typed or untyped message can be paired only with strongly-typed, untyped or void message.", od.Name));
80                                         if (isRpc && hasVoid)
81                                                 throw new InvalidOperationException (String.Format ("This operation '{0}' is defined as RPC and contains a message with void, which is not allowed.", od.Name));
82                                 } else {
83                                         hasParameter |= !md.IsVoid;
84                                         hasVoid |= md.IsVoid;
85                                 }
86                         }
87                 }
88
89                 public object DeserializeReply (Message message, object [] parameters)
90                 {
91                         return impl.DeserializeReply (message, parameters);
92                 }
93
94                 public Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
95                 {
96                         return impl.SerializeRequest (messageVersion, parameters);
97                 }
98
99                 public void DeserializeRequest (Message message, object [] parameters)
100                 {
101                         impl.DeserializeRequest (message, parameters);
102                 }
103
104                 public Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
105                 {
106                         return impl.SerializeReply (messageVersion, parameters, result);
107                 }
108         }
109
110         internal abstract class BaseMessagesFormatter
111                 : IDispatchMessageFormatter, IClientMessageFormatter
112         {
113                 MessageDescriptionCollection messages;
114                 bool isAsync;
115                 ParameterInfo [] requestMethodParams;
116                 ParameterInfo [] replyMethodParams;
117                 List<Type> operation_known_types = new List<Type> ();
118
119                 public BaseMessagesFormatter (MessageDescriptionCollection messages)
120                 {
121                         this.messages = messages;
122                 }
123
124                 public BaseMessagesFormatter (OperationDescription desc)
125                         : this (desc.Messages)
126                 {
127                         if (desc.SyncMethod != null)
128                         {
129                                 isAsync = false;
130                                 requestMethodParams = replyMethodParams = desc.SyncMethod.GetParameters ();
131                                 return;
132                         }
133                         isAsync = true;
134                         ParameterInfo [] methodParams = desc.BeginMethod.GetParameters ();
135                         requestMethodParams = new ParameterInfo [methodParams.Length - 2];
136                         Array.Copy (methodParams, requestMethodParams, requestMethodParams.Length);
137                         methodParams = desc.EndMethod.GetParameters ();
138                         replyMethodParams = new ParameterInfo [methodParams.Length - 1];
139                         Array.Copy (methodParams, replyMethodParams, replyMethodParams.Length);
140                         operation_known_types.AddRange (desc.KnownTypes);
141                 }
142
143                 // FIXME: this should be refactored and eliminated.
144                 // XmlSerializerFormatAttribute and DataContractFormatAttribute
145                 // should be handled at ContractDescription.GetContract (to fill
146                 // IOperationBehavior for each).
147                 //
148                 // Fixing the issue above should also fix "Formatter is already filled at initial state" issue described in EndpointDispatcher.cs and ContractDescription.cs.
149                 public static BaseMessagesFormatter Create (OperationDescription desc)
150                 {
151                         MethodInfo attrProvider = desc.SyncMethod ?? desc.BeginMethod;
152                         object [] attrs;
153                         attrs = attrProvider.GetCustomAttributes (typeof (XmlSerializerFormatAttribute), false);
154                         if (attrs != null && attrs.Length > 0)
155                                 return new XmlMessagesFormatter (desc, (XmlSerializerFormatAttribute) attrs [0]);
156
157                         attrs = attrProvider.GetCustomAttributes (typeof (DataContractFormatAttribute), false);
158                         DataContractFormatAttribute dataAttr = null;
159                         if (attrs != null && attrs.Length > 0)
160                                 dataAttr = (DataContractFormatAttribute) attrs [0];
161                         return new DataContractMessagesFormatter (desc, dataAttr);
162                 }
163
164                 public IEnumerable<Type> OperationKnownTypes {
165                         get { return operation_known_types; }
166                 }
167
168                 protected abstract Message PartsToMessage (
169                         MessageDescription md, MessageVersion version, string action, object [] parts);
170                 protected abstract object [] MessageToParts (MessageDescription md, Message message);
171                 protected abstract Dictionary<MessageHeaderDescription,object> MessageToHeaderObjects (MessageDescription md, Message message);
172
173                 public Message SerializeRequest (
174                         MessageVersion version, object [] parameters)
175                 {
176                         MessageDescription md = null;
177                         foreach (MessageDescription mdi in messages)
178                                 if (mdi.IsRequest)
179                                         md = mdi;
180
181                         object [] parts = CreatePartsArray (md.Body);
182                         var headers = md.Headers.Count > 0 ? new Dictionary<MessageHeaderDescription,object> () : null;
183                         if (md.MessageType != null)
184                                 MessageObjectToParts (md, parameters [0], headers, parts);
185                         else {
186                                 int index = 0;
187                                 foreach (ParameterInfo pi in requestMethodParams)
188                                         if (!pi.IsOut)
189                                                 parts [index++] = parameters [pi.Position];
190                         }
191                         var msg = PartsToMessage (md, version, md.Action, parts);
192                         if (headers != null)
193                                 foreach (var pair in headers)
194                                         if (pair.Value != null)
195                                                 msg.Headers.Add (CreateHeader (pair.Key, pair.Value));
196                         return msg;
197                 }
198
199                 public Message SerializeReply (
200                         MessageVersion version, object [] parameters, object result)
201                 {
202                         // use_response_converter
203
204                         MessageDescription md = null;
205                         foreach (MessageDescription mdi in messages)
206                                 if (!mdi.IsRequest)
207                                         md = mdi;
208
209                         object [] parts = CreatePartsArray (md.Body);
210                         var headers = md.Headers.Count > 0 ? new Dictionary<MessageHeaderDescription,object> () : null;
211                         if (md.MessageType != null)
212                                 MessageObjectToParts (md, result, headers, parts);
213                         else {
214                                 if (HasReturnValue (md.Body))
215                                         parts [0] = result;
216                                 int index = ParamsOffset (md.Body);
217                                 int paramsIdx = 0;
218                                 foreach (ParameterInfo pi in replyMethodParams)
219                                         if (pi.IsOut || pi.ParameterType.IsByRef)
220                                 parts [index++] = parameters [paramsIdx++];
221                         }
222                         string action = version.Addressing == AddressingVersion.None ? null : md.Action;
223                         var msg = PartsToMessage (md, version, action, parts);
224                         if (headers != null)
225                                 foreach (var pair in headers)
226                                         if (pair.Value != null)
227                                                 msg.Headers.Add (CreateHeader (pair.Key, pair.Value));
228                         return msg;
229                 }
230
231                 MessageHeader CreateHeader (MessageHeaderDescription mh, object value)
232                 {
233                         return MessageHeader.CreateHeader (mh.Name, mh.Namespace, value, mh.MustUnderstand, mh.Actor, mh.Relay);
234                 }
235
236                 public void DeserializeRequest (Message message, object [] parameters)
237                 {
238                         string action = message.Headers.Action;
239                         MessageDescription md = messages.Find (action);
240                         if (md == null)
241                                 throw new ActionNotSupportedException (String.Format ("Action '{0}' is not supported by this operation.", action));
242
243                         var headers = MessageToHeaderObjects (md, message);
244                         object [] parts = MessageToParts (md, message);
245                         if (md.MessageType != null) {
246 #if NET_2_1
247                                 parameters [0] = Activator.CreateInstance (md.MessageType);
248 #else
249                                 parameters [0] = Activator.CreateInstance (md.MessageType, true);
250 #endif
251                                 PartsToMessageObject (md, headers, parts, parameters [0]);
252                         }
253                         else
254                         {
255                                 int index = 0;
256                                 foreach (ParameterInfo pi in requestMethodParams)
257                                         if (!pi.IsOut) {
258                                                 parameters [index] = parts [index];
259                                                 index++;
260                                         }
261                         }
262                 }
263
264                 public object DeserializeReply (Message message, object [] parameters)
265                 {
266                         MessageDescription md = null;
267                         foreach (MessageDescription mdi in messages)
268                                 if (!mdi.IsRequest)
269                                         md = mdi;
270
271                         var headers = MessageToHeaderObjects (md, message);
272                         object [] parts = MessageToParts (md, message);
273                         if (md.MessageType != null) {
274 #if NET_2_1
275                                 object msgObject = Activator.CreateInstance (md.MessageType);
276 #else
277                                 object msgObject = Activator.CreateInstance (md.MessageType, true);
278 #endif
279                                 PartsToMessageObject (md, headers, parts, msgObject);
280                                 return msgObject;
281                         }
282                         else {
283                                 int index = ParamsOffset (md.Body);
284                                 foreach (ParameterInfo pi in replyMethodParams) {
285                                         if (pi.IsOut || pi.ParameterType.IsByRef)
286                                                 parameters [pi.Position] = parts [index++];
287                                 }
288                                 return HasReturnValue (md.Body) ? parts [0] : null;
289                         }
290                 }
291
292                 void PartsToMessageObject (MessageDescription md, Dictionary<MessageHeaderDescription,object> headers, object [] parts, object msgObject)
293                 {
294                         if (headers != null)
295                                 foreach (var pair in headers) {
296                                         var mi = pair.Key.MemberInfo;
297                                         if (mi is FieldInfo)
298                                                 ((FieldInfo) mi).SetValue (msgObject, pair.Value);
299                                         else
300                                                 ((PropertyInfo) mi).SetValue (msgObject, pair.Value, null);
301                                 }
302
303                         var l = new List<MessagePartDescription> (md.Body.Parts);
304                         if (md.Body.ReturnValue != null)
305                                 l.Add (md.Body.ReturnValue);
306                         foreach (MessagePartDescription partDesc in l)
307                                 if (partDesc.MemberInfo is FieldInfo)
308                                         ((FieldInfo) partDesc.MemberInfo).SetValue (msgObject, parts [partDesc.Index]);
309                                 else if (partDesc.MemberInfo is PropertyInfo)
310                                         ((PropertyInfo) partDesc.MemberInfo).SetValue (msgObject, parts [partDesc.Index], null);
311                                 // otherwise, it could be null (in case of undefined return value in MessageContract)
312                 }
313
314                 void MessageObjectToParts (MessageDescription md, object msgObject, Dictionary<MessageHeaderDescription,object> headers, object [] parts)
315                 {
316                         foreach (var headDesc in md.Headers) {
317                                 var mi = headDesc.MemberInfo;
318                                 if (mi is FieldInfo)
319                                         headers [headDesc] = ((FieldInfo) mi).GetValue (msgObject);
320                                 else
321                                         headers [headDesc] = ((PropertyInfo) mi).GetValue (msgObject, null);
322                         }
323
324                         var l = new List<MessagePartDescription> (md.Body.Parts);
325                         if (md.Body.ReturnValue != null)
326                                 l.Add (md.Body.ReturnValue);
327                         foreach (MessagePartDescription partDesc in l) {
328                                 if (partDesc.MemberInfo == null)
329                                         continue;
330                                 if (partDesc.MemberInfo is FieldInfo)
331                                         parts [partDesc.Index] = ((FieldInfo) partDesc.MemberInfo).GetValue (msgObject);
332                                 else
333                                         parts [partDesc.Index] = ((PropertyInfo) partDesc.MemberInfo).GetValue (msgObject, null);
334                         }
335                 }
336
337                 internal static bool HasReturnValue (MessageBodyDescription desc)
338                 {
339                         return desc.ReturnValue != null && desc.ReturnValue.Type != typeof (void);
340                 }
341
342                 protected static int ParamsOffset (MessageBodyDescription desc)
343                 {
344                         return HasReturnValue (desc) ? 1 : 0;
345                 }
346
347                 protected static object [] CreatePartsArray (MessageBodyDescription desc)
348                 {
349                         if (HasReturnValue (desc))
350                                 return new object [desc.Parts.Count + 1];
351                         return new object [desc.Parts.Count];
352                 }
353         }
354
355         class DataContractMessagesFormatter : BaseMessagesFormatter
356         {
357                 DataContractFormatAttribute attr;
358 #if !NET_2_1
359                 DataContractSerializerOperationBehavior serializerBehavior;
360 #endif
361
362                 public DataContractMessagesFormatter (OperationDescription desc, DataContractFormatAttribute attr)
363                         : base (desc)
364                 {
365 #if !NET_2_1
366                         this.serializerBehavior = desc.Behaviors.Find<DataContractSerializerOperationBehavior>();
367 #endif
368                         this.attr = attr;
369                 }
370
371                 public DataContractMessagesFormatter (MessageDescriptionCollection messages, DataContractFormatAttribute attr)
372                         : base (messages)
373                 {
374                         this.attr = attr;
375                 }
376
377                 Dictionary<MessagePartDescription, XmlObjectSerializer> serializers
378                         = new Dictionary<MessagePartDescription,XmlObjectSerializer> ();
379
380                 protected override Message PartsToMessage (
381                         MessageDescription md, MessageVersion version, string action, object [] parts)
382                 {
383                         return Message.CreateMessage (version, action, new DataContractBodyWriter (md.Body, this, parts));
384                 }
385
386                 protected override Dictionary<MessageHeaderDescription,object> MessageToHeaderObjects (MessageDescription md, Message message)
387                 {
388                         if (message.IsEmpty || md.Headers.Count == 0)
389                                 return null;
390                         
391                         var dic = new Dictionary<MessageHeaderDescription,object> ();
392                         for (int i = 0; i < message.Headers.Count; i++) {
393                                 var r = message.Headers.GetReaderAtHeader (i);
394                                 var mh = md.Headers.FirstOrDefault (h => h.Name == r.LocalName && h.Namespace == r.NamespaceURI);
395                                 if (mh != null)
396                                         dic [mh] = ReadHeaderObject (mh.Type, GetSerializer (mh), r);
397                         }
398                         return dic;
399                 }
400
401                 protected override object [] MessageToParts (
402                         MessageDescription md, Message message)
403                 {
404                         if (message.IsEmpty)
405                                 return null;
406
407                         int offset = ParamsOffset (md.Body);
408                         object [] parts = CreatePartsArray (md.Body);
409
410                         XmlDictionaryReader r = message.GetReaderAtBodyContents ();
411                         if (md.Body.WrapperName != null)
412                                 r.ReadStartElement (md.Body.WrapperName, md.Body.WrapperNamespace);
413
414                         for (r.MoveToContent (); r.NodeType == XmlNodeType.Element; r.MoveToContent ()) {
415                                 XmlQualifiedName key = new XmlQualifiedName (r.LocalName, r.NamespaceURI);
416                                 MessagePartDescription rv = md.Body.ReturnValue;
417                                 if (rv != null && rv.Name == key.Name && rv.Namespace == key.Namespace && rv.Type != typeof (void))
418                                         parts [0] = ReadMessagePart (md.Body.ReturnValue, r);
419                                 else if (md.Body.Parts.Contains (key)) {
420                                         MessagePartDescription p = md.Body.Parts [key];
421                                         parts [p.Index + offset] = ReadMessagePart (p, r);
422                                 }
423                                 else // Skip unknown elements
424                                         r.Skip ();
425                         }
426
427                         if (md.Body.WrapperName != null && !r.EOF)
428                                 r.ReadEndElement ();
429
430                         return parts;
431                 }
432
433                 object ReadMessagePart (MessagePartDescription part, XmlDictionaryReader r)
434                 {
435                         if (part.Type == typeof (Stream))
436                                 // FIXME: it seems TransferMode.Streamed* has different serialization than .Buffered. Need to differentiate serialization somewhere (not limited to here).
437                                 return new MemoryStream (Convert.FromBase64String (r.ReadElementContentAsString (part.Name, part.Namespace)));
438                         else
439                                 return GetSerializer (part).ReadObject (r);
440                 }
441
442                 XmlObjectSerializer GetSerializer (MessagePartDescription partDesc)
443                 {
444                         if (!serializers.ContainsKey (partDesc))
445 #if !NET_2_1
446                                 if (serializerBehavior != null)
447                                         serializers [partDesc] = serializerBehavior.CreateSerializer(
448                                                 partDesc.Type, partDesc.Name, partDesc.Namespace, OperationKnownTypes as IList<Type>);
449                                 else
450 #endif
451                                         serializers [partDesc] = new DataContractSerializer (
452                                                 partDesc.Type, partDesc.Name, partDesc.Namespace, OperationKnownTypes);
453                         return serializers [partDesc];
454                 }
455
456                 object ReadHeaderObject (Type type, XmlObjectSerializer serializer, XmlDictionaryReader reader)
457                 {
458                         // FIXME: it's a nasty workaround just to avoid UniqueId output as a string.
459                         // Seealso MessageHeader.DefaultMessageHeader.OnWriteHeaderContents().
460                         // Note that msg.Headers.GetHeader<UniqueId> () simply fails (on .NET too) and it is useless. The API is lame by design.
461                         if (type == typeof (UniqueId))
462                                 return new UniqueId (reader.ReadElementContentAsString ());
463                         else
464                                 return serializer.ReadObject (reader);
465                 }
466
467                 class DataContractBodyWriter : BodyWriter
468                 {
469                         MessageBodyDescription desc;
470                         object [] parts;
471                         DataContractMessagesFormatter parent;
472
473                         public DataContractBodyWriter (MessageBodyDescription desc, DataContractMessagesFormatter parent, object [] parts)
474                                 : base (false)
475                         {
476                                 this.desc = desc;
477                                 this.parent = parent;
478                                 this.parts = parts;
479                         }
480
481                         protected override void OnWriteBodyContents (XmlDictionaryWriter writer)
482                         {
483                                 int offset = HasReturnValue (desc) ? 1 : 0;
484                                 if (desc.WrapperName != null)
485                                         writer.WriteStartElement (desc.WrapperName, desc.WrapperNamespace);
486                                 if (HasReturnValue (desc))
487                                         WriteMessagePart (writer, desc, desc.ReturnValue, parts [0]);
488                                 foreach (MessagePartDescription partDesc in desc.Parts)
489                                         WriteMessagePart (writer, desc, partDesc, parts [partDesc.Index + offset]);
490                                 if (desc.WrapperName != null)
491                                         writer.WriteEndElement ();
492                         }
493
494                         void WriteMessagePart (
495                                 XmlDictionaryWriter writer, MessageBodyDescription desc, MessagePartDescription partDesc, object obj)
496                         {
497                                 // FIXME: it seems TransferMode.Streamed* has different serialization than .Buffered. Need to differentiate serialization somewhere (not limited to here).
498                                 if (partDesc.Type == typeof (Stream)) {
499                                         writer.WriteStartElement (partDesc.Name, partDesc.Namespace);
500                                         writer.WriteValue (new StreamProvider ((Stream) obj));
501                                         writer.WriteEndElement ();
502                                 }
503                                 else
504                                         parent.GetSerializer (partDesc).WriteObject (writer, obj);
505                         }
506                 }
507                 
508                 class StreamProvider : IStreamProvider
509                 {
510                         Stream s;
511                         bool busy;
512                         
513                         public StreamProvider (Stream s)
514                         {
515                                 this.s = s;
516                         }
517                         
518                         public Stream GetStream ()
519                         {
520                                 if (busy)
521                                         throw new InvalidOperationException ("Stream is already in use.");
522                                 busy = true;
523                                 return s;
524                         }
525                         
526                         public void ReleaseStream (Stream stream)
527                         {
528                                 if (stream == null)
529                                         throw new ArgumentNullException ("stream");
530                                 if (this.s != stream)
531                                         throw new ArgumentException ("Incorrect parameter stream");
532                                 busy = false;
533                         }
534                 }
535         }
536 }