/*************************************************************************
* Copyright 2009-2016 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* 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 General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.binding;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.annotation.Nullable;
import javax.persistence.Transient;
import org.apache.bcel.util.ClassPath;
import org.apache.log4j.Logger;
import org.jibx.binding.Utility;
import org.jibx.binding.classes.BoundClass;
import org.jibx.binding.classes.BranchWrapper;
import org.jibx.binding.classes.ClassCache;
import org.jibx.binding.classes.ClassFile;
import org.jibx.binding.classes.MungedClass;
import org.jibx.binding.def.BindingDefinition;
import org.jibx.binding.model.BindingElement;
import org.jibx.binding.model.ElementBase;
import org.jibx.binding.model.IncludeElement;
import org.jibx.binding.model.MappingElement;
import org.jibx.binding.model.MappingElementBase;
import org.jibx.binding.model.ValidationContext;
import org.jibx.runtime.JiBXException;
import org.jibx.util.ClasspathUrlExtender;
import com.eucalyptus.bootstrap.BillOfMaterials;
import com.eucalyptus.bootstrap.ServiceJarDiscovery;
import com.eucalyptus.component.annotation.ComponentMessage;
import com.eucalyptus.crypto.Digest;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.records.Logs;
import com.eucalyptus.system.Ats;
import com.eucalyptus.system.BaseDirectory;
import com.eucalyptus.system.SubDirectory;
import com.eucalyptus.util.Exceptions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.google.common.io.Resources;
public class BindingCache {
private static Logger LOG = Logger.getLogger( BindingCache.class );
public static void compileBindings( ) {
BindingFileSearch.compile( );
}
enum BindingFileSearch implements Predicate<URI> {
INSTANCE;
private static final String BINDING_EMPTY = "<binding>\n</binding>";
private static final Boolean BINDING_DEBUG = System.getProperty( "euca.binding.debug" ) != null;
private static final Boolean BINDING_DEBUG_EXTREME = System.getProperty( "euca.binding.debug.extreme" ) != null;
private static List<URI> BINDING_LIST = Lists.newArrayList( );
private static ConcurrentMap<String, Class> BINDING_CLASS_MAP = Maps.newConcurrentMap( );
/**
* We need to track the default computed element name assignments per class in order to
* determine multiple assignments during binnding time so we can do conflict resolution.
*/
private static Multimap<String, Class> BINDING_CLASS_ELEMENT_MAP = HashMultimap.create( );
private static final String BINDING_CACHE_JAR_PREFIX = "jar.";
private static final String BINDING_CACHE_BINDING_PREFIX = "binding.";
private static final String BINDING_CACHE_DIGEST_LIST = "classcache.properties";
private static final File CACHE_LIST = SubDirectory.CLASSCACHE.getChildFile( BINDING_CACHE_DIGEST_LIST );
private final Class<?> MSG_BASE_CLASS;
private final Class<?> MSG_DATA_CLASS;
private static final String FILE_PATTERN = System.getProperty( "euca.binding.pattern", ".*\\-binding.xml" );
private static final Properties CURRENT_PROPS = new Properties( );
private BindingFileSearch( ) {
try {
MSG_BASE_CLASS = Class.forName( "edu.ucsb.eucalyptus.msgs.BaseMessage" );
MSG_DATA_CLASS = Class.forName( "edu.ucsb.eucalyptus.msgs.EucalyptusData" );
} catch ( Exception ex ) {
LOG.error( ex, ex );
throw Exceptions.toUndeclared( ex );
}
}
public boolean check( ) {
final Properties oldProps = new Properties( );
if ( BindingFileSearch.CACHE_LIST.exists( ) ) {
try {
try ( final Reader propIn = Files.newReader( BindingFileSearch.CACHE_LIST, Charset.defaultCharset( ) ) ) {
oldProps.load( propIn );
}
} catch ( Exception ex ) {
LOG.debug( ex, ex );
}
}
Map<String, String> oldBindings = Maps.fromProperties( oldProps );
Map<String, String> newBindings = Maps.fromProperties( BindingFileSearch.CURRENT_PROPS );
if ( oldBindings.equals( newBindings ) ) {
LOG.info( "Found up-to-date binding class cache: skipping message binding." );
return true;
} else {
LOG.info( "Binding class cache expired, rebuilding." );
DeleteRecursively.PREDICATE.apply( SubDirectory.CLASSCACHE.getFile( ) );
if ( !SubDirectory.CLASSCACHE.getFile( ).mkdirs( ) && !SubDirectory.CLASSCACHE.getFile( ).exists( ) ) {
LOG.error( "Error creating class cache directory: " + SubDirectory.CLASSCACHE.getFile( ).getAbsolutePath( ) );
}
return false;
}
}
enum DeleteRecursively implements Predicate<File> {
PREDICATE;
@Override
public boolean apply( @Nullable File input ) {
if ( input != null ) try {
if ( input.isDirectory( ) ) {
LOG.info( "Cleaning up class cache: " + input.getCanonicalPath( ) );
Iterables.all( Arrays.asList( input.listFiles( ) ), DeleteRecursively.PREDICATE );
if ( !input.delete( ) ) {
LOG.error( "Unable to delete directory: " + input.getAbsolutePath( ) );
}
} else {
if ( !input.delete( ) ) {
LOG.error( "Unable to delete file: " + input.getAbsolutePath( ) );
}
}
} catch ( SecurityException ex ) {
LOG.error( ex );
throw ex;
} catch ( Exception ex ) {
LOG.error( ex );
}
return true;
}
}
public void store( ) throws IOException {
Writer propOut = new FileWriter( CACHE_LIST );
try {
try {
CURRENT_PROPS.store( propOut, "Binding class cache generated on: " );
propOut.close( );
} catch ( Exception ex ) {
LOG.error( ex, ex );
propOut.close( );
}
} catch ( IOException ex ) {
DeleteRecursively.PREDICATE.apply( SubDirectory.CLASSCACHE.getFile( ) );
if ( !SubDirectory.CLASSCACHE.getFile( ).mkdirs( ) && !SubDirectory.CLASSCACHE.getFile( ).exists( ) ) {
LOG.error( "Error creating class cache directory: " + SubDirectory.CLASSCACHE.getFile( ).getAbsolutePath( ) );
}
throw ex;
}
}
enum FileProcessingMode {
/**
* Scan files but do not update cache
*/
Scan,
/**
* Scan and build classcache
*/
Process,
}
public void process( final FileProcessingMode mode, File f ) throws Exception {
if ( f.isDirectory( ) ) {
File[] files = f.listFiles( new FilenameFilter( ) {
@Override
public boolean accept( File dir, String name ) {
return name.matches( FILE_PATTERN );
}
} );
for ( File ff : files ) {
byte[] bindingBytes = Files.toByteArray( ff );
this.addCurrentBinding( bindingBytes, ff.getName( ), "file:" + ff.getAbsolutePath( ) );
}
} else {
byte[] digestBytes = Files.hash( f, Hashing.md5() ).asBytes( );
String digest = BaseEncoding.base16( ).lowerCase( ).encode( digestBytes );
CURRENT_PROPS.put( BINDING_CACHE_JAR_PREFIX + f.getName( ), digest );
final JarFile jar = new JarFile( f );
final List<JarEntry> jarList = Collections.list( jar.entries( ) );
final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader( );
for ( final JarEntry j : jarList ) {
try {
if ( j.getName( ).matches( FILE_PATTERN ) ) {
byte[] bindingBytes = ByteStreams.toByteArray( jar.getInputStream( j ) );
String bindingName = j.getName( );
String bindingFullPath = "jar:file:" + f.getAbsolutePath( ) + "!/" + bindingName;
this.addCurrentBinding( bindingBytes, bindingName, bindingFullPath );
} else if ( FileProcessingMode.Process == mode && j.getName( ).matches( ".*\\.class.{0,1}" ) ) {
final String classGuess = j.getName( ).replace( '/', '.' ).replaceAll( "\\.class.{0,1}", "" );
final Class candidate = systemClassLoader.loadClass( classGuess );
if ( MSG_BASE_CLASS.isAssignableFrom( candidate ) || MSG_DATA_CLASS.isAssignableFrom( candidate ) ) {
ByteSource classSupplier = Resources.asByteSource( ClassLoader.getSystemResource( j.getName( ) ) );
File destClassFile = SubDirectory.CLASSCACHE.getChildFile( j.getName( ) );
if ( !destClassFile.exists( ) ) {
Files.createParentDirs( destClassFile );
try ( final InputStream in = classSupplier.openBufferedStream( );
final OutputStream out = new FileOutputStream( destClassFile ) ) {
ByteStreams.copy( in, out );
}
Logs.extreme( ).debug( "Caching: " + j.getName( ) + " => " + destClassFile.getAbsolutePath( ) );
}
BINDING_CLASS_MAP.putIfAbsent( classGuess, candidate );
BINDING_CLASS_ELEMENT_MAP.put( candidate.getSimpleName( ), candidate );
}
}
} catch ( RuntimeException ex ) {
LOG.error( ex, ex );
jar.close( );
throw ex;
}
}
jar.close( );
}
}
private void addCurrentBinding( byte[] bindingBytes, String bindingName, String bindingFullPath ) {
LOG.debug( "Binding cache: loading binding from: " + bindingFullPath );
BINDING_LIST.add( URI.create( bindingFullPath ) );
String digest = BaseEncoding.base16( ).lowerCase( ).encode( Hashing.md5( ).hashBytes( bindingBytes ).asBytes( ) );
String entryName = BINDING_CACHE_BINDING_PREFIX + bindingName;
if ( !CURRENT_PROPS.containsKey( entryName ) ) {
CURRENT_PROPS.put( entryName, digest );
} else {
//TODO:GRZE finish up this case.
LOG.info( "Duplicate binding entry: " + CURRENT_PROPS.getProperty( entryName ) );
}
}
@Override
public boolean apply( URI input ) {
try {
ValidationContext vctx = BindingElement.newValidationContext( );
BindingElement root = BindingElement.validateBinding( input.toASCIIString( ), input.toURL( ), input.toURL( ).openStream( ), vctx );
Predicate<BindingElement> writeFile = new Predicate<BindingElement>( ) {
@SuppressWarnings( "unchecked" )
@Override
public boolean apply( BindingElement input ) {
for ( ElementBase child : ( List<ElementBase> ) input.topChildren( ) ) {
try {
if ( child instanceof MappingElement ) {
MappingElementBase mapping = ( MappingElementBase ) child;
ClassFile classFile = mapping.getHandledClass( ).getClassFile( );
String classFileName = classFile.getName( ).replace( ".", "/" ) + ".class";
ByteSource classSupplier = Resources.asByteSource( ClassLoader.getSystemResource( classFileName ) );
File destClassFile = SubDirectory.CLASSCACHE.getChildFile( classFileName );
if ( !destClassFile.exists( ) ) {
Files.createParentDirs( destClassFile );
try ( final InputStream in = classSupplier.openBufferedStream( );
final OutputStream out = new FileOutputStream( destClassFile ) ) {
ByteStreams.copy( in, out );
}
}
ClassFile.getClassFile( classFile.getName( ) );
Logs.extreme( ).debug( "Caching: " + classFile.getName( ) + " => " + destClassFile.getAbsolutePath( ) );
} else if ( child instanceof IncludeElement ) {
IncludeElement includeElement = ( IncludeElement ) child;
BindingElement bind = includeElement.getBinding( );
if ( bind != null ) {
this.apply( bind );
} else {
Files.write( BINDING_EMPTY.getBytes( ), SubDirectory.CLASSCACHE.getChildFile( includeElement.getIncludePath( ).replace( "classpath:", "" ) ) );
}
}
} catch ( Exception ex ) {
LOG.error( "Failed in caching message class for mapping element: " + ((MappingElementBase)child).getClassName( ) + " because of: " + ex.getMessage( ), ex );
}
}
return true;
}
};
if ( !writeFile.apply( root ) ) {
writeFile.apply( root );
}
return true;
} catch ( Exception ex ) {
throw Exceptions.toUndeclared( ex );
}
}
public static void compile( ) {
LOG.info( "Binding cache: scanning message and binding files." );
processFiles( FileProcessingMode.Scan );
if ( BindingFileSearch.INSTANCE.check( ) ) {
LOG.info( "Binding cache: nothing to do." );
} else {
LOG.info( "Binding cache: processing message and binding files." );
processFiles( FileProcessingMode.Process );
LOG.info( "Binding cache: regenerating cache." );
try {
LOG.info( "Binding cache: generating internal bindings." );
// generate msgs-binding
InternalSoapBindingGenerator gen = new InternalSoapBindingGenerator( );
for ( Class genBindClass : BindingFileSearch.BINDING_CLASS_MAP.values( ) ) {
Logs.extreme( ).debug( "Generating binding: " + genBindClass );
gen.processClass( genBindClass );
}
gen.close( );
BINDING_LIST.add( gen.getOutFile( ).toURI( ) );
byte[] digestBytes = Files.hash( gen.getOutFile( ), Hashing.md5() ).asBytes( );
String digest = BaseEncoding.base16( ).lowerCase( ).encode( digestBytes );
CURRENT_PROPS.put( BINDING_CACHE_BINDING_PREFIX + gen.getOutFile( ).getName( ), digest );
LOG.info( "Binding cache: populating cache from transitive closure of bindings." );
// load *-binding.xml, populate cache w/ all referenced files
BindingFileSearch.reset( Utility.getClassPaths( ) );
Iterables.all( BindingFileSearch.BINDING_LIST, BindingFileSearch.INSTANCE );
BindingFileSearch.reset( Utility.getClassPaths( ) );
LOG.info( "Binding cache: loading and validating bindings." );
Map<URI, BindingDefinition> bindingDefs = Maps.newTreeMap( );
for ( URI binding : BINDING_LIST ) {
String shortPath = binding.toURL( ).getPath( ).replaceAll( ".*!/", "" );
String sname = Utility.bindingFromFileName( shortPath );
BindingDefinition def = Utility.loadBinding( binding.toASCIIString( ), sname, binding.toURL( ).openStream( ), binding.toURL( ), true );
bindingDefs.put( binding, def );
def.print( );
}
LOG.info( "Binding cache: compiling bindings." );
for ( Entry<URI, BindingDefinition> def : bindingDefs.entrySet( ) ) {
try {
LOG.info( "Binding cache: " + def.getKey( ) );
def.getValue( ).generateCode( BindingFileSearch.BINDING_DEBUG, BindingFileSearch.BINDING_DEBUG_EXTREME );
} catch ( RuntimeException e ) {
throw new JiBXException( "\n*** Error during code generation for file '" +
def.getKey( ) + "' -\n this may be due to an error in " +
"your binding or classpath, or to an error in the " +
"JiBX code ***\n", e );
}
}
ClassFile[][] lists = MungedClass.fixDispositions( );
for ( BindingDefinition def : bindingDefs.values( ) ) {
def.addClassList( lists[0], lists[1] );
}
MungedClass.writeChanges( );
LOG.info( "Binding cache: wrote " + lists[0].length + " files" );
LOG.info( "Binding cache: kept " + lists[1].length + " files unchanged:" );
LOG.info( "Binding cache: deleted " + lists[2].length + " files:" );
BindingFileSearch.INSTANCE.store( );
System.exit( 123 );//success! now we restart.
} catch ( Exception ex ) {
LOG.error( ex, ex );
System.exit( 1 );
throw new Error( "Failed to prepare the system while trying to compile bindings: " + ex.getMessage( ), ex );
}
}
}
public static void processFiles( final FileProcessingMode mode ) {
BindingFileSearch.CURRENT_PROPS.clear( );
final File libDir = new File( BaseDirectory.LIB.toString( ) );
for ( final File f : libDir.listFiles( ) ) {
if ( f.getName( ).startsWith( "eucalyptus" ) && f.getName( ).endsWith( ".jar" )
&& !f.getName( ).matches( ".*-ext-.*" ) ) {
EventRecord.here( ServiceJarDiscovery.class, EventType.BOOTSTRAP_INIT_SERVICE_JAR, f.getName( ) ).info( );
try {
BindingFileSearch.INSTANCE.process( mode, f );
} catch ( final Throwable e ) {
LOG.error( e.getMessage( ) );
}
}
}
for ( String pathName : ClassPath.getClassPath( ).split( File.pathSeparator ) ) {
File pathFile = new File( pathName );
if ( pathFile.isDirectory( ) ) {
try {
BindingFileSearch.INSTANCE.process( mode, pathFile );
} catch ( final Throwable e ) {
LOG.error( e.getMessage( ) );
}
}
}
}
public static String[] reset( String[] paths ) {
ClassCache.setPaths( paths );
ClassFile.setPaths( paths );
ClasspathUrlExtender.setClassLoader( ClassFile.getClassLoader( ) );
BoundClass.reset( );
MungedClass.reset( );
BindingDefinition.reset( );
BranchWrapper.setTracking( false );
BranchWrapper.setErrorOverride( false );
return paths;
}
}
private static class InternalSoapBindingGenerator {
private final String ns = "http://msgs.eucalyptus.com/" + BillOfMaterials.getVersion( );
private static String INDENT = "";
private final File outFile;
private PrintWriter out;
private String bindingName;
private int indent = 0;
private final Map<String, TypeBinding> typeBindings = ImmutableMap.<String, TypeBinding>builder( )
.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( Double.class.getCanonicalName( ), new DoubleTypeBinding( ) )
.put( "boolean", new BooleanTypeBinding( ) )
.put( "int", new IntegerTypeBinding( ) )
.put( "long", new LongTypeBinding( ) )
.put( "double", new DoubleTypeBinding( ) )
.put( java.util.Date.class.getCanonicalName( ), new StringTypeBinding( ) )
.build( );
private static final List<String> badClasses = ImmutableList.of(
".*HttpResponseStatus",
".*Closure",
".*Channel",
".*\\.JiBX_*"
);
private static final List<String> badFields = ImmutableList.of(
"__.*",
"\\w*\\$\\w*\\$*.*",
"class\\$.*",
"metaClass",
"JiBX_.*",
"serialVersionUID"
);
public InternalSoapBindingGenerator( ) {
this.outFile = new File( SubDirectory.CLASSCACHE.getFile( ).getAbsolutePath( ) + "/msgs-binding.xml" );
}
private static Set<String> classNames = new TreeSet<>( );
public void processClass( Class klass ) {
if ( this.out == null ) {
if ( this.outFile.exists( ) ) {
if ( !this.outFile.delete( ) ) {
LOG.error( "Error deleting binding file: " + this.outFile.getAbsolutePath( ) );
}
}
try {
this.out = new PrintWriter( this.outFile );
} catch ( FileNotFoundException e ) {
e.printStackTrace( System.err );
System.exit( -1 );//GRZE: special case to fail build
}
this.bindingName = this.ns.replaceAll( "(http://)|(/$)", "" ).replaceAll( "[./-]", "_" );
this.out.write( "<binding xmlns:euca=\"" + this.ns + "\" name=\"" + this.bindingName + "\">\n" );
this.out.write( " <namespace uri=\"" + this.ns + "\" default=\"elements\" prefix=\"euca\"/>\n" );
this.out.flush( );
}
if ( !classNames.contains( klass.getName( ) ) ) {
classNames.add( klass.getName( ) );
final String mapping = new RootObjectTypeBinding( klass ).process( );
this.out.write( mapping );
this.out.flush( );
} else {
Logs.extreme( ).debug( "Skipping duplicate class: " + klass );
}
}
public void close( ) {
try {
this.out.flush( );
this.out.write( "</binding>" );
this.out.flush( );
this.out.close( );
} catch ( Exception ex ) {
ex.printStackTrace( );
}
}
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 ) {
Logs.extreme( ).debug(
String.format( "IGNORE: %-70s [type=null] NO GENERIC TYPE FOR LIST\n", field.getDeclaringClass( ).getCanonicalName( ) + "." + field.getName( ) ) );
return new NoopTypeBinding( field );
} else if ( this.typeBindings.containsKey( listType.getCanonicalName( ) ) ) {
return new CollectionTypeBinding( field.getName( ), this.typeBindings.get( listType.getCanonicalName( ) ) );
} else if ( BindingFileSearch.INSTANCE.MSG_DATA_CLASS.isAssignableFrom( listType ) ) {
return new CollectionTypeBinding( field.getName( ), new ObjectTypeBinding( field.getName( ), listType ) );
} else if ( Enum.class.isAssignableFrom( listType ) ) {
return new CollectionTypeBinding( field.getName( ), new EnumTypeBinding( field.getName( ), listType ) );
} else {
Logs.extreme( ).debug(
String.format( "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 ( this.typeBindings.containsKey( itsType.getCanonicalName( ) ) ) {
TypeBinding t = this.typeBindings.get( itsType.getCanonicalName( ) );
try {
t = this.typeBindings.get( itsType.getCanonicalName( ) ).getClass( ).newInstance( );
} catch ( Exception e ) {}
return t.value( field.getName( ) );
} else if ( BindingFileSearch.INSTANCE.MSG_DATA_CLASS.isAssignableFrom( field.getType( ) ) ) {
return new ObjectTypeBinding( field );
} else if ( Enum.class.isAssignableFrom( field.getType() ) ) {
return new EnumTypeBinding( field.getName( ), field.getType( ) );
} else {
Logs.extreme( ).debug( String.format( "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;
this.abs =
Object.class.equals( type.getSuperclass() ) ||
type.getSuperclass().getSimpleName().equals( "EucalyptusData" );
}
@Override
public String getTypeName( ) {
return this.type.getCanonicalName( );
}
public String process( ) {
if ( this.type.getCanonicalName( ) != null ) {
this.elem( Elem.mapping );
if ( this.abs ) {
this.attr( "abstract", "true" );
} else {
String elementName = this.type.getSimpleName( );
/**
* GRZE: This tells us there is an element naming conflict.
* Since we cannot agree, nobody gets the element name
* ==> We prepend the component simple name (if we can find it)
*/
if ( BindingFileSearch.BINDING_CLASS_ELEMENT_MAP.get( elementName ).size( ) > 1 ) {
if ( Ats.inClassHierarchy( this.type ).has( ComponentMessage.class ) ) {
ComponentMessage compMsg = Ats.inClassHierarchy( this.type ).get( ComponentMessage.class );
elementName = compMsg.value( ).getSimpleName( ) + "." + elementName;
LOG.debug( "Binding generation encountered an element naming conflict. Using " + elementName + " for " + this.type.getCanonicalName( ) );
} else {
/**
* GRZE:WTF: this is a degenerate case which is ugly:
* 1. we have found a naming conflict for the element
* 2. we have /not/ found a component id which would allows us to give the element a
* qualified unique name
* So...
* 3. we use the fully qualified class name...
* ==> the auteur has screwed up way earlier and it isn't our problem here.
* Log something so we don't feel to guilty.
*/
elementName = this.type.getCanonicalName( );
LOG.error( "BUG: Fix your message type definitions for " + this.type );
LOG.error( "BUG: Binding generation encountered an element naming conflict. Using " + elementName + " for " + this.type.getCanonicalName( ) );
}
}
//GRZE:TODO: looks like namespace mapping doesn't actual account for naming conflicts, come back to this later.
// this.attr( "ns", namespace );
this.attr( "name", elementName );
this.attr( "extends", this.type.getSuperclass( ).getCanonicalName( ) );
}
this.attr( "class", this.type.getCanonicalName( ) );
//GRZE:TODO: looks like namespace mapping doesn't actual account for naming conflicts, come back to this later.
// this.elem( Elem.namespace ).attr( "uri", namespace ).attr( "default", "elements" ).attr( "prefix", this.nsPrefix ).end( );
if ( BindingFileSearch.INSTANCE.MSG_BASE_CLASS.isAssignableFrom( this.type.getSuperclass( ) )
|| BindingFileSearch.INSTANCE.MSG_DATA_CLASS.isAssignableFrom( this.type.getSuperclass( ) ) ) {
this.elem( Elem.structure ).attr( "map-as", this.type.getSuperclass( ).getCanonicalName( ) ).end( );
}
for ( Field f : this.type.getDeclaredFields( ) ) {
if ( !Ats.from( f ).has( Transient.class ) || Modifier.isTransient( f.getModifiers( ) ) ) {
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 < InternalSoapBindingGenerator.this.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 = this.buf.toString( );
this.buf = new StringBuilder( this.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 ) ) {
Logs.extreme( ).debug( "Ignoring field with bad type: " + field.getDeclaringClass( ).getCanonicalName( ) + "." + name + " of type " + type
+ " due to: final modifier" );
} else if ( Modifier.isStatic( mods ) ) {
Logs.extreme( ).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 ) ) {
Logs.extreme( ).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 ) ) {
Logs.extreme( ).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 indentCount;
boolean children;
public ElemItem( Elem name, int indent, boolean children ) {
this.name = name;
this.indentCount = indent;
this.children = children;
}
@Override
public String toString( ) {
return String.format( "ElemItem [name=%s, indent=%s, children=%s]", this.name, this.indentCount, this.children );
}
}
private Deque<ElemItem> elemStack = new LinkedList<>( );
enum Elem {
structure,
collection,
value,
mapping,
binding,
namespace
}
class NoopTypeBinding extends TypeBinding {
public NoopTypeBinding( Field field ) {
}
@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", this.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 EnumTypeBinding extends TypeBinding {
private final String name;
private final Class<?> enumType;
EnumTypeBinding( final String name,
final Class<?> enumType ) {
this.name = name;
this.enumType = enumType;
}
@Override
public String getTypeName( ) {
return enumType.getCanonicalName( );
}
@Override
public String toString() {
value( name );
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;
Logs.extreme( ).debug( "Found list type: " + type.getClass( ).getCanonicalName( ) );
}
@Override
public String getTypeName( ) {
return this.type.getTypeName( );
}
@Override
public String toString( ) {
Logs.extreme( ).debug( "Found list type: " + this.type.getTypeName( ) + " for name: " + this.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 DoubleTypeBinding extends TypeBinding {
@Override
public String getTypeName( ) {
return Double.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( );
}
}
public File getOutFile( ) {
return this.outFile;
}
}
}