FileDocCategorySizeDatePackage
PropertyInjector.javaAPI DocApache Lucene 2.1.010344Wed Feb 14 10:46:06 GMT 2007org.apache.lucene.gdata.server.registry.configuration

PropertyInjector.java

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.lucene.gdata.server.registry.configuration;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.lucene.gdata.utils.ReflectionUtils;

/**
 * PropertyInjector is used to set member variables / properties of classes via
 * <i>setter</i> methods using the
 * {@link org.apache.lucene.gdata.server.registry.configuration.ComponentConfiguration}
 * class.
 * <p>
 * To populate a object with properties from a ComponentConfiguration instance
 * the class or a superclass of the object to populate has to provide at least
 * one setter method with a single parameter. The object to populate is set via
 * the {@link PropertyInjector#setTargetObject} method. The class of the object
 * will be analyzed for setter methods having a "set" prefix in their method
 * name. If one of the found setter methods is annotated with
 * {@link org.apache.lucene.gdata.server.registry.configuration.Requiered} this
 * property is interpreted as a mandatory property. Mandatory properties must be
 * available in the provided ComponentConfiguration, if not the injection will
 * fail.<br>
 * The
 * {@link org.apache.lucene.gdata.server.registry.configuration.ComponentConfiguration}
 * contains key / value pairs where the key must match the signature of the
 * setter method without the 'set' prefix and must begin with a lower case
 * character. <span>Key<code>bufferSize</code> does match a method signature
 * of <code>setBufferSize</code></span> The type of the parameter will be
 * reflected via the Reflection API and instantiated with the given value if
 * possible.
 * </p>
 * <p>
 * Setter methods without a <code>Required</code> annotation will be set if
 * the property is present in the ComponentConfiguration
 * </p>
 * <p>This class does not support overloaded setter methods.</p>
 * @author Simon Willnauer
 * @see org.apache.lucene.gdata.server.registry.configuration.Requiered
 * @see org.apache.lucene.gdata.server.registry.configuration.ComponentConfiguration
 */
public class PropertyInjector {
    private static final String SETTER_PREFIX = "set";

    private Class targetClass;

    private Object target;

    private Map<String, Method> requieredProperties = new HashMap<String, Method>();

    private Map<String, Method> optionalProperties = new HashMap<String, Method>();

    /**
     * Sets the object to be populated with the properties provided in the ComponentConfiguration.
     * @param o - the object to populate
     */
    public void setTargetObject(final Object o) {
        if (o == null)
            throw new IllegalArgumentException("TargetObject must not be null");
        this.target = o;
        this.targetClass = o.getClass();
        try {
            registerProperties(this.targetClass);
        } catch (Exception e) {
            throw new InjectionException("can access field -- "
                    + e.getMessage(), e);

        }
        if (this.requieredProperties.isEmpty()
                && this.optionalProperties.isEmpty())
            throw new InjectionException(
                    "Given type has no public setter methods -- "
                            + o.getClass().getName());

    }

    protected int getRequiredSize() {
        return this.requieredProperties.size();
    }

    protected int getOptionalSize() {
        return this.optionalProperties.size();
    }

    private void registerProperties(final Class clazz)
            throws SecurityException, NoSuchFieldException {
        if (clazz == null)
            return;
        Method[] methodes = clazz.getMethods();
        for (int i = 0; i < methodes.length; i++) {
            if (methodes[i].getName()
                    .startsWith(PropertyInjector.SETTER_PREFIX)) {
                String methodName = methodes[i].getName();
                String fieldName = getFieldName(methodName);
                if (methodes[i].getAnnotation(Requiered.class) != null)
                    this.requieredProperties.put(fieldName, methodes[i]);
                else
                    this.optionalProperties.put(fieldName, methodes[i]);

            }

        }
        registerProperties(clazz.getSuperclass());
    }

