Package de.dfki.util.xmlrpc.common

Source Code of de.dfki.util.xmlrpc.common.ApiParameter

/**
*
*/
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;
}
TOP

Related Classes of de.dfki.util.xmlrpc.common.ApiParameter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.