Moved ProviderCollectionTest.cs from System assembly to System.Configuration.
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Description / WsdlExporter.cs
1 //
2 // WsdlExporter.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //      Ankit Jain <JAnkit@novell.com>
7 //
8 // Copyright (C) 2005 Novell, Inc.  http://www.novell.com
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29 using System;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.ServiceModel;
33 using System.ServiceModel.Channels;
34 using System.Web.Services.Description;
35 using System.Xml;
36 using System.Xml.Schema;
37 using System.Xml.Serialization;
38 using System.Runtime.Serialization;
39
40 using SMBinding = System.ServiceModel.Channels.Binding;
41 using SMMessage = System.ServiceModel.Channels.Message;
42
43 using WSServiceDescription = System.Web.Services.Description.ServiceDescription;
44 using WSBinding = System.Web.Services.Description.Binding;
45 using WSMessage = System.Web.Services.Description.Message;
46 using QName = System.Xml.XmlQualifiedName;
47
48 namespace System.ServiceModel.Description
49 {
50         [MonoTODO]
51         public class WsdlExporter : MetadataExporter
52         {
53                 private MetadataSet metadata;
54                 private ServiceDescriptionCollection wsdl_colln;
55                 private XsdDataContractExporter xsd_exporter;
56                 private Hashtable exported_contracts;
57
58                 public override MetadataSet GetGeneratedMetadata ()
59                 {
60                         if (metadata != null)
61                                 return metadata;
62
63                         metadata = new MetadataSet ();
64                         foreach (WSServiceDescription sd in GeneratedWsdlDocuments)
65                                 metadata.MetadataSections.Add (
66                                         MetadataSection.CreateFromServiceDescription (sd));
67
68                         foreach (XmlSchema xs in GeneratedXmlSchemas.Schemas ())
69                                 metadata.MetadataSections.Add (
70                                         MetadataSection.CreateFromSchema (xs));
71                                 
72                         return metadata;
73                 }
74
75                 public override void ExportContract (ContractDescription contract)
76                 {
77                         ExportContractInternal (contract);      
78                 }
79
80                 List<IWsdlExportExtension> ExportContractInternal (ContractDescription contract)
81                 {
82                         QName qname = new QName (contract.Name, contract.Namespace);
83                         if (ExportedContracts.ContainsKey (qname))
84                                 throw new ArgumentException (String.Format (
85                                         "A ContractDescription with Namespace : {0} and Name : {1} has already been exported.", 
86                                         contract.Namespace, contract.Name));
87
88                         WSServiceDescription sd = GetServiceDescription (contract.Namespace);
89
90                         List<IWsdlExportExtension> extensions = new List<IWsdlExportExtension> ();
91                         foreach (IWsdlExportExtension extn in contract.Behaviors.FindAll<IWsdlExportExtension> ())
92                                 extensions.Add (extn);
93
94                         XmlDocument xdoc = new XmlDocument ();
95
96                         PortType ws_port = new PortType ();
97                         ws_port.Name = contract.Name;
98
99                         foreach (OperationDescription sm_op in contract.Operations) {
100                                 Operation ws_op = new Operation ();
101                                 ws_op.Name = sm_op.Name;
102
103                                 foreach (MessageDescription sm_md in sm_op.Messages) {
104                                         //OperationMessage
105                                         OperationMessage ws_opmsg;
106                                         WSMessage ws_msg = new WSMessage ();
107                                         MessagePart ws_msgpart;
108                                         if (sm_md.Direction == MessageDirection.Input) {
109                                                 ws_opmsg = new OperationInput ();
110                                                 ws_msg.Name = String.Concat (ws_port.Name, "_", ws_op.Name, "_", "InputMessage");
111                                                 ws_msgpart = ExportMessageBodyDescription (sm_md.Body, ws_op.Name, sd.TargetNamespace);
112                                         } else {
113                                                 ws_opmsg = new OperationOutput ();
114                                                 ws_msg.Name = String.Concat (ws_port.Name, "_", ws_op.Name, "_", "OutputMessage");
115                                                 ws_msgpart = ExportMessageBodyDescription (sm_md.Body, ws_op.Name + "Response", sd.TargetNamespace);
116                                         }
117                                         ws_msg.Parts.Add (ws_msgpart);  
118
119                                         /* FIXME: Faults */
120
121                                         //Action
122                                         XmlAttribute attr = xdoc.CreateAttribute ("wsaw", "Action", "http://www.w3.org/2006/05/addressing/wsdl");
123                                         attr.Value = sm_md.Action;
124                                         ws_opmsg.ExtensibleAttributes = new XmlAttribute [] { attr };
125                                         
126                                         //FIXME: Set .Input & .Output
127
128                                         ws_opmsg.Message = new QName (ws_msg.Name, sd.TargetNamespace);
129                                         ws_op.Messages.Add (ws_opmsg);
130                                         sd.Messages.Add (ws_msg);
131                                 }
132
133                                 ws_port.Operations.Add (ws_op);
134
135                                 foreach (IWsdlExportExtension extn in sm_op.Behaviors.FindAll<IWsdlExportExtension> ())
136                                         extensions.Add (extn);
137                         }
138
139                         //Add Imports for <types
140                         XmlSchema xs_import = new XmlSchema ();
141                         xs_import.TargetNamespace = String.Concat (
142                                         contract.Namespace, 
143                                         contract.Namespace.EndsWith ("/") ? "" : "/",
144                                         "Imports");
145                         foreach (XmlSchema schema in GeneratedXmlSchemas.Schemas ()) {
146                                 XmlSchemaImport imp = new XmlSchemaImport ();
147                                 imp.Namespace = schema.TargetNamespace;
148                                 xs_import.Includes.Add (imp);
149                         }
150                         sd.Types.Schemas.Add (xs_import);
151
152                         sd.PortTypes.Add (ws_port);
153                         ExportedContracts [qname] = contract;
154
155                         WsdlContractConversionContext context = new WsdlContractConversionContext (contract, ws_port);
156                         foreach (IWsdlExportExtension extn in extensions)
157                                 extn.ExportContract (this, context);
158
159                         return extensions;
160                 }
161
162                 public override void ExportEndpoint (ServiceEndpoint endpoint)
163                 {
164                         List<IWsdlExportExtension> extensions = ExportContractInternal (endpoint.Contract);
165                         
166                         //FIXME: Namespace
167                         WSServiceDescription sd = GetServiceDescription ("http://tempuri.org/");
168                         if (sd.TargetNamespace != endpoint.Contract.Namespace) {
169                                 sd.Namespaces.Add ("i0", endpoint.Contract.Namespace);
170
171                                 //Import
172                                 Import import = new Import ();
173                                 import.Namespace = endpoint.Contract.Namespace;
174
175                                 sd.Imports.Add (import);
176                         }
177                         
178                         if (endpoint.Binding == null)
179                                 throw new ArgumentException (String.Format (
180                                         "Binding for ServiceEndpoint named '{0}' is null",
181                                         endpoint.Name));
182
183                         bool msg_version_none =
184                                 endpoint.Binding.MessageVersion != null &&
185                                 endpoint.Binding.MessageVersion.Equals (MessageVersion.None);
186                         //ExportBinding
187                         WSBinding ws_binding = new WSBinding ();
188                         
189                         //<binding name = .. 
190                         ws_binding.Name = String.Concat (endpoint.Binding.Name, "_", endpoint.Contract.Name);
191
192                         //<binding type = ..
193                         ws_binding.Type = new QName (endpoint.Contract.Name, endpoint.Contract.Namespace);
194                         sd.Bindings.Add (ws_binding);
195
196                         if (!msg_version_none) {
197                                 SoapBinding soap_binding = new SoapBinding ();
198                                 soap_binding.Transport = SoapBinding.HttpTransport;
199                                 soap_binding.Style = SoapBindingStyle.Document;
200                                 ws_binding.Extensions.Add (soap_binding);
201                         }
202
203                         //      <operation
204                         foreach (OperationDescription sm_op in endpoint.Contract.Operations){
205                                 OperationBinding op_binding = new OperationBinding ();
206                                 op_binding.Name = sm_op.Name;
207
208                                 //FIXME: Move to IWsdlExportExtension .. ?
209                                 foreach (MessageDescription sm_md in sm_op.Messages) {
210                                         if (sm_md.Direction == MessageDirection.Input) {
211                                                 //<input
212                                                 InputBinding in_binding = new InputBinding ();
213
214                                                 if (!msg_version_none) {
215                                                         SoapBodyBinding soap_body_binding = new SoapBodyBinding ();
216                                                         soap_body_binding.Use = SoapBindingUse.Literal;
217                                                         in_binding.Extensions.Add (soap_body_binding);
218
219                                                         //Set Action
220                                                         //<operation > <soap:operation soapAction .. >
221                                                         SoapOperationBinding soap_operation_binding = new SoapOperationBinding ();
222                                                         soap_operation_binding.SoapAction = sm_md.Action;
223                                                         soap_operation_binding.Style = SoapBindingStyle.Document;
224                                                         op_binding.Extensions.Add (soap_operation_binding);
225                                                 }
226
227                                                 op_binding.Input = in_binding;
228                                         } else {
229                                                 //<output
230                                                 OutputBinding out_binding = new OutputBinding ();
231
232                                                 if (!msg_version_none) {
233                                                         SoapBodyBinding soap_body_binding = new SoapBodyBinding ();
234                                                         soap_body_binding.Use = SoapBindingUse.Literal;
235                                                         out_binding.Extensions.Add (soap_body_binding);
236                                                 }
237                                                 
238                                                 op_binding.Output = out_binding;
239                                         }
240                                 }
241
242                                 ws_binding.Operations.Add (op_binding);
243                         }
244
245                         //Add <service
246                         Port ws_port = ExportService (sd, ws_binding, endpoint.Address, msg_version_none);
247
248                         //Call IWsdlExportExtension.ExportEndpoint
249                         WsdlContractConversionContext contract_context = new WsdlContractConversionContext (
250                                 endpoint.Contract, sd.PortTypes [endpoint.Contract.Name]);
251                         WsdlEndpointConversionContext endpoint_context = new WsdlEndpointConversionContext (
252                                 contract_context, endpoint, ws_port, ws_binding);
253
254                         foreach (IWsdlExportExtension extn in extensions)
255                                 extn.ExportEndpoint (this, endpoint_context);
256
257                                 
258                 }
259
260                 Port ExportService (WSServiceDescription sd, WSBinding ws_binding, EndpointAddress address, bool msg_version_none)
261                 {
262                         if (address == null)
263                                 return null;
264
265                         Service ws_svc = GetService (sd, "service");
266                         sd.Name = "service";
267
268                                 Port ws_port = new Port ();
269                                 ws_port.Name = ws_binding.Name;
270                                 ws_port.Binding = new QName (ws_binding.Name, sd.TargetNamespace);
271
272                                 if (!msg_version_none) {
273                                         SoapAddressBinding soap_addr = new SoapAddressBinding ();
274                                         soap_addr.Location = address.Uri.AbsoluteUri;
275
276                                         ws_port.Extensions.Add (soap_addr);
277                                 }
278
279                         ws_svc.Ports.Add (ws_port);
280
281                         return ws_port;
282                 }
283
284                 Service GetService (WSServiceDescription sd, string name)
285                 {
286                         Service svc = sd.Services [name];
287                         if (svc != null)
288                                 return svc;
289
290                         svc = new Service ();
291                         svc.Name = name;
292                         sd.Services.Add (svc);
293
294                         return svc;
295                 }
296
297                 WSServiceDescription GetServiceDescription (string ns)
298                 {
299                         foreach (WSServiceDescription sd in GeneratedWsdlDocuments) {
300                                 if (sd.TargetNamespace == ns)
301                                         return sd;
302                         }
303
304                         WSServiceDescription ret = new WSServiceDescription ();
305                         ret.TargetNamespace = ns;
306                         ret.Namespaces = GetNamespaces (ns);
307                         GeneratedWsdlDocuments.Add (ret);
308
309                         metadata = null;
310
311                         return ret;
312                 }
313
314                 public ServiceDescriptionCollection GeneratedWsdlDocuments {
315                         get {
316                                 if (wsdl_colln == null)
317                                         wsdl_colln = new ServiceDescriptionCollection ();
318                                 return wsdl_colln;
319                         }
320                 }
321
322                 public XmlSchemaSet GeneratedXmlSchemas {
323                         get { return XsdExporter.Schemas; }
324                 }
325
326                 public void ExportEndpoints (
327                         IEnumerable<ServiceEndpoint> endpoints,
328                         XmlQualifiedName name)
329                 {
330                         throw new NotImplementedException ();
331                 }
332
333                 XsdDataContractExporter XsdExporter {
334                         get {
335                                 if (xsd_exporter == null)
336                                         xsd_exporter = new XsdDataContractExporter ();
337
338                                 return xsd_exporter;
339                         }
340                 }
341
342                 Hashtable ExportedContracts {
343                         get {
344                                 if (exported_contracts == null)
345                                         exported_contracts = new Hashtable ();
346                                 return exported_contracts;
347                         }
348                 }
349                 
350                 XmlSerializerNamespaces GetNamespaces (string target_namespace)
351                 {
352                         XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces ();
353
354                         namespaces.Add ("soap", "http://schemas.xmlsoap.org/wsdl/soap/");
355                         namespaces.Add ("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
356                         namespaces.Add ("soapenc", "http://schemas.xmlsoap.org/soap/encoding/");
357                         namespaces.Add ("tns", target_namespace);
358                         namespaces.Add ("wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
359                         namespaces.Add ("wsp", "http://schemas.xmlsoap.org/ws/2004/09/policy");
360                         namespaces.Add ("wsap", "http://schemas.xmlsoap.org/ws/2004/08/addressing/policy");
361                         namespaces.Add ("msc", "http://schemas.microsoft.com/ws/2005/12/wsdl/contract");
362                         namespaces.Add ("wsaw", "http://www.w3.org/2006/05/addressing/wsdl");
363                         namespaces.Add ("soap12", "http://schemas.xmlsoap.org/wsdl/soap12/");
364                         namespaces.Add ("wsa10", "http://www.w3.org/2005/08/addressing");
365                         namespaces.Add ("wsdl", "http://schemas.xmlsoap.org/wsdl/");
366
367                         return namespaces;
368                 }
369
370                 MessagePart ExportMessageBodyDescription (MessageBodyDescription msgbody, string name, string ns)
371                 {
372                         MessagePart msgpart = new MessagePart ();
373                         string part_name = IsTypeMessage (msgbody);
374
375                         if (part_name != null) {
376                                 msgpart.Name = part_name;
377                                 msgpart.Type = ExportTypeMessage (); //FIXME: Cache this
378                         } else {
379                                 msgpart.Name = "parameters";
380                                 msgpart.Element = ExportParameters (msgbody, name, ns);
381                         }
382                         return msgpart;
383                 }
384
385                 /* Sets the @name if the param or return type is SMMessage */
386                 string IsTypeMessage (MessageBodyDescription msgbody)
387                 {
388                         MessagePartDescription part = null;
389
390                         if (msgbody.Parts.Count == 0)
391                                 part = msgbody.ReturnValue;
392                         else if (msgbody.Parts.Count == 1)
393                                 part = msgbody.Parts [0];
394
395                         if (part != null && (part.Type.FullName == typeof (SMMessage).FullName))
396                                 return part.Name;
397
398                         return null;
399                 }
400
401                 QName ExportParameters (MessageBodyDescription msgbody, string name, string ns)
402                 {
403                         XmlSchema xs = GetSchema (ns);
404                         //FIXME: Extract to a HasElement method ?
405                         foreach (XmlSchemaObject o in xs.Items) {
406                                 XmlSchemaElement e = o as XmlSchemaElement;
407                                 if (e == null)
408                                         continue;
409
410                                 if (e.Name == name)
411                                         throw new InvalidOperationException (String.Format (
412                                                 "Message element named '{0}:{1}' has already been exported.",
413                                                 ns, name));
414                         }
415                                 
416                         //Create the element for "parameters"
417                         XmlSchemaElement schema_element = new XmlSchemaElement ();
418                         schema_element.Name = name;
419
420                         XmlSchemaComplexType complex_type = new XmlSchemaComplexType ();
421                         //Generate Sequence representing the message/parameters
422                         //FIXME: MessageContractAttribute
423                 
424                         XmlSchemaSequence sequence = new XmlSchemaSequence ();
425                         XmlSchemaElement element = null;
426
427                         if (msgbody.ReturnValue == null) {
428                                 //parameters
429                                 foreach (MessagePartDescription part in msgbody.Parts) {
430                                         if (part.Type == null)
431                                                 //FIXME: Eg. when WsdlImporter is used to import a wsdl
432                                                 throw new NotImplementedException ();
433                                         
434                                         element = GetSchemaElementForPart (part, xs);
435                                         sequence.Items.Add (element);
436                                 }
437                         } else {
438                                 //ReturnValue
439                                 if (msgbody.ReturnValue.Type != typeof (void)) {
440                                         element = GetSchemaElementForPart (msgbody.ReturnValue, xs);
441                                         sequence.Items.Add (element);
442                                 }
443                         }
444
445                         complex_type.Particle = sequence;
446                         schema_element.SchemaType = complex_type;
447
448                         xs.Items.Add (schema_element);
449                         GeneratedXmlSchemas.Reprocess (xs);
450
451                         return new QName (schema_element.Name, xs.TargetNamespace);
452                 }
453
454                 //Exports <xs:type for SMMessage
455                 //FIXME: complex type for this can be made static
456                 QName ExportTypeMessage ()
457                 {
458                         XmlSchema xs = GetSchema ("http://schemas.microsoft.com/Message");
459                         QName qname = new QName ("MessageBody", xs.TargetNamespace);
460
461                         foreach (XmlSchemaObject o in xs.Items) {
462                                 XmlSchemaComplexType ct = o as XmlSchemaComplexType;
463                                 if (ct == null)
464                                         continue;
465
466                                 if (ct.Name == "MessageBody")
467                                         //Already exported
468                                         return qname;
469                         }
470
471                         XmlSchemaComplexType complex_type = new XmlSchemaComplexType ();
472                         complex_type.Name = "MessageBody";
473                         XmlSchemaSequence sequence = new XmlSchemaSequence ();
474
475                         XmlSchemaAny any = new XmlSchemaAny ();
476                         any.MinOccurs = 0;
477                         any.MaxOccursString = "unbounded";
478                         any.Namespace = "##any";
479
480                         sequence.Items.Add (any);
481                         complex_type.Particle = sequence;
482
483                         xs.Items.Add (complex_type);
484                         GeneratedXmlSchemas.Reprocess (xs);
485                         
486                         return qname;
487                 }
488
489                 XmlSchemaElement GetSchemaElementForPart (MessagePartDescription part, XmlSchema schema)
490                 {
491                         XmlSchemaElement element = new XmlSchemaElement ();
492
493                         element.Name = part.Name;
494                         XsdExporter.Export (part.Type);
495                         element.SchemaTypeName = XsdExporter.GetSchemaTypeName (part.Type);
496                         AddImport (schema, element.SchemaTypeName.Namespace);
497
498                         //FIXME: nillable, minOccurs
499                         if (XsdExporter.GetSchemaType (part.Type) is XmlSchemaComplexType ||
500                                 part.Type == typeof (string))
501                                 element.IsNillable = true;
502                         element.MinOccurs = 0;
503
504                         return element;
505                 }
506
507                 //FIXME: Replace with a dictionary ?
508                 void AddImport (XmlSchema schema, string ns)
509                 {
510                         if (ns == XmlSchema.Namespace || schema.TargetNamespace == ns)
511                                 return;
512
513                         foreach (XmlSchemaObject o in schema.Includes) {
514                                 XmlSchemaImport import = o as XmlSchemaImport;
515                                 if (import == null)
516                                         continue;
517                                 if (import.Namespace == ns)
518                                         return;
519                         }
520
521                         XmlSchemaImport imp = new XmlSchemaImport ();
522                         imp.Namespace = ns;
523                         schema.Includes.Add (imp);
524                 }
525
526                 XmlSchema GetSchema (string ns)
527                 {
528                         ICollection colln = GeneratedXmlSchemas.Schemas (ns);
529                         if (colln.Count > 0) { 
530                                 if (colln.Count > 1)
531                                         throw new Exception ("More than 1 schema found for ns = " + ns);
532                                 //FIXME: HORRIBLE!
533                                 foreach (object o in colln)
534                                         return (o as XmlSchema);
535                         }
536
537                         XmlSchema schema = new XmlSchema ();
538                         schema.TargetNamespace = ns;
539                         schema.ElementFormDefault = XmlSchemaForm.Qualified;
540                         GeneratedXmlSchemas.Add (schema);
541
542                         return schema;
543                 }
544
545         }
546 }