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