/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.internal.soa.esb.webservice;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.wsdl.Binding;
import javax.wsdl.BindingFault;
import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.BindingOutput;
import javax.wsdl.Definition;
import javax.wsdl.Fault;
import javax.wsdl.Input;
import javax.wsdl.Message;
import javax.wsdl.Operation;
import javax.wsdl.Output;
import javax.wsdl.Part;
import javax.wsdl.PortType;
import javax.wsdl.Types;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.ExtensionRegistry;
import javax.wsdl.extensions.ExtensionSerializer;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.wsdl.factory.WSDLFactory;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.jboss.internal.soa.esb.util.XMLHelper;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.Service;
import org.jboss.soa.esb.dom.YADOMUtil;
import org.jboss.soa.esb.listeners.config.WebserviceInfo;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.ibm.wsdl.extensions.schema.SchemaImpl;
import com.ibm.wsdl.extensions.soap.SOAPAddressImpl;
import com.ibm.wsdl.extensions.soap.SOAPBindingImpl;
import com.ibm.wsdl.extensions.soap.SOAPBodyImpl;
import com.ibm.wsdl.extensions.soap.SOAPFaultImpl;
import com.ibm.wsdl.extensions.soap.SOAPOperationImpl;
public class ESBContractGenerator {
private static final String SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema" ;
private static final String SCHEMA_NAME = "schema" ;
private static final String SCHEMA_PREFIX = "xs" ;
private static final String SCHEMA_ELEMENT = SCHEMA_PREFIX + ':' + SCHEMA_NAME ;
private static final String SCHEMA_TARGETNAMESPACE_ATTRIBUTE = "targetNamespace" ;
private static final String SCHEMA_VERSION_ATTRIBUTE = "version" ;
private static final String SCHEMA_VERSION_VALUE = "1.0" ;
private static final String SCHEMA_INCLUDE_ELEMENT = SCHEMA_PREFIX + ":include" ;
private static final String IMPORT_SCHEMA_LOCATION_ATTRIBUTE = "schemaLocation" ;
private static final QName XSD_QN = new QName(SCHEMA_NAMESPACE, SCHEMA_NAME);
private static final String WSDL_NAMESPACE = "http://schemas.xmlsoap.org/wsdl/" ;
private static final String WSDL_REQUIRED = "required" ;
private static final String WSAW_NAMESPACE = "http://www.w3.org/2006/05/addressing/wsdl" ;
private static final String WSAW_PREFIX = "wsaw" ;
private static final QName WSAW_ACTION_QN = new QName(WSAW_NAMESPACE, "Action", WSAW_PREFIX) ;
private static final QName WSAW_USING_ADDRESSING_QN = new QName(WSAW_NAMESPACE, "UsingAddressing", WSAW_PREFIX) ;
private static WSDLFactory wsdlFactory ;
public static String generateWSDL(final WebserviceInfo serviceConfig, final ESBServiceEndpointInfo serviceInfo, final ClassLoader classLoader) throws ConfigurationException {
final Definition def = getWSDLFactory().newDefinition() ;
final String namespace = serviceInfo.getNamespace() ;
def.setTargetNamespace(namespace);
def.addNamespace("tns", namespace);
def.addNamespace("soap", "http://schemas.xmlsoap.org/wsdl/soap/");
if (serviceInfo.isAddressing()) {
def.getExtensionRegistry().registerSerializer(Binding.class, WSAW_USING_ADDRESSING_QN, new UsingAddressingSerializer()) ;
def.addNamespace(WSAW_PREFIX, WSAW_NAMESPACE) ;
}
// add types
Types types = def.createTypes();
def.setTypes(types);
// Keeps track of schema types added to avoid duplicates.
Set<String> schemasAdded = new HashSet<String>();
Message reqMessage = null;
Message resMessage = null;
List<Message> faultMessages = null;
String inXsd = serviceConfig.getInXsd();
String outXsd = serviceConfig.getOutXsd();
String faultXsd = serviceConfig.getFaultXsd();
int nsSuffixCounter = 0 ;
if (inXsd != null) {
final QName[] elementNames = getElementNames(inXsd, classLoader, schemasAdded, def, ++nsSuffixCounter) ;
if ((elementNames == null) || (elementNames.length == 0)) {
throw new ConfigurationException("File defined in inXsd attribute '" + inXsd + "' not found in classpath or contains no elements.");
} else {
reqMessage = addMessage(def, elementNames[0], serviceInfo.getRequestName(), "in");
}
}
if (outXsd != null) {
final QName[] elementNames = getElementNames(outXsd, classLoader, schemasAdded, def, ++nsSuffixCounter) ;
if ((elementNames == null) || (elementNames.length == 0)) {
throw new ConfigurationException("File defined in outXsd attribute '" + outXsd + "' not found in classpath or contains no elements.");
} else {
resMessage = addMessage(def, elementNames[0], serviceInfo.getResponseName(), "out");
}
}
if ((faultXsd != null) && !serviceInfo.isOneWay()) {
final String[] xsds = faultXsd.split(",") ;
faultMessages = new ArrayList<Message>();
for(String xsd: xsds) {
final QName[] elementNames = getElementNames(xsd, classLoader, schemasAdded, def, ++nsSuffixCounter) ;
if ((elementNames == null) || (elementNames.length == 0)) {
throw new ConfigurationException("File defined in faultXsd attribute '" + xsd + "' not found in classpath or contains no elements.");
} else {
addFaultMessage(faultMessages, def, elementNames, serviceInfo.getFaultName(), "fault");
}
}
}
PortType portType = addPortType(def, serviceInfo, reqMessage,
resMessage, faultMessages);
Binding binding = addBinding(def, serviceInfo, portType);
addService(def, serviceInfo, binding);
StringWriter sw = new java.io.StringWriter();
try {
getWSDLFactory().newWSDLWriter().writeWSDL(def, sw);
} catch (WSDLException e) {
final Service service = serviceConfig.getService() ;
throw new ConfigurationException("Failed to generate wsdl for service:" + service.getCategory() + "/" + service.getName() , e);
}
return sw.toString();
}
private static QName[] getElementNames(final String resource, final ClassLoader classLoader, final Set<String> schemasAdded,
Definition def, int nsSuffixCounter) throws ConfigurationException
{
final String schemaLocation = getRelativeResource(resource) ;
final InputStream is = getResourceAsStream(schemaLocation, classLoader) ;
if (is == null)
{
return null ;
}
final Document doc ;
try
{
try
{
doc = YADOMUtil.parseStream(is, false, false);
}
finally
{
is.close() ;
}
}
catch (final Exception ex)
{
throw new ConfigurationException("Unexpected exception parsing schema resource: " + schemaLocation, ex) ;
}
final Element schemaElement = doc.getDocumentElement() ;
final String targetNamespace = YADOMUtil.getAttribute(schemaElement, SCHEMA_TARGETNAMESPACE_ATTRIBUTE, "") ;
final boolean added ;
try
{
added = add(schemaElement, schemasAdded) ;
}
catch (final Exception ex)
{
throw new ConfigurationException("Unexpected exception adding schema to known resources: " + schemaLocation, ex) ;
}
if (added)
{
if (def.getNamespace(targetNamespace) == null)
{
def.addNamespace("esbns" + nsSuffixCounter, targetNamespace) ;
}
final Types types = def.getTypes() ;
SchemaImpl schemaImpl = new SchemaImpl();
final Document schemaDoc = YADOMUtil.createDocument() ;
final Element xsdElement = schemaDoc.createElementNS(SCHEMA_NAMESPACE, SCHEMA_ELEMENT) ;
xsdElement.setAttribute(SCHEMA_TARGETNAMESPACE_ATTRIBUTE, targetNamespace) ;
xsdElement.setAttribute(SCHEMA_VERSION_ATTRIBUTE, SCHEMA_VERSION_VALUE) ;
final Element xsdInclude = schemaDoc.createElementNS(SCHEMA_NAMESPACE, SCHEMA_INCLUDE_ELEMENT) ;
xsdInclude.setAttribute(IMPORT_SCHEMA_LOCATION_ATTRIBUTE, schemaLocation) ;
xsdElement.appendChild(xsdInclude) ;
schemaImpl.setElement(xsdElement) ;
schemaImpl.setElementType(XSD_QN);
types.addExtensibilityElement(schemaImpl);
}
final NodeList nodes = YADOMUtil.getNodeList(schemaElement, "/schema/element") ;
final int numNodes = nodes.getLength() ;
final QName[] result = new QName[numNodes] ;
for (int count = 0 ; count < numNodes ; count++)
{
final Node node = nodes.item(count) ;
result[count] = new QName(targetNamespace, YADOMUtil.getAttribute((Element) node, "name", "")) ;
}
return result ;
}
private static String getRelativeResource(final String resource)
{
final int length = resource.length() ;
for(int count = 0 ; count < length ; count++)
{
if (resource.charAt(count) != '/')
{
return resource.substring(count) ;
}
}
return "" ;
}
private static InputStream getResourceAsStream(final String resource, final ClassLoader classLoader)
{
if (classLoader != null)
{
return classLoader.getResourceAsStream(resource) ;
}
return null ;
}
private static boolean add(final Element schemaElement, final Set<String> schemasAdded) throws SAXException, IOException, TransformerException, ParserConfigurationException
{
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
transformer.transform(new DOMSource(schemaElement), result);
String newType = writer.toString();
if (schemasAdded.size() == 0)
{
return schemasAdded.add(newType);
}
else
{
boolean addSchema = true ;
for (String existingType : schemasAdded)
{
if (XMLHelper.compareXMLContent(existingType, newType))
{
addSchema = false ;
break ;
}
}
if (addSchema)
{
return schemasAdded.add(newType);
}
}
return false;
}
private static Message addMessage(Definition def, QName elementName, String msgName, String partName) {
// add request message
Message msg = def.createMessage();
msg.setQName(new QName(def.getTargetNamespace(), msgName));
msg.setUndefined(false);
Part part = def.createPart();
part.setName(partName);
part.setElementName(elementName) ;
msg.addPart(part);
def.addMessage(msg);
return msg;
}
private static void addFaultMessage(final List<Message> faultMessages,
Definition def, QName[] elementNames, String msgName, String partName) {
// add request message
final int numElements = (elementNames == null ? 0 : elementNames.length) ;
for (int i = 0; i < numElements; i++) {
final int nameIndex = i + 1;
Message msg = def.createMessage();
msg.setQName(new QName(def.getTargetNamespace(), msgName
+ nameIndex));
msg.setUndefined(false);
Part part = def.createPart();
part.setName(partName + nameIndex);
part.setElementName(elementNames[i]);
msg.addPart(part);
def.addMessage(msg);
faultMessages.add(msg);
}
}
private static PortType addPortType(Definition def, final ESBServiceEndpointInfo serviceInfo,
Message inMessage, Message outMessage, List<Message> faultMessages) {
// add port type
PortType portType = def.createPortType();
portType.setQName(new QName(def.getTargetNamespace(), serviceInfo.getPortName())) ;
Operation op = def.createOperation();
op.setUndefined(false);
op.setName(serviceInfo.getOperationName());
if (inMessage != null) {
Input in = def.createInput();
in.setMessage(inMessage);
in.setName(inMessage.getQName().getLocalPart());
if (serviceInfo.isAddressing()) {
in.setExtensionAttribute(WSAW_ACTION_QN, serviceInfo.getRequestAction()) ;
}
op.setInput(in);
}
if (outMessage != null) {
Output out = def.createOutput();
out.setMessage(outMessage);
out.setName(outMessage.getQName().getLocalPart());
if (serviceInfo.isAddressing()) {
out.setExtensionAttribute(WSAW_ACTION_QN, serviceInfo.getResponseAction()) ;
}
op.setOutput(out);
}
int count = 1 ;
if (faultMessages != null) {
for (Message message : faultMessages) {
Fault fault = def.createFault();
fault.setMessage(message);
fault.setName("fault" + (count++));
op.addFault(fault);
}
}
portType.addOperation(op);
portType.setUndefined(false);
def.addPortType(portType);
return portType;
}
private static Binding addBinding(Definition def, final ESBServiceEndpointInfo serviceInfo, PortType portType) {
// add binding
Binding binding = def.createBinding();
binding.setUndefined(false);
binding.setPortType(portType);
binding.setQName(new QName(def.getTargetNamespace(), serviceInfo.getBindingName())) ;
SOAPBindingImpl soapBinding = new SOAPBindingImpl();
soapBinding.setStyle("document");
soapBinding.setTransportURI("http://schemas.xmlsoap.org/soap/http");
binding.addExtensibilityElement(soapBinding);
if (serviceInfo.isAddressing()) {
binding.addExtensibilityElement(new UsingAddressingExtension()) ;
}
BindingOperation bop = def.createBindingOperation();
bop.setName(serviceInfo.getOperationName());
Operation op = (Operation) portType.getOperations().get(0);
bop.setOperation(op);
SOAPOperation soapOperation = new SOAPOperationImpl() ;
soapOperation.setSoapActionURI(serviceInfo.getResponseAction()) ;
bop.addExtensibilityElement(soapOperation) ;
if (op.getInput() != null) {
BindingInput binput = def.createBindingInput();
bop.setBindingInput(binput);
SOAPBodyImpl soapBody = new SOAPBodyImpl();
soapBody.setUse("literal");
binput.setName(serviceInfo.getRequestName()) ;
binput.addExtensibilityElement(soapBody);
}
if (op.getOutput() != null) {
BindingOutput boutput = def.createBindingOutput();
bop.setBindingOutput(boutput);
SOAPBodyImpl soapBody = new SOAPBodyImpl();
soapBody.setUse("literal");
boutput.setName(serviceInfo.getResponseName()) ;
boutput.addExtensibilityElement(soapBody);
}
final Map faults = op.getFaults() ;
if (faults != null) {
Iterator iterator = op.getFaults().values().iterator();
while (iterator.hasNext()) {
Fault fault = (Fault) iterator.next();
BindingFault bfault = def.createBindingFault();
bfault.setName(fault.getName());
bop.addBindingFault(bfault);
SOAPFaultImpl soapFault = new SOAPFaultImpl();
soapFault.setName(fault.getName());
soapFault.setUse("literal");
bfault.addExtensibilityElement(soapFault);
}
}
binding.addBindingOperation(bop);
def.addBinding(binding);
return binding;
}
private static void addService(Definition def, final ESBServiceEndpointInfo serviceInfo, Binding binding) {
// create service
javax.wsdl.Service service = def.createService();
service.setQName(new QName(def.getTargetNamespace(), serviceInfo.getServiceName()));
javax.wsdl.Port port = def.createPort();
port.setBinding(binding);
port.setName(serviceInfo.getPortName());
SOAPAddressImpl soapAddress = new SOAPAddressImpl();
soapAddress.setLocationURI("http://change_this_URI/"+serviceInfo.getServletPath());
port.addExtensibilityElement(soapAddress);
service.addPort(port);
def.addService(service);
}
private synchronized static WSDLFactory getWSDLFactory()
throws ConfigurationException
{
if (wsdlFactory == null)
{
try
{
wsdlFactory = AccessController.doPrivileged(new PrivilegedExceptionAction<WSDLFactory>() {
public WSDLFactory run() throws WSDLException
{
return WSDLFactory.newInstance();
}
}) ;
}
catch (final PrivilegedActionException pae)
{
throw new ConfigurationException("Failed to instantiate the WSDL factory", pae.getCause()) ;
}
}
return wsdlFactory ;
}
private static class UsingAddressingExtension implements ExtensibilityElement {
public QName getElementType() {
return WSAW_USING_ADDRESSING_QN ;
}
public Boolean getRequired() {
return Boolean.TRUE ;
}
public void setElementType(final QName qname) {
}
public void setRequired(final Boolean required) {
}
}
private static class UsingAddressingSerializer implements ExtensionSerializer
{
public void marshall(final Class parentType, final QName elementType,
final ExtensibilityElement extension, final PrintWriter pw,
final Definition definition, final ExtensionRegistry registry)
throws WSDLException {
if (extension != null) {
final String prefix = definition.getPrefix(elementType.getNamespaceURI()) ;
pw.print(" <"+prefix+":"+elementType.getLocalPart()) ;
if (extension.getRequired().booleanValue()) {
final String wsdlPrefix = definition.getPrefix(WSDL_NAMESPACE) ;
pw.print(" " + wsdlPrefix + ":" + WSDL_REQUIRED + "=\"true\"") ;
}
pw.println("/>") ;
}
}
}
}