/* * Copyright 2013 Guidewire Software, Inc. */ package gw.xml.ws; import gw.config.CommonServices; import gw.internal.schema.gw.xsd.w3c.soap11.Binding; import gw.internal.schema.gw.xsd.w3c.soap11.Body; import gw.internal.schema.gw.xsd.w3c.soap11.enums.TStyleChoice; import gw.internal.schema.gw.xsd.w3c.wsdl.Definitions; import gw.internal.schema.gw.xsd.w3c.wsdl.anonymous.elements.*; import gw.internal.schema.gw.xsd.w3c.wsdl.types.complex.TParam; import gw.internal.schema.gw.xsd.w3c.xmlschema.*; import gw.internal.schema.gw.xsd.w3c.xmlschema.anonymous.elements.ExplicitGroup_Element; import gw.internal.schema.gw.xsd.w3c.xmlschema.anonymous.elements.TopLevelElement_ComplexType; import gw.internal.schema.gw.xsd.w3c.xmlschema.enums.FormChoice; import gw.internal.xml.xsd.typeprovider.XmlSchemaIndex; import gw.internal.xml.xsd.typeprovider.XmlSchemaResourceTypeLoaderBase; import gw.lang.PublishInGosu; import gw.lang.PublishedType; import gw.lang.PublishedTypes; import gw.lang.reflect.IConstructorInfo; import gw.lang.reflect.IMethodInfo; import gw.lang.reflect.ITypeInfo; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.module.IModule; import gw.util.GosuExceptionUtil; import gw.xml.*; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.*; import java.util.List; /** * This class can be used to recursively fetch a WSDL and its dependencies from a remote URL. In Gosu, there is * no need to generate code to use a WSDL. Simply drop the WSDL and it's associated resources into any Gosu * classpath. */ @PublishInGosu @PublishedTypes({ @PublishedType(fromType = "java.util.LinkedHashMap<java.lang.String, gw.internal.schema.gw.xsd.w3c.wsdl.Definitions>", toType = "java.util.LinkedHashMap<java.lang.String, gw.xsd.w3c.wsdl.Definitions>"), @PublishedType(fromType = "java.util.LinkedHashMap<java.lang.String, gw.internal.schema.gw.xsd.w3c.xmlschema.Schema>", toType = "java.util.LinkedHashMap<java.lang.String, gw.xsd.w3c.xmlschema.Schema>") }) public final class Wsdl2Gosu { /** * Recursively fetches the WSDLs and associated resources at the specified location. In Gosu, there is no * codegen phase, simply drop the fetched files into your Gosu classpath. * @param uri the uri of the WSDL or schema * @param outputDir the output directory, which will be created if necessary, and existing XSD and WSDL files will be overwritten here */ public static void fetch( URI uri, File outputDir ) throws IOException, URISyntaxException { fetch( Collections.singletonList( uri ), outputDir ); } /** * Recursively fetches the WSDLs and associated resources at the specified locations. In Gosu, there is no * codegen phase, simply drop the fetched files into your Gosu classpath. * @param urls the urls of the WSDLs and/or schemas * @param outputDir the output directory, which will be created if necessary, and existing XSD and WSDL files will be overwritten here */ public static void fetch( List<URI> urls, File outputDir ) throws IOException, URISyntaxException { LinkedHashMap<String, Definitions> definitions = new LinkedHashMap<String, Definitions>(); LinkedHashMap<String, Schema> schemas = new LinkedHashMap<String, Schema>(); for ( URI uri : urls ) { XmlElement root = parseDocumentAtUri( uri ); if ( root.getQName().equals( Schema.$QNAME ) ) { fetch( uri, schemas ); } else if ( root.getQName().equals( Definitions.$QNAME ) ) { fetch( uri, definitions, schemas ); } else { throw new XmlException( "Unrecognized root element of XML at " + uri + "; Expected " + Definitions.$QNAME + " or " + Schema.$QNAME + ", but was " + root.getQName() ); } } writeResults( outputDir, definitions, schemas ); CommonServices.getFileSystem().clearAllCaches(); XmlSchemaResourceTypeLoaderBase.refreshSchemasFromAllTypeLoaders(); System.out.println( "Wsdl2Gosu - Done writing results" ); } private static <T extends XmlElement> T parseDocumentAtUri( URI uri, Class<T> clazz, XmlParseOptions options ) { try { if ( "local".equals( uri.getScheme() ) ) { ITypeInfo clientConnectorTypeInfo = TypeSystem.getByFullName("gw.internal.xml.ws.LocalWebservicesClientConnector").getTypeInfo(); IConstructorInfo ctor = clientConnectorTypeInfo.getCallableConstructor(TypeSystem.get(URI.class)); Object conn = ctor.getConstructor().newInstance(uri); ITypeInfo responseTypeInfo = TypeSystem.getByFullName("gw.internal.xml.ws.WebservicesResponse").getTypeInfo(); Object response = clientConnectorTypeInfo.getProperty("Response").getAccessor().getValue(conn); InputStream is = (InputStream) responseTypeInfo.getProperty("InputStream").getAccessor().getValue(response); //noinspection unchecked return (T) clazz.getMethod( "parse", InputStream.class, XmlParseOptions.class ).invoke( null, is, options ); } else { //noinspection unchecked return (T) clazz.getMethod( "parse", URL.class, XmlParseOptions.class ).invoke( null, uri.toURL(), options ); } } catch ( InvocationTargetException itex ) { throw GosuExceptionUtil.forceThrow( itex.getTargetException() ); } catch ( Exception ex ) { throw GosuExceptionUtil.forceThrow( ex ); } } private static <T extends XmlElement> T parseDocumentAtUri( URI uri, Class<T> clazz ) { return parseDocumentAtUri( uri, clazz, null ); } private static XmlElement parseDocumentAtUri( URI uri ) { return parseDocumentAtUri( uri, XmlElement.class ); } /** * Recursively fetches the WSDLs and associated schemas at the specified location. In Gosu, there is no * codegen phase, simply drop the fetched files into your Gosu classpath. * @param uri the uri of the WSDL * @param definitions a map into which to place the fetched WSDL definitions * @param schemas a map into which to place the fetched schemas */ public static void fetch( URI uri, LinkedHashMap<String, Definitions> definitions, LinkedHashMap<String, Schema> schemas ) throws IOException, URISyntaxException { fetch( uri, new HashMap<URI,Map<Boolean,String>>(), new HashSet<String>(), FileType.WSDL, definitions, schemas, false ); convertRpcToDocument( definitions, schemas ); System.out.println( "Wsdl2Gosu - Done fetching schemas" ); } /** * Recursively fetches the schemas at the specified location. In Gosu, there is no * codegen phase, simply drop the fetched files into your Gosu classpath. * @param uri the uri of the WSDL * @param schemas a map into which to place the fetched schemas */ public static void fetch( URI uri, LinkedHashMap<String, Schema> schemas ) throws IOException, URISyntaxException { fetch( uri, new HashMap<URI,Map<Boolean,String>>(), new HashSet<String>(), FileType.SCHEMA, new LinkedHashMap<String, Definitions>(), schemas, false ); System.out.println( "Wsdl2Gosu - Done fetching schemas" ); } private static void writeResults( File outputDir, LinkedHashMap<String, Definitions> definitions, LinkedHashMap<String, Schema> schemas ) throws FileNotFoundException { System.out.println( "Wsdl2Gosu - Writing results to " + outputDir ); //noinspection ResultOfMethodCallIgnored outputDir.mkdirs(); for ( Map.Entry<String, Definitions> entry : definitions.entrySet() ) { FileOutputStream fos = new FileOutputStream( new File( outputDir, entry.getKey() ) ); try { entry.getValue().writeTo( fos ); } finally { try { fos.close(); } catch ( IOException e ) { //noinspection ThrowFromFinallyBlock throw GosuExceptionUtil.forceThrow( e ); } } } for ( Map.Entry<String, Schema> entry : schemas.entrySet() ) { FileOutputStream fos = new FileOutputStream( new File( outputDir, entry.getKey() ) ); try { XmlSerializationOptions options = new XmlSerializationOptions(); options.setSort( false ); options.setValidate( false ); entry.getValue().writeTo( fos, options ); } finally { try { fos.close(); } catch ( IOException e ) { //noinspection ThrowFromFinallyBlock throw GosuExceptionUtil.forceThrow( e ); } } } } private static void convertRpcToDocument( LinkedHashMap<String, Definitions> definitions, LinkedHashMap<String, Schema> schemas ) throws URISyntaxException { System.out.println( "Wsdl2Gosu - Converting RPC style to Document style if necessary" ); List<Runnable> todo = new ArrayList<Runnable>(); for ( Definitions def : definitions.values() ) { for ( TDefinitions_Binding binding : def.Binding() ) { Binding soapBinding = (Binding) binding.getChild( Binding.$QNAME ); if ( soapBinding != null && soapBinding.Style() == TStyleChoice.Rpc ) { Definitions portTypeWsdl = findWsdlForNamespace( binding.Type(), def, definitions ); TDefinitions_PortType portType = findPortType( binding.Type(), portTypeWsdl ); soapBinding.setComment( "Converted from rpc style to document style by Wsdl2Gosu" ); soapBinding.setStyle$( TStyleChoice.Document ); for ( final TBinding_Operation bindingOperation : binding.Operation() ) { Body soapBody = (Body) bindingOperation.Input().getChild( Body.$QNAME ); final URI bodyNamespace = soapBody.Namespace(); Schema schema = findSchemaForNamespace( bodyNamespace.toString(), def, definitions, schemas ); if ( schema == null ) { schema = new Schema(); schema.setTargetNamespace$( bodyNamespace ); if ( def.Types().isEmpty() ) { def.Types().add( new TDefinitions_Types() ); } def.Types().get( 0 ).addChild( schema ); } // now we have a schema for this target namespace that we can modify TPortType_Operation operation = findOperation( bindingOperation.Name(), portType ); if ( operation.Input() != null ) { processParam( definitions, todo, portTypeWsdl, bodyNamespace, schema, operation.Input().getTypeInstance(), bindingOperation.Name() ); } if ( operation.Output() != null ) { processParam( definitions, todo, portTypeWsdl, bodyNamespace, schema, operation.Output().getTypeInstance(), bindingOperation.Name() + "Response" ); } } } } } for ( Runnable runnable : todo ) { runnable.run(); } } private static void processParam( LinkedHashMap<String, Definitions> definitions, List<Runnable> todo, Definitions portTypeWsdl, final URI bodyNamespace, Schema schema, TParam tparam, final String elementName ) { Element element = null; if ( findElement( schema, elementName ) == null ) { element = new Element(); element.setComment( "Created by Wsdl2Gosu" ); element.setName$( elementName ); element.setComplexType$( new TopLevelElement_ComplexType() ); element.ComplexType().setSequence$( new Sequence() ); schema.Element().add( element ); } QName messageName = tparam.Message(); Definitions messageWsdl = findWsdlForNamespace( messageName, portTypeWsdl, definitions ); final TDefinitions_Message message = findMessage( messageName, messageWsdl ); if ( element != null ) { // convert multiple parts to a single part for ( TMessage_Part part : message.Part() ) { ExplicitGroup_Element paramElement = new ExplicitGroup_Element(); paramElement.setName$( part.Name() ); paramElement.setType$( part.Type() ); if ( schema.ElementFormDefault() == FormChoice.Qualified ) { paramElement.setForm$( FormChoice.Unqualified ); } element.ComplexType().Sequence().Element().add( paramElement ); } } todo.add( new Runnable() { public void run() { TMessage_Part part = new TMessage_Part(); part.setName$( "input" ); part.setElement$( new QName( bodyNamespace.toString(), elementName ) ); message.setPart$( Collections.singletonList( part ) ); } } ); } private static TPortType_Operation findOperation( String name, TDefinitions_PortType portType ) { for ( TPortType_Operation operation : portType.Operation() ) { if ( operation.Name().equals( name ) ) { return operation; } } throw new RuntimeException( "Unable to find portType operation named " + name ); } private static Element findElement( Schema schema, String name ) { for ( Element element : schema.Element() ) { if ( element.Name().equals( name ) ) { return element; } } return null; } private static Schema findSchemaForNamespace( String namespace, Definitions def, LinkedHashMap<String, Definitions> definitions, LinkedHashMap<String, Schema> schemas ) { Schema targetSchema = findSchemaForNamespaceShallow( namespace, def, schemas ); if ( targetSchema != null ) { return targetSchema; } for ( TDefinitions_Import imprt : def.Import() ) { Definitions targetWsdl = definitions.get( imprt.Location().toString() ); if ( targetWsdl == null ) { throw new RuntimeException( "Unable to find referenced WSDL, namespace " + imprt.Namespace() + " at location " + imprt.Location() ); } targetSchema = findSchemaForNamespaceShallow( namespace, targetWsdl, schemas ); if ( targetSchema != null ) { return targetSchema; } } return null; } private static Schema findSchemaForNamespaceShallow( String namespace, Definitions def, LinkedHashMap<String, Schema> schemas ) { for ( TDefinitions_Types types : def.Types() ) { for ( XmlElement element : types.getChildren( Schema.$QNAME ) ) { Schema schema = (Schema) element; String targetNamespace = schema.TargetNamespace() == null ? XMLConstants.NULL_NS_URI : schema.TargetNamespace().toString(); if ( targetNamespace.equals( namespace ) ) { return schema; } for ( Import imprt : schema.Import() ) { String importNamespace = imprt.Namespace() == null ? XMLConstants.NULL_NS_URI : imprt.Namespace().toString(); if ( importNamespace.equals( namespace ) ) { Schema importedSchema = schemas.get( imprt.SchemaLocation().toString() ); if ( importedSchema == null ) { throw new RuntimeException( "Unable to find referenced schema, namespace " + imprt.Namespace() + " at location " + imprt.SchemaLocation() ); } return importedSchema; } } } } return null; } private static TDefinitions_PortType findPortType( QName portTypeName, Definitions targetWsdl ) { for ( TDefinitions_PortType portType : targetWsdl.PortType() ) { if ( portType.Name().equals( portTypeName.getLocalPart() ) ) { return portType; } } throw new RuntimeException( "Unable to find wsdl:portType " + portTypeName ); } private static TDefinitions_Message findMessage( QName messageName, Definitions targetWsdl ) { for ( TDefinitions_Message message : targetWsdl.Message() ) { if ( message.Name().equals( messageName.getLocalPart() ) ) { return message; } } throw new RuntimeException( "Unable to find wsdl:message " + messageName ); } private static Definitions findWsdlForNamespace( QName qName, Definitions def, LinkedHashMap<String, Definitions> definitions ) { String myTargetNamespace = def.TargetNamespace() == null ? XMLConstants.NULL_NS_URI : def.TargetNamespace().toString(); String requiredTargetNamespace = qName.getNamespaceURI(); if ( myTargetNamespace.equals( requiredTargetNamespace ) ) { return def; } for ( TDefinitions_Import imprt : def.Import() ) { String importedNamespace = imprt.Namespace() == null ? XMLConstants.NULL_NS_URI : imprt.Namespace().toString(); if ( importedNamespace.equals( requiredTargetNamespace ) ) { Definitions targetWsdl = definitions.get( imprt.Location().toString() ); if ( targetWsdl == null ) { throw new RuntimeException( "Unable to find referenced WSDL, namespace " + imprt.Namespace() + " at location " + imprt.Location() ); } return targetWsdl; } } throw new RuntimeException( "Unable to find WSDL for " + qName ); } private static String fetch( URI uri, HashMap<URI, Map<Boolean,String>> alreadyFetched, HashSet<String> usedNamespaces, FileType fileType, LinkedHashMap<String, Definitions> definitions, LinkedHashMap<String, Schema> schemas, boolean isInclude ) throws IOException, URISyntaxException { Map<Boolean, String> isIncludeMap = alreadyFetched.get( uri ); if ( isIncludeMap == null ) { isIncludeMap = new HashMap<Boolean, String>(); alreadyFetched.put( uri, isIncludeMap ); } String filename = isIncludeMap.get( isInclude ); String extension = null; if ( filename == null ) { filename = uri.getPath(); int idx = filename.lastIndexOf( '/' ); if ( idx >= 0 ) { filename = filename.substring( idx + 1 ); } idx = filename.lastIndexOf( '?' ); if ( idx >= 0 ) { filename = filename.substring( 0, idx ); } // remove extension if any idx = filename.lastIndexOf( '.' ); if ( idx >= 0 ) { extension = filename.substring( idx + 1 ); filename = filename.substring( 0, idx ); } if ( filename.length() == 0 ) { filename = "_"; } try { String suffix = ""; int suffixNum = 2; while ( true ) { if ( usedNamespaces.add( isInclude + "-" + XmlSchemaIndex.normalizeSchemaNamespace( filename + suffix ).toLowerCase() ) ) { filename += suffix; break; } if ( extension != null ) { filename += '_' + extension; extension = null; } else { suffix = String.valueOf( suffixNum++ ); } } } catch ( Exception ex ) { throw new RuntimeException( ex ); } switch ( fileType ) { case SCHEMA: filename = filename.replace( '.', '_' ); filename += ( isInclude ? ".xsdi" : ".xsd" ); break; case WSDL: filename = filename.replace( '.', '_' ); filename += ".wsdl"; break; } System.out.println( "Wsdl2Gosu - Fetching " + uri + " -> " + filename ); isIncludeMap.put( isInclude, filename ); switch ( fileType ) { case SCHEMA: { Schema schema = parseDocumentAtUri( uri, Schema.class ); fetchDependentSchemas( uri, alreadyFetched, usedNamespaces, schema, definitions, schemas ); schemas.put( filename, schema ); break; } case WSDL: { XmlParseOptions options = new XmlParseOptions(); IModule webservicesModule = TypeSystem.getGlobalModule(); List<XmlSchemaAccess> additionalSchemas = new ArrayList<XmlSchemaAccess>(); additionalSchemas.add( XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace( webservicesModule, "gw.xsd.w3c.xmlschema" ).getXmlSchemaAccess() ); additionalSchemas.add( XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace( webservicesModule, "gw.xsd.w3c.soap11" ).getXmlSchemaAccess() ); additionalSchemas.add( XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace( webservicesModule, "gw.xsd.w3c.soap12" ).getXmlSchemaAccess() ); additionalSchemas.add( XmlSchemaResourceTypeLoaderBase.findSchemaForNamespace( webservicesModule, "gw.xsd.w3c.soap11_encoding" ).getXmlSchemaAccess() ); options.setAdditionalSchemas( additionalSchemas ); Definitions wsdlDefinitions = parseDocumentAtUri( uri, Definitions.class, options ); fetchDependentWsdls( uri, alreadyFetched, usedNamespaces, wsdlDefinitions, definitions, schemas ); for ( TDefinitions_Types type : wsdlDefinitions.Types() ) { for ( XmlElement schema : type.getChildren( Schema.$QNAME ) ) { fetchDependentSchemas( uri, alreadyFetched, usedNamespaces, (Schema) schema, definitions, schemas ); } } definitions.put( filename, wsdlDefinitions ); break; } } } return filename; } private static void fetchDependentWsdls( URI uri, HashMap<URI, Map<Boolean, String>> alreadyFetched, HashSet<String> usedNamespaces, Definitions wsdlDefinitions, LinkedHashMap<String, Definitions> definitions, LinkedHashMap<String, Schema> schemas ) throws IOException, URISyntaxException { for ( TDefinitions_Import imprt : wsdlDefinitions.Import() ) { URI child = uri.resolve( imprt.Location() ); String filename = fetch( child, alreadyFetched, usedNamespaces, FileType.WSDL, definitions, schemas, false ); imprt.setLocation$( new URI( filename ) ); } } private static void fetchDependentSchemas( URI uri, HashMap<URI, Map<Boolean, String>> alreadyFetched, HashSet<String> usedNamespaces, Schema schema, LinkedHashMap<String, Definitions> definitions, LinkedHashMap<String, Schema> schemas ) throws IOException, URISyntaxException { for ( Import imprt : schema.Import() ) { if ( imprt.SchemaLocation() != null ) { URI child = uri.resolve( imprt.SchemaLocation() ); String filename = fetch( child, alreadyFetched, usedNamespaces, FileType.SCHEMA, definitions, schemas, false ); imprt.setSchemaLocation$( new URI( filename ) ); } } for ( Include include : schema.Include() ) { URI child = uri.resolve( include.SchemaLocation() ); String filename = fetch( child, alreadyFetched, usedNamespaces, FileType.SCHEMA, definitions, schemas, true ); include.setSchemaLocation$( new URI( filename ) ); } for ( Redefine redefine : schema.Redefine() ) { URI child = uri.resolve( redefine.SchemaLocation() ); String filename = fetch( child, alreadyFetched, usedNamespaces, FileType.SCHEMA, definitions, schemas, true ); redefine.setSchemaLocation$( new URI( filename ) ); } } private static enum FileType { WSDL, SCHEMA } }