some updates to advanced bean initialization
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / dao / initializer / AdvancedBeanInitializer.java
1 /**
2 *
3 */
4 package eu.etaxonomy.cdm.persistence.dao.initializer;
5
6 import java.beans.PropertyDescriptor;
7 import java.io.Serializable;
8 import java.lang.reflect.InvocationTargetException;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.Collections;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16
17 import javax.persistence.Transient;
18
19 import org.apache.commons.beanutils.PropertyUtils;
20 import org.apache.log4j.Logger;
21 import org.hibernate.Hibernate;
22 import org.hibernate.HibernateException;
23 import org.hibernate.Query;
24 import org.hibernate.collection.internal.AbstractPersistentCollection;
25 import org.hibernate.proxy.HibernateProxy;
26 import org.springframework.beans.factory.annotation.Autowired;
27
28 import eu.etaxonomy.cdm.model.common.CdmBase;
29 import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
30 import eu.etaxonomy.cdm.persistence.dao.hibernate.HibernateBeanInitializer;
31
32 /**
33 * For now this is a test if we can improve performance for bean initializing
34 * @author a.mueller
35 * @date 2013-10-25
36 *
37 */
38 public class AdvancedBeanInitializer extends HibernateBeanInitializer {
39
40 public static final Logger logger = Logger.getLogger(AdvancedBeanInitializer.class);
41
42 @Autowired
43 ICdmGenericDao genericDao;
44
45 @Override
46 public void initialize(Object bean, List<String> propertyPaths) {
47 List<Object> beanList = new ArrayList<Object>(1);
48 beanList.add(bean);
49 initializeAll(beanList, propertyPaths);
50 }
51
52 //TODO optimize algorithm ..
53 @Override
54 public <C extends Collection<?>> C initializeAll(C beanList, List<String> propertyPaths) {
55
56 if (beanList == null || beanList.isEmpty()){
57 return beanList;
58 }
59
60 //TODO new required?
61 // invokePropertyAutoInitializers(bean);
62
63 if(propertyPaths == null){ //TODO if AutoInitializer is not requiredfor top level bean, this can be merged with previous "if"
64 return beanList;
65 }
66
67
68 //new
69 BeanInitNode rootPath = BeanInitNode.createInitTree(propertyPaths);
70 System.out.println(rootPath.toStringTree());
71
72
73 if(logger.isDebugEnabled()){ logger.debug(">> starting to initialize beanlist ; class(e.g.):" + beanList.iterator().next().getClass().getSimpleName());}
74 rootPath.addBeans(beanList);
75 initializeNodeRecursive(rootPath);
76
77
78 //old - keep for safety (this may help to initialize those beans that are not yet correctly initialized by the AdvancedBeanInitializer
79 if(logger.isDebugEnabled()){logger.debug("Start old initalizer ... ");};
80 for (Object bean :beanList){
81 Collections.sort(propertyPaths);
82 for(String propPath : propertyPaths){
83 // initializePropertyPath(bean, propPath);
84 }
85 }
86
87 if(logger.isDebugEnabled()){ logger.debug(" Completed initialization of beanlist "); }
88 return beanList;
89
90 }
91
92
93 //new
94 private void initializeNodeRecursive(BeanInitNode rootPath) {
95 initializeNode(rootPath);
96 for (BeanInitNode childPath : rootPath.getChildrenList()){
97 initializeNodeRecursive(childPath);
98 }
99 rootPath.resetBeans();
100 }
101
102 /**
103 * Initializes the given single <code>propPath</code> String.
104 *
105 * @param bean
106 * @param propPath
107 */
108 private void initializeNode(BeanInitNode node) {
109 if(logger.isDebugEnabled()){logger.debug(" processing " + node.toString());}
110 if (node.isRoot()){
111 return;
112 }else if (node.isWildcard()){
113 initializeNodeWildcard(node);
114 } else {
115 initializeNodeNoWildcard(node);
116 }
117 }
118
119 // if propPath only contains a wildcard (* or $)
120 // => do a batch initialization of *toOne or *toMany relations
121 private void initializeNodeWildcard(BeanInitNode node) {
122 // boolean initToMany = node.isToManyWildcard();
123 Map<Class<?>, Set<Object>> parentBeans = node.getParentBeans();
124 for (Class<?> clazz : parentBeans.keySet()){
125 //new
126 for (Object bean : parentBeans.get(clazz)){
127
128 if(Collection.class.isAssignableFrom(bean.getClass())){
129 // old: initializeAllEntries((Collection<?>)bean, true, initToMany); //TODO is this a possible case at all??
130 throw new RuntimeException("Collection no longer expected in 'initializeNodeWildcard()'. Therefore an exception is thrown.");
131 } else if(Map.class.isAssignableFrom(bean.getClass())) {
132 // old: initializeAllEntries(((Map<?,?>)bean).values(), true, initToMany); ////TODO is this a possible case at all??
133 throw new RuntimeException("Map no longer expected in 'initializeNodeWildcard()'. Therefore an exception is thrown.");
134 } else{
135 prepareBeanWildcardForBulkLoad(node, bean);
136 }
137 }
138 //end new
139
140 // initializeNodeWildcardOld(initToMany, beans, clazz); //if switched on move bulkLoadLazies up
141 }
142
143 //
144 bulkLoadLazies(node);
145 }
146
147 /**
148 * @param initToMany
149 * @param beans
150 * @param clazz
151 */
152 private void initializeNodeWildcardOld(boolean initToMany,
153 Map<Class<?>, Set<Object>> beans, Class<?> clazz) {
154 for (Object bean : beans.get(clazz)){
155
156 if(Collection.class.isAssignableFrom(bean.getClass())){
157 initializeAllEntries((Collection<?>)bean, true, initToMany);
158 } else if(Map.class.isAssignableFrom(bean.getClass())) {
159 initializeAllEntries(((Map<?,?>)bean).values(), true, initToMany);
160 } else{
161 initializeBean(bean, true, initToMany);
162 }
163 }
164 }
165
166 private void prepareBeanWildcardForBulkLoad(BeanInitNode node, Object bean){
167
168 if(logger.isDebugEnabled()){logger.debug(">> prepare bulk wildcard initialization of a bean of type " + bean.getClass().getSimpleName()); }
169 Set<Class<?>> restrictions = new HashSet<Class<?>>();
170 restrictions.add(CdmBase.class);
171 if(node.isToManyWildcard()){
172 restrictions.add(Collection.class);
173 }
174 Set<PropertyDescriptor> props = getProperties(bean, restrictions);
175 for(PropertyDescriptor propertyDescriptor : props){
176 try {
177 String property = propertyDescriptor.getName();
178
179 // invokeInitialization(bean, propertyDescriptor);
180 Object propertyValue = PropertyUtils.getProperty( bean, property);
181
182 preparePropertyValueForBulkLoadOrStore(node, bean, property, propertyValue );
183
184 } catch (IllegalAccessException e) {
185 logger.error("Illegal access on property " + propertyDescriptor.getName());
186 } catch (InvocationTargetException e) {
187 logger.info("Cannot invoke property " + propertyDescriptor.getName() + " not found");
188 } catch (NoSuchMethodException e) {
189 logger.info("Property " + propertyDescriptor.getName() + " not found");
190 }
191 }
192 if(logger.isDebugEnabled()){logger.debug(" completed bulk wildcard initialization of a bean");}
193 }
194
195
196
197 // propPath contains either a single field or a nested path
198 // split next path token off and keep the remaining as nestedPath
199 private void initializeNodeNoWildcard(BeanInitNode node) {
200
201 String property = node.getPath();
202 int pos;
203
204 // is the property indexed?
205 Integer index = null;
206 if((pos = property.indexOf('[')) > 0){
207 String indexString = property.substring(pos + 1, property.indexOf(']'));
208 index = Integer.valueOf(indexString);
209 property = property.substring(0, pos);
210 }
211
212 try {
213 //Class targetClass = HibernateProxyHelper.getClassWithoutInitializingProxy(bean); // used for debugging
214
215 for (Class<?> parentClazz : node.getParentBeans().keySet()){
216 if (logger.isDebugEnabled()){logger.debug(" invoke initialization on "+ node.toString()+ " beans of class " + parentClazz.getSimpleName() + " ... ");}
217
218 Set<Object> parentBeans = node.getParentBeans().get(parentClazz);
219
220 if (index != null){
221 logger.warn("Property path index not yet implemented for 'new'");
222 }
223 //new
224 for (Object parentBean : parentBeans){
225 Object propertyValue = PropertyUtils.getProperty(parentBean, property);
226 preparePropertyValueForBulkLoadOrStore(node, parentBean, property, propertyValue);
227 }
228
229 //end new
230
231 // initializeNodeNoWildcardOld(node, property, index, parentBeans); //move bulkLoadLazies up again, if uncomment this line
232 }
233 bulkLoadLazies(node);
234
235 } catch (IllegalAccessException e) {
236 logger.error("Illegal access on property " + property);
237 } catch (InvocationTargetException e) {
238 logger.error("Cannot invoke property " + property + " not found");
239 } catch (NoSuchMethodException e) {
240 logger.info("Property " + property + " not found");
241 }
242 }
243
244 /**
245 * @param node
246 * @param property
247 * @param index
248 * @param parentBeans
249 * @throws IllegalAccessException
250 * @throws InvocationTargetException
251 * @throws NoSuchMethodException
252 */
253 private void initializeNodeNoWildcardOld(BeanInitNode node,
254 String property, Integer index, Set<Object> parentBeans)
255 throws IllegalAccessException, InvocationTargetException,
256 NoSuchMethodException {
257 for (Object bean : parentBeans){
258
259 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(bean, property);
260 if (logger.isDebugEnabled()){logger.debug(" unwrap " + node.toStringNoWildcard() + " ... ");}
261 // [1] initialize the bean named by property
262 Object unwrappedPropertyBean = invokeInitialization(bean, propertyDescriptor);
263 if (logger.isDebugEnabled()){logger.debug(" unwrap " + node.toStringNoWildcard() + " - DONE ");}
264
265
266 // [2]
267 // handle property
268 if(unwrappedPropertyBean != null ){
269 initializeNodeSinglePropertyOld(node, property, index, bean, unwrappedPropertyBean);
270 }
271 }
272 }
273
274 /**
275 * @param node
276 * @param propertyValue
277 * @param parentBean
278 * @param param
279 */
280 private void preparePropertyValueForBulkLoadOrStore(BeanInitNode node, Object parentBean, String param, Object propertyValue) {
281 BeanInitNode sibling = node.getSibling(param);
282
283 if (propertyValue instanceof AbstractPersistentCollection ){
284 //collections
285 if (!node.hasWildcardToManySibling()){ //if wildcard sibling exists the lazies are already prepared there
286 AbstractPersistentCollection collection = (AbstractPersistentCollection)propertyValue;
287 if (collection.wasInitialized()){
288 storeInitializedCollection(collection, node, param);
289 }else{
290 // Class<?> parentClass = parentBean.getClass();
291 // int parentId = ((CdmBase)parentBean).getId();
292 if (sibling != null){
293 sibling.putLazyCollection(collection);
294 }else{
295 node.putLazyCollection(collection);
296 }
297 }
298 }
299 }else{
300 //singles
301 if (!node.hasWildcardToOneSibling()){ //if wildcard exists the lazies are already prepared there
302 if (! Hibernate.isInitialized(propertyValue)){
303 if (propertyValue instanceof HibernateProxy){
304 Serializable id = ((HibernateProxy)propertyValue).getHibernateLazyInitializer().getIdentifier();
305 Class<?> persistedClass = ((HibernateProxy)propertyValue).getHibernateLazyInitializer().getPersistentClass();
306 if (sibling != null){
307 sibling.putLazyBean(persistedClass, id);
308 }else{
309 node.putLazyBean(persistedClass, id);
310 }
311
312 }else{
313 logger.warn("Lazy value is not of type HibernateProxy. This is not yet handled.");
314 }
315 }else if (propertyValue == null){
316 // do nothing
317 }else{
318 if (propertyValue instanceof HibernateProxy){ //TODO remove hibernate dependency
319 propertyValue = initializeInstance(propertyValue);
320 }
321 autoinitializeBean(propertyValue);
322 node.addBean(propertyValue);
323 }
324 }
325 }
326 }
327
328 private void autoinitializeBean(Object bean) {
329 invokePropertyAutoInitializers(bean);
330 }
331
332 private void storeInitializedCollection(AbstractPersistentCollection persistedCollection,
333 BeanInitNode node, String param) {
334 Collection<?> collection;
335
336 if (persistedCollection instanceof Collection) {
337 collection = (Collection<?>) persistedCollection;
338 }else if (persistedCollection instanceof Map) {
339 collection = ((Map<?,?>)persistedCollection).values();
340 }else{
341 throw new RuntimeException ("Non Map and non Collection cas not handled in storeInitializedCollection()");
342 }
343 for (Object value : collection){
344 preparePropertyValueForBulkLoadOrStore(node, null, param, value);
345 }
346 }
347
348 private void bulkLoadLazies(BeanInitNode node) {
349
350 if (logger.isDebugEnabled()){logger.debug("bulk load " + node);}
351
352 //beans
353 for (Class<?> clazz : node.getLazyBeans().keySet()){
354 Set<Serializable> idSet = node.getLazyBeans().get(clazz);
355 if (idSet != null && ! idSet.isEmpty()){
356
357 if (logger.isDebugEnabled()){logger.debug("bulk load beans of class " + clazz.getSimpleName());}
358 //TODO use entity name
359 String hql = " SELECT c FROM %s as c %s WHERE c.id IN (:idSet) ";
360 hql = String.format(hql, clazz.getSimpleName(), addAutoinitFetchLoading(clazz, "c"));
361 Query query = genericDao.getHqlQuery(hql);
362 query.setParameterList("idSet", idSet);
363 List<Object> list = query.list();
364
365 if (logger.isDebugEnabled()){logger.debug("initialize bulk loaded beans of class " + clazz.getSimpleName());}
366 for (Object object : list){
367 if (object instanceof HibernateProxy){ //TODO remove hibernate dependency
368 object = initializeInstance(object);
369 }
370 autoinitializeBean(object);
371 node.addBean(object);
372 }
373 if (logger.isDebugEnabled()){logger.debug("bulk load - DONE");}
374 }
375 }
376 node.resetLazyBeans();
377
378 //collections
379 for (Class<?> ownerClazz : node.getLazyCollections().keySet()){
380 Map<String, Set<Serializable>> lazyParams = node.getLazyCollections().get(ownerClazz);
381 for (String param : lazyParams.keySet()){
382 Set<Serializable> idSet = lazyParams.get(param);
383 if (idSet != null && ! idSet.isEmpty()){
384 if (logger.isDebugEnabled()){logger.debug("bulk load " + node + " collections ; ownerClass=" + ownerClazz.getSimpleName() + " ; param = " + param);}
385
386 //TODO use entity name ??
387 //get from repository
388 List<Object[]> list;
389 String hql = "SELECT oc, oc.%s " +
390 " FROM %s as oc INNER JOIN FETCH oc.%s as col " +
391 " WHERE oc.id IN (:idSet) ";
392
393 // String hql = "SELECT oc.%s " +
394 // " FROM %s as oc WHERE oc.id IN (:idSet) ";
395 // param = workAroundBeanInconsistency(param, ownerClazz.getSimpleName());
396 hql = String.format(hql, param, ownerClazz.getSimpleName(), param );
397
398 try {
399
400 Query query = genericDao.getHqlQuery(hql);
401 query.setParameterList("idSet", idSet);
402 list = query.list();
403 } catch (HibernateException e) {
404 e.printStackTrace();
405 throw e;
406 }
407
408 //getTarget and add to child node
409 if (logger.isDebugEnabled()){logger.debug("initialize bulk loaded " + node + " collections - DONE");}
410 for (Object[] listItems : list){
411 Object newBean = listItems[1];
412 if (newBean instanceof HibernateProxy){
413 newBean = initializeInstance(newBean);
414 }
415 autoinitializeBean(newBean);
416 node.addBean(newBean);
417 }
418 if (logger.isDebugEnabled()){logger.debug("bulk load " + node + " collections - DONE");}
419 }
420 }
421 }
422 for (AbstractPersistentCollection collection : node.getUninitializedCollections()){
423 if (! collection.wasInitialized()){ //should not happen anymore
424 collection.forceInitialization();
425 }
426 }
427
428 node.resetLazyCollections();
429
430 if (logger.isDebugEnabled()){logger.debug("bulk load " + node + " - DONE ");}
431
432 }
433
434
435 private String addAutoinitFetchLoading(Class<?> clazz, String beanAlias) {
436 Set<AutoPropertyInitializer<CdmBase>> inits = getAutoInitializers(clazz);
437 String result = "";
438 for (AutoPropertyInitializer<CdmBase> init: inits){
439 result +=init.hibernateFetchJoin(clazz, beanAlias);
440 }
441 return result;
442 }
443
444 private Set<AutoPropertyInitializer<CdmBase>> getAutoInitializers(Class<?> clazz) {
445 Set<AutoPropertyInitializer<CdmBase>> result = new HashSet<AutoPropertyInitializer<CdmBase>>();
446 for(Class<? extends CdmBase> superClass : getBeanAutoInitializers().keySet()){
447 if(superClass.isAssignableFrom(clazz)){
448 result.add(getBeanAutoInitializers().get(superClass));
449 }
450 }
451 return result;
452 }
453
454 /**
455 * Rename bean attributes to hibernate (field) attribute, due to bean inconsistencies
456 * #3841
457 * @param param
458 * @param ownerClass
459 * @return
460 */
461 private String workAroundBeanInconsistency(String param, String ownerClass) {
462 if (ownerClass.contains("Description") && param.equals("elements")){
463 //DescriptionBase.descriptionElements -> elements
464 return "descriptionElements";
465 }else if(ownerClass.equals("Classification") && param.equals("childNodes")){
466 return "rootNodes";
467 }else{
468 return param;
469 }
470 }
471
472 /**
473 * @param node
474 * @param property
475 * @param index
476 * @param bean
477 * @param unwrappedPropertyBean
478 */
479 private void initializeNodeSinglePropertyOld(BeanInitNode node, String property,
480 Integer index, Object bean, Object unwrappedPropertyBean) {
481 Collection<?> collection = null;
482 if(Map.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {
483 collection = ((Map<?,?>)unwrappedPropertyBean).values();
484 }else if (Collection.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {
485 collection = (Collection<?>) unwrappedPropertyBean;
486 }
487 if (collection != null){
488 //collection or map
489 if (logger.isDebugEnabled()){logger.debug(" initialize collection for " + node.toStringNoWildcard() + " ... ");}
490 int i = 0;
491 for (Object entrybean : collection) {
492 if(index == null){
493 node.addBean(entrybean);
494 } else if(index.equals(i)){
495 node.addBean(entrybean);
496 break;
497 }
498 i++;
499 }
500 if (logger.isDebugEnabled()){logger.debug(" initialize collection for " + node.toString() + " - DONE ");}
501
502 }else {
503 // nested bean
504 node.addBean(unwrappedPropertyBean);
505 setProperty(bean, property, unwrappedPropertyBean);
506 }
507 }
508
509 }