// // WsdlExporter.cs // // Author: // Atsushi Enomoto // Ankit Jain // Martin Baulig // // Copyright (C) 2005 Novell, Inc. http://www.novell.com // Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.ServiceModel.Channels; using System.Web.Services.Description; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using System.Runtime.Serialization; using SMBinding = System.ServiceModel.Channels.Binding; using SMMessage = System.ServiceModel.Channels.Message; using WSServiceDescription = System.Web.Services.Description.ServiceDescription; using WSBinding = System.Web.Services.Description.Binding; using WSMessage = System.Web.Services.Description.Message; using QName = System.Xml.XmlQualifiedName; namespace System.ServiceModel.Description { [MonoTODO] public class WsdlExporter : MetadataExporter { class ContractExportMap { public ContractExportMap (QName qname, ContractDescription contract, List results) { QName = qname; Contract = contract; Results = results; } public QName QName { get; private set; } public ContractDescription Contract { get; private set; } public List Results { get; private set; } } class EndpointExportMap { public EndpointExportMap (string name, ServiceEndpoint endpoint) { Name = name; Endpoint = endpoint; } public string Name { get; private set; } public ServiceEndpoint Endpoint { get; private set; } } MetadataSet metadata; ServiceDescriptionCollection wsdl_colln; XsdDataContractExporter xsd_exporter; Dictionary exported_contracts; List exported_endpoints; public override MetadataSet GetGeneratedMetadata () { if (metadata != null) return metadata; metadata = new MetadataSet (); foreach (WSServiceDescription sd in GeneratedWsdlDocuments) metadata.MetadataSections.Add ( MetadataSection.CreateFromServiceDescription (sd)); foreach (XmlSchema xs in GeneratedXmlSchemas.Schemas ()) if (xs.TargetNamespace != XmlSchema.Namespace) metadata.MetadataSections.Add ( MetadataSection.CreateFromSchema (xs)); return metadata; } public override void ExportContract (ContractDescription contract) { ExportContractInternal (contract); } ContractExportMap ExportContractInternal (ContractDescription contract) { if (ExportedContracts.ContainsKey (contract)) return ExportedContracts [contract]; QName qname = new QName (contract.Name, contract.Namespace); if (ExportedContracts.Any (m => m.Value.QName == qname)) throw new ArgumentException (String.Format ( "A ContractDescription with Namespace : {0} and Name : {1} has already been exported.", contract.Namespace, contract.Name)); WSServiceDescription sd = GetServiceDescription (contract.Namespace); List extensions = new List (); foreach (IWsdlExportExtension extn in contract.Behaviors.FindAll ()) extensions.Add (extn); XmlDocument xdoc = new XmlDocument (); PortType ws_port = new PortType (); ws_port.Name = contract.Name; foreach (OperationDescription sm_op in contract.Operations) { Operation ws_op = new Operation (); ws_op.Name = sm_op.Name; foreach (MessageDescription sm_md in sm_op.Messages) { //OperationMessage OperationMessage ws_opmsg; WSMessage ws_msg = new WSMessage (); MessagePart ws_msgpart; if (sm_md.Direction == MessageDirection.Input) { ws_opmsg = new OperationInput (); ws_msg.Name = String.Concat (ws_port.Name, "_", ws_op.Name, "_", "InputMessage"); ws_msgpart = ExportMessageBodyDescription (sm_md.Body, ws_op.Name, sd.TargetNamespace); } else { ws_opmsg = new OperationOutput (); ws_msg.Name = String.Concat (ws_port.Name, "_", ws_op.Name, "_", "OutputMessage"); ws_msgpart = ExportMessageBodyDescription (sm_md.Body, ws_op.Name + "Response", sd.TargetNamespace); } ws_msg.Parts.Add (ws_msgpart); /* FIXME: Faults */ //Action XmlAttribute attr = xdoc.CreateAttribute ("wsaw", "Action", "http://www.w3.org/2006/05/addressing/wsdl"); attr.Value = sm_md.Action; ws_opmsg.ExtensibleAttributes = new XmlAttribute [] { attr }; //FIXME: Set .Input & .Output ws_opmsg.Message = new QName (ws_msg.Name, sd.TargetNamespace); ws_op.Messages.Add (ws_opmsg); sd.Messages.Add (ws_msg); } ws_port.Operations.Add (ws_op); foreach (IWsdlExportExtension extn in sm_op.Behaviors.FindAll ()) extensions.Add (extn); } //Add Imports for m.Endpoint == endpoint); if (map != null) return map; int index = 0; var baseName = String.Concat (endpoint.Binding.Name, "_", endpoint.Contract.Name); var name = baseName; while (ExportedEndpoints.Exists (m => m.Name == name)) name = String.Concat (baseName, (++index).ToString ()); map = new EndpointExportMap (name, endpoint); ExportedEndpoints.Add (map); var contract = ExportContractInternal (endpoint.Contract); //FIXME: Namespace WSServiceDescription sd = GetServiceDescription ("http://tempuri.org/"); if (sd.TargetNamespace != endpoint.Contract.Namespace) { sd.Namespaces.Add ("i0", endpoint.Contract.Namespace); //Import Import import = new Import (); import.Namespace = endpoint.Contract.Namespace; sd.Imports.Add (import); } if (endpoint.Binding == null) throw new ArgumentException (String.Format ( "Binding for ServiceEndpoint named '{0}' is null", endpoint.Name)); var extensions = new List (); var extensionTypes = new Dictionary (); if (contract.Results != null) { foreach (var extension in contract.Results) { var type = extension.GetType (); if (extensionTypes.ContainsKey (type)) continue; extensionTypes.Add (type, extension); extensions.Add (extension); } } var bindingElements = endpoint.Binding.CreateBindingElements (); foreach (var element in bindingElements) { var extension = element as IWsdlExportExtension; if (extension == null) continue; var type = extension.GetType (); if (extensionTypes.ContainsKey (type)) continue; extensionTypes.Add (type, extension); extensions.Add (extension); } //ExportBinding WSBinding ws_binding = new WSBinding (); // soap_operation_binding.SoapAction = sm_md.Action; soap_operation_binding.Style = SoapBindingStyle.Document; op_binding.Extensions.Add (soap_operation_binding); } void CreateOutputBinding (ServiceEndpoint endpoint, OperationBinding op_binding, MessageDescription sm_md) { var out_binding = new OutputBinding (); op_binding.Output = out_binding; var message_version = endpoint.Binding.MessageVersion ?? MessageVersion.None; if (message_version == MessageVersion.None) return; SoapBodyBinding soap_body_binding; if (message_version.Envelope == EnvelopeVersion.Soap11) { soap_body_binding = new SoapBodyBinding (); } else if (message_version.Envelope == EnvelopeVersion.Soap12) { soap_body_binding = new Soap12BodyBinding (); } else { throw new InvalidOperationException (); } soap_body_binding.Use = SoapBindingUse.Literal; out_binding.Extensions.Add (soap_body_binding); } Port ExportService (WSServiceDescription sd, WSBinding ws_binding, EndpointAddress address) { if (address == null) return null; Service ws_svc = GetService (sd, "service"); sd.Name = "service"; Port ws_port = new Port (); ws_port.Name = ws_binding.Name; ws_port.Binding = new QName (ws_binding.Name, sd.TargetNamespace); ws_svc.Ports.Add (ws_port); return ws_port; } Service GetService (WSServiceDescription sd, string name) { Service svc = sd.Services [name]; if (svc != null) return svc; svc = new Service (); svc.Name = name; sd.Services.Add (svc); return svc; } WSServiceDescription GetServiceDescription (string ns) { foreach (WSServiceDescription sd in GeneratedWsdlDocuments) { if (sd.TargetNamespace == ns) return sd; } WSServiceDescription ret = new WSServiceDescription (); ret.TargetNamespace = ns; ret.Namespaces = GetNamespaces (ns); GeneratedWsdlDocuments.Add (ret); metadata = null; return ret; } public ServiceDescriptionCollection GeneratedWsdlDocuments { get { if (wsdl_colln == null) wsdl_colln = new ServiceDescriptionCollection (); return wsdl_colln; } } public XmlSchemaSet GeneratedXmlSchemas { get { return XsdExporter.Schemas; } } public void ExportEndpoints ( IEnumerable endpoints, XmlQualifiedName wsdlServiceQName) { if (endpoints == null) throw new ArgumentNullException ("endpoints"); if (wsdlServiceQName == null) throw new ArgumentNullException ("wsdlServiceQName"); foreach (ServiceEndpoint ep in endpoints) { if (ep.Contract.Name == ServiceMetadataBehavior.MexContractName) continue; ExportEndpoint (ep); } } XsdDataContractExporter XsdExporter { get { if (xsd_exporter == null) xsd_exporter = new XsdDataContractExporter (); return xsd_exporter; } } Dictionary ExportedContracts { get { if (exported_contracts == null) exported_contracts = new Dictionary (); return exported_contracts; } } List ExportedEndpoints { get { if (exported_endpoints == null) exported_endpoints = new List (); return exported_endpoints; } } XmlSerializerNamespaces GetNamespaces (string target_namespace) { XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces (); namespaces.Add ("soap", "http://schemas.xmlsoap.org/wsdl/soap/"); namespaces.Add ("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); namespaces.Add ("soapenc", "http://schemas.xmlsoap.org/soap/encoding/"); namespaces.Add ("tns", target_namespace); namespaces.Add ("wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing"); namespaces.Add ("wsp", "http://schemas.xmlsoap.org/ws/2004/09/policy"); namespaces.Add ("wsap", "http://schemas.xmlsoap.org/ws/2004/08/addressing/policy"); namespaces.Add ("msc", "http://schemas.microsoft.com/ws/2005/12/wsdl/contract"); namespaces.Add ("wsaw", "http://www.w3.org/2006/05/addressing/wsdl"); namespaces.Add ("soap12", "http://schemas.xmlsoap.org/wsdl/soap12/"); namespaces.Add ("wsa10", "http://www.w3.org/2005/08/addressing"); namespaces.Add ("wsdl", "http://schemas.xmlsoap.org/wsdl/"); return namespaces; } MessagePart ExportMessageBodyDescription (MessageBodyDescription msgbody, string name, string ns) { MessagePart msgpart = new MessagePart (); string part_name = IsTypeMessage (msgbody); if (part_name != null) { msgpart.Name = part_name; msgpart.Type = ExportTypeMessage (); //FIXME: Cache this } else { msgpart.Name = "parameters"; msgpart.Element = ExportParameters (msgbody, name, ns); } return msgpart; } /* Sets the @name if the param or return type is SMMessage */ string IsTypeMessage (MessageBodyDescription msgbody) { MessagePartDescription part = null; if (msgbody.Parts.Count == 0) part = msgbody.ReturnValue; else if (msgbody.Parts.Count == 1) part = msgbody.Parts [0]; if (part != null && (part.Type.FullName == typeof (SMMessage).FullName)) return part.Name; return null; } QName ExportParameters (MessageBodyDescription msgbody, string name, string ns) { XmlSchema xs = GetSchema (ns); //FIXME: Extract to a HasElement method ? foreach (XmlSchemaObject o in xs.Items) { XmlSchemaElement e = o as XmlSchemaElement; if (e == null) continue; if (e.Name == name) throw new InvalidOperationException (String.Format ( "Message element named '{0}:{1}' has already been exported.", ns, name)); } //Create the element for "parameters" XmlSchemaElement schema_element = new XmlSchemaElement (); schema_element.Name = name; XmlSchemaComplexType complex_type = new XmlSchemaComplexType (); //Generate Sequence representing the message/parameters //FIXME: MessageContractAttribute XmlSchemaSequence sequence = new XmlSchemaSequence (); XmlSchemaElement element = null; if (msgbody.ReturnValue == null) { //parameters foreach (MessagePartDescription part in msgbody.Parts) { if (part.Type == null) //FIXME: Eg. when WsdlImporter is used to import a wsdl throw new NotImplementedException (); element = GetSchemaElementForPart (part, xs); sequence.Items.Add (element); } } else { //ReturnValue if (msgbody.ReturnValue.Type != typeof (void)) { element = GetSchemaElementForPart (msgbody.ReturnValue, xs); sequence.Items.Add (element); } } complex_type.Particle = sequence; schema_element.SchemaType = complex_type; xs.Items.Add (schema_element); GeneratedXmlSchemas.Reprocess (xs); return new QName (schema_element.Name, xs.TargetNamespace); } //Exports 0) { if (colln.Count > 1) throw new Exception ("More than 1 schema found for ns = " + ns); //FIXME: HORRIBLE! foreach (object o in colln) return (o as XmlSchema); } XmlSchema schema = new XmlSchema (); schema.TargetNamespace = ns; schema.ElementFormDefault = XmlSchemaForm.Qualified; GeneratedXmlSchemas.Add (schema); return schema; } PolicyConversionContext ExportPolicy (ServiceEndpoint endpoint, WSBinding binding) { var context = new CustomPolicyConversionContext (endpoint); var elements = endpoint.Binding.CreateBindingElements (); foreach (var element in elements) { var exporter = element as IPolicyExportExtension; if (exporter == null) continue; try { exporter.ExportPolicy (this, context); } catch (Exception ex) { var error = AddError ( "Failed to export endpoint '{0}': policy exporter " + "'{1}' threw an exception: {2}", endpoint.Name, element.GetType (), ex); throw new MetadataExportException (error, ex); } } var assertions = context.GetBindingAssertions (); if (assertions.Count == 0) return context; var doc = new XmlDocument (); var policy = doc.CreateElement ("wsp", "Policy", PolicyImportHelper.PolicyNS); doc.AppendChild (policy); var exactlyOne = doc.CreateElement ("wsp", "ExactlyOne", PolicyImportHelper.PolicyNS); var all = doc.CreateElement ("wsp", "All", PolicyImportHelper.PolicyNS); policy.AppendChild (exactlyOne); exactlyOne.AppendChild (all); foreach (var assertion in assertions) { var imported = doc.ImportNode (assertion, true); all.AppendChild (imported); } binding.Extensions.Add (policy); return context; } } }