/**
*
*/
package de.dfki.util.xmlrpc.common;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.WildcardType;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import de.dfki.util.xmlrpc.XmlRpc.Type;
import de.dfki.util.xmlrpc.annotation.AnnotationException;
import de.dfki.util.xmlrpc.annotation.AnnotationUtils;
import de.dfki.util.xmlrpc.annotation.XmlRpc;
import de.dfki.util.xmlrpc.annotation.XmlRpcBean;
import de.dfki.util.xmlrpc.conversion.ConversionUtils;
import de.dfki.util.xmlrpc.conversion.Convertable;
import de.dfki.util.xmlrpc.conversion.NoParameterBoundConverter;
import de.dfki.util.xmlrpc.conversion.NoSeparateParameterConverter;
import de.dfki.util.xmlrpc.conversion.ParameterConverter;
import de.dfki.util.xmlrpc.conversion.ParameterConverterRegistry;
import de.dfki.util.xmlrpc.conversion.StandardXmlRpcTypeConverter;
import de.dfki.util.xmlrpc.conversion.TypeConversionException;
import de.dfki.util.xmlrpc.conversion.XmlRpcBeanConverter;
/**
* Represents a parameter (either passed to a method or returned from it).
* All the annotation information is retrieved an stored. This information includes
* annotations for the parameter and annotations for the method (return value).
*
* @author lauer
*/
public class ApiParameter
{
private static Logger mLog = Logger.getLogger( ApiParameter.class.getName() );
public static Logger log() { return( mLog ); }
/** Derives the class object associated with a certain type. */
private static Class<?> getClassFromType( java.lang.reflect.Type type )
{
assert type != null;
if (type instanceof Class)
{
return( (Class<?>)type );
}
if( type instanceof ParameterizedType )
{
ParameterizedType pt = (ParameterizedType)type;
java.lang.reflect.Type t = pt.getRawType();
return( getClassFromType( t ) );
}
if( type instanceof GenericArrayType )
{
GenericArrayType gat = (GenericArrayType)type;
java.lang.reflect.Type t = gat.getGenericComponentType();
Class<?> componentCls = getClassFromType( t );
Object o = Array.newInstance( componentCls, 0 );
return( o.getClass() );
}
if( type instanceof WildcardType )
{
WildcardType wct = (WildcardType)type;
java.lang.reflect.Type[] upperBounds = wct.getUpperBounds();
if (upperBounds.length >= 1)
{
return( getClassFromType( upperBounds[0] ) );
}
}
//no clue!
return( Object.class );
}
/** Retrieves the contained type of a given generic type.
* @return The content type. <code>null</code>, if the type was not a container.
*/
private static java.lang.reflect.Type getContentType( java.lang.reflect.Type type )
{
if( type instanceof ParameterizedType )
{
ParameterizedType pt = (ParameterizedType)type;
java.lang.reflect.Type[] typeArgs = pt.getActualTypeArguments();
java.lang.reflect.Type t = null;
if (typeArgs.length >= 1)
{
t = typeArgs[typeArgs.length-1];
}
return( t );
}
if( type instanceof GenericArrayType )
{
GenericArrayType gat = (GenericArrayType)type;
java.lang.reflect.Type t = gat.getGenericComponentType();
return( t );
}
if (type instanceof Class)
{
Class<?> cls = (Class<?>)type;
if (cls.isArray())
{
java.lang.reflect.Type t = cls.getComponentType();
return( t );
}
}
//no content
return( null );
}
/**
* Creates a parameter description for a method return parameter.
* @throws TypeConversionException
*/
public static ApiParameter createReturnParameter( Method m )
throws TypeConversionException
{
ApiParameter param = createFrom( AnnotationUtils.getAnnotationsFrom( m ), m.getGenericReturnType(), m.getReturnType() );
return( param );
}
/**
* Collects the information about a single api parameter.
*
* @param parameterAnnotations
* annotations associated with the method the parameter belongs to (may
* be <code>null</code> or empty).
* @param apiParameterType
* type information taken from the methods generic type description.
* @param apiParameterClass
* class of the parameter.
* @return The condensed information about the parameter.
*
* @throws TypeConversionException
*/
public static ApiParameter createFrom( Annotation[] parameterAnnotations,
java.lang.reflect.Type apiParameterType,
Class<?> apiParameterClass )
throws TypeConversionException
{
Class<? extends ParameterConverter<?,?>> separateConverterCls = null;
Class<? extends Convertable<?>> convertableCls = null;
ApiParameter containerContent = null;
if( apiParameterType == null )
{
throw( new TypeConversionException( "Cannot create ApiParameter description without type!" ) );
}
if (apiParameterClass == null)
{
apiParameterClass = getClassFromType( apiParameterType );
}
//try a std mapping
Type xmlRpcType = StandardXmlRpcTypeConverter.mapJavaTypeToXmlRpcType( apiParameterClass );
boolean isXmlRpcCompatible = false;
if (xmlRpcType != null || Object.class.equals( apiParameterClass ) )
{
isXmlRpcCompatible = true;
}
if (xmlRpcType == null && apiParameterClass.isArray())
{
isXmlRpcCompatible = true;
xmlRpcType = Type.ARRAY;
}
// let's see what the annotations tell us
//first: parameter annotations / types with nested content information maps, collections, arrays
//Contains containsAnno = AnnotationUtils.getAnnotation( parameterAnnotations, Contains.class );
java.lang.reflect.Type contentType = getContentType( apiParameterType );
if (/*containsAnno != null ||*/ contentType != null && xmlRpcType != Type.BASE64 )
{
containerContent = createFrom( new Annotation[0], contentType, getClassFromType( contentType ) );
}
XmlRpcBean xmlRpcBeanAnno = AnnotationUtils.getAnnotationForClass( apiParameterClass, XmlRpcBean.class );
if (xmlRpcBeanAnno != null)
{
try
{
xmlRpcType = Type.STRUCT;
ParameterConverter<?,?> c = new XmlRpcBeanConverter( apiParameterClass );
//register a converter for especially for this bean type
ParameterConverterRegistry.setParameterConverterForClass( apiParameterClass, c );
ParameterConverterRegistry.readParameterConverterMappingsFromApiClass( apiParameterClass );
}
catch( Exception e )
{
throw( new TypeConversionException( "Error creating bean converter:", e ) );
}
}
//second: annotations for used types
XmlRpc xmlRpcAnno = AnnotationUtils.getAnnotationForClass( apiParameterClass, XmlRpc.class );
if( xmlRpcAnno != null )
{
xmlRpcType = xmlRpcAnno.type();
convertableCls = xmlRpcAnno.concrete();
separateConverterCls = xmlRpcAnno.converter();
//usesConvertable = a concete class != parameter class does conversion
boolean usesConvertable = !convertableCls.equals( NoParameterBoundConverter.class );
//usesConverter = a seperate converter does conversion
boolean usesConverter = !separateConverterCls.equals( NoSeparateParameterConverter.class );
//isConvertable = the parameter does the work itself
boolean isConvertable = !usesConvertable && !usesConverter;
if (usesConvertable && usesConverter)
{
throw( new AnnotationException( "Parameter can only be either a convertable or use a parameter converter, not both at a time!" ) );
}
if (isConvertable)
{
convertableCls = validateConvertableClass( apiParameterClass, xmlRpcType );
}
else if (usesConvertable)
{
validateConvertableClass( convertableCls, xmlRpcType );
}
else if (usesConverter)
{
checkParameterConverter( separateConverterCls, xmlRpcType );
}
}
ParameterConverter<?,?> converter = ParameterConverterRegistry.getParameterConverterByClass( separateConverterCls );
if (converter == null)
{
//maybe a mapping
converter = ParameterConverterRegistry.getParameterConverterForClass( apiParameterClass );
}
if( converter != null )
{
xmlRpcType = converter.getXmlRpcRepresentationType();
}
if( xmlRpcType == null && !isXmlRpcCompatible
&& de.dfki.util.xmlrpc.XmlRpc.treatUnknownTypesAsBeans() )
{
//the last chance: treat this type as a bean
log().info( "Found XML-RPC unaware type '" + apiParameterClass.getName() + "' treating it as XmlRpcBean!" );
converter = new XmlRpcBeanConverter( apiParameterClass );
xmlRpcType = converter.getXmlRpcRepresentationType();
ParameterConverterRegistry.setParameterConverterForClass( apiParameterClass, converter );
log().info( "XmlRpcBeanConverter registered for " + apiParameterClass );
}
if( xmlRpcType == null && !isXmlRpcCompatible )
{
throw ( new TypeConversionException( "Cannot find a XML-RPC compliant type conversion for class "
+ apiParameterClass.getName() + ". Maybe missing annotation?" ) );
}
ApiParameter param = new ApiParameter( xmlRpcType, apiParameterClass, converter, convertableCls, isXmlRpcCompatible, containerContent );
return( param );
}
/**
* Checks if a class fulfills the requirements of a convertable object. If this is the case the class is returned
* as a Convertable class.
*/
@SuppressWarnings("unchecked")
public static Class<? extends Convertable<?>> validateConvertableClass( Class<?> cls, Type xmlRpcType )
throws TypeConversionException
{
//avoid redundant checks
if (mAlreadyChecked.contains( cls ))
return((Class<? extends Convertable<?>>)cls);
//class is a Convertable
if (!Convertable.class.isAssignableFrom( cls ))
{
throw( new InvalidConverterException( cls + " must implement " + Convertable.class ) );
}
//and has a public constructor
Constructor<?> ctor = ConversionUtils.findConvertableConstructor( cls, xmlRpcType );
if (ctor == null)
{
throw( new InvalidConverterException( cls + " must have a public single argument constructor which consumes a parameter of type " + xmlRpcType.getCorrespondingParameterType() ) );
}
//TODO: toXmlRpc does return the right type?
mAlreadyChecked.add( cls );
return( (Class<? extends Convertable<?>>)cls );
}
private static Set<Class<?>> mAlreadyChecked = new HashSet<Class<?>>();
public static void checkParameterConverter( Class<? extends ParameterConverter<?,?>> cls, Type xmlRpcType )
throws TypeConversionException
{
//is a ParameterConverter?
if (!ParameterConverter.class.isAssignableFrom( cls ))
{
throw( new InvalidConverterException( cls + " must implement " + ParameterConverter.class ) );
}
//has a public non args constructor
try
{
cls.getConstructor( new Class[0] );
}
catch( SecurityException e )
{
throw( new InvalidConverterException( "Cannot access constructor of parameter converter '" + cls + "'", e ) );
}
catch( NoSuchMethodException e )
{
throw( new InvalidConverterException( "Parameter converter '" + cls + "' does not have a public noarg constructor" ) );
}
//TODO: check: conversion method parameters fit XML-RPC type
}
public static ApiParameter createCallParameterFrom( Type xmlRpcType )
{
ApiParameter p = new ApiParameter( xmlRpcType, xmlRpcType.getCorrespondingParameterType(), null, null, true, null );
return( p );
}
public static ApiParameter createReturnParameterFrom( Type xmlRpcType )
{
ApiParameter p = new ApiParameter( xmlRpcType, xmlRpcType.getCorrespondingBaseType(), null, null, true, null );
return( p );
}
public static ApiParameter createFrom( Class<?> targetClass )
throws TypeConversionException
{
ApiParameter p = createFrom( null, targetClass, targetClass );
return( p );
}
ApiParameter( Type xmlRpcType,
Class<?> apiClass,
ParameterConverter<?,?> converter,
Class<? extends Convertable<?>> convertableCls,
boolean isXmlRpcCompatible,
ApiParameter containerConent )
{
mXmlRpcType = xmlRpcType;
mApiRepClass = apiClass;
mSeparateConverter = converter;
mConvertableCls = convertableCls;
mIsXmlRpcCompatible = isXmlRpcCompatible;
mContainerContent = containerConent;
}
public boolean isArray()
{
return( getApiRepresentationClass().isArray() );
}
public Class<?> getApiRepresentationClass()
{
return mApiRepClass;
}
public void setApiRepresentationClass( Class<?> apiClass )
{
mApiRepClass = apiClass;
}
public Type getXmlRpcType()
{
return mXmlRpcType;
}
public void setXmlRpcType( Type xmlRpcType )
{
mXmlRpcType = xmlRpcType;
}
public ParameterConverter<?,?> getSeparateParameterConverter()
{
return mSeparateConverter;
}
public Class<? extends Convertable<?>> getConvertableClass()
{
return( mConvertableCls );
}
public boolean isXmlRpcCompatible()
{
return mIsXmlRpcCompatible;
}
public void setXmlRpcCompatible( boolean isXmlRpcCompatible )
{
mIsXmlRpcCompatible = isXmlRpcCompatible;
}
public boolean usesConvertable()
{
return( getConvertableClass() != null && !getConvertableClass().equals( NoParameterBoundConverter.class ) );
}
public ApiParameter getContainerContent()
{
return( mContainerContent );
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
return( false );
if (!(obj instanceof ApiParameter))
return( false );
ApiParameter other = (ApiParameter)obj;
boolean eq = mXmlRpcType.equals( other.mXmlRpcType );
eq = eq && ((mApiRepClass == null && other.mApiRepClass == null) || mApiRepClass.equals( other.mApiRepClass ));
eq = eq && ((mSeparateConverter == null && other.mSeparateConverter == null) || (mSeparateConverter != null && mSeparateConverter.equals( other.mSeparateConverter )) );
eq = eq && ((mConvertableCls == null && other.mConvertableCls == null) || (mConvertableCls != null && mConvertableCls.equals( other.mConvertableCls )) );
eq = eq && ((mContainerContent == null && other.mContainerContent == null) || ( mContainerContent != null && mContainerContent.equals( other.mContainerContent )));
eq = eq && mIsXmlRpcCompatible == other.mIsXmlRpcCompatible;
return( eq );
}
@Override
public String toString()
{
String s = "ApiParameter(" + mXmlRpcType + "," + mApiRepClass + "," + mSeparateConverter + "," + mConvertableCls + "," + mIsXmlRpcCompatible + "," + mContainerContent + ")";
return( s );
}
private Type mXmlRpcType;
private Class<?> mApiRepClass;
private ParameterConverter<?,?> mSeparateConverter;
private Class<? extends Convertable<?>> mConvertableCls;
private ApiParameter mContainerContent;
private boolean mIsXmlRpcCompatible;
}