blob: 3bc784401aca3d8c71cf8c572a9b8fb60f84b1f5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tuscany.sca.binding.ws.wsdlgen;
//FIXME: trim the import list down to what's really needed
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.wsdl.Binding;
import javax.wsdl.Definition;
import javax.wsdl.Import;
import javax.wsdl.Message;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.PortType;
import javax.wsdl.Service;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.extensions.soap12.SOAP12Address;
import javax.wsdl.extensions.soap12.SOAP12Binding;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLWriter;
import javax.xml.namespace.QName;
import org.apache.tuscany.sca.assembly.AbstractContract;
import org.apache.tuscany.sca.assembly.Component;
import org.apache.tuscany.sca.assembly.CompositeService;
import org.apache.tuscany.sca.binding.ws.WebServiceBinding;
import org.apache.tuscany.sca.interfacedef.Interface;
import org.apache.tuscany.sca.interfacedef.wsdl.WSDLDefinition;
import org.apache.tuscany.sca.interfacedef.wsdl.WSDLInterface;
import org.apache.tuscany.sca.monitor.Monitor;
import org.apache.tuscany.sca.monitor.Problem;
import org.apache.tuscany.sca.monitor.Problem.Severity;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* WSDLServiceGenerator generates a binding WSDL service document.
*
* @version $Rev$ $Date$
*/
public class WSDLServiceGenerator {
// the following switch is temporary for debugging
public static boolean printWSDL; // external code sets this to print generated WSDL
private static final Logger logger = Logger.getLogger(WSDLServiceGenerator.class.getName());
private static final QName TRANSPORT_JMS_QUALIFIED_INTENT =
new QName("http://docs.oasis-open.org/ns/opencsa/sca/200912", "transport.jms");
private static final String DEFAULT_QUEUE_CONNECTION_FACTORY = "TuscanyQueueConnectionFactory";
private static final String ADDRESS = "Address";
private WSDLServiceGenerator() {
// this class has static methods only and cannot be instantiated
}
/**
* Log a warning message.
* @param problem
*/
private static void logWarning(Problem problem) {
Logger problemLogger = Logger.getLogger(problem.getSourceClassName(), problem.getResourceBundleName());
if (problemLogger != null){
problemLogger.logp(Level.WARNING, problem.getSourceClassName(), null, problem.getMessageId(), problem.getMessageParams());
} else {
logger.severe("Can't get logger " + problem.getSourceClassName()+ " with bundle " + problem.getResourceBundleName());
}
}
/**
* Report a warning.
* @param message
* @param binding
* @param parameters
*/
private static void warning(Monitor monitor, String message, WebServiceBinding wsBinding, String... messageParameters) {
Problem problem = monitor.createProblem(WSDLServiceGenerator.class.getName(), "wsdlgen-validation-messages", Severity.WARNING, wsBinding, message, (Object[])messageParameters);
if (monitor != null) {
monitor.problem(problem);
} else {
logWarning(problem);
}
}
/**
* Report an error.
* @param message
* @param binding
* @param parameters
*/
private static void error(Monitor monitor, String message, WebServiceBinding wsBinding, String... messageParameters) {
Problem problem = monitor.createProblem(WSDLServiceGenerator.class.getName(), "wsdlgen-validation-messages", Severity.ERROR, wsBinding, message, (Object[])messageParameters);
if (monitor != null) {
monitor.problem(problem);
} else {
throw new WSDLGenerationException(problem.toString(), null, problem);
}
}
/**
* Generate a suitably configured WSDL definition
*/
protected static Definition configureWSDLDefinition(WebServiceBinding wsBinding,
Component component,
AbstractContract contract,
Monitor monitor) {
//[nash] changes to the builder sequence avoid calling this for a CompositeService
assert !(contract instanceof CompositeService);
String contractName = contract.getName();
List<Port> ports = new ArrayList<Port>();
WSDLDefinition wsdlDefinition = wsBinding.getUserSpecifiedWSDLDefinition();
if (wsdlDefinition == null) {
error(monitor, "NoWsdlInterface", wsBinding, component.getName(), contract.getName());
return null;
}
Definition def = wsdlDefinition.getDefinition();
if (wsdlDefinition.getBinding() == null) {
// The WSDL document was provided by the user. Generate a new
// WSDL document with imports from the user-provided document.
WSDLFactory factory = null;
try {
factory = WSDLFactory.newInstance();
} catch (WSDLException e) {
throw new WSDLGenerationException(e);
}
Definition newDef = factory.newDefinition();
// Construct a target namespace from the base URI of the user's
// WSDL document (is this what we should be using?) and a path
// computed according to the SCA Web Service binding spec.
String nsName = component.getName() + "/" + contractName;
String namespaceURI = null;
try {
URI userTNS = new URI(def.getTargetNamespace());
namespaceURI = userTNS.resolve("/" + nsName).toString();
} catch (URISyntaxException e1) {
throw new WSDLGenerationException(e1);
} catch (IllegalArgumentException e2) {
throw new WSDLGenerationException(e2);
}
// set name and targetNamespace attributes on the definition
String defsName = component.getName() + "." + contractName;
newDef.setQName(new QName(namespaceURI, defsName));
newDef.setTargetNamespace(namespaceURI);
newDef.addNamespace("tns", namespaceURI);
// set wsdl namespace prefix on the definition
newDef.addNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/");
// import the service or reference interface portType
List<WSDLDefinition> imports = new ArrayList<WSDLDefinition>();
Interface interfaze = wsBinding.getBindingInterfaceContract().getInterface();
if (interfaze instanceof WSDLInterface) {
PortType portType = ((WSDLInterface)interfaze).getPortType();
boolean ok = importPortType(portType, wsdlDefinition, newDef, imports);
if (!ok) {
error(monitor, "PortTypeNotFound", wsBinding, portType.getQName().toString(),
component.getName(), contract.getName());
}
}
// import an existing binding if specified
Binding binding = wsBinding.getBinding();
if (binding != null) {
boolean ok = importBinding(binding, wsdlDefinition, newDef, imports);
if (ok) {
boolean ok2 = importPortType(binding.getPortType(), wsdlDefinition, newDef, imports);
if (!ok2) {
error(monitor, "PortTypeNotFound", wsBinding, binding.getPortType().getQName().toString(),
component.getName(), contract.getName());
}
} else {
error(monitor, "BindingNotFound", wsBinding, binding.getQName().toString(),
component.getName(), contract.getName());
}
}
// import bindings and portTypes needed by services and ports
QName serviceQName = wsBinding.getServiceName();
String portName = wsBinding.getPortName();
if (serviceQName != null) {
Service service = def.getService(serviceQName);
if (portName != null) {
Port port = service.getPort(portName);
Port newPort = copyPort(newDef, port, wsBinding);
if (newPort != null) {
importBinding(port.getBinding(), wsdlDefinition, newDef, imports);
ports.add(newPort);
} else {
error(monitor, "InvalidPort", wsBinding, serviceQName.toString(), portName,
component.getName(), contract.getName());
}
} else {
for (Object port : service.getPorts().values()) {
Port newPort = copyPort(newDef, (Port)port, wsBinding);
if (newPort != null) {
importBinding(((Port)port).getBinding(), wsdlDefinition, newDef, imports);
ports.add(newPort);
} else {
// not an error, just ignore the port
warning(monitor, "IgnoringPort", wsBinding, serviceQName.toString(), ((Port)port).getName(),
component.getName(), contract.getName());
}
}
if (ports.size() == 0) {
error(monitor, "NoValidPorts", wsBinding, serviceQName.toString(),
component.getName(), contract.getName());
}
}
}
// replace original WSDL definition by the generated definition
def = newDef;
} else {
// The WSDL definition was generated by Interface2WSDLGenerator.
// Reuse it instead of creating a new definition here.
}
// add a service and ports to the generated definition
WSDLDefinitionGenerator helper =
new WSDLDefinitionGenerator(BindingWSDLGenerator.requiresSOAP12(wsBinding));
WSDLInterface wi = (WSDLInterface)wsBinding.getBindingInterfaceContract().getInterface();
PortType portType = wi.getPortType();
Service service = helper.createService(def, portType);
if (wsBinding.getBinding() == null && ports.size() == 0) {
Binding binding = helper.createBinding(def, portType);
if (BindingWSDLGenerator.requiresSOAP12(wsBinding)) {
def.addNamespace("SOAP12", "http://schemas.xmlsoap.org/wsdl/soap12/");
} else {
def.addNamespace("SOAP11", "http://schemas.xmlsoap.org/wsdl/soap/");
}
helper.createBindingOperations(def, binding, portType);
binding.setUndefined(false);
// set binding style based on the interface specified by the
// user if one is available
// TODO - set encoding style also currently default to literal
if (wsdlDefinition != null && wsdlDefinition.getDefinition() != null){
Message firstMessage = (Message)wsdlDefinition.getDefinition().getMessages().values().iterator().next();
Part firstPart = (Part)firstMessage.getParts().values().iterator().next();
if (firstPart.getTypeName() != null){
for (Object ext : binding.getExtensibilityElements()){
if (ext instanceof SOAPBinding){
((SOAPBinding)ext).setStyle("rpc");
break;
}
}
}
}
def.addBinding(binding);
String endpointURI = computeActualURI(wsBinding, null);
Port port = helper.createPort(def, binding, service, endpointURI);
wsBinding.setService(service);
wsBinding.setPort(port);
} else {
if (ports.size() > 0) {
// there are one or more user-specified valid ports
for (Port port : ports) {
service.addPort(port);
}
if (ports.size() == 1) {
// only one port, so use it
wsBinding.setPort(ports.get(0));
} else {
// multiple ports, make them all available
wsBinding.setPort(null);
}
} else {
// no valid user-specified ports, so create a suitably configured port
String endpointURI = computeActualURI(wsBinding, null);
Port port = helper.createPort(def, wsBinding.getBinding(), service, endpointURI);
if (BindingWSDLGenerator.requiresSOAP12(wsBinding)) {
def.addNamespace("SOAP12", "http://schemas.xmlsoap.org/wsdl/soap12/");
} else {
def.addNamespace("SOAP11", "http://schemas.xmlsoap.org/wsdl/soap/");
}
wsBinding.setPort(port);
}
wsBinding.setService(service);
}
// for debugging
if (printWSDL) {
try {
System.out.println("Generated WSDL for " + component.getName() + "/" + contractName);
WSDLWriter writer = javax.wsdl.factory.WSDLFactory.newInstance().newWSDLWriter();
writer.writeWSDL(def, System.out);
} catch (WSDLException e) {
throw new WSDLGenerationException(e);
}
}
return def;
}
private static boolean importPortType(PortType portType,
WSDLDefinition wsdlDef,
Definition newDef,
List<WSDLDefinition> imports) {
return addImport(portType.getQName(), PortType.class, wsdlDef, newDef, imports);
}
private static boolean importBinding(Binding binding,
WSDLDefinition wsdlDef,
Definition newDef,
List<WSDLDefinition> imports) {
boolean ok = addImport(binding.getQName(), Binding.class, wsdlDef, newDef, imports);
if (ok) {
List bindingExtensions = binding.getExtensibilityElements();
for (final Object extension : bindingExtensions) {
if (extension instanceof SOAPBinding) {
newDef.addNamespace("SOAP11", "http://schemas.xmlsoap.org/wsdl/soap/");
}
if (extension instanceof SOAP12Binding) {
newDef.addNamespace("SOAP12", "http://schemas.xmlsoap.org/wsdl/soap12/");
}
}
}
return ok;
}
private static boolean addImport(QName name,
Class type,
WSDLDefinition wsdlDef,
Definition newDef,
List<WSDLDefinition> imports) {
String namespace = name.getNamespaceURI();
if (newDef.getImports(namespace) == null) {
WSDLDefinition impDef = findDefinition(wsdlDef, name, type);
if (impDef != null) {
Import imp = newDef.createImport();
imp.setNamespaceURI(namespace);
imp.setLocationURI(impDef.getURI().toString());
imp.setDefinition(impDef.getDefinition());
newDef.addNamespace("ns" + imports.size(), namespace);
newDef.addImport(imp);
imports.add(impDef);
return true;
} else {
// import was not added because element not found
return false;
}
}
return true;
}
private static WSDLDefinition findDefinition(WSDLDefinition wsdlDef, QName name, Class type) {
if (wsdlDef == null || name == null) {
return wsdlDef;
}
if (wsdlDef.getURI() != null) { // not a facade
Definition def = wsdlDef.getDefinition();
Map types = type == PortType.class ? def.getPortTypes() : def.getBindings();
if (types.get(name) != null) {
return wsdlDef;
}
}
for (WSDLDefinition impDef : wsdlDef.getImportedDefinitions()) {
WSDLDefinition d = findDefinition(impDef, name, type);
if (d != null) {
return d;
}
}
return null;
}
private static Port copyPort(Definition def, Port port, WebServiceBinding wsBinding) {
Port newPort = def.createPort();
newPort.setName(port.getName());
newPort.setBinding(port.getBinding());
List portExtensions = port.getExtensibilityElements();
for (final Object extension : portExtensions) {
ExtensibilityElement newExt = null;
if (extension instanceof SOAPAddress) {
def.addNamespace("SOAP11", "http://schemas.xmlsoap.org/wsdl/soap/");
try {
newExt = def.getExtensionRegistry().createExtension(
Port.class, WSDLDefinitionGenerator.SOAP_ADDRESS);
} catch (WSDLException e) {
}
String uri = computeActualURI(wsBinding, port);
((SOAPAddress)newExt).setLocationURI(uri);
newPort.addExtensibilityElement(newExt);
} else if (extension instanceof SOAP12Address) {
def.addNamespace("SOAP12", "http://schemas.xmlsoap.org/wsdl/soap12/");
try {
newExt = def.getExtensionRegistry().createExtension(
Port.class, WSDLDefinitionGenerator.SOAP12_ADDRESS);
} catch (WSDLException e) {
}
String uri = computeActualURI(wsBinding, port);
((SOAP12Address)newExt).setLocationURI(uri);
newPort.addExtensibilityElement(newExt);
} else {
// we don't support ports with other extensibility elements such as HTTPAddress
return null;
}
}
return newPort;
}
/**
* Compute the endpoint URI based on section 2.1.1 of the WS binding Specification
* 1. The URIs in the endpoint(s) of the referenced WSDL, which may be relative
* 2. The URI specified by the wsa:Address element of the
* wsa:EndpointReference, which may be relative
* 3. The explicitly stated URI in the "uri" attribute of the binding.ws element,
* which may be relative,
* 4. The implicit URI as defined by in section 1.7 in the SCA Assembly Specification
* If the <binding.ws> has no wsdlElement but does have a uri attribute then
* the uri takes precedence over any implicitly used WSDL.
*
*/
private static String computeActualURI(WebServiceBinding wsBinding, Port port) {
URI eprURI = null;
if (wsBinding.getEndPointReference() != null) {
eprURI = getEPR(wsBinding);
}
URI wsdlURI = null;
if (wsBinding.getServiceName() != null && wsBinding.getBindingName() == null) {
// <binding.ws> explicitly points at a WSDL port, may be a relative URI
wsdlURI = getEndpoint(port);
}
// if the WSDL port/endpoint has an absolute URI use that
if (wsdlURI != null && wsdlURI.isAbsolute()) {
return wsdlURI.toString();
}
// if the wsa:EndpointReference has an address element with an absolute URI use that
if (eprURI != null && eprURI.isAbsolute()) {
return eprURI.toString();
}
// either there is no WSDL port endpoint URI or that URI is relative
String actualURI = wsBinding.getURI();
if (eprURI != null && eprURI.toString().length() != 0) {
// there is a relative URI in the binding EPR
actualURI = actualURI + "/" + eprURI;
}
if (wsdlURI != null && wsdlURI.toString().length() != 0) {
// there is a relative URI in the WSDL port
actualURI = actualURI + "/" + wsdlURI;
}
if (actualURI != null) {
actualURI = URI.create(actualURI).normalize().toString();
}
return actualURI;
}
private static URI getEPR(WebServiceBinding wsBinding) {
NodeList nodeList = wsBinding.getEndPointReference().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode instanceof Element && ADDRESS.equals(childNode.getLocalName())) {
NodeList addrNodes = childNode.getChildNodes();
for (int j = 0; j < addrNodes.getLength(); j++) {
Node addrNode = addrNodes.item(j);
if (addrNode instanceof Text) {
return URI.create(((Text)addrNode).getWholeText());
}
}
}
}
return null;
}
/**
* Returns the endpoint of a given port.
*/
private static URI getEndpoint(Port wsdlPort) {
if (wsdlPort != null) {
List<?> wsdlPortExtensions = wsdlPort.getExtensibilityElements();
for (Object extension : wsdlPortExtensions) {
if (extension instanceof SOAPAddress) {
String uri = ((SOAPAddress)extension).getLocationURI();
return (uri == null || "".equals(uri)) ? null : URI.create(uri);
}
if (extension instanceof SOAP12Address) {
SOAP12Address address = (SOAP12Address)extension;
String uri = address.getLocationURI();
return (uri == null || "".equals(uri)) ? null : URI.create(uri);
}
}
}
return null;
}
}