    private String getFieldName(final String setterMethodName) {
        // remove 'set' prefix --> first char as lowerCase
        String retVal = setterMethodName.substring(3);
        String firstLetter = retVal.substring(0, 1);
        retVal = retVal.replaceFirst(firstLetter, firstLetter.toLowerCase());
        return retVal;
    }

    /**
     * Injects the properties stored in the <code>ComponentConfiguration</code>
     * to the corresponding methods of the target object
     * @param bean - configuration bean containing all properties to set.
     * 
     */
    public void injectProperties(final ComponentConfiguration bean) {
        if (bean == null)
            throw new IllegalArgumentException("bean must not be null");
        if (this.target == null)
            throw new IllegalStateException("target is not set -- null");
        Set<Entry<String, Method>> requiered = this.requieredProperties
                .entrySet();
        // set required properties
        for (Entry<String, Method> entry : requiered) {
            if (!bean.contains(entry.getKey()))
                throw new InjectionException(
                        "Required property can not be set -- value not in configuration bean; Property: "
                                + entry.getKey()
                                + "for class "
                                + this.targetClass.getName());
            populate(bean, entry);

        }
        Set<Entry<String, Method>> optinal = this.optionalProperties.entrySet();
        // set optional properties
        for (Entry<String, Method> entry : optinal) {
            if (bean.contains(entry.getKey()))
                populate(bean, entry);
        }

    }

    private void populate(ComponentConfiguration bean,
            Entry<String, Method> entry) {
        String value = bean.get(entry.getKey());
        Method m = entry.getValue();
        Class<?>[] parameterTypes = m.getParameterTypes();
        if (parameterTypes.length > 1)
            throw new InjectionException("Setter has more than one parameter "
                    + m.getName() + " -- can not invoke method -- ");
        Object parameter = null;
        try {

            parameter = createObject(value, parameterTypes[0]);
        } catch (InjectionException e) {
            throw new InjectionException(
                    "parameter object creation failed for method "
                            + m.getName() + " in class: "
                            + this.targetClass.getName(), e);
        }
        // only setters with one parameter are supported
        Object[] parameters = { parameter };
        try {
            m.invoke(this.target, parameters);
        } catch (Exception e) {
            throw new InjectionException("Can not set value of type "
                    + value.getClass().getName()
                    + " -- can not invoke method -- " + e.getMessage(), e);

        }
    }

    private Object createObject(String s, Class<?> clazz) {

        try {
            // if class is requested use s as fully qualified class name
            if (clazz == Class.class)
                return Class.forName(s);
            // check for primitive type
            if (clazz.isPrimitive())
                clazz = ReflectionUtils.getPrimitiveWrapper(clazz);
            boolean defaultConst = false;
            boolean stringConst = false;
            Constructor[] constructors = clazz.getConstructors();
            if (constructors.length == 0)
                defaultConst = true;
            for (int i = 0; i < constructors.length; i++) {
                if (constructors[i].getParameterTypes().length == 0) {
                    defaultConst = true;
                    continue;
                }
                if (constructors[i].getParameterTypes().length == 1
                        && constructors[i].getParameterTypes()[0]
                                .equals(String.class))
                    stringConst = true;
            }
            /*
             * if there is a string constructor use the string as a parameter
             */
            if (stringConst) {
                Constructor constructor = clazz
                        .getConstructor(new Class[] { String.class });
                return constructor.newInstance(new Object[] { s });
            }
            /*
             * if no string const. but a default const. -- use the string as a
             * class name
             */
            if (defaultConst)
                return Class.forName(s).newInstance();
            throw new InjectionException(
                    "Parameter can not be created -- no default or String constructor found for class "
                            + clazz.getName());

        } catch (Exception e) {

            throw new InjectionException("can not create object for setter", e);
        }

    }

    /**
     * Sets all members to their default values and clears the internal used
     * {@link Map} instances
     * 
     * @see Map#clear()
     */
    public void clear() {
        this.target = null;
        this.targetClass = null;
        this.optionalProperties.clear();
        this.requieredProperties.clear();
    }

    

}