Merge pull request #4453 from lambdageek/bug-49721
[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 wsdlBinding)
157                 {
158                         return ImportBinding (wsdlBinding, 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 ("Policy")) {
241                                         context.AddPolicyAssertion (xml);
242                                         continue;
243                                 }
244                                 if (!xml.LocalName.Equals ("PolicyReference"))
245                                         continue;
246                                 var uri = xml.GetAttribute ("URI");
247
248                                 if (!uri.StartsWith ("#")) {
249                                         // FIXME
250                                         AddWarning (
251                                                 "Failed to resolve unknown policy reference `{0}' for " +
252                                                 "binding `{1}'.", uri, binding.Name);
253                                         continue;
254                                 }
255
256                                 foreach (var sext in binding.ServiceDescription.Extensions) {
257                                         var sxml = sext as XmlElement;
258                                         if (sxml == null)
259                                                 continue;
260                                         if (!sxml.NamespaceURI.Equals (Constants.WspNamespace))
261                                                 continue;
262                                         if (!sxml.LocalName.Equals ("Policy"))
263                                                 continue;
264                                         var id = sxml.GetAttribute ("Id", Constants.WsuNamespace);
265                                         if (!uri.Substring (1).Equals (id))
266                                                 continue;
267                                         context.AddPolicyAssertion (sxml);
268                                 }
269                         }
270
271                         foreach (IPolicyImportExtension extension in PolicyImportExtensions) {
272                                 try {
273                                         extension.ImportPolicy (this, context);
274                                 } catch (Exception ex) {
275                                         AddWarning (
276                                                 "PolicyImportException `{0}' threw an exception while " +
277                                                 "trying to import policy references for endpoint `{1}': {2}",
278                                                 extension.GetType ().Name, endpoint.Name, ex.Message);
279                                 }
280                         }
281                 }
282
283                 PortType GetPortTypeFromBinding (WSBinding binding)
284                 {
285                         foreach (WSServiceDescription sd in wsdl_documents) {
286                                 var port_type = sd.PortTypes [binding.Type.Name];
287                                 if (port_type != null)
288                                         return port_type;
289                         }
290                         
291                         throw new MetadataImportException (AddError (
292                                 "PortType named {0} not found in namespace {1}.",
293                                 binding.Type.Name, binding.Type.Namespace));
294                 }
295                 
296                 public override Collection<ContractDescription> ImportAllContracts ()
297                 {
298                         if (contracts != null)
299                                 return contracts;
300
301                         contracts = new Collection<ContractDescription> ();
302
303                         foreach (WSServiceDescription sd in wsdl_documents) {
304                                 foreach (PortType pt in sd.PortTypes) {
305                                         var cd = ImportContract (pt, false);
306                                         if (cd != null)
307                                                 contracts.Add (cd);
308                                 }
309                         }
310
311                         return contracts;
312                 }
313
314                 public override ServiceEndpointCollection ImportAllEndpoints ()
315                 {
316                         if (endpoint_colln != null)
317                                 return endpoint_colln;
318
319                         endpoint_colln = new ServiceEndpointCollection ();
320
321                         foreach (WSServiceDescription wsd in wsdl_documents) {
322                                 foreach (Service service in wsd.Services) {
323                                         foreach (Port port in service.Ports) {
324                                                 var sep = ImportEndpoint (port, false);
325                                                 if (sep != null)
326                                                         endpoint_colln.Add (sep);
327                                         }
328                                 }
329                         }
330
331                         return endpoint_colln;
332                 }
333
334                 public ContractDescription ImportContract (PortType wsdlPortType)
335                 {
336                         return ImportContract (wsdlPortType, true);
337                 }
338
339                 ContractDescription ImportContract (PortType portType, bool throwOnError)
340                 {
341                         if (contractHash.ContainsKey (portType)) {
342                                 var cd = contractHash [portType];
343                                 if (cd != null)
344                                         return cd;
345
346                                 if (!throwOnError)
347                                         return null;
348
349                                 throw new InvalidOperationException (String.Format (
350                                         "Failed to import contract for port type `{0}', " +
351                                         "an error has already been reported.", portType.Name));
352                         }
353
354                         try {
355                                 var cd = DoImportContract (portType);
356                                 contractHash.Add (portType, cd);
357                                 return cd;
358                         } catch (MetadataImportException) {
359                                 contractHash.Add (portType, null);
360                                 if (throwOnError)
361                                         throw;
362                                 return null;
363                         } catch (Exception ex) {
364                                 contractHash.Add (portType, null);
365                                 var error = AddError (
366                                         "Failed to import contract for port type `{0}': {1}",
367                                         portType.Name, ex.Message);
368                                 if (throwOnError)
369                                         throw new MetadataImportException (error, ex);
370                                 return null;
371                         }
372                 }
373
374                 ContractDescription DoImportContract (PortType wsdlPortType)
375                 {
376                         BeforeImport ();
377
378                         ContractDescription cd = new ContractDescription (wsdlPortType.Name, wsdlPortType.ServiceDescription.TargetNamespace);
379
380                         foreach (Operation op in wsdlPortType.Operations) {
381                                 OperationDescription op_descr = new OperationDescription (op.Name, cd);
382
383                                 foreach (OperationMessage opmsg in op.Messages) {
384                                         /* OperationMessageCollection */
385                                         MessageDescription msg_descr;
386                                         MessageDirection dir = MessageDirection.Input;
387                                         string action = "";
388
389                                         if (opmsg.GetType () == typeof (OperationInput))
390                                                 dir = MessageDirection.Input;
391                                         else if (opmsg.GetType () == typeof (OperationOutput))
392                                                 dir = MessageDirection.Output;
393                                         /* FIXME: OperationFault--> OperationDescription.Faults ? */
394
395                                         if (opmsg.ExtensibleAttributes != null) {
396                                                 for (int i = 0; i < opmsg.ExtensibleAttributes.Length; i++) {
397                                                         if (opmsg.ExtensibleAttributes [i].LocalName == "Action" &&
398                                                                 opmsg.ExtensibleAttributes [i].NamespaceURI == "http://www.w3.org/2006/05/addressing/wsdl")
399                                                                 /* addressing:Action */
400                                                                 action = opmsg.ExtensibleAttributes [i].Value;
401                                                         /* FIXME: other attributes ? */
402                                                 }
403                                         }
404
405                                         // fill Action from operation binding if required.
406                                         if (action == "") {
407                                                 if (dir != MessageDirection.Input)
408                                                         action = GetActionFromOperationBinding (wsdlPortType, op.Name);
409                                                 else
410                                                         action = "*";
411                                         }
412
413                                         msg_descr = new MessageDescription (action, dir);
414                                         /* FIXME: Headers ? */
415
416                                         op_descr.Messages.Add (msg_descr);
417                                 }
418
419                                 cd.Operations.Add (op_descr);
420                         }
421
422                         WsdlContractConversionContext context = new WsdlContractConversionContext (cd, wsdlPortType);
423                         foreach (IWsdlImportExtension extension in wsdl_extensions)
424                                 extension.ImportContract (this, context);
425
426                         return cd;
427                 }
428
429                 string GetActionFromOperationBinding (PortType pt, string opName)
430                 {
431                         foreach (WSBinding binding in pt.ServiceDescription.Bindings) {
432                                 foreach (OperationBinding ob in binding.Operations) {
433                                         if (ob.Name != opName)
434                                                 continue;
435                                         foreach (var ext in ob.Extensions) {
436                                                 var sob = ext as SoapOperationBinding;
437                                                 if (sob == null)
438                                                         continue;
439                                                 return sob.SoapAction;
440                                         }
441                                         return String.Empty;
442                                 }
443                         }
444                         return String.Empty;
445                 }
446
447                 public ServiceEndpoint ImportEndpoint (Port wsdlPort)
448                 {
449                         return ImportEndpoint (wsdlPort, true);
450                 }
451
452                 ServiceEndpoint ImportEndpoint (Port port, bool throwOnError)
453                 {
454                         ServiceEndpoint endpoint;
455                         if (endpointHash.ContainsKey (port)) {
456                                 endpoint = endpointHash [port];
457                                 if (endpoint != null)
458                                         return endpoint;
459                                 
460                                 if (!throwOnError)
461                                         return null;
462                                 
463                                 throw new InvalidOperationException (String.Format (
464                                         "Failed to import port `{0}', an error has " +
465                                         "already been reported before.", port.Name));
466                         }
467
468                         var binding = port.Service.ServiceDescription.Bindings [port.Binding.Name];
469                         if (binding == null) {
470                                 endpointHash.Add (port, null);
471                                 var error = AddError (
472                                         "Failed to import port `{0}': cannot find binding `{1}' that " +
473                                         "this port depends on.", port.Name, port.Binding.Name);
474                                 if (throwOnError)
475                                         throw new MetadataImportException (error);
476                                 return null;
477                         }
478
479                         try {
480                                 endpoint = ImportBinding (binding, throwOnError);
481                         } catch (Exception ex) {
482                                 endpointHash.Add (port, null);
483                                 var error = AddError (
484                                         "Failed to import port `{0}': error while trying to import " +
485                                         "binding `{1}' that this port depends on: {2}",
486                                         port.Name, port.Binding.Name, ex.Message);
487                                 if (throwOnError)
488                                         throw new MetadataImportException (error, ex);
489                                 return null;
490                         }
491
492                         if (endpoint == null) {
493                                 endpointHash.Add (port, null);
494                                 AddError (
495                                         "Failed to import port `{0}': error while trying to import " +
496                                         "binding `{1}' that this port depends on.",
497                                         port.Name, port.Binding.Name);
498                                 return null;
499                         }
500
501                         try {
502                                 ImportEndpoint (port, binding, endpoint, throwOnError);
503                                 endpointHash.Add (port, endpoint);
504                                 return endpoint;
505                         } catch (MetadataImportException) {
506                                 endpointHash.Add (port, null);
507                                 if (throwOnError)
508                                         throw;
509                                 return null;
510                         } catch (Exception ex) {
511                                 endpointHash.Add (port, null);
512                                 var error = AddError (
513                                         "Failed to import port `{0}': {1}", port.Name, ex.Message);
514                                 if (throwOnError)
515                                         throw new MetadataImportException (error, ex);
516                                 return null;
517                         }
518                 }
519
520                 void ImportEndpoint (Port port, WSBinding wsb, ServiceEndpoint sep, bool throwOnError)
521                 {
522                         BeforeImport ();
523
524                         var port_type = GetPortTypeFromBinding (wsb);
525
526                         var contract_context = new WsdlContractConversionContext (sep.Contract, port_type);
527                         WsdlEndpointConversionContext endpoint_context = new WsdlEndpointConversionContext (
528                                         contract_context, sep, port, wsb);
529
530                         foreach (IWsdlImportExtension extension in wsdl_extensions)
531                                 extension.ImportEndpoint (this, endpoint_context);
532                 }
533
534                 void ImportEndpoints (ServiceEndpointCollection coll, WSBinding binding)
535                 {
536                         foreach (WSServiceDescription wsd in wsdl_documents) {
537                                 foreach (WS.Service service in wsd.Services) {
538                                         foreach (WS.Port port in service.Ports) {
539                                                 if (!binding.Name.Equals (port.Binding.Name))
540                                                         continue;
541                                                 var sep = ImportEndpoint (port, false);
542                                                 if (sep != null)
543                                                         coll.Add (sep);
544                                         }
545                                 }
546                         }
547                 }
548
549                 public ServiceEndpointCollection ImportEndpoints (WSBinding wsdlBinding)
550                 {
551                         var coll = new ServiceEndpointCollection ();
552                         ImportEndpoints (coll, wsdlBinding);
553                         return coll;
554                 }
555
556                 public ServiceEndpointCollection ImportEndpoints (PortType wsdlPortType)
557                 {
558                         var coll = new ServiceEndpointCollection ();
559
560                         foreach (WSServiceDescription wsd in wsdl_documents) {
561                                 foreach (WS.Binding binding in wsd.Bindings) {
562                                         if (!binding.Type.Name.Equals (wsdlPortType.Name))
563                                                 continue;
564
565                                         ImportEndpoints (coll, binding);
566                                 }
567                         }
568
569                         return coll;
570                 }
571
572                 public ServiceEndpointCollection ImportEndpoints (Service wsdlService)
573                 {
574                         var coll = new ServiceEndpointCollection ();
575                         
576                         foreach (Port port in wsdlService.Ports) {
577                                 var sep = ImportEndpoint (port, false);
578                                 if (sep != null)
579                                         coll.Add (sep);
580                         }
581
582                         return coll;
583                 }
584         }
585 }