/*************************************************************************** * Copyright (C) 2010-2011 by Fabrizio Montesi <famontesi@gmail.com> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * * For details about the authors of this software, see the AUTHORS file. * ***************************************************************************/ package joliex.wsdl; import com.ibm.wsdl.extensions.schema.SchemaImpl; import com.sun.xml.xsom.XSSchemaSet; import com.sun.xml.xsom.parser.XSOMParser; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.wsdl.Binding; import javax.wsdl.Definition; import javax.wsdl.Fault; import javax.wsdl.Message; import javax.wsdl.Operation; import javax.wsdl.Part; import javax.wsdl.Port; import javax.wsdl.PortType; import javax.wsdl.Service; import javax.wsdl.Types; import javax.wsdl.extensions.ExtensibilityElement; import javax.wsdl.extensions.http.HTTPAddress; import javax.wsdl.extensions.http.HTTPBinding; import javax.wsdl.extensions.soap.SOAPAddress; import javax.wsdl.extensions.soap.SOAPBinding; import javax.xml.namespace.QName; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import jolie.lang.Constants; import jolie.lang.NativeType; import jolie.lang.parse.ast.types.TypeDefinition; import jolie.lang.parse.ast.types.TypeDefinitionLink; import jolie.lang.parse.ast.types.TypeInlineDefinition; import jolie.lang.parse.context.URIParsingContext; import jolie.util.Pair; import jolie.xml.xsd.XsdToJolieConverter; import jolie.xml.xsd.XsdUtils; import jolie.xml.xsd.impl.XsdToJolieConverterImpl; import joliex.wsdl.impl.Interface; import joliex.wsdl.impl.OutputPort; import org.w3c.dom.Element; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * * @author Fabrizio Montesi */ public class WSDLConverter { private enum Style { DOCUMENT, HTTP, RPC; } private final Writer writer; private final Definition definition; private int indentationLevel = 0; private Map< String, OutputPort > outputPorts = new HashMap< String, OutputPort >(); private Map< String, Interface > interfaces = new HashMap< String, Interface >(); private List< TypeDefinition > typeDefinitions = new ArrayList< TypeDefinition >(); private final XSOMParser schemaParser; private final TransformerFactory transformerFactory; public WSDLConverter( Definition definition, Writer writer ) { this.writer = writer; this.definition = definition; transformerFactory = TransformerFactory.newInstance(); schemaParser = new XSOMParser(); schemaParser.setErrorHandler( new ErrorHandler() { public void warning( SAXParseException exception ) throws SAXException { throw new SAXException( exception ); } public void error( SAXParseException exception ) throws SAXException { throw new SAXException( exception ); } public void fatalError( SAXParseException exception ) throws SAXException { throw new SAXException( exception ); } } ); } private void indent() { indentationLevel++; } private void unindent() { indentationLevel--; } private void writeLine( String s ) throws IOException { for( int i = 0; i < indentationLevel; i++ ) { writer.write( "\t" ); } writer.write( s ); writer.write( '\n' ); } private void parseSchemaElement( Element element ) throws IOException { try { Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty( "indent", "yes" ); StringWriter sw = new StringWriter(); StreamResult result = new StreamResult( sw ); DOMSource source = new DOMSource( element ); transformer.transform( source, result ); InputSource schemaSource = new InputSource( new StringReader( sw.toString() ) ); schemaSource.setSystemId( definition.getDocumentBaseURI() ); schemaParser.parse( schemaSource ); } catch( TransformerConfigurationException e ) { throw new IOException( e ); } catch( TransformerException e ) { throw new IOException( e ); } catch( SAXException e ) { throw new IOException( e ); } } public void convert() throws IOException { convertTypes(); Map< QName, Service > services = definition.getServices(); for( Entry< QName, Service > service : services.entrySet() ) { convertService( service.getValue() ); } writeData(); } private void convertTypes() throws IOException { Types types = definition.getTypes(); if ( types != null ) { List< ExtensibilityElement > list = types.getExtensibilityElements(); for( ExtensibilityElement element : list ) { if ( element instanceof SchemaImpl ) { Element schemaElement = ((SchemaImpl)element).getElement(); // We need to inject the namespaces declared in parent nodes into the schema element Map< String, String > namespaces = definition.getNamespaces(); for( Entry< String, String > entry : namespaces.entrySet() ) { if ( schemaElement.getAttribute( "xmlns:" + entry.getKey() ).isEmpty() ) { schemaElement.setAttribute( "xmlns:" + entry.getKey(), entry.getValue() ); } } parseSchemaElement( schemaElement ); } } try { XSSchemaSet schemaSet = schemaParser.getResult(); if ( schemaSet == null ) { throw new IOException( "An error occurred while parsing the WSDL types section" ) ; } XsdToJolieConverter schemaConverter = new XsdToJolieConverterImpl( schemaSet, false, null ); typeDefinitions.addAll( schemaConverter.convert() ); } catch( SAXException e ) { throw new IOException( e ); } catch( XsdToJolieConverter.ConversionException e ) { throw new IOException( e ); } } } private String getCardinalityString( TypeDefinition type ) { if ( type.cardinality().equals( Constants.RANGE_ONE_TO_ONE ) ) { return ""; } else if ( type.cardinality().min() == 0 && type.cardinality().max() == 1 ) { return "?"; } else if ( type.cardinality().min() == 0 && type.cardinality().max() == Integer.MAX_VALUE ) { return "*"; } else { return new StringBuilder() .append( '[' ) .append( type.cardinality().min() ) .append( ',' ) .append( type.cardinality().max() ) .append( ']' ) .toString(); } } private void writeType( TypeDefinition type, boolean subType ) throws IOException { StringBuilder builder = new StringBuilder(); if ( subType ) { builder.append( '.' ); } else { builder.append( "type " ); } builder.append( type.id() ) .append( getCardinalityString( type ) ) .append( ':' ); if ( type instanceof TypeDefinitionLink ) { TypeDefinitionLink link = (TypeDefinitionLink)type; builder.append( link.linkedTypeName() ); if ( subType == false ) { builder.append( '\n' ); } writeLine( builder.toString() ); } else if ( type.untypedSubTypes() ) { builder.append( "undefined" ); writeLine( builder.toString() ); } else { builder.append( nativeTypeToString( type.nativeType() ) ); if ( type.hasSubTypes() ) { builder.append( " {" ); } writeLine( builder.toString() ); if ( type.hasSubTypes() ) { indent(); for( Entry< String, TypeDefinition > entry : type.subTypes() ) { writeType( entry.getValue(), true ); } unindent(); } if ( type.hasSubTypes() ) { writeLine( "}" ); } if ( subType == false ) { writeLine( "" ); } } } private static String nativeTypeToString( NativeType nativeType ) { return (nativeType == null) ? "" : nativeType.id(); } private void writeData() throws IOException { for( TypeDefinition typeDefinition : typeDefinitions ) { writeType( typeDefinition, false ); } for( Entry< String, Interface > entry : interfaces.entrySet() ) { writeInterface( entry.getValue() ); writeLine( "" ); } for( Entry< String, OutputPort > entry : outputPorts.entrySet() ) { writeOutputPort( entry.getValue() ); writeLine( "" ); } writer.flush(); } private void writeOutputPort( OutputPort port ) throws IOException { writeLine( "outputPort " + port.name() + " {" ); writeLine( "Location: \"" + port.location() + "\"" ); writeLine( "Protocol: " + port.protocol() ); writeLine( "Interfaces: " + port.interfaceName() ); writeLine( "}" ); } private String owOperationToString( joliex.wsdl.impl.Operation operation ) { StringBuilder builder = new StringBuilder(); builder.append( operation.name() ); builder.append( '(' ); if ( operation.requestTypeName() == null ) { builder.append( "void" ); } else { builder.append( operation.requestTypeName() ); } builder.append( ')' ); return builder.toString(); } private String rrOperationToString( joliex.wsdl.impl.Operation operation ) { StringBuilder builder = new StringBuilder(); builder.append( operation.name() ); builder.append( '(' ); if ( operation.requestTypeName() == null ) { builder.append( "void" ); } else { builder.append( operation.requestTypeName() ); } builder.append( ')' ); builder.append( '(' ); if ( operation.responseTypeName() == null ) { builder.append( "void" ); } else { builder.append( operation.responseTypeName() ); } builder.append( ')' ); if ( operation.faults().isEmpty() == false ) { builder.append( " throws " ); List< Pair< String, String > > faults = operation.faults(); int i = 0; for( i = 0; i < faults.size() - 1; i++ ) { builder.append( faults.get( i ).key() ) .append( '(' ) .append( faults.get( i ).value() ) .append( ')' ) .append( ' ' ); } builder.append( faults.get( i ).key() ) .append( '(' ) .append( faults.get( i ).value() ) .append( ')' ); } return builder.toString(); } private void writeInterface( Interface iface ) throws IOException { writeLine( "interface " + iface.name() + " {" ); if ( iface.oneWayOperations().isEmpty() == false ) { writeLine( "OneWay:" ); indent(); int i; for( i = 0; i < iface.oneWayOperations().size() - 1; i++ ) { writeLine( owOperationToString( iface.oneWayOperations().get( i ) ) + "," ); } writeLine( owOperationToString( iface.oneWayOperations().get( i ) ) ); unindent(); } if ( iface.requestResponseOperations().isEmpty() == false ) { writeLine( "RequestResponse:" ); indent(); int i; for( i = 0; i < iface.requestResponseOperations().size() - 1; i++ ) { /*if ( iface.requestResponseOperations().get( i ).comment().isEmpty() == false ) { writeLine( "// " + iface.requestResponseOperations().get( i ).comment() ); }*/ writeLine( rrOperationToString( iface.requestResponseOperations().get( i ) ) + "," ); } writeLine( rrOperationToString( iface.requestResponseOperations().get( i ) ) ); unindent(); } writeLine( "}" ); } private void convertService( Service service ) throws IOException { //String comment = service.getDocumentationElement().getNodeValue(); for( Entry< String, Port > entry : (Set< Entry<String, Port> >)service.getPorts().entrySet() ) { convertPort( entry.getValue() ); } } private void convertPort( Port port ) throws IOException { String comment = ""; String name = port.getName(); String protocol = "soap"; String location = "socket://localhost:80/"; if ( port.getDocumentationElement() != null ) { comment = port.getDocumentationElement().getNodeValue(); } List< ExtensibilityElement > extElements = port.getExtensibilityElements(); for( ExtensibilityElement element : extElements ) { if ( element instanceof SOAPAddress ) { location = ((SOAPAddress)element).getLocationURI().toString(); StringBuilder builder = new StringBuilder(); builder.append( "soap {\n" ) .append( "\t.wsdl = \"" ) .append( definition.getDocumentBaseURI() ) .append( "\";\n" ) .append( "\t.wsdl.port = \"" ) .append( port.getName() ) .append( "\"\n}"); protocol = builder.toString(); } else if ( element instanceof HTTPAddress ) { location = ((HTTPAddress)element).getLocationURI().toString(); protocol = "http"; } } try { URI uri = new URI( location ); uri = new URI( "socket", uri.getUserInfo(), uri.getHost(), ( uri.getPort() < 1 ) ? 80 : uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment() ); location = uri.toString(); } catch( URISyntaxException e ) { e.printStackTrace(); } Binding binding = port.getBinding(); PortType portType = binding.getPortType(); convertPortType( portType, binding ); outputPorts.put( name, new OutputPort( name, location, protocol, portType.getQName().getLocalPart(), comment ) ); } private void convertPortType( PortType portType, Binding binding ) throws IOException { String comment = ""; if ( portType.getDocumentationElement() != null ) { comment = portType.getDocumentationElement().getNodeValue(); } Style style = Style.DOCUMENT; for( ExtensibilityElement element : (List<ExtensibilityElement>)binding.getExtensibilityElements() ) { if ( element instanceof SOAPBinding ) { if ( "rpc".equals(((SOAPBinding)element).getStyle()) ) { style = Style.RPC; } } else if ( element instanceof HTTPBinding ) { style = Style.HTTP; } } Interface iface = new Interface( portType.getQName().getLocalPart(), comment ); List< Operation > operations = portType.getOperations(); for( Operation operation : operations ) { if ( operation.getOutput() == null ) { iface.addOneWayOperation( convertOperation( operation, style ) ); } else { iface.addRequestResponseOperation( convertOperation( operation, style ) ); } } interfaces.put( iface.name(), iface ); } private String convertOperationMessage( Message message, String operationName, Style style ) throws IOException { String typeName = ""; Map< String, Part > parts = message.getParts(); if ( parts.size() > 1 || style == Style.RPC ) { typeName = message.getQName().getLocalPart(); TypeInlineDefinition requestType = new TypeInlineDefinition( URIParsingContext.DEFAULT, typeName, NativeType.VOID, jolie.lang.Constants.RANGE_ONE_TO_ONE ); for( Entry< String, Part > entry : parts.entrySet() ) { Part part = entry.getValue(); if ( part.getElementName() == null ) { if ( part.getTypeName() == null ) { throw new IOException( "Could not parse message part " + entry.getKey() + " for operation " + operationName + "." ); } TypeDefinitionLink link = new TypeDefinitionLink( URIParsingContext.DEFAULT, part.getName(), jolie.lang.Constants.RANGE_ONE_TO_ONE, XsdUtils.xsdToNativeType( part.getTypeName().getLocalPart() ).id() ); requestType.putSubType( link ); } else { TypeDefinitionLink link = new TypeDefinitionLink( URIParsingContext.DEFAULT, part.getName(), jolie.lang.Constants.RANGE_ONE_TO_ONE, part.getElementName().getLocalPart() ); requestType.putSubType( link ); } } typeDefinitions.add( requestType ); } else { for( Entry< String, Part > entry : parts.entrySet() ) { Part part = entry.getValue(); if ( part.getElementName() == null ) { if ( part.getTypeName() == null ) { throw new IOException( "Could not parse message part " + entry.getKey() + " for operation " + operationName + "." ); } typeName = XsdUtils.xsdToNativeType( part.getTypeName().getLocalPart() ).id(); } else { typeName = part.getElementName().getLocalPart(); NativeType nativeType = XsdUtils.xsdToNativeType( typeName ); if ( nativeType != null ) { typeName = nativeType.id(); } } } } return typeName; } private joliex.wsdl.impl.Operation convertOperation( Operation operation, Style style ) throws IOException { String comment = ""; if ( operation.getDocumentationElement() != null ) { operation.getDocumentationElement().getNodeValue(); } String requestTypeName = convertOperationMessage( operation.getInput().getMessage(), operation.getName(), style ); String responseTypeName = convertOperationMessage( operation.getOutput().getMessage(), operation.getName(), style ); Map< String, Part > parts; List< Pair< String, String > > faultList = new ArrayList< Pair< String, String > >(); Map< String, Fault > faults = operation.getFaults(); for( Entry< String, Fault > entry : faults.entrySet() ) { String faultName = entry.getKey(); String faultTypeName = null; parts = entry.getValue().getMessage().getParts(); if ( parts.size() > 1 ) { String typeName = faultName = faultTypeName; TypeInlineDefinition faultType = new TypeInlineDefinition( URIParsingContext.DEFAULT, typeName, NativeType.VOID, jolie.lang.Constants.RANGE_ONE_TO_ONE ); for( Entry< String, Part > partEntry : parts.entrySet() ) { Part part = partEntry.getValue(); if ( part.getElementName() == null ) { if ( part.getTypeName() == null ) { throw new IOException( "Could not parse message part " + entry.getKey() + " for operation " + operation.getName() + "." ); } TypeDefinitionLink link = new TypeDefinitionLink( URIParsingContext.DEFAULT, part.getName(), jolie.lang.Constants.RANGE_ONE_TO_ONE, XsdUtils.xsdToNativeType( part.getTypeName().getLocalPart() ).id() ); faultType.putSubType( link ); } else { TypeDefinitionLink link = new TypeDefinitionLink( URIParsingContext.DEFAULT, part.getName(), jolie.lang.Constants.RANGE_ONE_TO_ONE, part.getElementName().getLocalPart() ); faultType.putSubType( link ); } } typeDefinitions.add( faultType ); } else { for( Entry< String, Part > e : parts.entrySet() ) { Part part = e.getValue(); if ( part.getElementName() == null ) { if ( part.getTypeName() == null ) { throw new IOException( "Could not parse message part " + e.getKey() + " for operation " + operation.getName() + ", fault " + entry.getKey() + "." ); } faultTypeName = XsdUtils.xsdToNativeType( part.getTypeName().getLocalPart() ).id(); } else { faultTypeName = part.getElementName().getLocalPart(); NativeType nativeType = XsdUtils.xsdToNativeType( faultTypeName ); if ( nativeType != null ) { faultTypeName = nativeType.id(); } } } } faultList.add( new Pair< String, String >( faultName, faultTypeName ) ); } return new joliex.wsdl.impl.Operation( operation.getName(), requestTypeName, responseTypeName, faultList, comment ); } }