/*
* #%L
* BroadleafCommerce Common Libraries
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* 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.
* #L%
*/
package org.broadleafcommerce.common.util.dao;
import javassist.util.proxy.ProxyFactory;
import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.ArrayUtils;
import org.broadleafcommerce.common.exception.ExceptionHelper;
import org.broadleafcommerce.common.exception.ProxyDetectionException;
import org.broadleafcommerce.common.presentation.AdminPresentationClass;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.ejb.HibernateEntityManager;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.Type;
import org.springframework.util.ReflectionUtils;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
public class DynamicDaoHelperImpl implements DynamicDaoHelper {
public static final Object LOCK_OBJECT = new Object();
public static final Map<Class<?>, Class<?>[]> POLYMORPHIC_ENTITY_CACHE = new LRUMap<Class<?>, Class<?>[]>(1000);
public static final Map<Class<?>, Class<?>[]> POLYMORPHIC_ENTITY_CACHE_WO_EXCLUSIONS = new LRUMap<Class<?>, Class<?>[]>(1000);
public static final String JAVASSIST_PROXY_KEY_PHRASE = "_$$_";
public static Class<?> getNonProxyImplementationClassIfNecessary(Class<?> candidate) {
Class<?> response = candidate;
//TODO Although unusual, there are other proxy types in Hibernate than just javassist proxies (Broadleaf defaults to javassist).
//At some point, should try to account for those here in a performant way.
if (ProxyFactory.isProxyClass(candidate)) {
//TODO This is not ideal. While this works consistently, I don't think it's guaranteed that the proxy classname
//will always have this format in the future. We'll at least throw an exception if our formatting detection fails
//so that we're aware of it.
if (!candidate.getName().contains(JAVASSIST_PROXY_KEY_PHRASE)) {
throw new ProxyDetectionException(String.format("Cannot determine the original implementation class for " +
"the javassist proxy. Expected to find the keyphrase (%s) in the proxy classname (%s).",
JAVASSIST_PROXY_KEY_PHRASE, candidate.getName()));
}
String implName = candidate.getName().substring(0, candidate.getName().lastIndexOf(JAVASSIST_PROXY_KEY_PHRASE));
try {
response = Class.forName(implName);
} catch (ClassNotFoundException e) {
throw ExceptionHelper.refineException(e);
}
}
return response;
}
@Override
public Class<?>[] getAllPolymorphicEntitiesFromCeiling(Class<?> ceilingClass, SessionFactory sessionFactory,
boolean includeUnqualifiedPolymorphicEntities, boolean useCache) {
ceilingClass = getNonProxyImplementationClassIfNecessary(ceilingClass);
Class<?>[] cache = null;
synchronized(LOCK_OBJECT) {
if (useCache) {
if (includeUnqualifiedPolymorphicEntities) {
cache = POLYMORPHIC_ENTITY_CACHE.get(ceilingClass);
} else {
cache = POLYMORPHIC_ENTITY_CACHE_WO_EXCLUSIONS.get(ceilingClass);
}
}
if (cache == null) {
List<Class<?>> entities = new ArrayList<Class<?>>();
for (Object item : sessionFactory.getAllClassMetadata().values()) {
ClassMetadata metadata = (ClassMetadata) item;
Class<?> mappedClass = metadata.getMappedClass();
if (mappedClass != null && ceilingClass.isAssignableFrom(mappedClass)) {
entities.add(mappedClass);
}
}
Class<?>[] sortedEntities = sortEntities(ceilingClass, entities);
List<Class<?>> filteredSortedEntities = new ArrayList<Class<?>>();
for (int i = 0; i < sortedEntities.length; i++) {
Class<?> item = sortedEntities[i];
if (includeUnqualifiedPolymorphicEntities) {
filteredSortedEntities.add(sortedEntities[i]);
} else {
if (isExcludeClassFromPolymorphism(item)) {
continue;
} else {
filteredSortedEntities.add(sortedEntities[i]);
}
}
}
Class<?>[] filteredEntities = new Class<?>[filteredSortedEntities.size()];
filteredEntities = filteredSortedEntities.toArray(filteredEntities);
cache = filteredEntities;
if (includeUnqualifiedPolymorphicEntities) {
POLYMORPHIC_ENTITY_CACHE.put(ceilingClass, filteredEntities);
} else {
POLYMORPHIC_ENTITY_CACHE_WO_EXCLUSIONS.put(ceilingClass, filteredEntities);
}
}
}
return cache;
}
@Override
public Class<?>[] getUpDownInheritance(Class<?> testClass, SessionFactory sessionFactory,
boolean includeUnqualifiedPolymorphicEntities, boolean useCache, EJB3ConfigurationDao ejb3ConfigurationDao) {
Class<?>[] pEntities = getAllPolymorphicEntitiesFromCeiling(testClass, sessionFactory, includeUnqualifiedPolymorphicEntities, useCache);
if (ArrayUtils.isEmpty(pEntities)) {
return pEntities;
}
Class<?> topConcreteClass = pEntities[pEntities.length - 1];
List<Class<?>> temp = new ArrayList<Class<?>>(pEntities.length);
temp.addAll(Arrays.asList(pEntities));
Collections.reverse(temp);
boolean eof = false;
while (!eof) {
Class<?> superClass = topConcreteClass.getSuperclass();
PersistentClass persistentClass = ejb3ConfigurationDao.getConfiguration().getClassMapping(superClass.getName());
if (persistentClass == null) {
eof = true;
} else {
temp.add(0, superClass);
topConcreteClass = superClass;
}
}
return temp.toArray(new Class<?>[temp.size()]);
}
@Override
public Class<?>[] sortEntities(Class<?> ceilingClass, List<Class<?>> entities) {
/*
* Sort entities with the most derived appearing first
*/
Class<?>[] sortedEntities = new Class<?>[entities.size()];
List<Class<?>> stageItems = new ArrayList<Class<?>>();
stageItems.add(ceilingClass);
int j = 0;
while (j < sortedEntities.length) {
List<Class<?>> newStageItems = new ArrayList<Class<?>>();
boolean topLevelClassFound = false;
for (Class<?> stageItem : stageItems) {
Iterator<Class<?>> itr = entities.iterator();
while(itr.hasNext()) {
Class<?> entity = itr.next();
checkitem: {
if (ArrayUtils.contains(entity.getInterfaces(), stageItem) || entity.equals(stageItem)) {
topLevelClassFound = true;
break checkitem;
}
if (topLevelClassFound) {
continue;
}
if (entity.getSuperclass().equals(stageItem) && j > 0) {
break checkitem;
}
continue;
}
sortedEntities[j] = entity;
itr.remove();
j++;
newStageItems.add(entity);
}
}
if (newStageItems.isEmpty()) {
throw new IllegalArgumentException("There was a gap in the inheritance hierarchy for (" + ceilingClass.getName() + ")");
}
stageItems = newStageItems;
}
ArrayUtils.reverse(sortedEntities);
return sortedEntities;
}
@Override
public boolean isExcludeClassFromPolymorphism(Class<?> clazz) {
//We filter out abstract classes because they can't be instantiated.
if (Modifier.isAbstract(clazz.getModifiers())) {
return true;
}
//We filter out classes that are marked to exclude from polymorphism
AdminPresentationClass adminPresentationClass = clazz.getAnnotation(AdminPresentationClass.class);
if (adminPresentationClass == null) {
return false;
} else if (adminPresentationClass.excludeFromPolymorphism()) {
return true;
}
return false;
}
@Override
public Map<String, Object> getIdMetadata(Class<?> entityClass, HibernateEntityManager entityManager) {
entityClass = getNonProxyImplementationClassIfNecessary(entityClass);
Map<String, Object> response = new HashMap<String, Object>();
SessionFactory sessionFactory = entityManager.getSession().getSessionFactory();
ClassMetadata metadata = sessionFactory.getClassMetadata(entityClass);
if (metadata == null) {
return null;
}
String idProperty = metadata.getIdentifierPropertyName();
response.put("name", idProperty);
Type idType = metadata.getIdentifierType();
response.put("type", idType);
return response;
}
@Override
public List<String> getPropertyNames(Class<?> entityClass, HibernateEntityManager entityManager) {
entityClass = getNonProxyImplementationClassIfNecessary(entityClass);
ClassMetadata metadata = getSessionFactory(entityManager).getClassMetadata(entityClass);
List<String> propertyNames = new ArrayList<String>();
Collections.addAll(propertyNames, metadata.getPropertyNames());
return propertyNames;
}
@Override
public List<Type> getPropertyTypes(Class<?> entityClass, HibernateEntityManager entityManager) {
entityClass = getNonProxyImplementationClassIfNecessary(entityClass);
ClassMetadata metadata = getSessionFactory(entityManager).getClassMetadata(entityClass);
List<Type> propertyTypes = new ArrayList<Type>();
Collections.addAll(propertyTypes, metadata.getPropertyTypes());
return propertyTypes;
}
@Override
public SessionFactory getSessionFactory(HibernateEntityManager entityManager) {
return entityManager.getSession().getSessionFactory();
}
@Override
public Serializable getIdentifier(Object entity, EntityManager em) {
Class<?> entityClass = getNonProxyImplementationClassIfNecessary(entity.getClass());
if (entityClass.getAnnotation(Entity.class) != null) {
Field idField = getIdField(entityClass, em);
try {
return (Serializable) idField.get(entity);
} catch (IllegalAccessException e) {
throw ExceptionHelper.refineException(e);
}
}
return null;
}
@Override
public Serializable getIdentifier(Object entity, Session session) {
Class<?> entityClass = getNonProxyImplementationClassIfNecessary(entity.getClass());
if (entityClass.getAnnotation(Entity.class) != null) {
Field idField = getIdField(entityClass, session);
try {
return (Serializable) idField.get(entity);
} catch (IllegalAccessException e) {
throw ExceptionHelper.refineException(e);
}
}
return null;
}
@Override
public Field getIdField(Class<?> clazz, EntityManager em) {
clazz = getNonProxyImplementationClassIfNecessary(clazz);
ClassMetadata metadata = em.unwrap(Session.class).getSessionFactory().getClassMetadata(clazz);
Field idField = ReflectionUtils.findField(clazz, metadata.getIdentifierPropertyName());
idField.setAccessible(true);
return idField;
}
@Override
public Field getIdField(Class<?> clazz, Session session) {
clazz = getNonProxyImplementationClassIfNecessary(clazz);
ClassMetadata metadata = session.getSessionFactory().getClassMetadata(clazz);
Field idField = ReflectionUtils.findField(clazz, metadata.getIdentifierPropertyName());
idField.setAccessible(true);
return idField;
}
}