package de.dfki.util.xmlrpc.conversion;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.dfki.util.xmlrpc.XmlRpc;
import de.dfki.util.xmlrpc.XmlRpc.Type;
import de.dfki.util.xmlrpc.common.ApiParameter;
/**
* Advanced type converter able to handle user-specific types which define conversion methods
* to XML-RPC compliant types.
*
* @author lauer
*/
public class EnhancedTypeConverter
extends StandardXmlRpcTypeConverter
{
private static Logger mLog = Logger.getLogger( EnhancedTypeConverter.class.getName() );
public static Logger log()
{
return ( mLog );
}
public static byte[] NULL_MASK_VALUE = new byte[0];
public boolean isNullMaskValue( Object value )
{
return( value.getClass().equals( byte[].class )
&& Arrays.equals( (byte[])value, NULL_MASK_VALUE ) );
}
@Override
public Object convertToUserRepresentation( ApiParameter apiParam, Object parameterValueInXmlRpcRep )
throws TypeConversionException
{
assert apiParam != null;
Object userRepValue = null;
final Class<?> xmlRpcRepClass = parameterValueInXmlRpcRep.getClass();
final Type paramXmlRpcType = apiParam.getXmlRpcType();
final Class<?> apiRepClass = apiParam.getApiRepresentationClass();
final boolean isNullMaskValue = isNullMaskValue( parameterValueInXmlRpcRep ) && XmlRpc.usesAutomaticNullMasking();
//void type?
if (apiParam.getXmlRpcType() == Type.NONE)
{
return( null );
}
//we have detected a null-value mask
if (apiParam.getXmlRpcType() != Type.BASE64 && isNullMaskValue)
{
parameterValueInXmlRpcRep = null ;
}
//does the api use a XML-RPC type?
if (apiParam.isXmlRpcCompatible())
{
//using containers?
if (Collection.class.isAssignableFrom( apiRepClass ))
{
userRepValue = convertCollectionToUserRepresentation( apiRepClass, apiParam.getContainerContent(), (Collection<?>)parameterValueInXmlRpcRep );
}
else if (Map.class.isAssignableFrom( apiRepClass ))
{
userRepValue = convertMapToUserRepresentation( apiRepClass, apiParam.getContainerContent(), (Map<?,?>)parameterValueInXmlRpcRep );
}
else if (apiParam.isArray() && apiParam.getXmlRpcType() != Type.BASE64 && apiParam.getXmlRpcType() != Type.STRINGasBASE64 )
{
//transform a collection into an array
userRepValue = convertArrayToUserRepresentation( apiParam.getContainerContent(), (Collection<?>)parameterValueInXmlRpcRep );
}
else
{
//no containers, just plain objects
userRepValue = super.convertToUserRepresentation( apiParam, parameterValueInXmlRpcRep );
}
return( userRepValue );
}
//we use an own type as api parameter (this is an extension to XML-RPC)
if (parameterValueInXmlRpcRep != null && !areCompatible( paramXmlRpcType, parameterValueInXmlRpcRep ))
{
throw( new TypeConversionException( "Incompatible parameter class " + xmlRpcRepClass + ". Cannot map to " + paramXmlRpcType ) );
}
userRepValue = convertToUserRepWithConverter( apiParam, parameterValueInXmlRpcRep );
// if (userRepValue == null)
// {
// throw( new TypeConversionException( "Failed to convert class " + xmlRpcRepClass.getName() + " to api class " + apiRepClass.getName() ) );
// }
return( userRepValue );
}
@Override
public Object convertToXmlRpcRepresentation( final ApiParameter apiParam, final Object parameterValue )
throws TypeConversionException
{
final Class<?> apiRepClass = apiParam.getApiRepresentationClass(); //type of the api-parameter to handle
boolean isPrimitiveParam = apiRepClass.isPrimitive();
boolean maskNullValues = XmlRpc.usesAutomaticNullMasking();
Object xmlRpcValue = null; //the xmlRpc value to send over the wire
//do not handle void types
if (apiRepClass.equals( void.class ) /*|| parameterInUserRep == null*/ )
{
return( null );
}
//do we have a valid non-null parameter. This check is necessary when using the MethodCall.add method.
if (parameterValue != null)
{
final Class<?> userRepClass = parameterValue.getClass();
if((isPrimitiveParam && !checkPrimitiveType( apiRepClass, userRepClass ))
|| (!isPrimitiveParam && !apiRepClass.isAssignableFrom( userRepClass )))
{
throw( new TypeConversionException( "Incompatible parameter class " + userRepClass + ". Needed instance of class " + apiRepClass ) );
}
}
//
if (Collection.class.isAssignableFrom( apiRepClass ))
{
xmlRpcValue = convertCollectionToXmlRpc( apiParam.getContainerContent(), (Collection<?>)parameterValue );
}
else if (Map.class.isAssignableFrom( apiRepClass ))
{
xmlRpcValue = convertMapToXmlRpc( apiParam.getContainerContent(), (Map<?,?>)parameterValue );
}
else if (apiParam.isArray() && apiParam.getXmlRpcType() != Type.BASE64 && apiParam.getXmlRpcType() != Type.STRINGasBASE64)
{
xmlRpcValue = convertArrayToXmlRpc( apiParam.getContainerContent(), parameterValue );
}
//parameter uses converters?
if (apiParam.usesConvertable())
{
xmlRpcValue = convertToXmlRpcWithConvertable( parameterValue );
}
else if (apiParam.getSeparateParameterConverter() != null)
{
xmlRpcValue = convertToXmlRpcWithSeparateConverter( parameterValue, apiParam.getSeparateParameterConverter() );
}
//if all before failed: try standard
if (xmlRpcValue == null)
{
xmlRpcValue = super.convertToXmlRpcRepresentation( apiParam, parameterValue );
}
//still no success: can we mask the null?
if (parameterValue == null
&& maskNullValues
&& apiParam.getXmlRpcType() != Type.BASE64)
{
xmlRpcValue = NULL_MASK_VALUE;
}
//all else failed: we cannot convert this value!
if (xmlRpcValue == null)
{
//generate a special error message for null-parameter values
if (parameterValue == null)
{
throw( new TypeConversionException( "'null' parameter could not be handled. Use a ParameterConverter or enable automatic null-masking by calling XmlRpc.useAutomaticNullMasking( true )." ) );
}
//we found a xml-rpc unaware type
//standard failure message
throw( new TypeConversionException( "Conversion failed. Incompatible type and no annotation found." ) );
}
return( xmlRpcValue );
}
private boolean checkPrimitiveType( Class<?> apiRepClass, Class<?> userRepClass )
{
boolean ok = false;
if (apiRepClass.equals( Integer.TYPE ))
ok = userRepClass.equals( Integer.class );
else if (apiRepClass.equals( Double.TYPE ))
ok = userRepClass.equals( Double.class );
else if (apiRepClass.equals( Float.TYPE ))
ok = userRepClass.equals( Float.class );
else if (apiRepClass.equals( Boolean.TYPE ))
ok = userRepClass.equals( Boolean.class );
return( ok );
}
/** Converts a whole collection recursively. TODO: avoid collection copy, if conversion does not change values. */
protected Object convertCollectionToXmlRpc( ApiParameter apiParam, Collection<?> parameterInUserRep )
throws TypeConversionException
{
if (apiParam == null || parameterInUserRep == null)
return( parameterInUserRep );
Collection<Object> tmpCollection = new LinkedList<Object>();
Iterator<?> i = parameterInUserRep.iterator();
while( i.hasNext() )
{
Object element = i.next();
Object newElement = convertToXmlRpcRepresentation( apiParam, element );
tmpCollection.add( newElement );
}
return( tmpCollection );
}
/** Converts a whole map recursively. TODO: avoid map copy, if conversion does not change values. */
protected Object convertMapToXmlRpc( ApiParameter apiParam, Map<?,?> parameterInUserRep )
throws TypeConversionException
{
if (apiParam == null || parameterInUserRep == null)
return( parameterInUserRep );
//make a new map to avoid changing input parameters
Map<String,Object> xmlRpcRep = new HashMap<String,Object>();
for( Map.Entry<?,?> entry : parameterInUserRep.entrySet() )
{
String id = entry.getKey().toString();
Object element = entry.getValue();
Object newElement = convertToXmlRpcRepresentation( apiParam, element );
xmlRpcRep.put( id, newElement );
}
return( xmlRpcRep );
}
protected Object convertArrayToXmlRpc( ApiParameter apiParam, Object parameterInUserRep )
throws TypeConversionException
{
if (apiParam == null || parameterInUserRep == null)
return( parameterInUserRep );
assert( parameterInUserRep.getClass().isArray() );
//we know it's an array. But is it a primitive type or not? Cannot cast an int[] to Object[] :-(
int len = Array.getLength( parameterInUserRep );
Collection<Object> tmpCollection = new LinkedList<Object>();
for( int i=0; i<len; i++ )
{
Object element = Array.get( parameterInUserRep, i );
Object newElement = convertToXmlRpcRepresentation( apiParam, element );
tmpCollection.add( newElement );
}
return( tmpCollection );
}
protected Object convertToXmlRpcWithConvertable( Object parameterInUserRep )
throws TypeConversionException
{
if (log().isLoggable( Level.FINER ))
log().finer( "Converting parameter value '" + parameterInUserRep + "' by calling toXmlRpc method" );
//we have a null value: skip conversion (maybe to an auto-masked xml-rpc call)
if (parameterInUserRep == null)
{
return( null );
}
Object xmlRpcValue = null;
try
{
//call conversion method on parameter itself
Method m = parameterInUserRep.getClass().getMethod( "toXmlRpc" );
xmlRpcValue = m.invoke( parameterInUserRep, (Object[])null );
}
catch( Exception e )
{
throw( new TypeConversionException( e ) );
}
return( xmlRpcValue );
}
/** conversion: will pass null values to a converter. */
protected Object convertToXmlRpcWithSeparateConverter( Object parameterValueInUserRep,
ParameterConverter<?,?> converter )
throws TypeConversionException
{
if (log().isLoggable( Level.FINER ))
log().finer( "Converting '" + parameterValueInUserRep + "' by using converter '" + converter + "'(" + converter.getClass().getName() + ")" );
Object xmlRpcValue = null;
try
{
Method m = converter.getClass().getMethod( "toXmlRpc", Object.class );
xmlRpcValue = m.invoke( converter, parameterValueInUserRep );
}
catch( InvocationTargetException e )
{
throw( new TypeConversionException( e.getCause() ) );
}
catch( Exception e )
{
throw( new TypeConversionException( e ) );
}
return( xmlRpcValue );
}
//
// ------ methods for conversions XML-RPC -> Api
//
@SuppressWarnings("unchecked")
protected Collection<Object> createCollectionContainer( Class<?> apiContainerClass )
throws TypeConversionException
{
Collection<Object> container = null;
if (!apiContainerClass.isInterface())
{
//maybe we have a concrete type. Try to create an instance with noarg ctor
try
{
Object instance = apiContainerClass.newInstance();
return( (Collection<Object>)instance );
}
catch( Exception e )
{
//no we don't
}
}
if (Set.class.isAssignableFrom( apiContainerClass ))
{
container = new HashSet<Object>();
}
else if( List.class.isAssignableFrom( apiContainerClass ))
{
container = new LinkedList<Object>();
}
else if( Collection.class.isAssignableFrom( apiContainerClass ))
{
container = new LinkedList<Object>();
}
if (container == null)
{
throw( new TypeConversionException( "unable to create an instance of " + apiContainerClass ) );
}
return( container );
}
@SuppressWarnings("unchecked")
protected Map<String,Object> createMapContainer( Class<?> apiContainerClass )
throws TypeConversionException
{
Map<String,Object> container = null;
if (!apiContainerClass.isInterface())
{
//maybe we have a concrete type. Try to create an instance with noarg ctor
try
{
Object instance = apiContainerClass.newInstance();
return( (Map<String,Object>)instance );
}
catch( Exception e )
{
//no we don't
}
}
if (Map.class.isAssignableFrom( apiContainerClass ))
{
container = new HashMap<String,Object>();
}
if (container == null)
{
throw( new TypeConversionException( "unable to create an instance of " + apiContainerClass ) );
}
return( container );
}
/** Converts a whole collection recursively. TODO: avoid collection copy, if conversion does not change values. */
public Object convertCollectionToUserRepresentation( Class<?> apiContainerClass, ApiParameter apiParam, Collection<?> parameterInXmlRpcRep )
throws TypeConversionException
{
if (apiParam == null || parameterInXmlRpcRep == null)
return( parameterInXmlRpcRep );
Collection<Object> container = createCollectionContainer( apiContainerClass );
Iterator<?> i = parameterInXmlRpcRep.iterator();
while( i.hasNext() )
{
Object element = i.next();
Object newElement = convertToUserRepresentation( apiParam, element );
container.add( newElement );
}
return( container );
}
/** Converts a whole map recursively. TODO: avoid map copy, if conversion does not change values. */
public Object convertMapToUserRepresentation( Class<?> apiContainerClass, ApiParameter apiParam, Map<?,?> parameterInXmlRpcRep )
throws TypeConversionException
{
if (apiParam == null || parameterInXmlRpcRep == null)
return( parameterInXmlRpcRep );
//make a new map to avoid changing input parameters
Map<String,Object> userRep = createMapContainer( apiContainerClass );
for( Map.Entry<?,?> entry : parameterInXmlRpcRep.entrySet() )
{
final String id = entry.getKey().toString();
final Object element = entry.getValue();
Object newElement = convertToUserRepresentation( apiParam, element );
userRep.put( id, newElement );
}
return( userRep );
}
protected Object convertArrayToUserRepresentation( ApiParameter apiParam, Collection<?> parameterInXmlRpcRep )
throws TypeConversionException
{
if (apiParam == null || parameterInXmlRpcRep == null)
return( parameterInXmlRpcRep );
Object array = Array.newInstance( apiParam.getApiRepresentationClass(), parameterInXmlRpcRep.size() );
Iterator<?> i = parameterInXmlRpcRep.iterator();
int pos = 0;
while( i.hasNext() )
{
Object element = i.next();
Object newElement = convertToUserRepresentation( apiParam, element );
Array.set( array, pos, newElement );
pos++;
}
return( array );
}
/**
* Try to convert an object in XML-RPC representation into a parameter object compatible with the used api.
* First check: do we have a bound converter (parameter class does conversion itself).
* Second check: do we have an autonomous converter (a separate class which does the conversion)
*/
protected Object convertToUserRepWithConverter( ApiParameter apiParam, Object parameterInXmlRpcRep )
throws TypeConversionException
{
Object userRepValue = null;
if (apiParam.usesConvertable())
{
userRepValue = convertToUserRepWithConvertable( parameterInXmlRpcRep, apiParam.getXmlRpcType(), apiParam.getConvertableClass() );
}
else if (apiParam.getSeparateParameterConverter() != null)
{
userRepValue = convertToUserRepWithSeparateConverter( parameterInXmlRpcRep, apiParam.getSeparateParameterConverter() );
}
return( userRepValue );
}
protected Object convertToUserRepWithConvertable( Object parameterValueInXmlRpcRep,
Type xmlRpcType,
Class<? extends Convertable<?>> convertableCls )
throws TypeConversionException
{
if (parameterValueInXmlRpcRep == null)
return( null );
assert convertableCls != null;
Object userRepValue = null;
try
{
Constructor<?> ctor = ConversionUtils.findConvertableConstructor( convertableCls, xmlRpcType, parameterValueInXmlRpcRep.getClass() );
if (ctor == null)
{
throw( new TypeConversionException( "Failed to create parameter value instance. " + convertableCls + " needs public contructor with argument type " + parameterValueInXmlRpcRep.getClass() ) );
}
userRepValue = ctor.newInstance( parameterValueInXmlRpcRep );
}
catch( IllegalAccessException e )
{
throw( new TypeConversionException( "Failed to create parameter value instance. Can't access constructor of " + convertableCls, e ) );
}
catch( Exception e )
{
throw( new TypeConversionException( "Error while creating parameter value instance.", e ) );
}
return( userRepValue );
}
protected Object convertToUserRepWithSeparateConverter( Object parameterValueInXmlRpcRep,
ParameterConverter<?,?> converter )
throws TypeConversionException
{
Object userRepValue = null;
try
{
Method m = converter.getClass().getMethod( "createFrom", Object.class );
userRepValue = m.invoke( converter, parameterValueInXmlRpcRep );
}
catch( Throwable e )
{
if (e instanceof InvocationTargetException)
{
e = e.getCause();
}
throw ( new TypeConversionException( e ) );
}
return ( userRepValue ); }
}