/*
* 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.hibernate;
import java.util.Collections;
import java.util.Set;
import net.jcip.annotations.NotThreadSafe;
import net.sf.beanlib.CollectionPropertyName;
import net.sf.beanlib.provider.BeanPopulator;
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.BeanPopulatorBaseSpi;
import net.sf.beanlib.spi.BeanSourceHandler;
import net.sf.beanlib.spi.BeanTransformerSpi;
import net.sf.beanlib.spi.ChainedCustomBeanTransformer;
import net.sf.beanlib.spi.CustomBeanTransformerSpi;
import net.sf.beanlib.spi.DetailedPropertyFilter;
import net.sf.beanlib.spi.PropertyFilter;
/**
* Hibernate Bean Replicator.
* <p>
* This class can be used to conveniently replicate Hibernate objects that follow the JavaBean getter/setter convention
* on a best attempt basis. The replication is typically recursive in that the whole object graph of the input object is
* replicated into an equivalent output object graph, resolving circular references, and eagerly fetching proxied
* instances as necessary. However, the exact behavior of the replication process including
* <ul>
* <li>to what extent the input object graph should be traversed and/or replicated; and</li>
* <li>whether proxied instances should be eagerly fetched or not</li>
* </ul>
* can be controlled and/or supplemented by the client code via various options:
* <p>
* <ol>
* <li>All the configurable options of {@link BeanPopulatorBaseSpi} are available, as the replication of JavaBean
* properties inevitably involves bean population.</li>
* <p>
* <li>If the default property filter {@link HibernatePropertyFilter} is used,
* <ul>
* <li>An application package prefix used to determine if a property with a type of an entity bean class will be
* included for replication;</li>
* <li>The set of entity bean classes for matching properties that will be replicated;</li>
* <li>The set of collection and map properties that will be replicated;</li>
* <li>A {@link PropertyFilter vetoer} used to veto the propagation of a property</li>
* </ul>
* <p>
* <li>For anything else that the existing implementation fails to transform, client can provide one or multiple custom
* transformer factories via
* {@link #initCustomTransformerFactory(net.sf.beanlib.spi.CustomBeanTransformerSpi.Factory...)}.</li>
* </ol>
* <p>
* This was originally the base class for both the Hibernate 2.x and Hibernate 3.x replicators, but now Hibernate 2 is
* no longer supported.
*
* @see CustomBeanTransformerSpi
* @see net.sf.beanlib.hibernate4.Hibernate4BeanReplicator
* @author Joe D. Velopar
*/
@NotThreadSafe
public abstract class HibernateBeanReplicator implements BeanPopulatorBaseSpi {
/** Used to do the heavy lifting of Hibernate object transformation and replication. */
private final BeanTransformerSpi hibernateBeanTransformer;
/**
* You probably want to construct a {@link net.sf.beanlib.hibernate4.Hibernate4BeanReplicator
* Hibernate4BeanReplicator} directly instead of this ?
*/
protected HibernateBeanReplicator(BeanTransformerSpi hibernateBeanTransformer) {
if (hibernateBeanTransformer == null) {
throw new IllegalArgumentException("Argument hibernateBeanTransformer must not be null");
}
this.hibernateBeanTransformer = hibernateBeanTransformer;
}
/**
* Returns a copy of the given object. The exact behavior of the copy depends on how the replicator has been
* configured via the init* methods.
* <p>
* In the case when none of the init* methods is invoked, this method behaves like
* {@link HibernateBeanReplicator#deepCopy(Object)}, but without using the {@link HibernatePropertyFilter}.
*
* @param <T> type of the given object.
* @param from given object.
*/
@SuppressWarnings("unchecked")
public final <T> T copy(T from) {
return (T) (from == null ? null : copy(from, UnEnhancer.getActualClass(from)));
}
/**
* Returns an instance of the given class with values copied from the given object. The exact behavior of the copy
* depends on how the replicator has been configured via the init* methods.
* <p>
* In the case when none of the init* methods is invoked, this method behaves exactly like
* {@link HibernateBeanReplicator#deepCopy(Object, Class)}.
*
* @param <T> type of the given object.
* @param from given object.
* @param toClass target class of the returned object.
*/
public final <T> T copy(Object from, Class<T> toClass) {
if (from == null) {
return null;
}
try {
return hibernateBeanTransformer.transform(from, toClass, null);
} finally {
hibernateBeanTransformer.reset();
}
}
/**
* Convenient method to deep copy the given object using the default behavior.
* <p>
* Notes:
* <ul>
* <li>Use {@link HibernateBeanReplicator#copy(Object, Class)} instead of this method if you want to plug in a
* different (detailed) property filter.</li>
* </ul>
*
* @param <T> from object type.
* @param from given object to be copied.
* @return a deep clone of the from object.
*/
@SuppressWarnings("unchecked")
public final <T> T deepCopy(T from) {
return (T) (from == null ? null : deepCopy(from, UnEnhancer.getActualClass(from)));
}
/**
* Convenient method to deep copy the given object to an instance of the given class using the default behavior.
* <p>
* Notes:
* <ul>
* <li>Use {@link HibernateBeanReplicator#copy(Object, Class)} instead of this method if you want to plug in a
* different (detailed) property filter.</li>
* </ul>
*
* @param <T> to object type.
* @param from given object to be copied.
* @param toClass target class of the returned object.
* @return an instance of the given class with values deeply copied from the given object.
*/
public final <T> T deepCopy(Object from, Class<T> toClass) {
hibernateBeanTransformer.initPropertyFilter(new HibernatePropertyFilter());
return this.copy(from, toClass);
}
/**
* Convenient method to shallow copy the given object using the default behavior. Shallow copy means skipping those
* properties that are of type collection, map or under a package that doesn't start with "java.".
* <p>
* Notes:
* <ul>
* <li>Use {@link HibernateBeanReplicator#copy(Object, Class)} instead of this method if you want to plug in a
* different (detailed) property filter.</li>
* </ul>
*
* @see HibernatePropertyFilter
* @param <T> from object type.
* @param from given object to be copied.
* @return a shallow clone of the from object.
*/
@SuppressWarnings("unchecked")
public final <T> T shallowCopy(T from) {
return (T) (from == null ? null : shallowCopy(from, UnEnhancer.getActualClass(from)));
}
/**
* Convenient method to shallow copy the given object to an instance of the given class using the default behavior.
* Shallow copy means skipping those properties that are of type collection, map or under a package that doesn't
* start with "java.".
* <p>
* Notes:
* <ul>
* <li>Use {@link HibernateBeanReplicator#copy(Object, Class)} instead of this method if you want to plug in a
* different (detailed) property filter.</li>
* </ul>
*
* @param <T> to object type.
* @param from given object to be copied.
* @return an instance of the given class with values shallow copied from the given object.
*/
public final <T> T shallowCopy(Object from, Class<T> toClass) {
Set<? extends CollectionPropertyName<?>> emptyCollectionPropertyNameSet = Collections.emptySet();
Set<Class<?>> emptyEntityBeanClassSet = Collections.emptySet();
hibernateBeanTransformer.initPropertyFilter(new HibernatePropertyFilter().withCollectionPropertyNameSet(emptyCollectionPropertyNameSet)
.withEntityBeanClassSet(emptyEntityBeanClassSet));
return this.copy(from, toClass);
}
/**
* Initializes with one or more custom bean transformer factories that will be chained together.
*
* @see ChainedCustomBeanTransformer
*/
public final HibernateBeanReplicator initCustomTransformerFactory(CustomBeanTransformerSpi.Factory... customBeanTransformerFactories) {
if (customBeanTransformerFactories != null && customBeanTransformerFactories.length > 0) {
hibernateBeanTransformer.initCustomTransformerFactory(customBeanTransformerFactories.length == 1 ? customBeanTransformerFactories[0]
: new ChainedCustomBeanTransformer.Factory(customBeanTransformerFactories));
} else {
hibernateBeanTransformer.initCustomTransformerFactory(null);
}
return this;
}
// ========================== Bean Population configuration ==========================
/**
* Returns the property filter that is used to control what properties get propagated across and what get skipped.
*/
@Override
public final PropertyFilter getPropertyFilter() {
return hibernateBeanTransformer.getPropertyFilter();
}
/**
* Note this method is only applicable if either the {@link #copy(Object)} or {@link #copy(Object, Class)} is
* directly invoked, and is ignored otherwise (ie ignored if deep or shallow copy is invoked instead).
*
* @param propertyFilter is similar to {@link DetailedPropertyFilter} but with a simpler API that is used to control
* whether a specific JavaBean property should be propagated from a source bean to a target bean.
* @return the current object (ie this) for method chaining purposes.
*/
@Override
public final HibernateBeanReplicator initPropertyFilter(PropertyFilter propertyFilter) {
this.hibernateBeanTransformer.initPropertyFilter(propertyFilter);
return this;
}
/**
* Note this method is only applicable if either the {@link #copy(Object)} or {@link #copy(Object, Class)} is
* directly invoked, and is ignored otherwise (ie ignored if deep or shallow copy is invoked instead).
*
* @param detailedPropertyFilter is used to control whether a specific JavaBean property should be propagated from
* the source bean to the target bean.
* @return the current object (ie this) for method chaining purposes.
*/
@Override
public final HibernateBeanReplicator initDetailedPropertyFilter(DetailedPropertyFilter detailedPropertyFilter) {
this.hibernateBeanTransformer.initDetailedPropertyFilter(detailedPropertyFilter);
return this;
}
/**
* Used to configure a call-back (to produce whatever side-effects deemed necessary) that is invoked after the
* property value has been retrieved from the source bean, but before being propagated across to the target bean.
*
* @param 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.
* @return the current object (ie this) for method chaining purposes.
*/
@Override
public final HibernateBeanReplicator initBeanSourceHandler(BeanSourceHandler beanSourceHandler) {
this.hibernateBeanTransformer.initBeanSourceHandler(beanSourceHandler);
return this;
}
/**
* Used to control whether debug messages should be logged.
*
* @return the current object (ie this) for method chaining purposes.
*/
@Override
public final HibernateBeanReplicator initDebug(boolean debug) {
this.hibernateBeanTransformer.initDebug(debug);
return this;
}
/**
* Used to configure a finder to find the property getter methods of a source JavaBean.
*
* @param readerMethodFinder can be used to find the property getter methods of a source JavaBean.
* @return the current object (ie this) for method chaining purposes.
*/
@Override
public final HibernateBeanReplicator initReaderMethodFinder(BeanMethodFinder readerMethodFinder) {
this.hibernateBeanTransformer.initReaderMethodFinder(readerMethodFinder);
return this;
}
/**
* Used to configure a collector to collect the property setter methods of a target JavaBean.
*
* @param setterMethodCollector can be used to collect the property setter methods of a target JavaBean.
* @return the current object (ie this) for method chaining purposes.
*/
@Override
public final HibernateBeanReplicator initSetterMethodCollector(BeanMethodCollector setterMethodCollector) {
this.hibernateBeanTransformer.initSetterMethodCollector(setterMethodCollector);
return this;
}
@Override
public HibernateBeanReplicator initBeanPopulationExceptionHandler(BeanPopulationExceptionHandler beanPopulationExceptionHandler) {
this.hibernateBeanTransformer.initBeanPopulationExceptionHandler(beanPopulationExceptionHandler);
return this;
}
/**
* Used to conveniently provide the bean population related configuration options as a single configuration object.
*
* @param baseConfig is used to conveniently group all the other initializable options into a single unit.
* @return the current object (ie this) for method chaining purposes.
*/
@Override
public BeanPopulatorBaseSpi initBeanPopulatorBaseConfig(BeanPopulatorBaseConfig baseConfig) {
this.hibernateBeanTransformer.initBeanPopulatorBaseConfig(baseConfig);
return this;
}
@Override
public BeanPopulationExceptionHandler getBeanPopulationExceptionHandler() {
return hibernateBeanTransformer.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 hibernateBeanTransformer.getBeanPopulatorBaseConfig();
}
@Override
public BeanSourceHandler getBeanSourceHandler() {
return hibernateBeanTransformer.getBeanSourceHandler();
}
@Override
public boolean isDebug() {
return hibernateBeanTransformer.isDebug();
}
@Override
public DetailedPropertyFilter getDetailedPropertyFilter() {
return hibernateBeanTransformer.getDetailedPropertyFilter();
}
@Override
public BeanMethodFinder getReaderMethodFinder() {
return hibernateBeanTransformer.getReaderMethodFinder();
}
@Override
public BeanMethodCollector getSetterMethodCollector() {
return hibernateBeanTransformer.getSetterMethodCollector();
}
/**
* Convenient method to return the current property filter as HibernatePropertyFilter if the type matches; or null
* otherwise.
*/
public HibernatePropertyFilter getHibernatePropertyFilter() {
PropertyFilter f = hibernateBeanTransformer.getPropertyFilter();
return f instanceof HibernatePropertyFilter ? (HibernatePropertyFilter) f : null;
}
}