/*
* 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.hibernate4;
import static net.sf.beanlib.utils.ClassUtils.fqcn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sf.beanlib.CollectionPropertyName;
import net.sf.beanlib.hibernate.HibernateBeanReplicator;
import net.sf.beanlib.hibernate.HibernatePropertyFilter;
import net.sf.beanlib.hibernate.UnEnhancer;
import net.sf.beanlib.provider.collector.ProtectedSetterMethodCollector;
import net.sf.beanlib.spi.DetailedPropertyFilter;
import net.sf.beanlib.spi.PropertyFilter;
import org.apache.commons.lang.ArrayUtils;
/**
* Hibernate 4 Data Transfer Object Copier.
* <p>
* A simple facade of {@link HibernateBeanReplicator}, this class provides a simplified (but limited) API for the common
* use cases to conveniently replicate Hibernate objects that follow the JavaBean getter/setter convention on a best
* attempt basis.
* <p>
* Those application specific Hibernate objects would typically have the same package prefix.
* <p>
* 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 can be controlled and/or supplemented by the client code via 3
* main options:
* <p>
* <ol>
* <li>Specifying an application package prefix: property with a type of an entity bean class with package name that
* matchs the prefix will be replicated;</li>
* <li>Specifying a set of entity bean classes: property with a type of an entity bean class that is part of the set
* will be replicated;</li>
* <li>Specifying a set of collection and map properties that will be replicated;</li> </ul>
* </ol>
* For more advanced options and more control, consider using {@link Hibernate4BeanReplicator} directly.
*
* @see Hibernate4BeanReplicator
* @author Joe D. Velopar
*/
public class HibernateDtoCopier {
/**
* An entity bean under a package with a name that matches this prefix will be included for replication, eagerly
* fetched if necessary.
*/
private final String applicationPackagePrefix;
/** Constructs with application package prefix disabled. */
public HibernateDtoCopier() {
this.applicationPackagePrefix = "#"; // By default no application package is specified.
}
/**
* Constructs with an application package prefix.
*
* @see #applicationPackagePrefix
*/
public HibernateDtoCopier(String applicationPackagePrefix) {
this.applicationPackagePrefix = applicationPackagePrefix;
}
/**
* Constructs with an application package prefix, and a sample application class in a package with such prefix for
* sanity checking purposes.
*
* @see #applicationPackagePrefix
* @throws IllegalArgumentException if the given application package prefix does not match the package of the given
* sample application class.
*/
public HibernateDtoCopier(String applicationPackagePrefix, Class<?> applicationSampleClass) {
this.applicationPackagePrefix = applicationPackagePrefix;
if (applicationSampleClass != null && !fqcn(applicationSampleClass).startsWith(applicationPackagePrefix)) {
throw new IllegalArgumentException("The specified application package prefix " + applicationPackagePrefix
+ " is not consistent with the given sample application class " + applicationSampleClass);
}
}
/** Returns the implementation instance that performs the actual replication. */
protected Hibernate4BeanReplicator createHibernateBeanReplicator() {
return new Hibernate4BeanReplicator();
}
/**
* Returns a DTO by deep cloning the application specific entities of the given Hibernate "from" instance.
* <p>
* If you need to "blindly" deep clone the entire object graph, consider using
* {@link #hibernate2dto(Class, Object, Class[], CollectionPropertyName[])} specifying null for the
* interstedPropertyTypes and collectionPropertyNames.
*/
@SuppressWarnings("unchecked")
public <T> T hibernate2dtoFully(Object from) {
return (T) (from == null ? null : createHibernateBeanReplicator().deepCopy(from));
}
/**
* Returns a list of DTO's by deep cloning the application specific entities of the given collection of Hibernate
* beans.
* <p>
* If you need to "blindly" deep clone the entire object graph, consider using
* {@link #hibernate2dto(Class, Collection, Class[], CollectionPropertyName[])} specifying null for the
* interstedPropertyTypes and collectionPropertyNames.
*/
public List<?> hibernate2dtoFully(Collection<?> hibernateBeans) {
if (hibernateBeans == null)
return null;
List<Object> list = new ArrayList<Object>(hibernateBeans.size());
HibernateBeanReplicator replicator = createHibernateBeanReplicator();
for (Object obj : hibernateBeans)
list.add(replicator.deepCopy(obj));
return list;
}
/**
* Returns a DTO by cloning portion of the object graph of the given Hibernate bean, excluding all collection and
* map properties, and including only those properties with package names that match the application package prefix.
* <p>
* The replication is typically recursive in that the object graph of the input object is basically replicated into
* an equivalent output object graph, resolving circular references, and eagerly fetching proxied instances as
* necessary.
*
* @param entityBean Hibernate entity bean to be cloned
* @see #applicationPackagePrefix
*/
@SuppressWarnings("unchecked")
public <T> T hibernate2dto(Object entityBean) {
return (T) hibernate2dto(UnEnhancer.getActualClass(entityBean), entityBean);
}
/**
* Returns a DTO of the specified target entity type by cloning portion of the object graph of the given Hibernate
* bean, excluding all collection and map properties, and including only those properties with package names that
* match the application package prefix.
* <p>
* The replication is typically recursive in that the object graph of the input object is basically replicated into
* an equivalent output object graph, resolving circular references, and eagerly fetching proxied instances as
* necessary.
*
* @param targetEntityType target entity type
* @param entityBean Hibernate entity bean to be cloned
* @see #applicationPackagePrefix
*/
public <E, T> E hibernate2dto(Class<E> targetEntityType, T entityBean) {
if (entityBean == null)
return null;
return copy(targetEntityType, entityBean, ArrayUtils.EMPTY_CLASS_ARRAY);
}
/**
* Returns a DTO by partially cloning the object graph of the given Hibernate "from" instance that follows the
* JavaBean getter/setter convention on a best attempt basis, including only those properties that are explicitly
* specified, or implied by the application package prefix.
*
* @param from given Hibernate entity bean to be cloned
* @param interestedEntityTypes properties of these types will be included for cloning
* @param collectionPropertyNames collection and map properties to be included in the cloning
* @see #applicationPackagePrefix
*/
@SuppressWarnings("unchecked")
public <T> T hibernate2dto(T from, Class<?>[] interestedEntityTypes, CollectionPropertyName<?>[] collectionPropertyNames) {
return (T) hibernate2dto(UnEnhancer.getActualClass(from), from, interestedEntityTypes, collectionPropertyNames);
}
/**
* Returns a DTO of the specified target entity type by partially cloning the object graphs of the given Hibernate
* "from" instance that follows the JavaBean getter/setter convention on a best attempt basis, including only those
* properties that are explicitly specified, or implied by the application package prefix.
* <p>
* The replication is typically recursive in that the object graph of the input object is basically replicated into
* an equivalent output object graph, resolving circular references, and eagerly fetching proxied instances as
* necessary.
*
* @param targetEntityType target entity type
* @param from given Hibernate entity bean to be cloned
* @param interestedEntityTypes properties of these types will be included for cloning
* @param collectionPropertyNames collection and map properties to be included in the cloning
* @see HibernateDtoCopier#applicationPackagePrefix
*/
public <E, T> E hibernate2dto(Class<E> targetEntityType, T from, Class<?>[] interestedEntityTypes,
CollectionPropertyName<?>[] collectionPropertyNames) {
if (from == null)
return null;
return copy(targetEntityType, from, interestedEntityTypes, collectionPropertyNames);
}
/**
* Returns a list of DTO's by partially cloning the object graphs of the given collection of Hibernate entity beans,
* excluding all collection and map properties, and including only those properties with package names that match
* the application package prefix.
* <p>
* The replication is typically recursive in that the object graph of the input object is basically replicated into
* an equivalent output object graph, resolving circular references, and eagerly fetching proxied instances as
* necessary.
*
* @param hibernateBeans given collection of Hibernate Beans.
* @see #applicationPackagePrefix
*/
public List<?> hibernate2dto(Collection<?> hibernateBeans) {
if (hibernateBeans == null)
return null;
List<Object> list = new ArrayList<Object>(hibernateBeans.size());
for (Object entityBean : hibernateBeans) {
Object to = copy(entityBean, ArrayUtils.EMPTY_CLASS_ARRAY);
list.add(to);
}
return list;
}
/**
* Returns a list of DTO's of the specified target entity type by partially cloning the object graphs of the given
* collection of Hibernate entity beans that follows the JavaBean getter/setter convention on a best attempt basis,
* including only those properties that are explicitly specified, or implied by the application package prefix.
*
* @param hibernateBeans given collection of Hibernate entity beans to be cloned
* @param interestedEntityTypes properties of these types will be included for cloning
* @param collectionPropertyNames collection and map properties to be included in the cloning
* @see #applicationPackagePrefix
*/
public <E> List<E> hibernate2dto(Class<E> targetEntityType, Collection<?> hibernateBeans, Class<?>[] interestedEntityTypes,
CollectionPropertyName<?>[] collectionPropertyNames) {
if (hibernateBeans == null)
return null;
List<E> list = new ArrayList<E>(hibernateBeans.size());
for (Object entityBean : hibernateBeans) {
E to = copy(targetEntityType, entityBean, interestedEntityTypes, collectionPropertyNames);
list.add(to);
}
return list;
}
/**
* Returns a DTO by partially cloning the object graph of the given Hibernate "from" instance that follows the
* JavaBean getter/setter convention on a best attempt basis, excluding all collection and map properties.
* <p>
* The replication is typically recursive in that the object graph of the input object is basically replicated into
* an equivalent output object graph, resolving circular references, and eagerly fetching proxied instances as
* necessary.
*
* @param from given entity bean to be cloned
* @param interestedEntityTypes properties of these types will be included for cloning
* @see #applicationPackagePrefix
*/
private Object copy(Object from, Class<?>[] interestedEntityTypes) {
if (from == null)
return null;
return copy(UnEnhancer.getActualClass(from), from, interestedEntityTypes, CollectionPropertyName.EMPTY_ARRAY);
}
/**
* Returns a DTO of the specified target entity type by partially cloning the object graph of the given Hibernate
* "from" instance that follows the JavaBean getter/setter convention on a best attempt basis, excluding all
* collection and map properties.
* <p>
* The replication is typically recursive in that the object graph of the input object is basically replicated into
* an equivalent output object graph, resolving circular references, and eagerly fetching proxied instances as
* necessary.
*
* @param targetEntityType target entity type
* @param from given entity bean to be cloned
* @param interestedEntityTypes properties of these types will be included for cloning
* @see #applicationPackagePrefix
*/
private <E> E copy(Class<E> targetEntityType, Object from, Class<?>[] interestedEntityTypes) {
if (from == null)
return null;
return copy(targetEntityType, from, interestedEntityTypes, CollectionPropertyName.EMPTY_ARRAY);
}
/**
* Returns a DTO of the specified target entity type by partially cloning the object graph of the given Hibernate
* "from" instance that follows the JavaBean getter/setter convention on a best attempt basis, including only those
* properties that are explicitly specified, or implied by the application package prefix.
* <p>
* The replication is typically recursive in that the object graph of the input object is basically replicated into
* an equivalent output object graph, resolving circular references, and eagerly fetching proxied instances as
* necessary.
*
* @param targetEntityType target entity type
* @param from given Hibernate entity bean to be cloned
* @param interestedEntityTypes properties of these types will be included for cloning
* @param collectionPropertyNames collection and map properties to be included in the cloning
* @see #applicationPackagePrefix
*/
private <E> E copy(Class<E> targetEntityType, Object from, Class<?>[] interestedEntityTypes, CollectionPropertyName<?>[] collectionPropertyNames) {
if (from == null)
return null;
HibernateBeanReplicator replicator = createHibernateBeanReplicator();
// Assumes all entity classes
Set<Class<?>> entityBeanClassSet = null;
if (interestedEntityTypes != null) {
if (interestedEntityTypes.length == 0)
// no other entity classes
entityBeanClassSet = Collections.emptySet();
else
// entity classes explicitly specified
entityBeanClassSet = new HashSet<Class<?>>(Arrays.asList(interestedEntityTypes));
}
// Assumes all Collection properties
Set<CollectionPropertyName<?>> collectionPropertyNameSet = null;
if (collectionPropertyNames != null) {
if (collectionPropertyNames.length == 0)
// No Collection properties.
collectionPropertyNameSet = Collections.emptySet();
else
// Collection properties explicitly specified.
collectionPropertyNameSet = new HashSet<CollectionPropertyName<?>>(Arrays.asList(collectionPropertyNames));
}
PropertyFilter propertyFilter = new HibernatePropertyFilter(applicationPackagePrefix, entityBeanClassSet, collectionPropertyNameSet, null);
replicator.initPropertyFilter(propertyFilter).initDetailedPropertyFilter(DetailedPropertyFilter.ALWAYS_PROPAGATE)
.initSetterMethodCollector(new ProtectedSetterMethodCollector());
@SuppressWarnings("unchecked")
E ret = (E) replicator.copy(from, UnEnhancer.unenhanceClass(targetEntityType));
return ret;
}
}