fix autoinitializer bug in AdvancedBeanInitializer
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / dao / initializer / AbstractBeanInitializer.java
1 // $Id$
2 /**
3 * Copyright (C) 2009 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.cdm.persistence.dao.initializer;
11
12 import java.beans.PropertyDescriptor;
13 import java.lang.reflect.InvocationTargetException;
14 import java.lang.reflect.Method;
15 import java.lang.reflect.ParameterizedType;
16 import java.lang.reflect.Type;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23
24 import org.apache.commons.beanutils.PropertyUtils;
25 import org.apache.commons.lang.StringUtils;
26 import org.apache.log4j.Logger;
27 import org.springframework.beans.factory.annotation.Autowired;
28
29 import eu.etaxonomy.cdm.model.common.CdmBase;
30 import eu.etaxonomy.cdm.persistence.dao.IMethodCache;
31
32 /**
33 * @author a.kohlbecker
34 * @date 26.03.2009
35 *
36 */
37 public abstract class AbstractBeanInitializer implements IBeanInitializer{
38
39 public static final Logger logger = Logger.getLogger(AbstractBeanInitializer.class);
40
41 @Autowired
42 IMethodCache methodCache;
43
44 private Map<Class<? extends CdmBase>, AutoPropertyInitializer<CdmBase>> beanAutoInitializers = null;
45
46 /**
47 * @param beanAutoInitializers the beanAutoInitializers to set
48 */
49 public void setBeanAutoInitializers(Map<Class<? extends CdmBase>, AutoPropertyInitializer<CdmBase>> beanAutoInitializers) {
50 this.beanAutoInitializers = beanAutoInitializers;
51 }
52
53 /**
54 * @return the beanAutoInitializers
55 */
56 public Map<Class<? extends CdmBase>, AutoPropertyInitializer<CdmBase>> getBeanAutoInitializers() {
57 return beanAutoInitializers;
58 }
59
60 /* (non-Javadoc)
61 * @see eu.etaxonomy.cdm.persistence.dao.BeanInitializer#initializeInstance(java.lang.Object)
62 */
63 @Override
64 public abstract Object initializeInstance(Object proxy);
65
66 /* (non-Javadoc)
67 * @see eu.etaxonomy.cdm.persistence.dao.BeanInitializer#load(eu.etaxonomy.cdm.model.common.CdmBase)
68 */
69 @Override
70 public void load(Object bean) {
71 initializeBean(bean, true, false);
72 }
73
74 /* (non-Javadoc)
75 * @see eu.etaxonomy.cdm.persistence.dao.BeanInitializer#loadFully(eu.etaxonomy.cdm.model.common.CdmBase)
76 */
77 @Override
78 public void loadFully(Object bean) {
79 initializeBean(bean, true, true);
80 }
81
82 /**
83 * Initializes all *toOne relations of the given bean and all *toMany
84 * relations, depending on the state of the boolean parameters
85 * <code>cdmEntities</code> and <code>collections</code>
86 *
87 * @param bean
88 * the bean to initialize
89 * @param cdmEntities
90 * initialize all *toOne relations to cdm entities
91 * @param collections
92 * initialize all *toMany relations
93 */
94 public void initializeBean(Object bean, boolean cdmEntities, boolean collections){
95
96 if(logger.isDebugEnabled()){
97 logger.debug(">> starting initializeBean() of " + bean + " ;class:" + bean.getClass().getSimpleName());
98 }
99 Set<Class> restrictions = new HashSet<Class>();
100 if(cdmEntities){
101 restrictions.add(CdmBase.class);
102 }
103 if(collections){
104 restrictions.add(Collections.class);
105 }
106 Set<PropertyDescriptor> props = getProperties(bean, restrictions);
107 for(PropertyDescriptor propertyDescriptor : props){
108 try {
109
110 invokeInitialization(bean, propertyDescriptor);
111
112 } catch (IllegalAccessException e) {
113 logger.error("Illegal access on property " + propertyDescriptor.getName());
114 } catch (InvocationTargetException e) {
115 logger.info("Cannot invoke property " + propertyDescriptor.getName() + " not found");
116 } catch (NoSuchMethodException e) {
117 logger.info("Property " + propertyDescriptor.getName() + " not found");
118 }
119 }
120 if(logger.isDebugEnabled()){
121 logger.debug(" completed initializeBean() of " + bean);
122 }
123 }
124
125 /* (non-Javadoc)
126 * @see eu.etaxonomy.cdm.persistence.dao.BeanInitializer#initializeProperties(java.lang.Object, java.util.List)
127 */
128 //TODO optimize algorithm ..
129 @Override
130 public void initialize(Object bean, List<String> propertyPaths) {
131
132 invokePropertyAutoInitializers(bean);
133
134 if(propertyPaths == null){
135 return;
136 }
137
138 Collections.sort(propertyPaths);
139 //long startTime1 = System.nanoTime();
140 if(logger.isDebugEnabled()){
141 logger.debug(">> starting to initialize " + bean + " ;class:" + bean.getClass().getSimpleName());
142 }
143 for(String propPath : propertyPaths){
144 initializePropertyPath(bean, propPath);
145 }
146 //long estimatedTime1 = System.nanoTime() - startTime1;
147 //System.err.println(".");
148 //long startTime2 = System.nanoTime();
149 //for(String propPath : propertyPaths){
150 // initializePropertyPath(bean, propPath);
151 //}
152 //long estimatedTime2 = System.nanoTime() - startTime2;
153 //System.err.println("first pas: "+estimatedTime1+" ns; second pas: "+estimatedTime2+ " ns");
154 if(logger.isDebugEnabled()){
155 logger.debug(" Completed initialization of " + bean);
156 }
157
158 }
159
160 @Override
161 public <C extends Collection<?>> C initializeAll(C beanList, List<String> propertyPaths) {
162 if(propertyPaths != null){
163 for(Object bean : beanList){
164 initialize(bean, propertyPaths);
165 }
166 }
167 return beanList;
168 }
169
170 /**
171 * Initializes the given single <code>propPath</code> String.
172 *
173 * @param bean
174 * @param propPath
175 */
176 //changed form private to protected (AM)
177 protected void initializePropertyPath(Object bean, String propPath) {
178 if(logger.isDebugEnabled()){
179 logger.debug("processing " + propPath);
180 }
181
182
183 // [1]
184 // if propPath only contains a wildcard (* or $)
185 // => do a batch initialization of *toOne or *toMany relations
186 if(propPath.equals(LOAD_2ONE_WILDCARD)){
187 if(Collection.class.isAssignableFrom(bean.getClass())){
188 initializeAllEntries((Collection)bean, true, false);
189 } else if(Map.class.isAssignableFrom(bean.getClass())) {
190 initializeAllEntries(((Map)bean).values(), true, false);
191 } else{
192 initializeBean(bean, true, false);
193 }
194 } else if(propPath.equals(LOAD_2ONE_2MANY_WILDCARD)){
195 if(Collection.class.isAssignableFrom(bean.getClass())){
196 initializeAllEntries((Collection)bean, true, true);
197 } else if(Map.class.isAssignableFrom(bean.getClass())) {
198 initializeAllEntries(((Map)bean).values(), true, true);
199 } else {
200 initializeBean(bean, true, true);
201 }
202 } else {
203 // [2]
204 // propPath contains either a single field or a nested path
205
206 // split next path token off and keep the remaining as nestedPath
207 String property;
208 String nestedPath = null;
209 int pos;
210 if((pos = propPath.indexOf('.')) > 0){
211 nestedPath = propPath.substring(pos + 1);
212 property = propPath.substring(0, pos);
213 } else {
214 property = propPath;
215 }
216
217 // is the property indexed?
218 Integer index = null;
219 if((pos = property.indexOf('[')) > 0){
220 String indexString = property.substring(pos + 1, property.indexOf(']'));
221 index = Integer.valueOf(indexString);
222 property = property.substring(0, pos);
223 }
224
225 try {
226 //Class targetClass = HibernateProxyHelper.getClassWithoutInitializingProxy(bean); // used for debugging
227
228 // [2.a] initialize the bean named by property
229
230 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(bean, property);
231 Object unwrappedPropertyBean = invokeInitialization(bean, propertyDescriptor);
232
233 // [2.b]
234 // recurse into nested properties
235 if(unwrappedPropertyBean != null && nestedPath != null){
236 if (Collection.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {
237 // nested collection
238 int i = 0;
239 for (Object entrybean : (Collection) unwrappedPropertyBean) {
240 if(index == null){
241 initializePropertyPath(entrybean, nestedPath);
242 } else if(index.equals(i)){
243 initializePropertyPath(entrybean, nestedPath);
244 break;
245 }
246 i++;
247 }
248 } else if(Map.class.isAssignableFrom(unwrappedPropertyBean.getClass())) {
249 // nested map
250 int i = 0;
251 for (Object entrybean : ((Map) unwrappedPropertyBean).values()) {
252 if(index == null){
253 initializePropertyPath(entrybean, nestedPath);
254 } else if(index.equals(i)){
255 initializePropertyPath(entrybean, nestedPath);
256 break;
257 }
258 i++;
259 }
260 }else {
261 // nested bean
262 initializePropertyPath(unwrappedPropertyBean, nestedPath);
263 setProperty(bean, property, unwrappedPropertyBean);
264 }
265 }
266
267 } catch (IllegalAccessException e) {
268 logger.error("Illegal access on property " + property);
269 } catch (InvocationTargetException e) {
270 logger.error("Cannot invoke property " + property + " not found");
271 } catch (NoSuchMethodException e) {
272 logger.info("Property " + property + " not found");
273 }
274 }
275 }
276
277 private Object invokeInitialization(Object bean, PropertyDescriptor propertyDescriptor) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
278
279 if(propertyDescriptor == null || bean == null){
280 return null;
281 }
282
283 // (1)
284 // initialialization of the bean
285 //
286 Object propertyProxy = PropertyUtils.getProperty( bean, propertyDescriptor.getName());
287 Object propertyBean = initializeInstance(propertyProxy);
288
289 if(propertyBean != null){
290 // (2)
291 // auto initialialization of sub properties
292 //
293 if(CdmBase.class.isAssignableFrom(propertyBean.getClass())){
294
295 // initialization of a single bean
296 CdmBase cdmBaseBean = (CdmBase)propertyBean;
297 invokePropertyAutoInitializers(cdmBaseBean);
298
299 } else if(Collection.class.isAssignableFrom(propertyBean.getClass()) ||
300 Map.class.isAssignableFrom(propertyBean.getClass()) ) {
301
302 // it is a collection or map
303 Method readMethod = propertyDescriptor.getReadMethod();
304 Type genericReturnType = readMethod.getGenericReturnType();
305
306 if(genericReturnType instanceof ParameterizedType){
307 ParameterizedType type = (ParameterizedType) genericReturnType;
308 Type[] typeArguments = type.getActualTypeArguments();
309
310 if(typeArguments.length > 0
311 && typeArguments[0] instanceof Class<?>
312 && CdmBase.class.isAssignableFrom((Class<?>) typeArguments[0])){
313
314 if(Collection.class.isAssignableFrom((Class<?>) type.getRawType())){
315 for(CdmBase entry : ((Collection<CdmBase>)propertyBean)){
316 invokePropertyAutoInitializers(entry);
317 }
318 }
319 }
320
321 }
322 }
323 }
324
325 return propertyBean;
326 }
327
328 /**
329 * @param beanClass
330 * @param bean
331 * @return
332 */
333 protected final void invokePropertyAutoInitializers(Object bean) {
334
335 if(beanAutoInitializers == null || bean == null){
336 return;
337 }
338 if(!CdmBase.class.isAssignableFrom(bean.getClass())){
339 return;
340 }
341 CdmBase cdmBaseBean = (CdmBase)bean;
342 for(Class<? extends CdmBase> superClass : beanAutoInitializers.keySet()){
343 if(superClass.isAssignableFrom(bean.getClass())){
344 beanAutoInitializers.get(superClass).initialize(cdmBaseBean);
345 }
346 }
347 }
348
349 protected void setProperty(Object object, String property, Object value){
350 Method method = methodCache.getMethod(object.getClass(), "set" + StringUtils.capitalize(property), value.getClass());
351 if(method != null){
352 try {
353 method.invoke(object, value);
354 } catch (IllegalArgumentException e) {
355 // TODO Auto-generated catch block
356 e.printStackTrace();
357 } catch (IllegalAccessException e) {
358 // TODO Auto-generated catch block
359 e.printStackTrace();
360 } catch (InvocationTargetException e) {
361 // TODO Auto-generated catch block
362 e.printStackTrace();
363 }
364 }
365 }
366
367 /**
368 * @param collection of which all entities are to be initialized
369 * @param cdmEntities initialize all *toOne relations to cdm entities
370 * @param collections initialize all *toMany relations
371 */
372 private void initializeAllEntries(Collection collection, boolean cdmEntities, boolean collections) {
373 for(Object bean : collection){
374 initializeBean(bean, cdmEntities, collections);
375 }
376 }
377
378 /**
379 * Return all public bean properties which, exclusive those whose return type match any class defined in
380 * the parameter <code>typeRestrictions</code> or which are transient properties.
381 *
382 * @param bean
383 * @param typeRestrictions
384 * @return
385 */
386 @SuppressWarnings("unchecked")
387 public static Set<PropertyDescriptor> getProperties(Object bean, Set<Class> typeRestrictions) {
388
389 Set<PropertyDescriptor> properties = new HashSet<PropertyDescriptor>();
390 PropertyDescriptor[] prop = PropertyUtils.getPropertyDescriptors(bean);
391
392 for (int i = 0; i < prop.length; i++) {
393 //String propName = prop[i].getName();
394
395 // only read methods & skip transient getters
396 if( prop[i].getReadMethod() != null ){
397 try{
398 Class transientClass = Class.forName( "javax.persistence.Transient" );
399 if( prop[i].getReadMethod().getAnnotation( transientClass ) != null ){
400 continue;
401 }
402 }catch( ClassNotFoundException cnfe ){
403 // ignore
404 }
405 if(typeRestrictions != null && typeRestrictions.size() > 1){
406 for(Class type : typeRestrictions){
407 if(type.isAssignableFrom(prop[i].getPropertyType())){
408 properties.add(prop[i]);
409 }
410 }
411 } else {
412 properties.add(prop[i]);
413 }
414 }
415 }
416 return properties;
417 }
418
419 }