package com.eucalyptus.binding; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import java.lang.reflect.Modifier; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; public class InternalSoapBindingGenerator extends BindingGenerator { private static Logger LOG = Logger.getLogger( InternalSoapBindingGenerator.class ); private final String ns = "http://msgs.eucalyptus.com"; private static String INDENT = ""; private final File outFile; private PrintWriter out; private String bindingName; private int indent = 0; private Map<String, TypeBinding> typeBindings = new HashMap<String, TypeBinding>( ) { { put( Integer.class.getCanonicalName( ), new IntegerTypeBinding( ) ); put( Boolean.class.getCanonicalName( ), new BooleanTypeBinding( ) ); put( String.class.getCanonicalName( ), new StringTypeBinding( ) ); put( Long.class.getCanonicalName( ), new LongTypeBinding( ) ); put( "boolean", new BooleanTypeBinding( ) ); put( "int", new IntegerTypeBinding( ) ); put( "long", new LongTypeBinding( ) ); put( java.util.Date.class.getCanonicalName( ), new StringTypeBinding( ) ); } }; private static List<String> badClasses = new ArrayList<String>( ) { { add( ".*HttpResponseStatus" ); add( ".*Closure" ); add( ".*Channel" ); add( ".*\\.JiBX_*" ); } }; private static List<String> badFields = new ArrayList<String>( ) { { add( "__.*" ); add( "\\w*\\$\\w*\\$*.*" ); add( "class\\$.*" ); add( "metaClass" ); add( "JiBX_.*" ); } }; public InternalSoapBindingGenerator( ) { this.outFile = new File( "modules/msgs/src/main/resources/msgs-binding.xml" ); if ( outFile.exists( ) ) { outFile.delete( ); } try { this.out = new PrintWriter( outFile ); } catch ( FileNotFoundException e ) { e.printStackTrace( System.err ); System.exit( -1 ); } this.bindingName = this.ns.replaceAll( "(http://)|(/$)", "" ).replaceAll( "[./-]", "_" ); this.out.write( "<binding xmlns:euca=\"" + ns + "\" name=\"" + bindingName + "\">\n" ); this.out.write( " <namespace uri=\"" + ns + "\" default=\"elements\" prefix=\"euca\"/>\n" ); this.out.flush( ); } public ElemItem peek( ) { return this.elemStack.peek( ); } @Override public void processClass( Class klass ) { if ( BindingGenerator.DATA_TYPE.isAssignableFrom( klass ) || BindingGenerator.MSG_TYPE.isAssignableFrom( klass ) ) { String mapping = new RootObjectTypeBinding( klass ).process( ); out.write( mapping ); out.flush( ); } } @Override public void close( ) { this.out.write( "</binding>" ); this.out.flush( ); this.out.close( ); } public TypeBinding getTypeBinding( Field field ) { Class itsType = field.getType( ); if ( this.isIgnored( field ) ) { return new NoopTypeBinding( field ); } else if ( List.class.isAssignableFrom( itsType ) ) { Class listType = getTypeArgument( field ); if ( listType == null ) { System.err.printf( "IGNORE: %-70s [type=%s] NO GENERIC TYPE FOR LIST\n", field.getDeclaringClass( ).getCanonicalName( ) + "."+ field.getName( ), listType ); return new NoopTypeBinding( field ); } else if ( typeBindings.containsKey( listType.getCanonicalName( ) ) ) { return new CollectionTypeBinding( field.getName( ), typeBindings.get( listType.getCanonicalName( ) ) ); } else if ( BindingGenerator.DATA_TYPE.isAssignableFrom( listType ) ) { return new CollectionTypeBinding( field.getName( ), new ObjectTypeBinding( field.getName( ), listType ) ); } else { System.err.printf( "IGNORE: %-70s [type=%s] LIST'S GENERIC TYPE DOES NOT CONFORM TO EucalyptusData\n", field.getDeclaringClass( ).getCanonicalName( ) + "."+ field.getName( ), listType.getCanonicalName( ) ); return new NoopTypeBinding( field ); } } else if ( typeBindings.containsKey( itsType.getCanonicalName( ) ) ) { TypeBinding t = typeBindings.get( itsType.getCanonicalName( ) ); try { t = typeBindings.get( itsType.getCanonicalName( ) ).getClass( ).newInstance( ); } catch ( Exception e ) {} return t.value( field.getName( ) ); } else if ( BindingGenerator.DATA_TYPE.isAssignableFrom( field.getType( ) ) ) { return new ObjectTypeBinding( field ); } else { System.err.printf( "IGNORE: %-70s [type=%s] TYPE DOES NOT CONFORM TO EucalyptusData\n", field.getDeclaringClass( ).getCanonicalName( ) + "."+ field.getName( ), field.getType( ).getCanonicalName( ) ); return new NoopTypeBinding( field ); } } class RootObjectTypeBinding extends TypeBinding { private Class type; private boolean abs; public RootObjectTypeBinding( Class type ) { InternalSoapBindingGenerator.this.indent = 2; this.type = type; if ( Object.class.equals( type.getSuperclass( ) ) ) { this.abs = true; } else if ( type.getSuperclass( ).getSimpleName( ).equals( "EucalyptusData" ) ) { this.abs = true; } else { this.abs = false; } } @Override public String getTypeName( ) { return type.getCanonicalName( ); } public String process( ) { if( this.type.getCanonicalName( ) == null ) { new RuntimeException( "" + this.type ).printStackTrace( ); } else { this.elem( Elem.mapping ); if ( this.abs ) { this.attr( "abstract", "true" ); } else { this.attr( "name", this.type.getSimpleName( ) ).attr( "extends", this.type.getSuperclass( ).getCanonicalName( ) ); } this.attr( "class", this.type.getCanonicalName( ) ); if( BindingGenerator.MSG_TYPE.isAssignableFrom( this.type.getSuperclass( ) ) || BindingGenerator.DATA_TYPE.isAssignableFrom( this.type.getSuperclass( ) ) ) { this.elem( Elem.structure ).attr( "map-as", this.type.getSuperclass().getCanonicalName( ) ).end( ); } for ( Field f : type.getDeclaredFields( ) ) { TypeBinding tb = getTypeBinding( f ); if ( !( tb instanceof NoopTypeBinding ) ) { System.out.printf( "BOUND: %-70s [type=%s:%s]\n", f.getDeclaringClass( ).getCanonicalName( ) +"."+ f.getName( ), tb.getTypeName( ), f.getType( ).getCanonicalName( ) ); this.append( tb.toString( ) ); } } this.end( ); } return this.toString( ); } } @SuppressWarnings( "unchecked" ) public static Class getTypeArgument( Field f ) { Type t = f.getGenericType( ); if ( t != null && t instanceof ParameterizedType ) { Type tv = ( ( ParameterizedType ) t ).getActualTypeArguments( )[0]; if ( tv instanceof Class ) { return ( ( Class ) tv ); } } return null; } abstract class TypeBinding { private StringBuilder buf = new StringBuilder( ); public abstract String getTypeName( ); private TypeBinding reindent( int delta ) { InternalSoapBindingGenerator.this.indent += delta; INDENT = ""; for ( int i = 0; i < indent; i++ ) { INDENT += " "; } return this; } private TypeBinding indent( String addMe ) { this.reindent( +1 ).append( INDENT ).append( addMe ); return this; } private TypeBinding outdent( String addMe ) { this.reindent( -1 ).append( INDENT ).append( addMe ); return this; } protected TypeBinding append( Object o ) { this.buf.append( ""+o ); return this; } protected TypeBinding eolIn( ) { this.append( "\n" ).indent( INDENT ); return this; } protected TypeBinding eolOut( ) { this.append( "\n" ).outdent( INDENT ); return this; } protected TypeBinding eol( ) { this.append( "\n" ).append( INDENT ); return this; } protected TypeBinding value( String name ) { this.elem( Elem.value ).attr( "name", name ).attr( "field", name ).attr( "usage", "optional" ).attr( "style", "element" ).end( ); return this; } private TypeBinding begin( ) { ElemItem top = InternalSoapBindingGenerator.this.elemStack.peek( ); if ( top != null && top.children ) { this.eol( ); } else if ( top != null && !top.children ) { this.append( ">" ).eolIn( ); top.children = true; } else { this.eolIn( ); } return this; } protected TypeBinding elem( Elem name ) { this.begin( ).append( "<" ).append( name.toString( ) ).append( " " ); InternalSoapBindingGenerator.this.elemStack.push( new ElemItem( name, InternalSoapBindingGenerator.this.indent, false ) ); return this; } protected TypeBinding end( ) { ElemItem top = InternalSoapBindingGenerator.this.elemStack.pop( ); if ( top != null && top.children ) { this.eolOut( ).append( "</" ).append( top.name.toString( ) ).append( ">" ); } else if ( top != null && !top.children ) { this.append( "/>" ); } else { this.append( "/>" ); } return this; } protected TypeBinding attr( String name, String value ) { this.append( name ).append( "=\"" ).append( value ).append( "\" " ); return this; } public String toString( ) { String s = buf.toString( ); buf = new StringBuilder( buf.capacity( ) ); return s; } protected TypeBinding collection( String name ) { this.elem( Elem.structure ).attr( "name", name ).attr( "usage", "optional" ); this.elem( Elem.collection ).attr( "factory", "com.eucalyptus.binding.Binding.listFactory" ).attr( "field", name ) .attr( "item-type", this.getTypeName( ) ).attr( "usage", "required" ); this.elem( Elem.structure ).attr( "name", "item" ); this.elem( Elem.value ).attr( "name", "entry" ).end( ).end( ).end( ).end( ); return this; } } public boolean isIgnored( final Field field ) { final int mods = field.getModifiers( ); final String name = field.getName( ); final String type = field.getType( ).getSimpleName( ); if ( Modifier.isFinal( mods ) ) { LOG.debug( "Ignoring field with bad type: " + field.getDeclaringClass( ).getCanonicalName( ) + "." + name + " of type " + type + " due to: final modifier" ); } else if ( Modifier.isStatic( mods ) ) { LOG.debug( "Ignoring field with bad type: " + field.getDeclaringClass( ).getCanonicalName( ) + "." + name + " of type " + type + " due to: static modifier" ); } boolean ret = Iterables.any( badClasses, new Predicate<String>( ) { @Override public boolean apply( String arg0 ) { if ( type.matches( arg0 ) ) { LOG.debug( "Ignoring field with bad type: " + field.getDeclaringClass( ).getCanonicalName( ) + "." + name + " of type " + type + " due to: " + arg0 ); return true; } else { return false; } } } ); ret |= Iterables.any( badFields, new Predicate<String>( ) { @Override public boolean apply( String arg0 ) { if ( name.matches( arg0 ) ) { LOG.debug( "Ignoring field with bad name: " + field.getDeclaringClass( ).getCanonicalName( ) + "." + name + " of type " + type + " due to: " + arg0 ); return true; } else { return false; } } } ); return ret; } private class ElemItem { Elem name; int indent; boolean children; public ElemItem( Elem name, int indent, boolean children ) { this.name = name; this.indent = indent; this.children = children; } @Override public String toString( ) { return String.format( "ElemItem [name=%s, indent=%s, children=%s]", this.name, this.indent, Boolean.valueOf( children ) ); } } private Deque<ElemItem> elemStack = new LinkedList<ElemItem>( ); enum Elem { structure, collection, value, mapping, binding } class IgnoredTypeBinding extends NoopTypeBinding { public IgnoredTypeBinding( Field field ) { super( field ); } } class NoopTypeBinding extends TypeBinding { private String name; private Class type; public NoopTypeBinding( Field field ) { this.name = field.getName( ); this.type = field.getType( ); } @Override public String toString( ) { return ""; } @Override public String getTypeName( ) { return "NOOP"; } } class ObjectTypeBinding extends TypeBinding { private String name; private Class type; public ObjectTypeBinding( String name, Class type ) { this.name = name; this.type = type; } public ObjectTypeBinding( Field field ) { this.name = field.getName( ); this.type = field.getType( ); } @Override protected TypeBinding collection( String name ) { this.elem( Elem.structure ).attr( "name", name ).attr( "usage", "optional" ); this.elem( Elem.collection ).attr( "factory", "com.eucalyptus.binding.Binding.listFactory" ).attr( "field", name ).attr( "usage", "required" ); this.elem( Elem.structure ).attr( "name", "item" ).attr( "map-as", type.getCanonicalName( ) ); this.end( ).end( ).end( ); return this; } @Override public String getTypeName( ) { return this.type.getCanonicalName( ); } public String toString( ) { this.elem( Elem.structure ).attr( "name", this.name ).attr( "field", this.name ).attr( "map-as", this.type.getCanonicalName( ) ).attr( "usage", "optional" ).end( ); return super.toString( ); } } class CollectionTypeBinding extends TypeBinding { private TypeBinding type; private String name; public CollectionTypeBinding( String name, TypeBinding type ) { this.name = name; this.type = type; LOG.debug( "Found list type: " + type.getClass( ).getCanonicalName( ) ); } @Override public String getTypeName( ) { return type.getTypeName( ); } @Override public String toString( ) { LOG.debug( "Found list type: " + this.type.getTypeName( ) + " for name: " + name ); String ret = this.type.collection( this.name ).buf.toString( ); this.type.collection( this.name ).buf = new StringBuilder( ); return ret; } } class IntegerTypeBinding extends TypeBinding { @Override public String getTypeName( ) { return Integer.class.getCanonicalName( ); } } class LongTypeBinding extends TypeBinding { @Override public String getTypeName( ) { return Long.class.getCanonicalName( ); } } class StringTypeBinding extends TypeBinding { @Override public String getTypeName( ) { return String.class.getCanonicalName( ); } } class BooleanTypeBinding extends TypeBinding { @Override public String getTypeName( ) { return Boolean.class.getCanonicalName( ); } } }