/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed 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 net.sf.beanlib.provider;
import java.beans.Introspector;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import net.jcip.annotations.NotThreadSafe;
import net.jcip.annotations.ThreadSafe;
import net.sf.beanlib.BeanlibException;
import net.sf.beanlib.PropertyInfo;
import net.sf.beanlib.spi.BeanMethodCollector;
import net.sf.beanlib.spi.BeanMethodFinder;
import net.sf.beanlib.spi.BeanPopulationExceptionHandler;
import net.sf.beanlib.spi.BeanPopulatorBaseConfig;
import net.sf.beanlib.spi.BeanPopulatorSpi;
import net.sf.beanlib.spi.BeanSourceHandler;
import net.sf.beanlib.spi.BeanTransformerSpi;
import net.sf.beanlib.spi.DetailedPropertyFilter;
import net.sf.beanlib.spi.PropertyFilter;
import net.sf.beanlib.spi.Transformable;
import org.apache.log4j.Logger;
/**
* Default implementation of {@link BeanPopulatorSpi}.
* <p>
* A Bean Populator can be used to populate the properties from a JavaBean instance to another JavaBean instance. For
* example, <blockquote>
*
* <pre>
* Bean from = ...
* Bean to = ...
* new BeanPopulator(from, to).populate();
* </pre>
*
* </blockquote> By default, every public setter method of the target JavaBean is invoked with the value retrieved from
* the corresponding public getter method (of the same property name and type) of the source JavaBean.
* <p>
* How the set of setter methods and getter methods are determined can be overridden via the methods
* {@link BeanPopulator#initSetterMethodCollector(BeanMethodCollector)} and
* {@link #initReaderMethodFinder(BeanMethodFinder)} before the {@link #populate()} method is invoked.
* <p>
* During the property propagation process, various options exist to override the default behavior:
* <ol>
* <li>A {@link DetailedPropertyFilter} can be used to control whether a specific JavaBean property should be propagated
* across.<br>
* See {@link #initDetailedPropertyFilter(DetailedPropertyFilter)}.<br>
* By default, there is no detailed bean property selector configured.</li>
* <li>Similar to {@link DetailedPropertyFilter} but with a simpler API, a {@link PropertyFilter} can be used to control
* whether a specific JavaBean property should be propagated across.<br>
* See {@link #initPropertyFilter(PropertyFilter)}.<br>
* By default, there is no bean property selector configured.</li>
* <li>A {@link BeanSourceHandler} can be used to act as a call-back (to produce whatever side-effects deemed necessary)
* after the property value has been retrieved from the source bean, but before being propagated across to the target
* bean. <br>
* See {@link #initBeanSourceHandler(BeanSourceHandler)}.<br>
* By default, there is no bean source handler configured.</li>
* <li>A {@link Transformable} can be used to transform and replace the property value to be propagated across to the
* target bean.<br>
* See {@link #initTransformer(Transformable)}.<br>
* By default, there is no transformer configured.</li>
* <li>A {@link BeanPopulationExceptionHandler} can be used to handle any exception thrown.<br>
* See {@link #initBeanPopulationExceptionHandler(BeanPopulationExceptionHandler)} and
* {@link BeanPopulationExceptionHandler}.<br>
* By default, an abort policy is used in that any such exception will cause the propagation to terminate immediately,
* and the exception itself will get bubbled up as an unchecked exception.</li>
* </ol>
* Finally, all these options are grouped as a base configuration, which can be alternatively changed via
* {@link #initBeanPopulatorBaseConfig(BeanPopulatorBaseConfig)}.
*
* @author Joe D. Velopar
*/
@NotThreadSafe
public class BeanPopulator implements BeanPopulatorSpi {
public static final Factory factory = new Factory();
/**
* Bean Populator Factory, which implements the general factory interface of a BeanPopulatorSpi instance.
*
* @author Joe D. Velopar
*/
@ThreadSafe
public static class Factory implements BeanPopulatorSpi.Factory {
private Factory() {}
/**
* Notes the co-variant return type of a specific {@link BeanPopulatorSpi}.
*
* @see net.sf.beanlib.spi.BeanPopulatorSpi.Factory#newBeanPopulator(java.lang.Object, java.lang.Object)
*/
@Override
public BeanPopulator newBeanPopulator(Object from, Object to) {
return new BeanPopulator(from, to);
}
}
private final Logger log = Logger.getLogger(this.getClass());
private final Object fromBean;
private final Object toBean;
private BeanPopulatorBaseConfig baseConfig = new BeanPopulatorBaseConfig();
private Transformable transformer = new BeanTransformer(factory);
/**
* @param fromBean from bean
* @param toBean to bean
*/
public BeanPopulator(Object fromBean, Object toBean) {
this.fromBean = fromBean;
this.toBean = toBean;
}
private BeanTransformerSpi getBeanTransformerSpi() {
return (BeanTransformerSpi) (transformer instanceof BeanTransformerSpi ? transformer : null);
}
/**
* Processes a specific setter method for the toBean.
*
* @param setterMethod a specific method of the toBean
*/
private void processSetterMethod(Method setterMethod) {
String methodName = setterMethod.getName();
final String propertyString = methodName.substring(baseConfig.getSetterMethodCollector().getMethodPrefix().length());
if (baseConfig.isDebug()) {
if (log.isInfoEnabled()) {
log.info(new StringBuilder("processSetterMethod: processing propertyString=").append(propertyString).append("")
.append(", fromClass=").append(fromBean.getClass()).append(", toClass=").append(toBean.getClass()).toString());
}
}
Method readerMethod = baseConfig.getReaderMethodFinder().find(propertyString, fromBean);
if (readerMethod == null) {
return;
}
// Reader method of fromBean found
Class<?> paramType = setterMethod.getParameterTypes()[0];
String propertyName = Introspector.decapitalize(propertyString);
try {
doit(setterMethod, readerMethod, paramType, propertyName);
} catch (Exception ex) {
baseConfig.getBeanPopulationExceptionHandler().initFromBean(fromBean).initToBean(toBean).initPropertyName(propertyName)
.initReaderMethod(readerMethod).initSetterMethod(setterMethod).handleException(ex, log);
}
}
private <T> void doit(Method setterMethod, Method readerMethod, Class<T> paramType, final String propertyName) {
if (baseConfig.getDetailedPropertyFilter() != null) {
if (!baseConfig.getDetailedPropertyFilter().propagate(propertyName, fromBean, readerMethod, toBean, setterMethod)) {
return;
}
}
if (baseConfig.getPropertyFilter() != null) {
if (!baseConfig.getPropertyFilter().propagate(propertyName, readerMethod)) {
return;
}
}
Object propertyValue = this.invokeMethodAsPrivileged(fromBean, readerMethod, null);
if (baseConfig.getBeanSourceHandler() != null) {
baseConfig.getBeanSourceHandler().handleBeanSource(fromBean, readerMethod, propertyValue);
}
if (transformer != null) {
PropertyInfo propertyInfo = new PropertyInfo(propertyName, fromBean, toBean);
propertyValue = transformer.transform(propertyValue, paramType, propertyInfo);
}
if (baseConfig.isDebug()) {
if (log.isInfoEnabled()) {
log.info("processSetterMethod: setting propertyName=" + propertyName);
}
}
// Invoke setter method
Object[] args = { propertyValue };
this.invokeMethodAsPrivileged(toBean, setterMethod, args);
return;
}
/**
* Invoke the given method as a privileged action, if necessary.
*/
private Object invokeMethodAsPrivileged(final Object target, final Method method, final Object[] args) {
if (Modifier.isPublic(method.getModifiers())) {
try {
return method.invoke(target, args);
} catch (IllegalAccessException ex) {
// drop thru to try again
} catch (InvocationTargetException e) {
throw new BeanlibException(e.getTargetException());
} catch (RuntimeException ex) {
// the try-catch is unnecessary i know,
// but just so we can set a break point here
throw ex;
}
}
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
method.setAccessible(true);
try {
return method.invoke(target, args);
} catch (IllegalArgumentException e) {
throw new BeanlibException(e);
} catch (IllegalAccessException e) {
throw new BeanlibException(e);
} catch (InvocationTargetException e) {
throw new BeanlibException(e.getTargetException());
}
}
});
}
// --------------------------- BeanPopulatorSpi ---------------------------
@Override
public Transformable getTransformer() {
return transformer;
}
@Override
public BeanPopulator initTransformer(Transformable transformer) {
this.transformer = transformer;
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initBeanPopulatorBaseConfig(baseConfig);
}
return this;
}
@Override
public <T> T populate() {
if (getBeanTransformerSpi() != null) {
getBeanTransformerSpi().getClonedMap().put(fromBean, toBean);
}
// invoking all declaring setter methods of toBean from all matching getter methods of fromBean
for (Method m : baseConfig.getSetterMethodCollector().collect(toBean)) {
processSetterMethod(m);
}
@SuppressWarnings("unchecked")
T ret = (T) toBean;
return ret;
}
// -------------------------- BeanPopulatorBaseSpi --------------------------
@Override
public BeanPopulator initPropertyFilter(PropertyFilter propertyFilter) {
baseConfig.setPropertyFilter(propertyFilter);
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initPropertyFilter(propertyFilter);
}
return this;
}
@Override
public BeanPopulator initBeanSourceHandler(BeanSourceHandler beanSourceHandler) {
baseConfig.setBeanSourceHandler(beanSourceHandler);
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initBeanSourceHandler(beanSourceHandler);
}
return this;
}
@Override
public BeanPopulator initDebug(boolean debug) {
baseConfig.setDebug(debug);
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initDebug(debug);
}
return this;
}
@Override
public BeanPopulator initDetailedPropertyFilter(DetailedPropertyFilter detailedPropertyFilter) {
baseConfig.setDetailedPropertyFilter(detailedPropertyFilter);
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initDetailedPropertyFilter(detailedPropertyFilter);
}
return this;
}
@Override
public BeanPopulator initReaderMethodFinder(BeanMethodFinder readerMethodFinder) {
if (readerMethodFinder != null) {
baseConfig.setReaderMethodFinder(readerMethodFinder);
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initReaderMethodFinder(readerMethodFinder);
}
}
return this;
}
@Override
public BeanPopulator initSetterMethodCollector(BeanMethodCollector setterMethodCollector) {
if (setterMethodCollector != null) {
baseConfig.setSetterMethodCollector(setterMethodCollector);
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initSetterMethodCollector(setterMethodCollector);
}
}
return this;
}
@Override
public BeanPopulator initBeanPopulationExceptionHandler(BeanPopulationExceptionHandler beanPopulationExceptionHandler) {
baseConfig.setBeanPopulationExceptionHandler(beanPopulationExceptionHandler);
if (this.getBeanTransformerSpi() != null) {
this.getBeanTransformerSpi().initBeanPopulationExceptionHandler(beanPopulationExceptionHandler);
}
return this;
}
@Override
public BeanPopulator initBeanPopulatorBaseConfig(BeanPopulatorBaseConfig baseConfig) {
this.baseConfig = baseConfig;
if (getBeanTransformerSpi() != null) {
getBeanTransformerSpi().initBeanPopulatorBaseConfig(baseConfig);
}
return this;
}
@Override
public PropertyFilter getPropertyFilter() {
return baseConfig.getPropertyFilter();
}
@Override
public BeanPopulationExceptionHandler getBeanPopulationExceptionHandler() {
return baseConfig.getBeanPopulationExceptionHandler();
}
/**
* Notes if the returned base config is modified, a subsequent
* {@link BeanPopulator#initBeanPopulatorBaseConfig(BeanPopulatorBaseConfig)} needs to be invoked to keep the
* configuration in sync.
*/
@Override
public BeanPopulatorBaseConfig getBeanPopulatorBaseConfig() {
return baseConfig;
}
@Override
public BeanSourceHandler getBeanSourceHandler() {
return baseConfig.getBeanSourceHandler();
}
@Override
public boolean isDebug() {
return baseConfig.isDebug();
}
@Override
public DetailedPropertyFilter getDetailedPropertyFilter() {
return baseConfig.getDetailedPropertyFilter();
}
@Override
public BeanMethodFinder getReaderMethodFinder() {
return baseConfig.getReaderMethodFinder();
}
@Override
public BeanMethodCollector getSetterMethodCollector() {
return baseConfig.getSetterMethodCollector();
}
}