Merge pull request #513 from pruiz/xamarin-bug-8565-v2
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Description / WsdlImporter.cs
1 //
2 // WsdlImporter.cs
3 //
4 // Authors:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //      Ankit Jain <jankit@novell.com>  
7 //      Martin Baulig <martin.baulig@xamarin.com>
8 //
9 // Copyright (C) 2005 Novell, Inc.  http://www.novell.com
10 // Copyright (c) 2012 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.Collections.ObjectModel;
34 using System.ServiceModel;
35 using System.ServiceModel.Channels;
36 using System.Web.Services.Description;
37 using System.Xml;
38 using System.Xml.Schema;
39
40 using SMBinding = System.ServiceModel.Channels.Binding;
41 using WS = System.Web.Services.Description;
42 using WSServiceDescription = System.Web.Services.Description.ServiceDescription;
43 using WSBinding = System.Web.Services.Description.Binding;
44 using WSMessage = System.Web.Services.Description.Message;
45 using QName = System.Xml.XmlQualifiedName;
46
47 namespace System.ServiceModel.Description
48 {
49         [MonoTODO]
50         public class WsdlImporter : MetadataImporter
51         {
52                 ServiceDescriptionCollection wsdl_documents;
53                 XmlSchemaSet xmlschemas;
54                 List<XmlElement> policies; /* ?? */
55                 MetadataSet metadata;
56                 bool beforeImportCalled;
57
58                 KeyedByTypeCollection<IWsdlImportExtension> wsdl_extensions;
59                         
60                 //Imported
61                 Collection<ContractDescription> contracts = null;
62                 ServiceEndpointCollection endpoint_colln = null;
63
64                 // Contract by PortType
65                 Dictionary<PortType, ContractDescription> contractHash = null;
66                 // ServiceEndpoint by WSBinding
67                 Dictionary<WSBinding, ServiceEndpoint> bindingHash = null;
68                 // ServiceEndpoint by Port
69                 Dictionary<Port, ServiceEndpoint> endpointHash = null;
70
71                 public WsdlImporter (
72                         MetadataSet metadata,
73                         IEnumerable<IPolicyImportExtension> policyImportExtensions,
74                         IEnumerable<IWsdlImportExtension> wsdlImportExtensions)
75                         : base (policyImportExtensions)
76                 {
77                         if (metadata == null)
78                                 throw new ArgumentNullException ("metadata");
79                         
80                         if (wsdlImportExtensions == null) {
81                                 wsdl_extensions = new KeyedByTypeCollection<IWsdlImportExtension> ();
82
83                                 wsdl_extensions.Add (new DataContractSerializerMessageContractImporter ());
84                                 wsdl_extensions.Add (new XmlSerializerMessageContractImporter ());
85                                 wsdl_extensions.Add (new MessageEncodingBindingElementImporter ());
86                                 wsdl_extensions.Add (new TransportBindingElementImporter ());
87                                 wsdl_extensions.Add (new StandardBindingImporter ());
88                         } else {
89                                 wsdl_extensions = new KeyedByTypeCollection<IWsdlImportExtension> (wsdlImportExtensions);
90                         }
91
92                         // It is okay to fill these members immediately when WsdlImporter.ctor() is invoked
93                         // i.e. after this .ctor(), those metadata docs are not considered anymore.
94                         this.metadata = metadata;
95                         this.wsdl_documents = new ServiceDescriptionCollection ();
96                         this.xmlschemas = new XmlSchemaSet ();
97                         this.policies = new List<XmlElement> ();
98                         this.contractHash = new Dictionary<PortType, ContractDescription> ();
99                         this.bindingHash = new Dictionary<WSBinding, ServiceEndpoint> ();
100                         this.endpointHash = new Dictionary<Port, ServiceEndpoint> ();
101
102                         foreach (MetadataSection ms in metadata.MetadataSections) {
103                                 if (ms.Dialect == MetadataSection.ServiceDescriptionDialect &&
104                                         ms.Metadata.GetType () == typeof (WSServiceDescription))
105                                         wsdl_documents.Add ((WSServiceDescription) ms.Metadata);
106                                 else
107                                 if (ms.Dialect == MetadataSection.XmlSchemaDialect &&
108                                         ms.Metadata.GetType () == typeof (XmlSchema))
109                                         xmlschemas.Add ((XmlSchema) ms.Metadata);
110                         }
111                 }
112
113                 public WsdlImporter (MetadataSet metadata)
114                         : this (metadata, null, null)
115                 {
116                 }
117
118                 public ServiceDescriptionCollection WsdlDocuments {
119                         get { return wsdl_documents; }
120                 }
121
122                 public KeyedByTypeCollection <IWsdlImportExtension> WsdlImportExtensions {
123                         get { return wsdl_extensions; }
124                 }
125
126                 public XmlSchemaSet XmlSchemas {
127                         get { return xmlschemas; }
128                 }
129
130                 public Collection<SMBinding> ImportAllBindings ()
131                 {
132                         Collection<SMBinding> bindings = new Collection<SMBinding> ();
133
134                         foreach (WSServiceDescription sd in wsdl_documents) {
135                                 foreach (WSBinding binding in sd.Bindings) {
136                                         var endpoint = ImportBinding (binding, false);
137                                         if (endpoint != null)
138                                                 bindings.Add (endpoint.Binding);
139                                 }
140                         }
141
142                         return bindings;
143                 }
144
145                 void BeforeImport ()
146                 {
147                         if (beforeImportCalled)
148                                 return;
149
150                         foreach (IWsdlImportExtension extension in wsdl_extensions)
151                                 extension.BeforeImport (wsdl_documents, xmlschemas, policies);
152                         
153                         beforeImportCalled = true;
154                 }
155
156                 public SMBinding ImportBinding (WSBinding binding)
157                 {
158                         return ImportBinding (binding, true).Binding;
159                 }
160
161                 ServiceEndpoint ImportBinding (WSBinding binding, bool throwOnError)
162                 {
163                         if (bindingHash.ContainsKey (binding)) {
164                                 var sep = bindingHash [binding];
165                                 if (sep != null)
166                                         return sep;
167
168                                 if (!throwOnError)
169                                         return null;
170                                 
171                                 throw new InvalidOperationException (String.Format (
172                                         "Failed to import binding {0}, an error has " +
173                                         "already been reported before.", binding.Name));
174                         }
175
176                         try {
177                                 var port_type = GetPortTypeFromBinding (binding);
178                                 var contract = ImportContract (port_type);
179                                 var contract_context = new WsdlContractConversionContext (contract, port_type);
180                         
181                                 var sep = ImportBinding (binding, contract_context);
182                                 bindingHash.Add (binding, sep);
183                                 return sep;
184                         } catch (MetadataImportException) {
185                                 bindingHash.Add (binding, null);
186                                 if (throwOnError)
187                                         throw;
188                                 return null;
189                         } catch (Exception ex) {
190                                 bindingHash.Add (binding, null);
191                                 var error = AddError (
192                                         "Failed to import binding `{0}': {1}", binding.Name, ex.Message);
193                                 if (throwOnError)
194                                         throw new MetadataImportException (error, ex);
195                                 return null;
196                         }
197                 }
198
199                 ServiceEndpoint ImportBinding (WSBinding binding,
200                                                WsdlContractConversionContext contract_context)
201                 {
202                         BeforeImport ();
203
204                         var sep = new ServiceEndpoint (contract_context.Contract);
205                         
206                         var custom = new CustomBinding ();
207                         custom.Name = binding.Name;
208                         custom.Namespace = binding.ServiceDescription.TargetNamespace;
209                         
210                         sep.Binding = custom;
211
212                         try {
213                                 ImportPolicy (binding, sep);
214                         } catch (Exception ex) {
215                                 // FIXME: Policy import is still experimental.
216                                 AddWarning ("Exception while trying to import policy for " +
217                                             "binding `{0}': {1}", binding.Name, ex.Message);
218                         }
219                         
220                         var endpoint_context = new WsdlEndpointConversionContext (
221                                 contract_context, sep, null, binding);
222                         
223                         foreach (IWsdlImportExtension extension in wsdl_extensions)
224                                 extension.ImportEndpoint (this, endpoint_context);
225                         
226                         return sep;
227                 }
228
229                 void ImportPolicy (WSBinding binding, ServiceEndpoint endpoint)
230                 {
231                         var context = new Description.CustomPolicyConversionContext (binding, endpoint);
232                         var assertions = context.GetBindingAssertions ();
233
234                         foreach (var ext in binding.Extensions) {
235                                 var xml = ext as XmlElement;
236                                 if (xml == null)
237                                         continue;
238                                 if (!xml.NamespaceURI.Equals (Constants.WspNamespace))
239                                         continue;
240                                 if (!xml.LocalName.Equals ("PolicyReference"))
241                                         continue;
242                                 var uri = xml.GetAttribute ("URI");
243
244                                 if (!uri.StartsWith ("#")) {
245                                         // FIXME
246                                         AddWarning (
247                                                 "Failed to resolve unknown policy reference `{0}' for " +
248                                                 "binding `{1}'.", uri, binding.Name);
249                                         continue;
250                                 }
251
252                                 foreach (var sext in binding.ServiceDescription.Extensions) {
253                                         var sxml = sext as XmlElement;
254                                         if (sxml == null)
255                                                 continue;
256                                         if (!sxml.NamespaceURI.Equals (Constants.WspNamespace))
257                                                 continue;
258                                         if (!sxml.LocalName.Equals ("Policy"))
259                                                 continue;
260                                         var id = sxml.GetAttribute ("Id", Constants.WsuNamespace);
261                                         if (!uri.Substring (1).Equals (id))
262                                                 continue;
263                                         context.AddPolicyAssertion (sxml);
264                                 }
265                         }
266
267                         foreach (IPolicyImportExtension extension in PolicyImportExtensions) {
268                                 try {
269                                         extension.ImportPolicy (this, context);
270                                 } catch (Exception ex) {
271                                         AddWarning (
272                                                 "PolicyImportException `{0}' threw an exception while " +
273                                                 "trying to import policy references for endpoint `{1}': {2}",
274                                                 extension.GetType ().Name, endpoint.Name, ex.Message);
275                                 }
276                         }
277                 }
278
279                 PortType GetPortTypeFromBinding (WSBinding binding)
280                 {
281                         foreach (WSServiceDescription sd in wsdl_documents) {
282                                 var port_type = sd.PortTypes [binding.Type.Name];
283                                 if (port_type != null)
284                                         return port_type;
285                         }
286                         
287                         throw new MetadataImportException (AddError (
288                                 "PortType named {0} not found in namespace {1}.",
289                                 binding.Type.Name, binding.Type.Namespace));
290                 }
291                 
292                 public override Collection<ContractDescription> ImportAllContracts ()
293                 {
294                         if (contracts != null)
295                                 return contracts;
296
297                         contracts = new Collection<ContractDescription> ();
298
299                         foreach (WSServiceDescription sd in wsdl_documents) {
300                                 foreach (PortType pt in sd.PortTypes) {
301                                         var cd = ImportContract (pt, false);
302                                         if (cd != null)
303                                                 contracts.Add (cd);
304                                 }
305                         }
306
307                         return contracts;
308                 }
309
310                 public override ServiceEndpointCollection ImportAllEndpoints ()
311                 {
312                         if (endpoint_colln != null)
313                                 return endpoint_colln;
314
315                         endpoint_colln = new ServiceEndpointCollection ();
316
317                         foreach (WSServiceDescription wsd in wsdl_documents) {
318                                 foreach (Service service in wsd.Services) {
319                                         foreach (Port port in service.Ports) {
320                                                 var sep = ImportEndpoint (port, false);
321                                                 if (sep != null)
322                                                         endpoint_colln.Add (sep);
323                                         }
324                                 }
325                         }
326
327                         return endpoint_colln;
328                 }
329
330                 public ContractDescription ImportContract (PortType wsdlPortType)
331                 {
332                         return ImportContract (wsdlPortType, true);
333                 }
334
335                 ContractDescription ImportContract (PortType portType, bool throwOnError)
336                 {
337                         if (contractHash.ContainsKey (portType)) {
338                                 var cd = contractHash [portType];
339                                 if (cd != null)
340                                         return cd;
341
342                                 if (!throwOnError)
343                                         return null;
344
345                                 throw new InvalidOperationException (String.Format (
346                                         "Failed to import contract for port type `{0}', " +
347                                         "an error has already been reported.", portType.Name));
348                         }
349
350                         try {
351                                 var cd = DoImportContract (portType);
352                                 contractHash.Add (portType, cd);
353                                 return cd;
354                         } catch (MetadataImportException) {
355                                 contractHash.Add (portType, null);
356                                 if (throwOnError)
357                                         throw;
358                                 return null;
359                         } catch (Exception ex) {
360                                 contractHash.Add (portType, null);
361                                 var error = AddError (
362                                         "Failed to import contract for port type `{0}': {1}",
363                                         portType.Name, ex.Message);
364                                 if (throwOnError)
365                                         throw new MetadataImportException (error, ex);
366                                 return null;
367                         }
368                 }
369
370                 ContractDescription DoImportContract (PortType wsdlPortType)
371                 {
372                         BeforeImport ();
373
374                         ContractDescription cd = new ContractDescription (wsdlPortType.Name, wsdlPortType.ServiceDescription.TargetNamespace);
375
376                         foreach (Operation op in wsdlPortType.Operations) {
377                                 OperationDescription op_descr = new OperationDescription (op.Name, cd);
378
379                                 foreach (OperationMessage opmsg in op.Messages) {
380                                         /* OperationMessageCollection */
381                                         MessageDescription msg_descr;
382                                         MessageDirection dir = MessageDirection.Input;
383                                         string action = "";
384
385                                         if (opmsg.GetType () == typeof (OperationInput))
386                                                 dir = MessageDirection.Input;
387                                         else if (opmsg.GetType () == typeof (OperationOutput))
388                                                 dir = MessageDirection.Output;
389                                         /* FIXME: OperationFault--> OperationDescription.Faults ? */
390
391                                         if (opmsg.ExtensibleAttributes != null) {
392                                                 for (int i = 0; i < opmsg.ExtensibleAttributes.Length; i++) {
393                                                         if (opmsg.ExtensibleAttributes [i].LocalName == "Action" &&
394                                                                 opmsg.ExtensibleAttributes [i].NamespaceURI == "http://www.w3.org/2006/05/addressing/wsdl")
395                                                                 /* addressing:Action */
396                                                                 action = opmsg.ExtensibleAttributes [i].Value;
397                                                         /* FIXME: other attributes ? */
398                                                 }
399                                         }
400
401                                         // fill Action from operation binding if required.
402                                         if (action == "") {
403                                                 if (dir != MessageDirection.Input)
404                                                         action = GetActionFromOperationBinding (wsdlPortType, op.Name);
405                                                 else
406                                                         action = "*";
407                                         }
408
409                                         msg_descr = new MessageDescription (action, dir);
410                                         /* FIXME: Headers ? */
411
412                                         op_descr.Messages.Add (msg_descr);
413                                 }
414
415                                 cd.Operations.Add (op_descr);
416                         }
417
418                         WsdlContractConversionContext context = new WsdlContractConversionContext (cd, wsdlPortType);
419                         foreach (IWsdlImportExtension extension in wsdl_extensions)
420                                 extension.ImportContract (this, context);
421
422                         return cd;
423                 }
424
425                 string GetActionFromOperationBinding (PortType pt, string opName)
426                 {
427                         foreach (WSBinding binding in pt.ServiceDescription.Bindings) {
428                                 foreach (OperationBinding ob in binding.Operations) {
429                                         if (ob.Name != opName)
430                                                 continue;
431                                         foreach (var ext in ob.Extensions) {
432                                                 var sob = ext as SoapOperationBinding;
433                                                 if (sob == null)
434                                                         continue;
435                                                 return sob.SoapAction;
436                                         }
437                                         return String.Empty;
438                                 }
439                         }
440                         return String.Empty;
441                 }
442
443                 public ServiceEndpoint ImportEndpoint (Port wsdlPort)
444                 {
445                         return ImportEndpoint (wsdlPort, true);
446                 }
447
448                 ServiceEndpoint ImportEndpoint (Port port, bool throwOnError)
449                 {
450                         ServiceEndpoint endpoint;
451                         if (endpointHash.ContainsKey (port)) {
452                                 endpoint = endpointHash [port];
453                                 if (endpoint != null)
454                                         return endpoint;
455                                 
456                                 if (!throwOnError)
457                                         return null;
458                                 
459                                 throw new InvalidOperationException (String.Format (
460                                         "Failed to import port `{0}', an error has " +
461                                         "already been reported before.", port.Name));
462                         }
463
464                         var binding = port.Service.ServiceDescription.Bindings [port.Binding.Name];
465                         if (binding == null) {
466                                 endpointHash.Add (port, null);
467                                 var error = AddError (
468                                         "Failed to import port `{0}': cannot find binding `{1}' that " +
469                                         "this port depends on.", port.Name, port.Binding.Name);
470                                 if (throwOnError)
471                                         throw new MetadataImportException (error);
472                                 return null;
473                         }
474
475                         try {
476                                 endpoint = ImportBinding (binding, throwOnError);
477                         } catch (Exception ex) {
478                                 endpointHash.Add (port, null);
479                                 var error = AddError (
480                                         "Failed to import port `{0}': error while trying to import " +
481                                         "binding `{1}' that this port depends on: {2}",
482                                         port.Name, port.Binding.Name, ex.Message);
483                                 if (throwOnError)
484                                         throw new MetadataImportException (error, ex);
485                                 return null;
486                         }
487
488                         if (endpoint == null) {
489                                 endpointHash.Add (port, null);
490                                 AddError (
491                                         "Failed to import port `{0}': error while trying to import " +
492                                         "binding `{1}' that this port depends on.",
493                                         port.Name, port.Binding.Name);
494                                 return null;
495                         }
496
497                         try {
498                                 ImportEndpoint (port, binding, endpoint, throwOnError);
499                                 endpointHash.Add (port, endpoint);
500                                 return endpoint;
501                         } catch (MetadataImportException) {
502                                 endpointHash.Add (port, null);
503                                 if (throwOnError)
504                                         throw;
505                                 return null;
506                         } catch (Exception ex) {
507                                 endpointHash.Add (port, null);
508                                 var error = AddError (
509                                         "Failed to import port `{0}': {1}", port.Name, ex.Message);
510                                 if (throwOnError)
511                                         throw new MetadataImportException (error, ex);
512                                 return null;
513                         }
514                 }
515
516                 void ImportEndpoint (Port port, WSBinding wsb, ServiceEndpoint sep, bool throwOnError)
517                 {
518                         BeforeImport ();
519
520                         var port_type = GetPortTypeFromBinding (wsb);
521
522                         var contract_context = new WsdlContractConversionContext (sep.Contract, port_type);
523                         WsdlEndpointConversionContext endpoint_context = new WsdlEndpointConversionContext (
524                                         contract_context, sep, port, wsb);
525
526                         foreach (IWsdlImportExtension extension in wsdl_extensions)
527                                 extension.ImportEndpoint (this, endpoint_context);
528                 }
529
530                 void ImportEndpoints (ServiceEndpointCollection coll, WSBinding binding)
531                 {
532                         foreach (WSServiceDescription wsd in wsdl_documents) {
533                                 foreach (WS.Service service in wsd.Services) {
534                                         foreach (WS.Port port in service.Ports) {
535                                                 if (!binding.Name.Equals (port.Binding.Name))
536                                                         continue;
537                                                 var sep = ImportEndpoint (port, false);
538                                                 if (sep != null)
539                                                         coll.Add (sep);
540                                         }
541                                 }
542                         }
543                 }
544
545                 public ServiceEndpointCollection ImportEndpoints (WSBinding binding)
546                 {
547                         var coll = new ServiceEndpointCollection ();
548                         ImportEndpoints (coll, binding);
549                         return coll;
550                 }
551
552                 public ServiceEndpointCollection ImportEndpoints (PortType portType)
553                 {
554                         var coll = new ServiceEndpointCollection ();
555
556                         foreach (WSServiceDescription wsd in wsdl_documents) {
557                                 foreach (WS.Binding binding in wsd.Bindings) {
558                                         if (!binding.Type.Name.Equals (portType.Name))
559                                                 continue;
560
561                                         ImportEndpoints (coll, binding);
562                                 }
563                         }
564
565                         return coll;
566                 }
567
568                 public ServiceEndpointCollection ImportEndpoints (Service service)
569                 {
570                         var coll = new ServiceEndpointCollection ();
571                         
572                         foreach (Port port in service.Ports) {
573                                 var sep = ImportEndpoint (port, false);
574                                 if (sep != null)
575                                         coll.Add (sep);
576                         }
577
578                         return coll;
579                 }
580         }
581 }