Automatically generating WSDL
When a .js file is deployed as a service, the user is able to ask for a WSDL document for that service, using the ?wsdl (WSDL 1.1) and ?wsdl2 (WSDL 2.0) suffixes on the Endpoint address.
The abstract part of a WSDL (interface) is inferred from the .js file itself, making use of metadata specified by the author. The binding-level details are defaulted to a useful baseline, and configurable on a per-endpoint basis using the UI, which will provide a number of options for generic bindings, and eventually allow one to point to a custom binding written in WSDL (can we get away with supporting WSDL 2 only?).
Prototype
To use this file, change the source to provide the following:
- path to the .js file to be used
- targetNamepace URI
- serviceName
- list of function names within the .js file
Run it in Firefox (relies on E4X support) and press the Generate button. Copy the WSDL out into a file.
Annotations
See Javascript Web Service Annotations
TBDs
How to define the targetNamespace. Defining it on a per-operation basis is wrong. Do we need a WSGlobal.targetNamespace property? A global context would give us a place to hang other information.
- How to define and map a custom schema type into the language.
- What it means to have a simple type as a return type.
- How to handle MTOM. PHP defines mappings between xop:Include identifiers and a file system object. This probably means we need to have native file support from Ecmascript.
- The difference between getting the whole blob as a parameter and unwrapping a parameter from an rpc-style operation. Do we need an "unwrap" property to control this?
Mapping
{let serviceName be this.serviceName if it exists, otherwise the name of the .js file, omitting the ".js" extension.}
{let baseNamespace be this.targetNamespace if present, otherwise "http://services.mashup.wso2.org/" + serviceName}
{let schemaTargetNamespace be this.schemaTargetNamespace if present, otherwise baseNamespace + "?xsd"}
<wsdl:description targetNamespace={baseNamespace}>
{if this.documentation}
<wsdl:documentation>{this.documentation}</wsdl:documentation>
{endif}
<wsdl:types>
<xs:schema targetNamespace={schemaTargetNamespace} elementFormDefault="unqualified">
{if this.schemaLocation}
{for each function without functionName.visible=false and functionName.inputType != "#any"}
{let operationName be functionName.operationName, if present, otherwise functionName}
<xs:element name={operationName} >
<xs:complexType>
<xs:sequence>
{for each parameter in function}
<xs:element name={parameterName}
type={if functionName.inputType[parameterName] is present, functionName.inputType[parameterName], otherwise "xs:anyType"}/>
{end for each}
</xs:sequence>
</xs:complexType>
</xs:element>
{if functionName.outputType is present, and has any value but "xs:anyType"}
<xs:element name={operationName + "Response"} >
<xs:complexType>
<xs:sequence>
{for each parameter in function}
<xs:element name="return" type={functionName.outputType}/>
{end for each}
</xs:sequence>
</xs:complexType>
</xs:element>
{end if}
{end for each}
</xs:schema>
</wsdl:types>
<wsdl:interface name={serviceName + "Service"}>
{for each function without functionName.visible=false}
{let operationName be functionName.operationName, if present, otherwise functionName}
<wsdl:operation name={operationName}
pattern="http://www.w3.org/ns/wsdl/in-out"
style="http://www.w3.org/ns/wsdl/style/rpc"
wsdlx:safe={true if functionName.safe = true}
wrpc:signature={
{for each parameter in function}prefix representing schemaTargetNamespace + ":" + parameterName + " #in"{end for each}
+ " return #out"
}>
{if this.documentation}
<wsdl:documentation>{functionName.documentation}</wsdl:documentation>
{endif}
<wsdl:input element={"#none" if no parameters, "#any" if functionName.inputType="#any", otherwise {prefix representing schemaTargetNamespace + ":" + operationName}/>
<wsdl:output element={"#any" if functionName.outputType = "#any", otherwise {prefix representing schemaTargetNamespace + ":" + operationName + "Response"}/>
</wsdl:operation>
{end for each}
</wsdl:interface>
<wsdl:binding name={serviceName + "SOAP12Binding"} interface={prefix representing baseNamespace + ":" + serviceName + "Service"}
type="http://www.w3.org/ns/wsdl/soap" wsoap:version="1.2" wsoap:underlyingProtocol="the soap 1.2 http binding url">
{for each function without functionName.visible=false}
{let operationName be functionName.operationName, if present, otherwise functionName}
<wsdl:operation ref={prefix representing baseNamespace + ":" + operationName}
wsoap:action={baseNamespace + "\" + operationName}/>
{end for each}
</wsdl:binding>
<wsdl:binding name={serviceName + "SOAP11Binding"} interface={prefix representing baseNamespace + ":" + serviceName + "Service"}
type="http://www.w3.org/ns/wsdl/soap" wsoap:version="1.1" wsoap:underlyingProtocol="the soap 1.1 http binding url">
{for each function without functionName.visible=false}
{let operationName be functionName.operationName, if present, otherwise functionName}
<wsdl:operation ref={prefix representing baseNamespace + ":" + operationName}
wsoap:action={baseNamespace + "\" + operationName}/>
{end for each}
</wsdl:binding>
<wsdl:binding name={serviceName + "HTTPBinding"} interface={prefix representing baseNamespace + ":" + serviceName + "Service"}
type="http://www.w3.org/ns/wsdl/http" >
{for each function without functionName.visible=false}
{let operationName be functionName.operationName, if present, otherwise functionName}
<wsdl:operation ref={prefix representing baseNamespace + ":" + operationName}
whttp:location={operationName}/>
{end for each}
</wsdl:binding>
<wsdl:service name={serviceName}>
{if SOAP 1.2 Binding is enabled in the config screen}
<wsdl:endpoint name={serviceName + "SOAP12Endpoint"}
binding={prefix representing baseNamespace + ":" + serviceName + "SOAP12Binding"}
address={endpoint address from Mashup Server}/>
{end if}
{if SOAP 1.1 Binding is enabled in the config screen}
<wsdl:endpoint name={serviceName + "SOAP11Endpoint"}
binding={prefix representing baseNamespace + ":" + serviceName + "SOAP11Binding"}
address={endpoint address from Mashup Server}/>
{end if}
{if HTTP Binding is enabled in the config screen}
<wsdl:endpoint name={serviceName + "HTTPEndpoint"}
binding={prefix representing baseNamespace + ":" + serviceName + "HTTPBinding"}
address={endpoint address from Mashup Server}/>
{end if}
</wsdl:service>
</wsdl:description>
Limitations:
- Mixing user-defined schema types with undefined types (including simple types that require a wrapper.)
- Arrays unsupported
Notes:
- Need to add a "this.schemaLocation" property? Or can this information be gotten from somewhere else?
- Need to add "#any" as a value for an inputType or outputType.
- Assumes parameters aren't namespaced (elementFormDefault="unqualified"). This makes them much easier to access in E4X. But if we improve our unwrapping that rationale won't be very compelling any more. Is "qualified" better for other reasons? (Other than aesthetics?)