blob: 4ba57dc4ebdb072c94a2aada4d6c017599fd7503 [file] [log] [blame]
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.myrmidon.components.configurer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.avalon.excalibur.property.PropertyException;
import org.apache.avalon.excalibur.property.PropertyUtil;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.avalon.framework.logger.Loggable;
import org.apache.log.Logger;
import org.apache.myrmidon.components.converter.MasterConverter;
import org.apache.myrmidon.converter.Converter;
import org.apache.myrmidon.converter.ConverterException;
/**
* Class used to configure tasks.
*
* @author <a href="mailto:donaldp@apache.org">Peter Donald</a>
*/
public class DefaultConfigurer
extends AbstractLoggable
implements Configurer, Composable, Loggable
{
///Compile time constant to turn on extreme debugging
private final static boolean DEBUG = false;
/*
* TODO: Should reserved names be "configurable" ?
*/
///Attribute names that are reserved
private final static String RESERVED_ATTRIBUTES[] =
{
"logger"
};
///Element names that are reserved
private final static String RESERVED_ELEMENTS[] =
{
"content"
};
///Converter to use for converting between values
private MasterConverter m_converter;
public void compose( final ComponentManager componentManager )
throws ComponentException
{
m_converter = (MasterConverter)componentManager.lookup( MasterConverter.ROLE );
}
/**
* Configure a task based on a configuration in a particular context.
* This configuring can be done in different ways for different
* configurers.
* This one does it by first checking if object implements Configurable
* and if it does will pass the task the configuration - else it will use
* mapping rules to map configuration to types
*
* @param object the object
* @param configuration the configuration
* @param context the Context
* @exception ConfigurationException if an error occurs
*/
public void configure( final Object object,
final Configuration configuration,
final Context context )
throws ConfigurationException
{
if( DEBUG )
{
getLogger().debug( "Configuring " + object );
}
if( object instanceof Configurable )
{
if( DEBUG )
{
getLogger().debug( "Configuring object via Configurable interface" );
}
((Configurable)object).configure( configuration );
}
else
{
if( DEBUG )
{
getLogger().debug( "Configuring object via Configurable reflection" );
}
final String[] attributes = configuration.getAttributeNames();
for( int i = 0; i < attributes.length; i++ )
{
final String name = attributes[ i ];
final String value = configuration.getAttribute( name );
if( DEBUG )
{
getLogger().debug( "Configuring attribute name=" + name +
" value=" + value );
}
configureAttribute( object, name, value, context );
}
final Configuration[] children = configuration.getChildren();
for( int i = 0; i < children.length; i++ )
{
final Configuration child = children[ i ];
if( DEBUG )
{
getLogger().debug( "Configuring subelement name=" + child.getName() );
}
configureElement( object, child, context );
}
final String content = configuration.getValue( null );
if( null != content )
{
if( !content.trim().equals( "" ) )
{
if( DEBUG )
{
getLogger().debug( "Configuring content " + content );
}
configureContent( object, content, context );
}
}
}
}
/**
* Configure named attribute of object in a particular context.
* This configuring can be done in different ways for different
* configurers.
*
* @param object the object
* @param name the attribute name
* @param value the attribute value
* @param context the Context
* @exception ConfigurationException if an error occurs
*/
public void configure( final Object object,
final String name,
final String value,
final Context context )
throws ConfigurationException
{
configureAttribute( object, name, value, context );
}
/**
* Try to configure content of an object.
*
* @param object the object
* @param content the content value to be set
* @param context the Context
* @exception ConfigurationException if an error occurs
*/
private void configureContent( final Object object,
final String content,
final Context context )
throws ConfigurationException
{
setValue( object, "addContent", content, context );
}
private void configureAttribute( final Object object,
final String name,
final String value,
final Context context )
throws ConfigurationException
{
for( int i = 0; i < RESERVED_ATTRIBUTES.length; i++ )
{
if( RESERVED_ATTRIBUTES[ i ].equals( name ) )
{
throw new ConfigurationException( "Can not specify reserved attribute " +
name );
}
}
final String methodName = getMethodNameFor( name );
setValue( object, methodName, value, context );
}
private void setValue( final Object object,
final String methodName,
final String value,
final Context context )
throws ConfigurationException
{
// OMFG the rest of this is soooooooooooooooooooooooooooooooo
// slow. Need to cache results per class etc.
final Class clazz = object.getClass();
final Method methods[] = getMethodsFor( clazz, methodName );
if( 0 == methods.length )
{
throw new ConfigurationException( "Unable to set attribute via " + methodName +
" due to not finding any appropriate " +
"accessor method" );
}
setValue( object, value, context, methods );
}
private void setValue( final Object object,
final String value,
final Context context,
final Method methods[] )
throws ConfigurationException
{
try
{
final Object objectValue =
PropertyUtil.resolveProperty( value, context, false );
setValue( object, objectValue, methods, context );
}
catch( final PropertyException pe )
{
throw new ConfigurationException( "Error resolving property " + value,
pe );
}
}
private void setValue( final Object object,
Object value,
final Method methods[],
final Context context )
throws ConfigurationException
{
final Class sourceClass = value.getClass();
final String source = sourceClass.getName();
for( int i = 0; i < methods.length; i++ )
{
if( setValue( object, value, methods[ i ], sourceClass, source, context ) )
{
return;
}
}
throw new ConfigurationException( "Unable to set attribute via " +
methods[ 0 ].getName() + " as could not convert " +
source + " to a matching type" );
}
private boolean setValue( final Object object,
Object value,
final Method method,
final Class sourceClass,
final String source,
final Context context )
throws ConfigurationException
{
Class parameterType = method.getParameterTypes()[ 0 ];
if( parameterType.isPrimitive() )
{
parameterType = getComplexTypeFor( parameterType );
}
try
{
value = m_converter.convert( parameterType, value, context );
}
catch( final ConverterException ce )
{
if( DEBUG )
{
getLogger().debug( "Failed to find converter ", ce );
}
return false;
}
catch( final Exception e )
{
throw new ConfigurationException( "Error converting attribute for " +
method.getName(),
e );
}
try
{
method.invoke( object, new Object[] { value } );
}
catch( final IllegalAccessException iae )
{
//should never happen ....
throw new ConfigurationException( "Error retrieving methods with " +
"correct access specifiers",
iae );
}
catch( final InvocationTargetException ite )
{
throw new ConfigurationException( "Error calling method attribute " +
method.getName(),
ite );
}
return true;
}
private Class getComplexTypeFor( final Class clazz )
{
if( String.class == clazz ) return String.class;
else if( Integer.TYPE.equals( clazz ) ) return Integer.class;
else if( Long.TYPE.equals( clazz ) ) return Long.class;
else if( Short.TYPE.equals( clazz ) ) return Short.class;
else if( Byte.TYPE.equals( clazz ) ) return Byte.class;
else if( Boolean.TYPE.equals( clazz ) ) return Boolean.class;
else if( Float.TYPE.equals( clazz ) ) return Float.class;
else if( Double.TYPE.equals( clazz ) ) return Double.class;
else
{
throw new IllegalArgumentException( "Can not get complex type for non-primitive " +
"type " + clazz.getName() );
}
}
private Method[] getMethodsFor( final Class clazz, final String methodName )
{
final Method methods[] = clazz.getMethods();
final ArrayList matches = new ArrayList();
for( int i = 0; i < methods.length; i++ )
{
final Method method = methods[ i ];
if( methodName.equals( method.getName() ) &&
Method.PUBLIC == (method.getModifiers() & Method.PUBLIC) )
{
if( method.getReturnType().equals( Void.TYPE ) )
{
final Class parameters[] = method.getParameterTypes();
if( 1 == parameters.length )
{
matches.add( method );
}
}
}
}
return (Method[])matches.toArray( new Method[0] );
}
private Method[] getCreateMethodsFor( final Class clazz, final String methodName )
{
final Method methods[] = clazz.getMethods();
final ArrayList matches = new ArrayList();
for( int i = 0; i < methods.length; i++ )
{
final Method method = methods[ i ];
if( methodName.equals( method.getName() ) &&
Method.PUBLIC == (method.getModifiers() & Method.PUBLIC) )
{
final Class returnType = method.getReturnType();
if( !returnType.equals( Void.TYPE ) &&
!returnType.isPrimitive() )
{
final Class parameters[] = method.getParameterTypes();
if( 0 == parameters.length )
{
matches.add( method );
}
}
}
}
return (Method[])matches.toArray( new Method[0] );
}
private String getMethodNameFor( final String attribute )
{
return "set" + getJavaNameFor( attribute.toLowerCase() );
}
private String getJavaNameFor( final String name )
{
final StringBuffer sb = new StringBuffer();
int index = name.indexOf( '-' );
int last = 0;
while( -1 != index )
{
final String word = name.substring( last, index ).toLowerCase();
sb.append( Character.toUpperCase( word.charAt( 0 ) ) );
sb.append( word.substring( 1, word.length() ) );
last = index + 1;
index = name.indexOf( '-', last );
}
index = name.length();
final String word = name.substring( last, index ).toLowerCase();
sb.append( Character.toUpperCase( word.charAt( 0 ) ) );
sb.append( word.substring( 1, word.length() ) );
return sb.toString();
}
private void configureElement( final Object object,
final Configuration configuration,
final Context context )
throws ConfigurationException
{
final String name = configuration.getName();
for( int i = 0; i < RESERVED_ELEMENTS.length; i++ )
{
if( RESERVED_ATTRIBUTES[ i ].equals( name ) ) return;
}
final String javaName = getJavaNameFor( name );
// OMFG the rest of this is soooooooooooooooooooooooooooooooo
// slow. Need to cache results per class etc.
final Class clazz = object.getClass();
Method methods[] = getMethodsFor( clazz, "add" + javaName );
if( 0 != methods.length )
{
//guess it is first method ????
addElement( object, methods[ 0 ], configuration, context );
}
else
{
methods = getCreateMethodsFor( clazz, "create" + javaName );
if( 0 == methods.length )
{
throw new ConfigurationException( "Unable to set attribute " + javaName +
" due to not finding any appropriate " +
"accessor method" );
}
//guess it is first method ????
createElement( object, methods[ 0 ], configuration, context );
}
}
private void createElement( final Object object,
final Method method,
final Configuration configuration,
final Context context )
throws ConfigurationException
{
try
{
final Object created = method.invoke( object, new Object[ 0 ] );
configure( created, configuration, context );
}
catch( final ConfigurationException ce )
{
throw ce;
}
catch( final Exception e )
{
throw new ConfigurationException( "Error creating sub-element", e );
}
}
private void addElement( final Object object,
final Method method,
final Configuration configuration,
final Context context )
throws ConfigurationException
{
try
{
final Class clazz = method.getParameterTypes()[ 0 ];
final Object created = clazz.newInstance();
configure( created, configuration, context );
method.invoke( object, new Object[] { created } );
}
catch( final ConfigurationException ce )
{
throw ce;
}
catch( final Exception e )
{
throw new ConfigurationException( "Error creating sub-element", e );
}
}
}