* Methods.cs: Check for null when looking for a header serializer, since
[mono.git] / mcs / class / System.Web.Services / System.Web.Services.Protocols / Methods.cs
1 //
2 // Methods.cs: Information about a method and its mapping to a SOAP web service.
3 //
4 // Author:
5 //   Miguel de Icaza
6 //   Lluis Sanchez Gual (lluis@ximian.com)
7 //
8 // (C) 2003 Ximian, Inc.
9 //
10
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
32 using System.Reflection;
33 using System.Collections;
34 using System.Xml;
35 using System.Xml.Serialization;
36 using System.Web.Services;
37 using System.Web.Services.Description;
38
39 namespace System.Web.Services.Protocols {
40
41         //
42         // This class represents all the information we extract from a MethodInfo
43         // in the SoapHttpClientProtocol derivative stub class
44         //
45         internal class SoapMethodStubInfo : MethodStubInfo
46         {
47                 internal string Action;
48                 internal string Binding;
49
50                 // The name/namespace of the request 
51                 internal string RequestName;
52                 internal string RequestNamespace;
53
54                 // The name/namespace of the response.
55                 internal string ResponseName;
56                 internal string ResponseNamespace;
57                 
58                 internal bool OneWay;
59                 internal SoapParameterStyle ParameterStyle;
60                 internal SoapBindingStyle SoapBindingStyle;
61                 internal SoapBindingUse Use;
62
63                 internal HeaderInfo[] Headers;
64                 internal SoapExtensionRuntimeConfig[] SoapExtensions;
65                 
66                 internal XmlMembersMapping InputMembersMapping;
67                 internal XmlMembersMapping OutputMembersMapping;
68                 
69                 private int requestSerializerId;
70                 private int responseSerializerId;
71                 
72                 internal XmlSerializer RequestSerializer
73                 {
74                         get { return TypeStub.GetSerializer (requestSerializerId); }
75                 }
76                 
77                 internal XmlSerializer ResponseSerializer
78                 {
79                         get { return TypeStub.GetSerializer (responseSerializerId); }
80                 }
81
82                 //
83                 // Constructor
84                 //
85                 public SoapMethodStubInfo (TypeStubInfo typeStub, LogicalMethodInfo source, object kind, XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter)
86                 : base (typeStub, source)
87                 {
88                         SoapTypeStubInfo parent = (SoapTypeStubInfo) typeStub;
89                         XmlElementAttribute optional_ns = null;
90
91                         if (kind == null) {
92                                 Use = parent.Use;
93                                 RequestName = "";
94                                 RequestNamespace = "";
95                                 ResponseName = "";
96                                 ResponseNamespace = "";
97                                 ParameterStyle = parent.ParameterStyle;
98                                 SoapBindingStyle = parent.SoapBindingStyle;
99                                 OneWay = false;
100                         }
101                         else if (kind is SoapDocumentMethodAttribute){
102                                 SoapDocumentMethodAttribute dma = (SoapDocumentMethodAttribute) kind;
103                                 
104                                 Use = dma.Use;
105                                 if (Use == SoapBindingUse.Default) {
106                                         if (parent.SoapBindingStyle == SoapBindingStyle.Document)
107                                                 Use = parent.Use;
108                                         else
109                                                 Use = SoapBindingUse.Literal;
110                                 }
111                                 
112                                 Action = dma.Action;
113                                 Binding = dma.Binding;
114                                 RequestName = dma.RequestElementName;
115                                 RequestNamespace = dma.RequestNamespace;
116                                 ResponseName = dma.ResponseElementName;
117                                 ResponseNamespace = dma.ResponseNamespace;
118                                 ParameterStyle = dma.ParameterStyle;
119                                 if (ParameterStyle == SoapParameterStyle.Default)
120                                         ParameterStyle = parent.ParameterStyle;
121                                 OneWay = dma.OneWay;
122                                 SoapBindingStyle = SoapBindingStyle.Document;
123                         } else {
124                                 SoapRpcMethodAttribute rma = (SoapRpcMethodAttribute) kind;
125                                 Use = SoapBindingUse.Encoded;   // RPC always use encoded
126
127                                 Action = rma.Action;
128                                 Binding = rma.Binding;
129                                 RequestName = rma.RequestElementName;
130                                 RequestNamespace = rma.RequestNamespace;
131                                 ResponseNamespace = rma.ResponseNamespace;
132                                 ResponseName = rma.ResponseElementName;
133                                 ParameterStyle = SoapParameterStyle.Wrapped;
134                                 OneWay = rma.OneWay;
135                                 SoapBindingStyle = SoapBindingStyle.Rpc;
136
137                                 // For RPC calls, make all arguments be part of the empty namespace
138                                 optional_ns = new XmlElementAttribute ();
139                                 optional_ns.Namespace = "";
140                         }
141
142                         if (OneWay){
143                                 if (source.ReturnType != typeof (void))
144                                         throw new Exception ("OneWay methods should not have a return value.");
145                                 if (source.OutParameters.Length != 0)
146                                         throw new Exception ("OneWay methods should not have out/ref parameters.");
147                         }
148                         
149                         BindingInfo binfo = parent.GetBinding (Binding);
150                         if (binfo == null) throw new InvalidOperationException ("Type '" + parent.Type + "' is missing WebServiceBinding attribute that defines a binding named '" + Binding + "'.");
151                         
152                         string serviceNamespace = binfo.Namespace;
153                                 
154                         if (RequestNamespace == "") RequestNamespace = parent.LogicalType.GetWebServiceNamespace (serviceNamespace, Use);
155                         if (ResponseNamespace == "") ResponseNamespace = parent.LogicalType.GetWebServiceNamespace (serviceNamespace, Use);
156                         if (RequestName == "") RequestName = Name;
157                         if (ResponseName == "") ResponseName = Name + "Response";
158                         if (Action == null || Action == "")
159                                 Action = RequestNamespace.EndsWith("/") ? (RequestNamespace + Name) : (RequestNamespace + "/" + Name);
160                         
161                         bool hasWrappingElem = (ParameterStyle == SoapParameterStyle.Wrapped);
162                         bool writeAccessors = (SoapBindingStyle == SoapBindingStyle.Rpc);
163                         
164                         XmlReflectionMember [] in_members = BuildRequestReflectionMembers (optional_ns);
165                         XmlReflectionMember [] out_members = BuildResponseReflectionMembers (optional_ns);
166
167                         if (Use == SoapBindingUse.Literal) {
168                                 xmlImporter.IncludeTypes (source.CustomAttributeProvider);
169                                 InputMembersMapping = xmlImporter.ImportMembersMapping (RequestName, RequestNamespace, in_members, hasWrappingElem);
170                                 OutputMembersMapping = xmlImporter.ImportMembersMapping (ResponseName, ResponseNamespace, out_members, hasWrappingElem);
171                         }
172                         else {
173                                 soapImporter.IncludeTypes (source.CustomAttributeProvider);
174                                 InputMembersMapping = soapImporter.ImportMembersMapping (RequestName, RequestNamespace, in_members, hasWrappingElem, writeAccessors);
175                                 OutputMembersMapping = soapImporter.ImportMembersMapping (ResponseName, ResponseNamespace, out_members, hasWrappingElem, writeAccessors);
176                         }
177
178                         requestSerializerId = parent.RegisterSerializer (InputMembersMapping);
179                         responseSerializerId = parent.RegisterSerializer (OutputMembersMapping);
180
181                         object[] o = source.GetCustomAttributes (typeof (SoapHeaderAttribute));
182                         ArrayList headerList = new ArrayList (o.Length);
183                         
184                         for (int i = 0; i < o.Length; i++) {
185                                 SoapHeaderAttribute att = (SoapHeaderAttribute) o[i];
186                                 MemberInfo[] mems = source.DeclaringType.GetMember (att.MemberName);
187                                 if (mems.Length == 0) throw new InvalidOperationException ("Member " + att.MemberName + " not found in class " + source.DeclaringType.FullName + ".");
188                                 
189                                 HeaderInfo header = new HeaderInfo (mems[0], att);
190                                 headerList.Add (header);
191
192                                 if (!header.IsUnknownHeader)
193                                         parent.RegisterHeaderType (header.HeaderType, serviceNamespace, Use);
194                         }
195                         Headers = (HeaderInfo[]) headerList.ToArray (typeof(HeaderInfo));
196
197                         SoapExtensions = SoapExtension.GetMethodExtensions (source);
198                 }
199
200                 XmlReflectionMember [] BuildRequestReflectionMembers (XmlElementAttribute optional_ns)
201                 {
202                         ParameterInfo [] input = MethodInfo.InParameters;
203                         XmlReflectionMember [] in_members = new XmlReflectionMember [input.Length];
204
205                         for (int i = 0; i < input.Length; i++)
206                         {
207                                 XmlReflectionMember m = new XmlReflectionMember ();
208                                 m.IsReturnValue = false;
209                                 m.MemberName = input [i].Name;
210                                 m.MemberType = input [i].ParameterType;
211
212                                 m.XmlAttributes = new XmlAttributes (input[i]);
213                                 m.SoapAttributes = new SoapAttributes (input[i]);
214
215                                 if (m.MemberType.IsByRef)
216                                         m.MemberType = m.MemberType.GetElementType ();
217                                 if (optional_ns != null)
218                                         m.XmlAttributes.XmlElements.Add (optional_ns);
219                                 in_members [i] = m;
220                         }
221                         return in_members;
222                 }
223                 
224                 XmlReflectionMember [] BuildResponseReflectionMembers (XmlElementAttribute optional_ns)
225                 {
226                         ParameterInfo [] output = MethodInfo.OutParameters;
227                         bool has_return_value = !(OneWay || MethodInfo.ReturnType == typeof (void));
228                         XmlReflectionMember [] out_members = new XmlReflectionMember [(has_return_value ? 1 : 0) + output.Length];
229                         XmlReflectionMember m;
230                         int idx = 0;
231
232                         if (has_return_value)
233                         {
234                                 m = new XmlReflectionMember ();
235                                 m.IsReturnValue = true;
236                                 m.MemberName = RequestName + "Result";
237                                 m.MemberType = MethodInfo.ReturnType;
238
239                                 m.XmlAttributes = new XmlAttributes (MethodInfo.ReturnTypeCustomAttributeProvider);
240                                 m.SoapAttributes = new SoapAttributes (MethodInfo.ReturnTypeCustomAttributeProvider);
241
242                                 if (optional_ns != null)
243                                         m.XmlAttributes.XmlElements.Add (optional_ns);
244                                 idx++;
245                                 out_members [0] = m;
246                         }
247                         
248                         for (int i = 0; i < output.Length; i++)
249                         {
250                                 m = new XmlReflectionMember ();
251                                 m.IsReturnValue = false;
252                                 m.MemberName = output [i].Name;
253                                 m.MemberType = output [i].ParameterType;
254                                 m.XmlAttributes = new XmlAttributes (output[i]);
255                                 m.SoapAttributes = new SoapAttributes (output[i]);
256
257                                 if (m.MemberType.IsByRef)
258                                         m.MemberType = m.MemberType.GetElementType ();
259                                 if (optional_ns != null)
260                                         m.XmlAttributes.XmlElements.Add (optional_ns);
261                                 out_members [i + idx] = m;
262                         }
263                         return out_members;
264                 }
265
266                 public HeaderInfo GetHeaderInfo (Type headerType)
267                 {
268                         foreach (HeaderInfo headerInfo in Headers)
269                                 if (headerInfo.HeaderType == headerType) return headerInfo;
270                         return null;
271                 }
272         }
273
274         internal class HeaderInfo
275         {
276                 internal MemberInfo Member;
277                 internal SoapHeaderAttribute AttributeInfo;
278                 internal Type HeaderType;
279                 internal bool IsUnknownHeader;
280
281                 public HeaderInfo (MemberInfo member, SoapHeaderAttribute attributeInfo)
282                 {
283                         Member = member;
284                         AttributeInfo = attributeInfo;
285                         if (Member is PropertyInfo) HeaderType = ((PropertyInfo)Member).PropertyType;
286                         else HeaderType = ((FieldInfo)Member).FieldType;
287                         
288                         if (HeaderType == typeof(SoapHeader) || HeaderType == typeof(SoapUnknownHeader) ||
289                                 HeaderType == typeof(SoapHeader[]) || HeaderType == typeof(SoapUnknownHeader[]))
290                         {
291                                 IsUnknownHeader = true;
292                         }
293                         else if (!typeof(SoapHeader).IsAssignableFrom (HeaderType))
294                                 throw new InvalidOperationException (string.Format ("Header members type must be a SoapHeader subclass"));
295                 }
296                 
297                 public object GetHeaderValue (object ob)
298                 {
299                         if (Member is PropertyInfo) return ((PropertyInfo)Member).GetValue (ob, null);
300                         else return ((FieldInfo)Member).GetValue (ob);
301                 }
302
303                 public void SetHeaderValue (object ob, SoapHeader header)
304                 {
305                         object value = header;
306                         if (IsUnknownHeader && HeaderType.IsArray)
307                         {
308                                 SoapUnknownHeader uheader = header as SoapUnknownHeader;
309                                 SoapUnknownHeader[] array = (SoapUnknownHeader[]) GetHeaderValue (ob);
310                                 if (array == null || array.Length == 0) {
311                                         value = new SoapUnknownHeader[] { uheader };
312                                 }
313                                 else {
314                                         SoapUnknownHeader[] newArray = new SoapUnknownHeader [array.Length+1];
315                                         Array.Copy (array, newArray, array.Length);
316                                         newArray [array.Length] = uheader;
317                                         value = newArray;
318                                 }
319                         }
320                         
321                         if (Member is PropertyInfo) ((PropertyInfo)Member).SetValue (ob, value, null);
322                         else ((FieldInfo)Member).SetValue (ob, value);
323                 }
324                 
325                 public SoapHeaderDirection Direction
326                 {
327                         get { return AttributeInfo.Direction; }
328                 }
329         }
330
331
332         //
333         // Holds the metadata loaded from the type stub, as well as
334         // the metadata for all the methods in the type
335         //
336         internal class SoapTypeStubInfo : TypeStubInfo
337         {
338                 Hashtable[] header_serializers = new Hashtable [3];
339                 Hashtable[] header_serializers_byname = new Hashtable [3];
340                 Hashtable methods_byaction = new Hashtable (); 
341
342                 // Precomputed
343                 internal SoapParameterStyle      ParameterStyle;
344                 internal SoapServiceRoutingStyle RoutingStyle;
345                 internal SoapBindingUse          Use;
346                 internal SoapExtensionRuntimeConfig[][] SoapExtensions;
347                 internal SoapBindingStyle SoapBindingStyle;
348                 internal XmlReflectionImporter  xmlImporter;
349                 internal SoapReflectionImporter soapImporter;
350
351                 public SoapTypeStubInfo (LogicalTypeInfo logicalTypeInfo)
352                 : base (logicalTypeInfo)
353                 {
354                         xmlImporter = new XmlReflectionImporter ();
355                         soapImporter = new SoapReflectionImporter ();
356                         
357                         object [] o;
358
359                         o = Type.GetCustomAttributes (typeof (WebServiceBindingAttribute), false);
360                         
361                         if (typeof (SoapHttpClientProtocol).IsAssignableFrom (Type))
362                         {
363                                 if (o.Length == 0)
364                                         throw new InvalidOperationException ("WebServiceBindingAttribute is required on proxy class '" + Type + "'.");
365                                 if (o.Length > 1)
366                                         throw new InvalidOperationException ("Only one WebServiceBinding attribute may be specified on type '" + Type + "'.");
367                                         
368                                 // Remove the default binding, it is not needed since there is always
369                                 // a binding attribute.
370                                 Bindings.Clear ();
371                         }
372                                 
373                         foreach (WebServiceBindingAttribute at in o)
374                                 AddBinding (new BindingInfo (at, LogicalType.WebServiceNamespace));
375
376                         o = Type.GetCustomAttributes (typeof (SoapDocumentServiceAttribute), false);
377                         if (o.Length == 1){
378                                 SoapDocumentServiceAttribute a = (SoapDocumentServiceAttribute) o [0];
379
380                                 ParameterStyle = a.ParameterStyle;
381                                 RoutingStyle = a.RoutingStyle;
382                                 Use = a.Use;
383                                 SoapBindingStyle = SoapBindingStyle.Document;
384                         } else {
385                                 o = Type.GetCustomAttributes (typeof (SoapRpcServiceAttribute), false);
386                                 if (o.Length == 1){
387                                         SoapRpcServiceAttribute srs = (SoapRpcServiceAttribute) o [0];
388                                         
389                                         ParameterStyle = SoapParameterStyle.Wrapped;
390                                         RoutingStyle = srs.RoutingStyle;
391                                         Use = SoapBindingUse.Encoded;
392                                         SoapBindingStyle = SoapBindingStyle.Rpc;
393                                 } else {
394                                         ParameterStyle = SoapParameterStyle.Wrapped;
395                                         RoutingStyle = SoapServiceRoutingStyle.SoapAction;
396                                         Use = SoapBindingUse.Literal;
397                                         SoapBindingStyle = SoapBindingStyle.Document;
398                                 }
399                         }
400                         
401                         if (ParameterStyle == SoapParameterStyle.Default) ParameterStyle = SoapParameterStyle.Wrapped;
402                         if (Use == SoapBindingUse.Default) Use = SoapBindingUse.Literal;
403                         
404                         xmlImporter.IncludeTypes (Type);
405                         soapImporter.IncludeTypes (Type);
406
407                         SoapExtensions = SoapExtension.GetTypeExtensions (Type);
408                 }
409
410                 public override XmlReflectionImporter XmlImporter 
411                 {
412                         get { return xmlImporter; }
413                 }
414
415                 public override SoapReflectionImporter SoapImporter 
416                 {
417                         get { return soapImporter; }
418                 }
419                 
420                 public override string ProtocolName
421                 {
422                         get { return "Soap"; }
423                 }
424                 
425                 protected override MethodStubInfo CreateMethodStubInfo (TypeStubInfo parent, LogicalMethodInfo lmi, bool isClientProxy)
426                 {
427                         SoapMethodStubInfo res = null;
428                         object [] ats = lmi.GetCustomAttributes (typeof (SoapDocumentMethodAttribute));
429                         if (ats.Length == 0) ats = lmi.GetCustomAttributes (typeof (SoapRpcMethodAttribute));
430
431                         if (ats.Length == 0 && isClientProxy)
432                                 return null;
433                         else if (ats.Length == 0)
434                                 res = new SoapMethodStubInfo (parent, lmi, null, xmlImporter, soapImporter);
435                         else
436                                 res = new SoapMethodStubInfo (parent, lmi, ats[0], xmlImporter, soapImporter);
437                                 
438                         methods_byaction [res.Action] = res;
439                         return res;
440                 }
441                 
442                 internal void RegisterHeaderType (Type type, string serviceNamespace, SoapBindingUse use)
443                 {
444                         Hashtable serializers = header_serializers [(int)use];
445                         if (serializers == null) {
446                                 serializers = new Hashtable ();
447                                 header_serializers [(int)use] = serializers;
448                                 header_serializers_byname [(int)use] = new Hashtable ();
449                         }
450                         
451                         if (serializers.ContainsKey (type)) 
452                                 return;
453
454                         XmlTypeMapping tm;
455                         if (use == SoapBindingUse.Literal) {
456                                 XmlReflectionImporter ri = new XmlReflectionImporter ();
457                                 
458                                 // MS.NET reflects header classes in a weird way. The root element
459                                 // name is the CLR class name unless it is specified in an XmlRootAttribute.
460                                 // The usual is to use the xml type name by default, but not in this case.
461                                 
462                                 XmlRootAttribute root;
463                                 XmlAttributes ats = new XmlAttributes (type);
464                                 if (ats.XmlRoot != null) root = ats.XmlRoot;
465                                 else root = new XmlRootAttribute (type.Name);
466                                 
467                                 if (root.Namespace == null) root.Namespace = LogicalType.GetWebServiceLiteralNamespace (serviceNamespace);
468                                 if (root.ElementName == null) root.ElementName = type.Name;
469                                 
470                                 tm = ri.ImportTypeMapping (type, root);
471                         }
472                         else {
473                                 SoapReflectionImporter ri = new SoapReflectionImporter ();
474                                 tm = ri.ImportTypeMapping (type, LogicalType.GetWebServiceEncodedNamespace (serviceNamespace));
475                         }
476                         
477                         int sid = RegisterSerializer (tm);
478
479                         serializers [type] = sid;
480                         header_serializers_byname [(int)use] [new XmlQualifiedName (tm.ElementName, tm.Namespace)] = sid;
481                 }
482
483                 internal XmlSerializer GetHeaderSerializer (Type type, SoapBindingUse use)
484                 {
485                         Hashtable table = header_serializers [(int)use];
486                         if (table == null) return null;
487                                 
488                         return GetSerializer ((int) table [type]);
489                 }
490         
491                 internal XmlSerializer GetHeaderSerializer (XmlQualifiedName qname, SoapBindingUse use)
492                 {
493                         Hashtable table = header_serializers_byname [(int)use];
494                         if (table == null) return null;
495                         
496                         object serId = table [qname];
497                         if (serId == null) return null;
498                                 
499                         return GetSerializer ((int) serId);
500                 }               
501                 
502                 public SoapMethodStubInfo GetMethodForSoapAction (string name)
503                 {
504                         return (SoapMethodStubInfo) methods_byaction [name.Trim ('"',' ')];
505                 }
506         }
507 }