/*
* 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 static net.sf.beanlib.utils.ClassUtils.fqcn;
import static net.sf.beanlib.utils.ClassUtils.immutable;
import static net.sf.beanlib.utils.ClassUtils.isJavaPackage;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import net.sf.beanlib.CollectionPropertyName;
import net.sf.beanlib.spi.PropertyFilter;
/**
* A default implementation used to determine if a Hibernate property that follows the JavaBean getter/setter convention
* should be propagated. Each propagation decision can be controlled by specifying
* <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 net.sf.beanlib.spi.PropertyFilter vetoer} used to veto the propagation of a property</li>
* </ul>
*
* @author Joe D. Velopar
*/
public class HibernatePropertyFilter implements PropertyFilter {
/**
* The set of entity bean classes for matching properties that will be replicated, eagerly fetching if necessary.
* Null means all whereas empty means none.
*/
private Set<Class<?>> entityBeanClassSet;
/**
* The set of collection and map properties that will be replicated, eagerly fetching if necessary. Null means all
* whereas empty means none.
*/
private Set<? extends CollectionPropertyName<?>> collectionPropertyNameSet;
/** Used to veto the propagation of a property. */
private PropertyFilter vetoer;
/**
* An entity bean under a package with a name that matches this prefix will be included for replication, eagerly
* fetched if necessary. Otherwise, the semantics of {@link #entityBeanClassSet} applies.
*/
private final String applicationPackagePrefix;
/**
* Constructs with the specified options of controlling what to be replicated and what not.
*
* @param entityBeanClassSet The set of entity bean classes for matching properties that will be replicated, eagerly
* fetching if necessary. Null means all whereas empty means none.
* @param collectionPropertyNameSet The set of collection and map properties that will be replicated, eagerly
* fetching if necessary. Null means all whereas empty means none.
* @param vetoer used to veto the propagation of a JavaBean property.
*/
public HibernatePropertyFilter(Set<Class<?>> entityBeanClassSet, Set<? extends CollectionPropertyName<?>> collectionPropertyNameSet,
PropertyFilter vetoer) {
this.entityBeanClassSet = entityBeanClassSet;
this.collectionPropertyNameSet = collectionPropertyNameSet;
this.vetoer = vetoer;
this.applicationPackagePrefix = "#"; // disable by matching no packages
}
/**
* Constructs with the specified options of controlling what to be replicated and what not.
*
* @param applicationPackagePrefix An entity bean under a package with a name that matches this prefix will be
* included for replication, eagerly fetched if necessary. Otherwise, the semantics of
* {@link #entityBeanClassSet} applies.
* @param entityBeanClassSet The set of entity bean classes for matching properties that will be replicated, eagerly
* fetching if necessary. Null means all whereas empty means none.
* @param collectionPropertyNameSet The set of collection and map properties that will be replicated, eagerly
* fetching if necessary. Null means all whereas empty means none.
* @param vetoer used to veto the propagation of a JavaBean property.
*/
public HibernatePropertyFilter(String applicationPackagePrefix, Set<Class<?>> entityBeanClassSet,
Set<? extends CollectionPropertyName<?>> collectionPropertyNameSet, PropertyFilter vetoer) {
this.entityBeanClassSet = entityBeanClassSet;
this.collectionPropertyNameSet = collectionPropertyNameSet;
this.vetoer = vetoer;
this.applicationPackagePrefix = applicationPackagePrefix;
}
/**
* Constructs with the default behavior of replicating all properties recursively.
*/
public HibernatePropertyFilter() {
this.applicationPackagePrefix = "#"; // disable by matching no packages
}
/**
* Constructs with an application package prefix.
*
* @param applicationPackagePrefix An entity bean under a package with a name that matches this prefix will be
* included for replication, eagerly fetched if necessary. Otherwise, the semantics of
* {@link #entityBeanClassSet} applies.
*/
public HibernatePropertyFilter(String applicationPackagePrefix) {
this.applicationPackagePrefix = applicationPackagePrefix;
this.entityBeanClassSet = Collections.emptySet();
}
/**
* Returns the configured set of entity bean classes for matching properties that will be replicated, eagerly
* fetching if necessary; null if all entity bean classes are to be replicated; or empty if no entity bean class is
* to be replicated.
*/
public Set<Class<?>> getEntityBeanClassSet() {
return entityBeanClassSet;
}
/**
* Used to configure the set of entity bean classes for matching properties that will be replicated, eagerly
* fetching if necessary.
*
* @param entityBeanClassSet the set of entity bean classes for matching properties that will be replicated, eagerly
* fetching if necessary. null if all entity bean classes are to be replicated; or empty if no entity
* bean class is to be replicated.
* @return the current instance for method chaining purposes.
*/
public HibernatePropertyFilter withEntityBeanClassSet(Set<Class<?>> entityBeanClassSet) {
this.entityBeanClassSet = entityBeanClassSet;
return this;
}
/**
* Returns the configured set of collection and map properties that are to be replicated, eagerly fetching if
* necessary; null if all collection and map properties are to be replicated; or empty if no collection nor map
* properties are to be replicated.
*/
@SuppressWarnings("unchecked")
public Set<CollectionPropertyName<?>> getCollectionPropertyNameSet() {
return (Set<CollectionPropertyName<?>>) collectionPropertyNameSet;
}
/**
* Used to configure the set of collection and map properties that will be replicated, eagerly fetching if
* necessary.
*
* @param collectionPropertyNameSet set of collection and map properties that will be replicated, eagerly fetching
* if necessary; null if all collection and map properties are to be replicated; or empty if no
* collection nor map properties are to be replicated.
* @return the current instance for method chaining purposes.
*/
public HibernatePropertyFilter withCollectionPropertyNameSet(Set<? extends CollectionPropertyName<?>> collectionPropertyNameSet) {
this.collectionPropertyNameSet = collectionPropertyNameSet;
return this;
}
/**
* Returns the vetoer configured for vetoing the propagation of a property.
*/
public PropertyFilter getVetoer() {
return vetoer;
}
/**
* Used to configure a vetoer for vetoing the propagation of a property.
*
* @return the current instance for method chaining purposes.
*/
public HibernatePropertyFilter withVetoer(PropertyFilter vetoer) {
this.vetoer = vetoer;
return this;
}
@Override
public boolean propagate(String propertyName, Method readerMethod) {
if (propagateImpl(propertyName, readerMethod)) {
return vetoer == null ? true : vetoer.propagate(propertyName, readerMethod);
}
return false;
}
public boolean propagateImpl(String propertyName, Method readerMethod) {
Class<?> returnType = UnEnhancer.unenhanceClass(readerMethod.getReturnType());
if (immutable(returnType)) {
return true;
}
if (returnType.isArray()) {
if (immutable(returnType.getComponentType())) {
return true;
}
}
if (entityBeanClassSet == null) {
// All entity bean to be populated.
if (collectionPropertyNameSet == null) {
// all fields to be populated
return true;
}
return checkCollectionProperty(propertyName, readerMethod);
}
// Only a selected set of entity bean to be populated.
if (isJavaPackage(returnType)) {
// Not an entity bean.
if (collectionPropertyNameSet == null) {
// All Collection/Map properties to be populated.
return true;
}
return checkCollectionProperty(propertyName, readerMethod);
}
// An entity bean.
Class<?> superClass = returnType;
for (;;) {
if (entityBeanClassSet.contains(superClass) || isApplicationClass(superClass)) {
return true;
}
// check if it's ancestor is specified in entityBeanClassSet
superClass = superClass.getSuperclass();
if (superClass == null || superClass == Object.class) {
return false; // not specified in entityBeanClassSet
}
}
}
private boolean checkCollectionProperty(String propertyName, Method readerMethod) {
// Only a specified set of Collection/Map properties needs to be populated
Class<?> returnType = UnEnhancer.unenhanceClass(readerMethod.getReturnType());
if (Collection.class.isAssignableFrom(returnType) || Map.class.isAssignableFrom(returnType)) {
@SuppressWarnings("unchecked")
CollectionPropertyName<?> colPropName = new CollectionPropertyName(UnEnhancer.unenhanceClass(readerMethod.getDeclaringClass()),
propertyName);
// A Collection/Map property
return collectionPropertyNameSet.contains(colPropName);
}
// Not a Collection/Map property.
return true;
}
/** Returns true iff c is an application class. */
public boolean isApplicationClass(Class<?> c) {
return c != null && fqcn(c).startsWith(applicationPackagePrefix);
}
}