-/**\r
- *\r
- */\r
-package eu.etaxonomy.cdm.persistence.dao.initializer;\r
-\r
-import java.beans.PropertyDescriptor;\r
-import java.io.Serializable;\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.lang.reflect.Method;\r
-import java.lang.reflect.ParameterizedType;\r
-import java.lang.reflect.Type;\r
-import java.lang.reflect.TypeVariable;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-\r
-import org.apache.commons.beanutils.PropertyUtils;\r
-import org.apache.log4j.Logger;\r
-import org.hibernate.Hibernate;\r
-import org.hibernate.HibernateException;\r
-import org.hibernate.Query;\r
-import org.hibernate.collection.internal.AbstractPersistentCollection;\r
-import org.hibernate.collection.internal.PersistentMap;\r
-import org.hibernate.proxy.HibernateProxy;\r
-import org.springframework.beans.factory.annotation.Autowired;\r
-\r
-import eu.etaxonomy.cdm.model.common.CdmBase;\r
-import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;\r
-import eu.etaxonomy.cdm.persistence.dao.hibernate.HibernateBeanInitializer;\r
-\r
-/**\r
- * For now this is a test if we can improve performance for bean initializing\r
- * @author a.mueller\r
- * @date 2013-10-25\r
- *\r
- */\r
-public class AdvancedBeanInitializer extends HibernateBeanInitializer {\r
-\r
- public static final Logger logger = Logger.getLogger(AdvancedBeanInitializer.class);\r
-\r
- @Autowired\r
- ICdmGenericDao genericDao;\r
-\r
- @Override\r
- public void initialize(Object bean, List<String> propertyPaths) {\r
- List<Object> beanList = new ArrayList<Object>(1);\r
- beanList.add(bean);\r
- initializeAll(beanList, propertyPaths);\r
- }\r
-\r
- //TODO optimize algorithm ..\r
- @Override\r
- public <C extends Collection<?>> C initializeAll(C beanList, List<String> propertyPaths) {\r
-\r
- if (beanList == null || beanList.isEmpty()){\r
- return beanList;\r
- }\r
-\r
- //autoinitialize\r
- for (Object bean : beanList){\r
- autoinitializeBean(bean);\r
- }\r
-\r
- if(propertyPaths == null){\r
- return beanList;\r
- }\r
-\r
-\r
- //new\r
- BeanInitNode rootPath = BeanInitNode.createInitTree(propertyPaths);\r
- if (logger.isTraceEnabled()){logger.trace(rootPath.toStringTree());}\r
-\r
-\r
- if(logger.isDebugEnabled()){ logger.debug(">> starting to initialize beanlist ; class(e.g.):" + beanList.iterator().next().getClass().getSimpleName());}\r
- rootPath.addBeans(beanList);\r
- initializeNodeRecursive(rootPath);\r
-\r
-\r
- //old - keep for safety (this may help to initialize those beans that are not yet correctly initialized by the AdvancedBeanInitializer\r
- if(logger.isTraceEnabled()){logger.trace("Start old initalizer ... ");};\r
- for (Object bean :beanList){\r
- Collections.sort(propertyPaths);\r
- for(String propPath : propertyPaths){\r
-// initializePropertyPath(bean, propPath);\r
- }\r
- }\r
-\r
- if(logger.isDebugEnabled()){ logger.debug(" Completed initialization of beanlist "); }\r
- return beanList;\r
-\r
- }\r
-\r
-\r
- //new\r
- private void initializeNodeRecursive(BeanInitNode rootPath) {\r
- initializeNode(rootPath);\r
- for (BeanInitNode childPath : rootPath.getChildrenList()){\r
- initializeNodeRecursive(childPath);\r
- }\r
- rootPath.resetBeans();\r
- }\r
-\r
- /**\r
- * Initializes the given single <code>propPath</code> String.\r
- *\r
- * @param bean\r
- * @param propPath\r
- */\r
- private void initializeNode(BeanInitNode node) {\r
- if(logger.isDebugEnabled()){logger.debug(" processing " + node.toString());}\r
- if (node.isRoot()){\r
- return;\r
- }else if (node.isWildcard()){\r
- initializeNodeWildcard(node);\r
- } else {\r
- initializeNodeNoWildcard(node);\r
- }\r
- }\r
-\r
- // if propPath only contains a wildcard (* or $)\r
- // => do a batch initialization of *toOne or *toMany relations\r
- private void initializeNodeWildcard(BeanInitNode node) {\r
-// boolean initToMany = node.isToManyWildcard();\r
- Map<Class<?>, Set<Object>> parentBeans = node.getParentBeans();\r
- for (Class<?> clazz : parentBeans.keySet()){\r
- //new\r
- for (Object bean : parentBeans.get(clazz)){\r
-\r
- if(Collection.class.isAssignableFrom(bean.getClass())){\r
-// old: initializeAllEntries((Collection<?>)bean, true, initToMany); //TODO is this a possible case at all??\r
- throw new RuntimeException("Collection no longer expected in 'initializeNodeWildcard()'. Therefore an exception is thrown.");\r
- } else if(Map.class.isAssignableFrom(bean.getClass())) {\r
-// old: initializeAllEntries(((Map<?,?>)bean).values(), true, initToMany); ////TODO is this a possible case at all??\r
- throw new RuntimeException("Map no longer expected in 'initializeNodeWildcard()'. Therefore an exception is thrown.");\r
- } else{\r
- prepareBeanWildcardForBulkLoad(node, bean);\r
- }\r
- }\r
- //end new\r
-\r
-// initializeNodeWildcardOld(initToMany, beans, clazz); //if switched on move bulkLoadLazies up\r
- }\r
-\r
- //\r
- bulkLoadLazies(node);\r
- }\r
-\r
- /**\r
- * @param initToMany\r
- * @param beans\r
- * @param clazz\r
- */\r
- private void initializeNodeWildcardOld(boolean initToMany,\r
- Map<Class<?>, Set<Object>> beans, Class<?> clazz) {\r
- for (Object bean : beans.get(clazz)){\r
-\r
- if(Collection.class.isAssignableFrom(bean.getClass())){\r
- initializeAllEntries((Collection<?>)bean, true, initToMany);\r
- } else if(Map.class.isAssignableFrom(bean.getClass())) {\r
- initializeAllEntries(((Map<?,?>)bean).values(), true, initToMany);\r
- } else{\r
- initializeBean(bean, true, initToMany);\r
- }\r
- }\r
- }\r
-\r
- private void prepareBeanWildcardForBulkLoad(BeanInitNode node, Object bean){\r
-\r
- if(logger.isTraceEnabled()){logger.trace(">> prepare bulk wildcard initialization of a bean of type " + bean.getClass().getSimpleName()); }\r
- Set<Class<?>> restrictions = new HashSet<Class<?>>();\r
- restrictions.add(CdmBase.class);\r
- if(node.isToManyWildcard()){\r
- restrictions.add(Collection.class);\r
- }\r
- Set<PropertyDescriptor> props = getProperties(bean, restrictions);\r
- for(PropertyDescriptor propertyDescriptor : props){\r
- try {\r
- String property = propertyDescriptor.getName();\r
-\r
-// invokeInitialization(bean, propertyDescriptor);\r
- Object propertyValue = PropertyUtils.getProperty( bean, property);\r
-\r
- preparePropertyValueForBulkLoadOrStore(node, bean, property, propertyValue );\r
-\r
- } catch (IllegalAccessException e) {\r
- logger.error("Illegal access on property " + propertyDescriptor.getName());\r
- } catch (InvocationTargetException e) {\r
- logger.info("Cannot invoke property " + propertyDescriptor.getName() + " not found");\r
- } catch (NoSuchMethodException e) {\r
- logger.info("Property " + propertyDescriptor.getName() + " not found");\r
- }\r
- }\r
- if(logger.isTraceEnabled()){logger.trace(" completed bulk wildcard initialization of a bean");}\r
- }\r
-\r
-\r
-\r
- // propPath contains either a single field or a nested path\r
- // split next path token off and keep the remaining as nestedPath\r
- private void initializeNodeNoWildcard(BeanInitNode node) {\r
-\r
- String property = node.getPath();\r
- int pos;\r
-\r
- // is the property indexed?\r
- Integer index = null;\r
- if((pos = property.indexOf('[')) > 0){\r
- String indexString = property.substring(pos + 1, property.indexOf(']'));\r
- index = Integer.valueOf(indexString);\r
- property = property.substring(0, pos);\r
- }\r
-\r
- //Class targetClass = HibernateProxyHelper.getClassWithoutInitializingProxy(bean); // used for debugging\r
-\r
- for (Class<?> parentClazz : node.getParentBeans().keySet()){\r
- if (logger.isTraceEnabled()){logger.trace(" invoke initialization on "+ node.toString()+ " beans of class " + parentClazz.getSimpleName() + " ... ");}\r
-\r
- Set<Object> parentBeans = node.getParentBeans().get(parentClazz);\r
-\r
- if (index != null){\r
- logger.warn("Property path index not yet implemented for 'new'");\r
- }\r
- //new\r
- for (Object parentBean : parentBeans){\r
- String propertyName = mapFieldToPropertyName(property, parentBean.getClass().getSimpleName());\r
- try{\r
- Object propertyValue = PropertyUtils.getProperty(parentBean, propertyName);\r
- preparePropertyValueForBulkLoadOrStore(node, parentBean, property, propertyValue);\r
- } catch (IllegalAccessException e) {\r
- logger.error("Illegal access on property " + property);\r
- } catch (InvocationTargetException e) {\r
- logger.error("Cannot invoke property " + property + " not found");\r
- } catch (NoSuchMethodException e) {\r
- if (logger.isDebugEnabled()){logger.debug("Property " + propertyName + " not found for class " + parentClazz);}\r
- }\r
- }\r
-\r
- //end new\r
-\r
-// initializeNodeNoWildcardOld(node, property, index, parentBeans); //move bulkLoadLazies up again, if uncomment this line\r
- }\r
- bulkLoadLazies(node);\r
-\r
- }\r
-\r
- /**\r
- * @param node\r
- * @param property\r
- * @param index\r
- * @param parentBeans\r
- * @throws IllegalAccessException\r
- * @throws InvocationTargetException\r
- * @throws NoSuchMethodException\r
- */\r
- private void initializeNodeNoWildcardOld(BeanInitNode node,\r
- String property, Integer index, Set<Object> parentBeans)\r
- throws IllegalAccessException, InvocationTargetException,\r
- NoSuchMethodException {\r
- for (Object bean : parentBeans){\r
-\r
- PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(bean, property);\r
- if (logger.isTraceEnabled()){logger.trace(" unwrap " + node.toStringNoWildcard() + " ... ");}\r
- // [1] initialize the bean named by property\r
- Object unwrappedPropertyBean = invokeInitialization(bean, propertyDescriptor);\r
- if (logger.isTraceEnabled()){logger.trace(" unwrap " + node.toStringNoWildcard() + " - DONE ");}\r
-\r
-\r
- // [2]\r
- // handle property\r
- if(unwrappedPropertyBean != null ){\r
- initializeNodeSinglePropertyOld(node, property, index, bean, unwrappedPropertyBean);\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * @param node\r
- * @param propertyValue\r
- * @param parentBean\r
- * @param param\r
- */\r
- private void preparePropertyValueForBulkLoadOrStore(BeanInitNode node, Object parentBean, String param, Object propertyValue) {\r
- BeanInitNode sibling = node.getSibling(param);\r
-\r
- if (propertyValue instanceof AbstractPersistentCollection ){\r
- //collections\r
- if (!node.hasWildcardToManySibling()){ //if wildcard sibling exists the lazies are already prepared there\r
- AbstractPersistentCollection collection = (AbstractPersistentCollection)propertyValue;\r
- if (collection.wasInitialized()){\r
- storeInitializedCollection(collection, node, param);\r
- }else{\r
-// Class<?> parentClass = parentBean.getClass();\r
-// int parentId = ((CdmBase)parentBean).getId();\r
- if (sibling != null){\r
- sibling.putLazyCollection(collection);\r
- }else{\r
- node.putLazyCollection(collection);\r
- }\r
- }\r
- }\r
- }else{\r
- //singles\r
- if (!node.hasWildcardToOneSibling()){ //if wildcard exists the lazies are already prepared there\r
- if (! Hibernate.isInitialized(propertyValue)){\r
- if (propertyValue instanceof HibernateProxy){\r
- Serializable id = ((HibernateProxy)propertyValue).getHibernateLazyInitializer().getIdentifier();\r
- Class<?> persistedClass = ((HibernateProxy)propertyValue).getHibernateLazyInitializer().getPersistentClass();\r
- if (sibling != null){\r
- sibling.putLazyBean(persistedClass, id);\r
- }else{\r
- node.putLazyBean(persistedClass, id);\r
- }\r
-\r
- }else{\r
- logger.warn("Lazy value is not of type HibernateProxy. This is not yet handled.");\r
- }\r
- }else if (propertyValue == null){\r
- // do nothing\r
- }else{\r
- if (propertyValue instanceof HibernateProxy){ //TODO remove hibernate dependency\r
- propertyValue = initializeInstance(propertyValue);\r
- }\r
- autoinitializeBean(propertyValue);\r
- node.addBean(propertyValue);\r
- }\r
- }\r
- }\r
- }\r
-\r
- private void autoinitializeBean(Object bean) {\r
- invokePropertyAutoInitializers(bean);\r
- }\r
-\r
- private void autoinitializeBean(CdmBase bean, AutoInit autoInit) {\r
- for(AutoPropertyInitializer<CdmBase> init : autoInit.initlializers) {\r
- init.initialize(bean);\r
- }\r
- }\r
-\r
- private void storeInitializedCollection(AbstractPersistentCollection persistedCollection,\r
- BeanInitNode node, String param) {\r
- Collection<?> collection;\r
-\r
- if (persistedCollection instanceof Collection) {\r
- collection = (Collection<?>) persistedCollection;\r
- }else if (persistedCollection instanceof Map) {\r
- collection = ((Map<?,?>)persistedCollection).values();\r
- }else{\r
- throw new RuntimeException ("Non Map and non Collection cas not handled in storeInitializedCollection()");\r
- }\r
- for (Object value : collection){\r
- preparePropertyValueForBulkLoadOrStore(node, null, param, value);\r
- }\r
- }\r
-\r
- private void bulkLoadLazies(BeanInitNode node) {\r
-\r
- if (logger.isTraceEnabled()){logger.trace("bulk load " + node);}\r
-\r
- //beans\r
- for (Class<?> clazz : node.getLazyBeans().keySet()){\r
- Set<Serializable> idSet = node.getLazyBeans().get(clazz);\r
- if (idSet != null && ! idSet.isEmpty()){\r
-\r
- if (logger.isTraceEnabled()){logger.trace("bulk load beans of class " + clazz.getSimpleName());}\r
- //TODO use entity name\r
- String hql = " SELECT c FROM %s as c %s WHERE c.id IN (:idSet) ";\r
- AutoInit autoInit = addAutoinitFetchLoading(clazz, "c");\r
- hql = String.format(hql, clazz.getSimpleName(), autoInit.leftJoinFetch);\r
- if (logger.isTraceEnabled()){logger.trace(hql);}\r
- Query query = genericDao.getHqlQuery(hql);\r
- query.setParameterList("idSet", idSet);\r
- List<Object> list = query.list();\r
-\r
- if (logger.isTraceEnabled()){logger.trace("initialize bulk loaded beans of class " + clazz.getSimpleName());}\r
- for (Object object : list){\r
- if (object instanceof HibernateProxy){ //TODO remove hibernate dependency\r
- object = initializeInstance(object);\r
- }\r
- autoinitializeBean((CdmBase)object, autoInit);\r
- node.addBean(object);\r
- }\r
- if (logger.isTraceEnabled()){logger.trace("bulk load - DONE");}\r
- }\r
- }\r
- node.resetLazyBeans();\r
-\r
- //collections\r
- for (Class<?> ownerClazz : node.getLazyCollections().keySet()){\r
- Map<String, Set<Serializable>> lazyParams = node.getLazyCollections().get(ownerClazz);\r
- for (String param : lazyParams.keySet()){\r
- Set<Serializable> idSet = lazyParams.get(param);\r
- if (idSet != null && ! idSet.isEmpty()){\r
- if (logger.isTraceEnabled()){logger.trace("bulk load " + node + " collections ; ownerClass=" + ownerClazz.getSimpleName() + " ; param = " + param);}\r
-\r
- Type collectionEntitiyType = null;\r
- PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(ownerClazz);\r
- for(PropertyDescriptor d : descriptors) {\r
- if(d.getName().equals(param)) {\r
- Method readMethod = d.getReadMethod();\r
- ParameterizedType pt = (ParameterizedType) readMethod.getGenericReturnType();\r
- Type[] actualTypeArguments = pt.getActualTypeArguments();\r
- if(actualTypeArguments.length == 2) {\r
- // this must be a map of <Language, String> (aka LanguageString) there is no other case like this in the cdm\r
- // in case of Maps the returned Collection will be the Collection of the values, so collectionEntitiyType is the\r
- // second typeArgument\r
- collectionEntitiyType = actualTypeArguments[1];\r
- } else {\r
- collectionEntitiyType = actualTypeArguments[0];\r
- }\r
- if(collectionEntitiyType instanceof TypeVariable) {\r
- collectionEntitiyType = ((TypeVariable)collectionEntitiyType).getBounds()[0];\r
- }\r
- }\r
- }\r
-\r
- //TODO use entity name ??\r
- //get from repository\r
- List<Object[]> list;\r
- String hql = "SELECT oc " +\r
- " FROM %s as oc LEFT JOIN FETCH oc.%s as col %s" +\r
- " WHERE oc.id IN (:idSet) ";\r
-\r
- AutoInit autoInit = addAutoinitFetchLoading((Class)collectionEntitiyType, "col");\r
- hql = String.format(hql, ownerClazz.getSimpleName(), param,\r
- autoInit.leftJoinFetch);\r
-\r
- try {\r
- if (logger.isTraceEnabled()){logger.trace(hql);}\r
- Query query = genericDao.getHqlQuery(hql);\r
- query.setParameterList("idSet", idSet);\r
- list = query.list();\r
- if (logger.isTraceEnabled()){logger.trace("size of retrieved list is " + list.size());}\r
- } catch (HibernateException e) {\r
- e.printStackTrace();\r
- throw e;\r
- }\r
-\r
- //getTarget and add to child node\r
- if (logger.isTraceEnabled()){logger.trace("initialize bulk loaded " + node + " collections - DONE");}\r
- for (Object parentBean : list){\r
- try {\r
- Object propValue = PropertyUtils.getProperty(\r
- parentBean,\r
- mapFieldToPropertyName(param, parentBean.getClass().getSimpleName())\r
- );\r
-\r
- if (propValue == null){\r
- logger.trace("Collection is null");\r
- }else {\r
- if(propValue instanceof PersistentMap) {\r
- propValue = ((PersistentMap)propValue).values();\r
- }\r
- for(Object newBean : (Collection<Object>)propValue ) {\r
- if(newBean instanceof HibernateProxy){\r
- newBean = initializeInstance(newBean);\r
- }\r
-\r
- autoinitializeBean((CdmBase)newBean, autoInit);\r
-\r
- node.addBean(newBean);\r
- }\r
- }\r
- } catch (Exception e) {\r
- // TODO better throw an exception ?\r
- logger.error("error while getting collection property", e);\r
- }\r
- }\r
- if (logger.isTraceEnabled()){logger.trace("bulk load " + node + " collections - DONE");}\r
- }\r
- }\r
- }\r
- for (AbstractPersistentCollection collection : node.getUninitializedCollections()){\r
- if (! collection.wasInitialized()){ //should not happen anymore\r
- collection.forceInitialization();\r
- if (logger.isTraceEnabled()){logger.trace("forceInitialization of collection " + collection);}\r
- } else {\r
- if (logger.isTraceEnabled()){logger.trace("collection " + collection + " is initialized - OK!");}\r
- }\r
- }\r
-\r
- node.resetLazyCollections();\r
-\r
- if (logger.isDebugEnabled()){logger.debug("bulk load " + node + " - DONE ");}\r
-\r
- }\r
-\r
-\r
- private AutoInit addAutoinitFetchLoading(Class<?> clazz, String beanAlias) {\r
-\r
- AutoInit autoInit = new AutoInit();\r
- if(clazz != null) {\r
- Set<AutoPropertyInitializer<CdmBase>> inits = getAutoInitializers(clazz);\r
- for (AutoPropertyInitializer<CdmBase> init: inits){\r
- try {\r
- autoInit.leftJoinFetch +=init.hibernateFetchJoin(clazz, beanAlias);\r
- } catch (Exception e) {\r
- // the AutoPropertyInitializer is not supporting LEFT JOIN FETCH so it needs to be\r
- // used explicitly\r
- autoInit.initlializers.add(init);\r
- }\r
-\r
- }\r
- }\r
- return autoInit;\r
- }\r
-\r
- private Set<AutoPropertyInitializer<CdmBase>> getAutoInitializers(Class<?> clazz) {\r
- Set<AutoPropertyInitializer<CdmBase>> result = new HashSet<AutoPropertyInitializer<CdmBase>>();\r
- for(Class<? extends CdmBase> superClass : getBeanAutoInitializers().keySet()){\r
- if(superClass.isAssignableFrom(clazz)){\r
- result.add(getBeanAutoInitializers().get(superClass));\r
- }\r
- }\r
- return result;\r
- }\r
-\r
- /**\r
- * Rename hibernate (field) attribute to Bean property name, due to bean inconsistencies\r
- * #3841\r
- * @param param\r
- * @param ownerClass\r
- * @return\r
- */\r
- private String mapFieldToPropertyName(String param, String ownerClass) {\r
- if (ownerClass.contains("Description") && param.equals("descriptionElements")){\r
- return "elements";\r
- }\r
- if (ownerClass.startsWith("FeatureNode") && param.equals("children")) {\r
- return "childNodes";\r
- }\r
- if (ownerClass.startsWith("Media") && param.equals("description")) {\r
- return "allDescriptions";\r
- }\r
- else{\r
- return param;\r
- }\r
- }\r
-\r
- /**\r
- * @param node\r
- * @param property\r
- * @param index\r
- * @param bean\r
- * @param unwrappedPropertyBean\r
- */\r
- private void initializeNodeSinglePropertyOld(BeanInitNode node, String property,\r
- Integer index, Object bean, Object unwrappedPropertyBean) {\r
- Collection<?> collection = null;\r
- if(Map.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {\r
- collection = ((Map<?,?>)unwrappedPropertyBean).values();\r
- }else if (Collection.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {\r
- collection = (Collection<?>) unwrappedPropertyBean;\r
- }\r
- if (collection != null){\r
- //collection or map\r
- if (logger.isTraceEnabled()){logger.trace(" initialize collection for " + node.toStringNoWildcard() + " ... ");}\r
- int i = 0;\r
- for (Object entrybean : collection) {\r
- if(index == null){\r
- node.addBean(entrybean);\r
- } else if(index.equals(i)){\r
- node.addBean(entrybean);\r
- break;\r
- }\r
- i++;\r
- }\r
- if (logger.isTraceEnabled()){logger.trace(" initialize collection for " + node.toString() + " - DONE ");}\r
-\r
- }else {\r
- // nested bean\r
- node.addBean(unwrappedPropertyBean);\r
- setProperty(bean, property, unwrappedPropertyBean);\r
- }\r
- }\r
-\r
- private class AutoInit{\r
-\r
- String leftJoinFetch = "";\r
- Set<AutoPropertyInitializer<CdmBase>> initlializers = new HashSet<AutoPropertyInitializer<CdmBase>>();\r
-\r
- /**\r
- * @param leftJoinFetch\r
- * @param initlializers\r
- */\r
- public AutoInit() {\r
- }\r
- }\r
-\r
-}\r
+/**
+ *
+ */
+package eu.etaxonomy.cdm.persistence.dao.initializer;
+
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.log4j.Logger;
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.Query;
+import org.hibernate.collection.internal.AbstractPersistentCollection;
+import org.hibernate.collection.internal.PersistentMap;
+import org.hibernate.proxy.HibernateProxy;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import eu.etaxonomy.cdm.model.common.CdmBase;
+import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
+import eu.etaxonomy.cdm.persistence.dao.hibernate.HibernateBeanInitializer;
+
+/**
+ * For now this is a test if we can improve performance for bean initializing
+ * @author a.mueller
+ * @date 2013-10-25
+ *
+ */
+public class AdvancedBeanInitializer extends HibernateBeanInitializer {
+
+ public static final Logger logger = Logger.getLogger(AdvancedBeanInitializer.class);
+
+ @Autowired
+ ICdmGenericDao genericDao;
+
+ @Override
+ public void initialize(Object bean, List<String> propertyPaths) {
+ List<Object> beanList = new ArrayList<Object>(1);
+ beanList.add(bean);
+ initializeAll(beanList, propertyPaths);
+ }
+
+ //TODO optimize algorithm ..
+ @Override
+ public <C extends Collection<?>> C initializeAll(C beanList, List<String> propertyPaths) {
+
+ if (beanList == null || beanList.isEmpty()){
+ return beanList;
+ }
+
+ //autoinitialize
+ for (Object bean : beanList){
+ autoinitializeBean(bean);
+ }
+
+ if(propertyPaths == null){
+ return beanList;
+ }
+
+
+ //new
+ BeanInitNode rootPath = BeanInitNode.createInitTree(propertyPaths);
+ if (logger.isTraceEnabled()){logger.trace(rootPath.toStringTree());}
+
+
+ if(logger.isDebugEnabled()){ logger.debug(">> starting to initialize beanlist ; class(e.g.):" + beanList.iterator().next().getClass().getSimpleName());}
+ rootPath.addBeans(beanList);
+ initializeNodeRecursive(rootPath);
+
+
+ //old - keep for safety (this may help to initialize those beans that are not yet correctly initialized by the AdvancedBeanInitializer
+ if(logger.isTraceEnabled()){logger.trace("Start old initalizer ... ");};
+ for (Object bean :beanList){
+ Collections.sort(propertyPaths);
+ for(String propPath : propertyPaths){
+// initializePropertyPath(bean, propPath);
+ }
+ }
+
+ if(logger.isDebugEnabled()){ logger.debug(" Completed initialization of beanlist "); }
+ return beanList;
+
+ }
+
+
+ //new
+ private void initializeNodeRecursive(BeanInitNode rootPath) {
+ initializeNode(rootPath);
+ for (BeanInitNode childPath : rootPath.getChildrenList()){
+ initializeNodeRecursive(childPath);
+ }
+ rootPath.resetBeans();
+ }
+
+ /**
+ * Initializes the given single <code>propPath</code> String.
+ *
+ * @param bean
+ * @param propPath
+ */
+ private void initializeNode(BeanInitNode node) {
+ if(logger.isDebugEnabled()){logger.debug(" processing " + node.toString());}
+ if (node.isRoot()){
+ return;
+ }else if (node.isWildcard()){
+ initializeNodeWildcard(node);
+ } else {
+ initializeNodeNoWildcard(node);
+ }
+ }
+
+ // if propPath only contains a wildcard (* or $)
+ // => do a batch initialization of *toOne or *toMany relations
+ private void initializeNodeWildcard(BeanInitNode node) {
+// boolean initToMany = node.isToManyWildcard();
+ Map<Class<?>, Set<Object>> parentBeans = node.getParentBeans();
+ for (Class<?> clazz : parentBeans.keySet()){
+ //new
+ for (Object bean : parentBeans.get(clazz)){
+
+ if(Collection.class.isAssignableFrom(bean.getClass())){
+// old: initializeAllEntries((Collection<?>)bean, true, initToMany); //TODO is this a possible case at all??
+ throw new RuntimeException("Collection no longer expected in 'initializeNodeWildcard()'. Therefore an exception is thrown.");
+ } else if(Map.class.isAssignableFrom(bean.getClass())) {
+// old: initializeAllEntries(((Map<?,?>)bean).values(), true, initToMany); ////TODO is this a possible case at all??
+ throw new RuntimeException("Map no longer expected in 'initializeNodeWildcard()'. Therefore an exception is thrown.");
+ } else{
+ prepareBeanWildcardForBulkLoad(node, bean);
+ }
+ }
+ //end new
+
+// initializeNodeWildcardOld(initToMany, beans, clazz); //if switched on move bulkLoadLazies up
+ }
+
+ //
+ bulkLoadLazies(node);
+ }
+
+ /**
+ * @param initToMany
+ * @param beans
+ * @param clazz
+ */
+ private void initializeNodeWildcardOld(boolean initToMany,
+ Map<Class<?>, Set<Object>> beans, Class<?> clazz) {
+ for (Object bean : beans.get(clazz)){
+
+ if(Collection.class.isAssignableFrom(bean.getClass())){
+ initializeAllEntries((Collection<?>)bean, true, initToMany);
+ } else if(Map.class.isAssignableFrom(bean.getClass())) {
+ initializeAllEntries(((Map<?,?>)bean).values(), true, initToMany);
+ } else{
+ initializeBean(bean, true, initToMany);
+ }
+ }
+ }
+
+ private void prepareBeanWildcardForBulkLoad(BeanInitNode node, Object bean){
+
+ if(logger.isTraceEnabled()){logger.trace(">> prepare bulk wildcard initialization of a bean of type " + bean.getClass().getSimpleName()); }
+ Set<Class<?>> restrictions = new HashSet<Class<?>>();
+ restrictions.add(CdmBase.class);
+ if(node.isToManyWildcard()){
+ restrictions.add(Collection.class);
+ }
+ Set<PropertyDescriptor> props = getProperties(bean, restrictions);
+ for(PropertyDescriptor propertyDescriptor : props){
+ try {
+ String property = propertyDescriptor.getName();
+
+// invokeInitialization(bean, propertyDescriptor);
+ Object propertyValue = PropertyUtils.getProperty( bean, property);
+
+ preparePropertyValueForBulkLoadOrStore(node, bean, property, propertyValue );
+
+ } catch (IllegalAccessException e) {
+ logger.error("Illegal access on property " + propertyDescriptor.getName());
+ } catch (InvocationTargetException e) {
+ logger.info("Cannot invoke property " + propertyDescriptor.getName() + " not found");
+ } catch (NoSuchMethodException e) {
+ logger.info("Property " + propertyDescriptor.getName() + " not found");
+ }
+ }
+ if(logger.isTraceEnabled()){logger.trace(" completed bulk wildcard initialization of a bean");}
+ }
+
+
+
+ // propPath contains either a single field or a nested path
+ // split next path token off and keep the remaining as nestedPath
+ private void initializeNodeNoWildcard(BeanInitNode node) {
+
+ String property = node.getPath();
+ int pos;
+
+ // is the property indexed?
+ Integer index = null;
+ if((pos = property.indexOf('[')) > 0){
+ String indexString = property.substring(pos + 1, property.indexOf(']'));
+ index = Integer.valueOf(indexString);
+ property = property.substring(0, pos);
+ }
+
+ //Class targetClass = HibernateProxyHelper.getClassWithoutInitializingProxy(bean); // used for debugging
+
+ for (Class<?> parentClazz : node.getParentBeans().keySet()){
+ if (logger.isTraceEnabled()){logger.trace(" invoke initialization on "+ node.toString()+ " beans of class " + parentClazz.getSimpleName() + " ... ");}
+
+ Set<Object> parentBeans = node.getParentBeans().get(parentClazz);
+
+ if (index != null){
+ logger.warn("Property path index not yet implemented for 'new'");
+ }
+ //new
+ for (Object parentBean : parentBeans){
+ String propertyName = mapFieldToPropertyName(property, parentBean.getClass().getSimpleName());
+ try{
+ Object propertyValue = PropertyUtils.getProperty(parentBean, propertyName);
+ preparePropertyValueForBulkLoadOrStore(node, parentBean, property, propertyValue);
+ } catch (IllegalAccessException e) {
+ String message = "Illegal access on property " + property;
+ logger.error(message);
+ throw new RuntimeException(message, e);
+ } catch (InvocationTargetException e) {
+ String message = "Cannot invoke property " + property + " not found";
+ logger.error(message);
+ throw new RuntimeException(message, e);
+ } catch (NoSuchMethodException e) {
+ String message = "Property " + propertyName + " not found for class " + parentClazz;
+ logger.error(message);
+ throw new RuntimeException(message, e);
+ }
+ }
+
+ //end new
+
+// initializeNodeNoWildcardOld(node, property, index, parentBeans); //move bulkLoadLazies up again, if uncomment this line
+ }
+ bulkLoadLazies(node);
+
+ }
+
+ /**
+ * @param node
+ * @param property
+ * @param index
+ * @param parentBeans
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @throws NoSuchMethodException
+ */
+ private void initializeNodeNoWildcardOld(BeanInitNode node,
+ String property, Integer index, Set<Object> parentBeans)
+ throws IllegalAccessException, InvocationTargetException,
+ NoSuchMethodException {
+ for (Object bean : parentBeans){
+
+ PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(bean, property);
+ if (logger.isTraceEnabled()){logger.trace(" unwrap " + node.toStringNoWildcard() + " ... ");}
+ // [1] initialize the bean named by property
+ Object unwrappedPropertyBean = invokeInitialization(bean, propertyDescriptor);
+ if (logger.isTraceEnabled()){logger.trace(" unwrap " + node.toStringNoWildcard() + " - DONE ");}
+
+
+ // [2]
+ // handle property
+ if(unwrappedPropertyBean != null ){
+ initializeNodeSinglePropertyOld(node, property, index, bean, unwrappedPropertyBean);
+ }
+ }
+ }
+
+ /**
+ * @param node
+ * @param propertyValue
+ * @param parentBean
+ * @param param
+ */
+ private void preparePropertyValueForBulkLoadOrStore(BeanInitNode node, Object parentBean, String param, Object propertyValue) {
+ BeanInitNode sibling = node.getSibling(param);
+
+ if (propertyValue instanceof AbstractPersistentCollection ){
+ //collections
+ if (!node.hasWildcardToManySibling()){ //if wildcard sibling exists the lazies are already prepared there
+ AbstractPersistentCollection collection = (AbstractPersistentCollection)propertyValue;
+ if (collection.wasInitialized()){
+ storeInitializedCollection(collection, node, param);
+ }else{
+// Class<?> parentClass = parentBean.getClass();
+// int parentId = ((CdmBase)parentBean).getId();
+ if (sibling != null){
+ sibling.putLazyCollection(collection);
+ }else{
+ node.putLazyCollection(collection);
+ }
+ }
+ }
+ }else{
+ //singles
+ if (!node.hasWildcardToOneSibling()){ //if wildcard exists the lazies are already prepared there
+ if (! Hibernate.isInitialized(propertyValue)){
+ if (propertyValue instanceof HibernateProxy){
+ Serializable id = ((HibernateProxy)propertyValue).getHibernateLazyInitializer().getIdentifier();
+ Class<?> persistedClass = ((HibernateProxy)propertyValue).getHibernateLazyInitializer().getPersistentClass();
+ if (sibling != null){
+ sibling.putLazyBean(persistedClass, id);
+ }else{
+ node.putLazyBean(persistedClass, id);
+ }
+
+ }else{
+ logger.warn("Lazy value is not of type HibernateProxy. This is not yet handled.");
+ }
+ }else if (propertyValue == null){
+ // do nothing
+ }else{
+ if (propertyValue instanceof HibernateProxy){ //TODO remove hibernate dependency
+ propertyValue = initializeInstance(propertyValue);
+ }
+ autoinitializeBean(propertyValue);
+ node.addBean(propertyValue);
+ }
+ }
+ }
+ }
+
+ private void autoinitializeBean(Object bean) {
+ invokePropertyAutoInitializers(bean);
+ }
+
+ private void autoinitializeBean(CdmBase bean, AutoInit autoInit) {
+ for(AutoPropertyInitializer<CdmBase> init : autoInit.initlializers) {
+ init.initialize(bean);
+ }
+ }
+
+ private void storeInitializedCollection(AbstractPersistentCollection persistedCollection,
+ BeanInitNode node, String param) {
+ Collection<?> collection;
+
+ if (persistedCollection instanceof Collection) {
+ collection = (Collection<?>) persistedCollection;
+ }else if (persistedCollection instanceof Map) {
+ collection = ((Map<?,?>)persistedCollection).values();
+ }else{
+ throw new RuntimeException ("Non Map and non Collection cas not handled in storeInitializedCollection()");
+ }
+ for (Object value : collection){
+ preparePropertyValueForBulkLoadOrStore(node, null, param, value);
+ }
+ }
+
+ private void bulkLoadLazies(BeanInitNode node) {
+
+ if (logger.isTraceEnabled()){logger.trace("bulk load " + node);}
+
+ //beans
+ for (Class<?> clazz : node.getLazyBeans().keySet()){
+ Set<Serializable> idSet = node.getLazyBeans().get(clazz);
+ if (idSet != null && ! idSet.isEmpty()){
+
+ if (logger.isTraceEnabled()){logger.trace("bulk load beans of class " + clazz.getSimpleName());}
+ //TODO use entity name
+ String hql = " SELECT c FROM %s as c %s WHERE c.id IN (:idSet) ";
+ AutoInit autoInit = addAutoinitFetchLoading(clazz, "c");
+ hql = String.format(hql, clazz.getSimpleName(), autoInit.leftJoinFetch);
+ if (logger.isTraceEnabled()){logger.trace(hql);}
+ Query query = genericDao.getHqlQuery(hql);
+ query.setParameterList("idSet", idSet);
+ List<Object> list = query.list();
+
+ if (logger.isTraceEnabled()){logger.trace("initialize bulk loaded beans of class " + clazz.getSimpleName());}
+ for (Object object : list){
+ if (object instanceof HibernateProxy){ //TODO remove hibernate dependency
+ object = initializeInstance(object);
+ }
+ autoinitializeBean((CdmBase)object, autoInit);
+ node.addBean(object);
+ }
+ if (logger.isTraceEnabled()){logger.trace("bulk load - DONE");}
+ }
+ }
+ node.resetLazyBeans();
+
+ //collections
+ for (Class<?> ownerClazz : node.getLazyCollections().keySet()){
+ Map<String, Set<Serializable>> lazyParams = node.getLazyCollections().get(ownerClazz);
+ for (String param : lazyParams.keySet()){
+ Set<Serializable> idSet = lazyParams.get(param);
+ if (idSet != null && ! idSet.isEmpty()){
+ if (logger.isTraceEnabled()){logger.trace("bulk load " + node + " collections ; ownerClass=" + ownerClazz.getSimpleName() + " ; param = " + param);}
+
+ Type collectionEntitiyType = null;
+ PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(ownerClazz);
+ for(PropertyDescriptor d : descriptors) {
+ if(d.getName().equals(param)) {
+ Method readMethod = d.getReadMethod();
+ ParameterizedType pt = (ParameterizedType) readMethod.getGenericReturnType();
+ Type[] actualTypeArguments = pt.getActualTypeArguments();
+ if(actualTypeArguments.length == 2) {
+ // this must be a map of <Language, String> (aka LanguageString) there is no other case like this in the cdm
+ // in case of Maps the returned Collection will be the Collection of the values, so collectionEntitiyType is the
+ // second typeArgument
+ collectionEntitiyType = actualTypeArguments[1];
+ } else {
+ collectionEntitiyType = actualTypeArguments[0];
+ }
+ if(collectionEntitiyType instanceof TypeVariable) {
+ collectionEntitiyType = ((TypeVariable)collectionEntitiyType).getBounds()[0];
+ }
+ }
+ }
+
+ //TODO use entity name ??
+ //get from repository
+ List<Object[]> list;
+ String hql = "SELECT oc " +
+ " FROM %s as oc LEFT JOIN FETCH oc.%s as col %s" +
+ " WHERE oc.id IN (:idSet) ";
+
+ AutoInit autoInit = addAutoinitFetchLoading((Class)collectionEntitiyType, "col");
+ hql = String.format(hql, ownerClazz.getSimpleName(), param,
+ autoInit.leftJoinFetch);
+
+ try {
+ if (logger.isTraceEnabled()){logger.trace(hql);}
+ Query query = genericDao.getHqlQuery(hql);
+ query.setParameterList("idSet", idSet);
+ list = query.list();
+ if (logger.isTraceEnabled()){logger.trace("size of retrieved list is " + list.size());}
+ } catch (HibernateException e) {
+ e.printStackTrace();
+ throw e;
+ }
+
+ //getTarget and add to child node
+ if (logger.isTraceEnabled()){logger.trace("initialize bulk loaded " + node + " collections - DONE");}
+ for (Object parentBean : list){
+ try {
+ Object propValue = PropertyUtils.getProperty(
+ parentBean,
+ mapFieldToPropertyName(param, parentBean.getClass().getSimpleName())
+ );
+
+ if (propValue == null){
+ logger.trace("Collection is null");
+ }else {
+ if(propValue instanceof PersistentMap) {
+ propValue = ((PersistentMap)propValue).values();
+ }
+ for(Object newBean : (Collection<Object>)propValue ) {
+ if(newBean instanceof HibernateProxy){
+ newBean = initializeInstance(newBean);
+ }
+
+ autoinitializeBean((CdmBase)newBean, autoInit);
+
+ node.addBean(newBean);
+ }
+ }
+ } catch (Exception e) {
+ // TODO better throw an exception ?
+ logger.error("error while getting collection property", e);
+ }
+ }
+ if (logger.isTraceEnabled()){logger.trace("bulk load " + node + " collections - DONE");}
+ }
+ }
+ }
+ for (AbstractPersistentCollection collection : node.getUninitializedCollections()){
+ if (! collection.wasInitialized()){ //should not happen anymore
+ collection.forceInitialization();
+ if (logger.isTraceEnabled()){logger.trace("forceInitialization of collection " + collection);}
+ } else {
+ if (logger.isTraceEnabled()){logger.trace("collection " + collection + " is initialized - OK!");}
+ }
+ }
+
+ node.resetLazyCollections();
+
+ if (logger.isDebugEnabled()){logger.debug("bulk load " + node + " - DONE ");}
+
+ }
+
+
+ private AutoInit addAutoinitFetchLoading(Class<?> clazz, String beanAlias) {
+
+ AutoInit autoInit = new AutoInit();
+ if(clazz != null) {
+ Set<AutoPropertyInitializer<CdmBase>> inits = getAutoInitializers(clazz);
+ for (AutoPropertyInitializer<CdmBase> init: inits){
+ try {
+ autoInit.leftJoinFetch +=init.hibernateFetchJoin(clazz, beanAlias);
+ } catch (Exception e) {
+ // the AutoPropertyInitializer is not supporting LEFT JOIN FETCH so it needs to be
+ // used explicitly
+ autoInit.initlializers.add(init);
+ }
+
+ }
+ }
+ return autoInit;
+ }
+
+ private Set<AutoPropertyInitializer<CdmBase>> getAutoInitializers(Class<?> clazz) {
+ Set<AutoPropertyInitializer<CdmBase>> result = new HashSet<AutoPropertyInitializer<CdmBase>>();
+ for(Class<? extends CdmBase> superClass : getBeanAutoInitializers().keySet()){
+ if(superClass.isAssignableFrom(clazz)){
+ result.add(getBeanAutoInitializers().get(superClass));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Rename hibernate (field) attribute to Bean property name, due to bean inconsistencies
+ * #3841
+ * @param param
+ * @param ownerClass
+ * @return
+ */
+ private String mapFieldToPropertyName(String param, String ownerClass) {
+ if (ownerClass.contains("Description") && param.equals("descriptionElements")){
+ return "elements";
+ }
+ if (ownerClass.startsWith("FeatureNode") && param.equals("children")) {
+ return "childNodes";
+ }
+ if (ownerClass.startsWith("Media") && param.equals("description")) {
+ return "allDescriptions";
+ }
+ else{
+ return param;
+ }
+ }
+
+ /**
+ * @param node
+ * @param property
+ * @param index
+ * @param bean
+ * @param unwrappedPropertyBean
+ */
+ private void initializeNodeSinglePropertyOld(BeanInitNode node, String property,
+ Integer index, Object bean, Object unwrappedPropertyBean) {
+ Collection<?> collection = null;
+ if(Map.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {
+ collection = ((Map<?,?>)unwrappedPropertyBean).values();
+ }else if (Collection.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {
+ collection = (Collection<?>) unwrappedPropertyBean;
+ }
+ if (collection != null){
+ //collection or map
+ if (logger.isTraceEnabled()){logger.trace(" initialize collection for " + node.toStringNoWildcard() + " ... ");}
+ int i = 0;
+ for (Object entrybean : collection) {
+ if(index == null){
+ node.addBean(entrybean);
+ } else if(index.equals(i)){
+ node.addBean(entrybean);
+ break;
+ }
+ i++;
+ }
+ if (logger.isTraceEnabled()){logger.trace(" initialize collection for " + node.toString() + " - DONE ");}
+
+ }else {
+ // nested bean
+ node.addBean(unwrappedPropertyBean);
+ setProperty(bean, property, unwrappedPropertyBean);
+ }
+ }
+
+ private class AutoInit{
+
+ String leftJoinFetch = "";
+ Set<AutoPropertyInitializer<CdmBase>> initlializers = new HashSet<AutoPropertyInitializer<CdmBase>>();
+
+ /**
+ * @param leftJoinFetch
+ * @param initlializers
+ */
+ public AutoInit() {
+ }
+ }
+}