/*
* Created on 22.07.2003
*
*/
package de.dfki.util.xmlrpc;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Logger;
import de.dfki.util.xmlrpc.annotation.ConverterMappings;
import de.dfki.util.xmlrpc.client.ClientInvocationHandler;
import de.dfki.util.xmlrpc.client.StandardXmlRpcClient;
import de.dfki.util.xmlrpc.client.XmlRpcClient;
import de.dfki.util.xmlrpc.common.ApiParameter;
import de.dfki.util.xmlrpc.common.MethodSignature;
import de.dfki.util.xmlrpc.common.XmlRpcConnection;
import de.dfki.util.xmlrpc.conversion.EnhancedTypeConverter;
import de.dfki.util.xmlrpc.conversion.ParameterConverterRegistry;
import de.dfki.util.xmlrpc.conversion.TypeConversionException;
import de.dfki.util.xmlrpc.conversion.TypeConverter;
import de.dfki.util.xmlrpc.server.XmlRpcHandlerFactory;
/**
* Offers some useful static methods for creating remote clients and do type conversion.
*
* @author lauer
*/
public class XmlRpc
{
private static Logger mLog = Logger.getLogger( XmlRpc.class.getName() );
public static Logger log()
{
return ( mLog );
}
/**
* XML-RPC Type constants. Also maps XML-RPC types to java types.
*
* @author lauer
*/
public enum Type
{
INT(Integer.class, Integer.class, Integer.TYPE),
BOOLEAN(Boolean.class, Boolean.class, Boolean.TYPE),
STRING(String.class),
DOUBLE(Double.class, Double.class, Double.TYPE),
FLOAT(Float.class, Float.class, Float.TYPE), //non standard, but handy :-)
DATE(Date.class),
STRUCT(Map.class, Hashtable.class),
ARRAY(Collection.class, Vector.class),
BASE64(byte[].class),
STRINGasBASE64(String.class),
NONE(void.class);
private Type( Class<?> javaClass )
{
this( javaClass, javaClass, null );
}
private Type( Class<?> baseClass, Class<?> parameterClass )
{
this( baseClass, parameterClass, null );
}
/**
* Defines java type mappings for a XML-RPC type.
* @param baseClass Instances of this XML-RPC type are assignable to this type.
* @param parameterClass Parameters instances of this XML-RPC type generated by the XML-RPC runtime are of this type.
* @param primitiveType A XML-RPC type may be compatible to a primitive type.
*/
private Type( Class<?> baseClass, Class<?> parameterClass, Class<?> primitiveType )
{
mParameterClass = parameterClass;
mBaseClass = baseClass;
mPrimitiveType = primitiveType;
}
public Class<?> getCorrespondingBaseType()
{
return ( mBaseClass );
}
public Class<?> getCorrespondingParameterType()
{
return ( mParameterClass );
}
public Class<?> getCorrespondingPrimitiveType()
{
return( mPrimitiveType );
}
private Class<?> mBaseClass;
private Class<?> mParameterClass;
private Class<?> mPrimitiveType;
}
/**
* Create a remote client which connects to the given host and port using the specified API.
*
* @param apiClass
* The client interface class.
* @param handlerName
* The name of the server-side XML-RPC handler.
* @param host
* The host the server is located at.
* @param port
* The port the server is listening at.
* @return An instance of a XML-RPC client. This instance can also be casted to
* {@link XmlRpcClient}.
*/
public static <API> API createClient( final Class<API> apiClass,
final String handlerName,
final String host,
final int port )
{
return( createClient( apiClass, handlerName, XmlRpcConnection.connect( host, port ) ) );
}
/**
* Creates a XML-RPC client for the given API class. The client will be created in ClassLoader of
* <code>Thread.currentThread()</code>.
*
* @param apiClass
* The client interface class.
* @param handlerName
* The name of the server-side XML-RPC handler.
* @param xmlRpcConnection
* The connection the client should connect to.
* @return An instance of a XML-RPC client. This instance can also be casted to
* {@link XmlRpcClient}.
*/
public static <API> API createClient( final Class<API> apiClass,
final String handlerName,
final XmlRpcConnection xmlRpcConnection )
{
final Object apiClient = Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(),
new Class[]{ XmlRpcClient.class, apiClass },
new ClientInvocationHandler( new StandardXmlRpcClient() ) );
final XmlRpcClient asClient = (XmlRpcClient)apiClient;
asClient.setConnection( xmlRpcConnection );
asClient.setHandlerName( handlerName );
ParameterConverterRegistry.readParameterConverterMappingsFromApiClass( apiClass );
return( apiClass.cast( apiClient ) );
}
public static <API> API createClient( final Class<API> apiClass,
final XmlRpcConnection xmlRpcConnection )
{
return( createClient( apiClass, "", xmlRpcConnection ) );
}
/**
* Tests all methods of the given API whether they are XML-RPC compliant or not. Types which define proper type conversion
* operations are treated as compliant types.
*
* @param apiClass
* The API class.
* @return A Map which has an entry for each API method stating whether this method is XML-RPC compliant or not.
*/
public static Map<Method,Boolean> getMethodXmlRpcComplianceMap( final Class<?> apiClass )
{
Map<Method,Boolean> complianceMap = new HashMap<Method, Boolean>();
createClient( apiClass, null ); //need this to fetch api converter mappings not yet registered
for( Method method: apiClass.getDeclaredMethods() )
{
boolean isCompliant = true;
try
{
MethodSignature.createFromMethod( method );
}
catch( Throwable e )
{
isCompliant = false;
}
complianceMap.put( method, isCompliant );
}
return( complianceMap );
}
private static boolean mUseAutomaticNullMasking = false;
private static boolean mTreatUnknownTypesAsBeans = false;
/**
* Enables/Disables the automatic null-masking ability. That is, null-values in method calls
* and return statements are automatically masked (as a zero-length byte-array)
* and turned back to null-values on the receiver side.
* <br>
* NOTE: this feature works for all built-in XML-RPC types except BASE64 and for types
* with user-defined conversions.
*
* @param b true enables masking mode. false disables it.
*/
public static void useAutomaticNullMasking( boolean b )
{
mUseAutomaticNullMasking = b;
}
public static boolean usesAutomaticNullMasking()
{
return( mUseAutomaticNullMasking );
}
/**
* Enables the fall-back behaviour to treat all unknown types as XmlRpcBeans (instead of throwing an exception).
* @param b true, to enable this feature. false to disable it (=default)
*/
public static void treatUnknownTypesAsBeans( boolean b )
{
mTreatUnknownTypesAsBeans = b;
}
public static boolean treatUnknownTypesAsBeans()
{
return( mTreatUnknownTypesAsBeans );
}
private static TypeConverter mTypeConverter = new EnhancedTypeConverter();
public static TypeConverter getTypeConverter()
{
return( mTypeConverter );
}
/**
* Converts an instance of a certain type into a XML-RPC representation. If
* the type defines a type conversion (type annotation({@link de.dfki.util.xmlrpc.annotation.XmlRpc})
* this is used for conversion. Converter mappings used in the API interface ({@link ConverterMappings})
* will only be used if the API has been processed by a a
* {@link #createClient} or a
* {@link XmlRpcHandlerFactory#createHandlerFor(Object)} call before or a converter has been registered
* before using {@link ParameterConverterRegistry#setParameterConverterForClass(Class, Class)}.
*
* @throws TypeConversionException:
* Denotes problems while converting representations. This may
* include missing annotations, illegal object types, missing converter mappings, ...
*/
public static Object getXmlRpcRepresentation( Class<?> userPrepresentationType, Object toConvert )
throws TypeConversionException
{
// if (toConvert == null)
// {
// throw( new TypeConversionException( "Object to convert must not be null" ) );
// }
ApiParameter p = ApiParameter.createFrom( userPrepresentationType );
return( getTypeConverter().convertToXmlRpcRepresentation( p, toConvert ) );
}
/**
* Same as {@link #getXmlRpcRepresentation(Class, Object)}. The repesentation type is taken directly from
* the object to convert.
*/
public static Object getXmlRpcRepresentation( Object toConvert )
throws TypeConversionException
{
return( getXmlRpcRepresentation( toConvert.getClass() , toConvert ) );
}
/**
* Converts a XML-RPC representation into an instance of the given type. If
* the type defines a type conversion (type annotation({@link de.dfki.util.xmlrpc.annotation.XmlRpc})
* this is used for conversion. Converter mappings used in the API interface ({@link ConverterMappings})
* will only be used if the API has been processed by a a
* {@link #createClient} or a
* {@link XmlRpcHandlerFactory#createHandlerFor(Object)} call before.
*
* @param targetClass
* Type to convert to.
* @param xmlRpcRepresentation
* The XML-RPC representation.
* @throws TypeConversionException:
* Denotes problems while converting representations. This may
* include missing annotations, illegal object types, ...
*/
@SuppressWarnings("unchecked")
public static <T> T getUserRepresentation( Class<T> targetClass, Object xmlRpcRepresentation )
throws TypeConversionException
{
if (xmlRpcRepresentation == null)
{
throw( new TypeConversionException( "Object to convert must not be null" ) );
}
ApiParameter p = ApiParameter.createFrom( targetClass );
Object result = getTypeConverter().convertToUserRepresentation( p, xmlRpcRepresentation );
//Note: a Class.cast( Object ) call will not manage to convert, e.g. an Integer to primitive int
return( (T)result );
}
}