From a272f6e01521727a3c2b2499858211d363343bce Mon Sep 17 00:00:00 2001 From: chris grzegorczyk Date: Sat, 2 Feb 2013 01:55:27 -0800 Subject: [PATCH] Initial pass at autogenerating schema documents --- .../msgs/src/main/java/generate-xsd.groovy | 420 ++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 clc/modules/msgs/src/main/java/generate-xsd.groovy diff --git a/clc/modules/msgs/src/main/java/generate-xsd.groovy b/clc/modules/msgs/src/main/java/generate-xsd.groovy new file mode 100644 index 00000000000..b66e98b6449 --- /dev/null +++ b/clc/modules/msgs/src/main/java/generate-xsd.groovy @@ -0,0 +1,420 @@ +import java.lang.reflect.Field +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.jar.JarEntry +import java.util.jar.JarFile +import javax.xml.transform.OutputKeys +import javax.xml.transform.Source +import javax.xml.transform.Transformer +import javax.xml.transform.TransformerFactory +import javax.xml.transform.sax.SAXSource +import javax.xml.transform.sax.SAXTransformerFactory +import javax.xml.transform.stream.StreamResult +import org.xml.sax.InputSource +import com.eucalyptus.binding.HttpParameterMapping +import com.eucalyptus.component.ComponentId +import com.eucalyptus.component.ComponentId.ComponentMessage +import com.eucalyptus.system.Ats +import com.eucalyptus.util.Classes +import com.google.common.collect.HashMultimap +import com.google.common.collect.Multimap +import com.google.common.collect.Sets +import com.google.common.io.Files +import com.google.common.primitives.Primitives +import edu.ucsb.eucalyptus.msgs.BaseMessage + +boolean lameDebugFlag = System.getenv( ).containsKey( "DEBUG" ) ? true : false; +boolean lameVerboseFlag = System.getenv( ).containsKey( "VERBOSE" ) || lameDebugFlag ? true : false; +def log = { Object o, boolean eol = true -> + if ( lameVerboseFlag && !eol ) { + print o + } else if ( lameVerboseFlag && eol ) { + println o + } +} + +def usage = { + System.out.println "Usage: ./devel/groovy.sh ./clc/modules/msgs/src/main/java/generate-xsd.groovy [jar directory]" + System.out.println "" + System.out.println " jar directory - Optional path to where the eucalyptus-*.jar files are." + System.out.println " Defaults to System.getProperty(\"euca.src.dir\")}/clc/target" + System.out.println "" + System.out.println " Set either DEBUG or VERBOSE environment variables for more output." + System.out.println "" + System.exit(1) +} + +if ( args.length > 1 ) { + usage() +} + +String dirName = args.length == 1 ? args[0] : "${System.getProperty("euca.src.dir")}/clc/target"; +File libDir = new File( dirName ); +dirName = libDir.getCanonicalPath( ); +log "Using ${dirName}" +classList = [] +libDir.listFiles( ).findAll{ it.getName().endsWith("jar") }.each { File f -> + try { + JarFile jar = new JarFile( f ); + log "Reading ${f.getCanonicalPath()}" + List jarList = Collections.list( jar.entries( ) ); + jarList.each { JarEntry j -> + try { + if ( j.getName( ).matches( ".*\\.class.{0,1}" ) ) { + String classGuess = j.getName( ).replaceAll( "/", "." ).replaceAll( "\\.class.{0,1}", "" ); + try { + classList.add( ClassLoader.systemClassLoader.loadClass(classGuess) ); + } catch ( final ClassNotFoundException e ) { + log e.getMessage() + } + } + } catch ( RuntimeException ex ) { + ex.printStackTrace( ); + jar.close( ); + throw ex; + } + } + jar.close( ); + } catch ( final Throwable e ) { + } +} +if ( classList.isEmpty( ) ) { + System.err.println "ERROR: Failed to find any classes in the given jar directory: ${dirName}" + usage() +} + +Multimap,Class> messageTypes = HashMultimap.create(); +Multimap,Class> complexTypes = HashMultimap.create(); +Multimap,Class> collectionTypes = HashMultimap.create(); +Set failedTypes = Sets.newHashSet( ); +def componentFile = { Class compId -> new File("${compId.getSimpleName()}.wsdl") } +def writeComponentFile = { Class compId, String line -> + if ( lameDebugFlag ) { + log line + } + (componentFile(compId)).append( "${line}\n" ); +} + +xsTypeMap = [ + 'java.lang.String':'string', + 'java.lang.Long':'long', + 'long':'long', + 'java.lang.Integer':'int', + 'int':'int', + 'java.lang.Boolean':'boolean', + 'boolean':'boolean', + 'java.lang.Double':'float', + 'double':'float', + 'java.lang.Float':'float', + 'float':'float', +] + +def filterFieldNames = { Field f -> + ( !f.getName( ).startsWith( "_" ) + && !f.getName( ).startsWith( "\$" ) + && !f.getName( ).equals( "metaClass" ) + ) ? true : false; +} +def filterPrimitiveType = { Type t -> ( t.isPrimitive( ) || Primitives.isWrapperType( t ) || String.class.equals( t ) ) } +def filterPrimitives = { Field f -> filterFieldNames( f ) && filterPrimitiveType( f.getType( ) ) } +def filterCollections = { Field f -> filterFieldNames( f ) && Collection.class.isAssignableFrom( f.getType( ) ) } +def filterComplex = { Field f -> filterFieldNames( f ) && !filterPrimitives( f ) && !filterCollections( f ) } + +def filterMessageTypes = { Class c -> + BaseMessage.class.isAssignableFrom( c ) && !BaseMessage.class.equals( c ) && !c.toString( ).contains("\$") +} + +def messageHasAnnotation = { Class c -> + ats = Ats.inClassHierarchy( c ); + if ( !ats.has( ComponentMessage.class ) ) { + failedTypes.add( c ); + false + } else { + true + } +} + +def transformToGenericType = { Field f -> + Type t = f.getGenericType( ); + if ( t != null && t instanceof ParameterizedType ) { + Type tv = ( ( ParameterizedType ) t ).getActualTypeArguments( )[0]; + if ( tv instanceof Class ) { + ( ( Class ) tv ); + } + } +} + +def processMessageType = { Class compId, Class c -> + if ( !compId.newInstance( ).isPublicService( ) ) { + messageTypes.put( compId, c ); + c.getDeclaredFields( ).findAll( filterComplex ).each { Field f -> + complexTypes.put( compId, f.getType( ) ); + } + c.getDeclaredFields( ).findAll( filterCollections ).each { Field f -> + Class fc = transformToGenericType( f ); + if ( !filterPrimitiveType( fc ) ) { + collectionTypes.put( compId, fc ); + complexTypes.put( compId, fc ); + } + } + } + +} + +//find all the message types +classList.findAll( filterMessageTypes ).findAll( messageHasAnnotation ).each { Class c -> + ats = Ats.inClassHierarchy( c ); + ComponentMessage comp = ats.get( ComponentMessage.class ); + Class compId = comp.value( ); + processMessageType( compId, c ); +} + +failedTypes.each { Class c -> + //handle the unannotated {Walrus,Storage,etc.}ResponseType hierarchies by guessing their counterpart in the properly annotated hierarchy + if ( !Ats.inClassHierarchy( c ).has( ComponentMessage.class ) && c.getCanonicalName( ).endsWith( "ResponseType" ) ) { + try { + Class request = Class.forName( c.getCanonicalName( ).replaceAll("ResponseType\$","Type") ); + ats = Ats.inClassHierarchy( request ); + ComponentMessage comp = ats.get( ComponentMessage.class ); + Class compId = comp.value( ); + processMessageType( compId, c ); + } catch ( Exception e ) {} + } +} + + +messageTypes.keySet( ).each { Class compId -> + if ( componentFile(compId).exists() ) { + File f = componentFile(compId); + if( f.exists( ) ) { + f.delete(); + } + f.println( """\n""" ) + } +} + + +String wsdlHeader = """ +""" + +def wsdlFooter = { Class compId -> + """ + + + + +"""; +} + +String schemaHeader = """ + +""" + +String schemaFooter = """ + +""" + +def renderSimpleField = { Field f -> + """""" +} + +def renderComplexField = { Field f -> + """""" +} + +def renderCollectionField = { Field f -> + """""" +} + +def renderCollectionWrapper = { Class c -> + """ + + + + +""" +} + +def transformFieldToQuery = { Field f -> f.getName( ).substring( 0, 1 ).toUpperCase( ).concat( f.getName( ).substring( 1 ) ) } + +def renderFieldReference = { Class c, Field f -> + log("renderMessageType: ${c.getSimpleName()}.${f.getName()}",false) + fats = Ats.from( f ); + PREFIX=" " + comment = "" + if ( fats.has( HttpParameterMapping.class ) ) { + HttpParameterMapping mapping = fats.get( HttpParameterMapping.class ); + comment += "\n${PREFIX}\n" + } else { + comment += "\n${PREFIX}\n" + } + if ( filterPrimitives( f ) ) { + log " [primitive] ${f.getType()}" + return "${comment}${PREFIX}${renderSimpleField(f)}" + } else if ( filterCollections( f ) ) { + log " [collection] ${transformToGenericType(f)}" + return "${comment}${PREFIX}${renderCollectionField(f)}" + } else if ( filterComplex( f ) ) { + log " [complex] ${f.getType()}" + return "${comment}${PREFIX}${renderComplexField(f)}" + } +} + + + +def renderComplexType = { Class c -> + String header = """ + """ + String footer = """ + + +""" + Classes.ancestors( c ).each { Class a -> + a.getDeclaredFields().findAll(filterFieldNames).each { Field f -> + header += renderFieldReference( c, f ) + } + } + header + footer +} + +def renderMessageType = { Class c -> + String shortName = "${c.getSimpleName().replaceAll('Type$','')}"; + String header = """ + + """ + String footer = """ + + +""" + Classes.ancestors( c ).each { Class a -> + if ( filterMessageTypes( a ) ) { + a.getDeclaredFields().findAll(filterFieldNames).each { Field f -> + header += renderFieldReference( c, f ) + } + } + } + header + footer +} + +def renderMessagePart = { Class c -> + if ( !c.getSimpleName( ).endsWith( "ResponseType" ) ) { + """ + + + + + + """ + } else { + "" + } +} + +def renderPortTypeHeader = { Class compId -> + """ """ +} + +String portTypeFooter = "" + +def renderPortTypeOperation = { Class c -> + if ( !c.getSimpleName( ).endsWith( "ResponseType" ) ) { + """ + + + +""" + } else { + "" + } +} + +def renderBindingHeader = { Class compId -> + """ + +""" +} + +def renderBindingOperation = { Class c -> + """ + + + + + + + + + +""" +} +String bindingFooter = " " + +def generateSchema = { Class compId -> + writeComponentFile( compId, schemaHeader ); + complexTypes.get( compId ).each { Class complexType -> + log "generateSchema: ComplexType ${compId.getSimpleName()} ${complexType}" + writeComponentFile( compId, renderComplexType( complexType ) ); + } + collectionTypes.get( compId ).each { Class collectionType -> + log "generateSchema: Collection ${compId.getSimpleName()} ${collectionType}" + writeComponentFile( compId, renderCollectionWrapper( collectionType ) ); + } + messageTypes.get( compId ).each { Class msgType -> + log "generateSchema: Message ${compId.getSimpleName()} ${msgType}" + writeComponentFile( compId, renderMessageType( msgType ) ); + } + writeComponentFile( compId, schemaFooter ); +} + +def generateMessagePartsSection = { Class compId -> + //Message Parts + messageTypes.get( compId ).each { Class msgType -> + writeComponentFile( compId, renderMessagePart( msgType ) ); + } +} + +def generatePortTypeSection = { Class compId -> + //Port Type Operations + writeComponentFile( compId, renderPortTypeHeader( compId ) ); + messageTypes.get( compId ).each { Class msgType -> + writeComponentFile( compId, renderPortTypeOperation( msgType ) ); + } + writeComponentFile( compId, portTypeFooter ); +} + +def generateBindingSection = { Class compId -> + //Binding section + writeComponentFile( compId, renderBindingHeader( compId ) ); + messageTypes.get( compId ).each { Class msgType -> + writeComponentFile( compId, renderBindingOperation( msgType ) ); + } + writeComponentFile( compId, bindingFooter ); +} + +//log generateSchema( ConfigurationService.class ) +messageTypes.keySet( ).each { Class compId -> + writeComponentFile( compId, wsdlHeader ); + generateSchema( compId ); + generateMessagePartsSection( compId ); + generatePortTypeSection( compId ); + generateBindingSection( compId ); + writeComponentFile( compId, wsdlFooter( compId ) ); +} +failedTypes.findAll{ !messageTypes.containsValue( it ) }.each { Class c -> + log "ERROR: Failed to find @ComponentMessage in class hierarchy for: ${c}"; +} + +messageTypes.keySet( ).each { Class compId -> + File compFile = componentFile(compId); + File tmpFile = File.createTempFile(compFile.getName(),null); +// tmpFile.deleteOnExit( ); + log "Moving ${compFile.getCanonicalPath()} to ${tmpFile.getCanonicalPath()}" + Files.move(compFile,tmpFile); + + componentFile(compId).withPrintWriter{ PrintWriter w -> + log "Rewriting ${compFile.getCanonicalPath()}" + w.write(tmpFile.text.replaceAll("(?m)^[ \t]*\r?\n", "")); + } +}