1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.jcr.node2bean.impl;
35
36 import info.magnolia.cms.util.ContentUtil;
37 import info.magnolia.cms.util.SystemContentWrapper;
38 import info.magnolia.jcr.iterator.FilteringNodeIterator;
39 import info.magnolia.jcr.node2bean.Node2BeanException;
40 import info.magnolia.jcr.node2bean.Node2BeanTransformer;
41 import info.magnolia.jcr.node2bean.PropertyTypeDescriptor;
42 import info.magnolia.jcr.node2bean.TransformationState;
43 import info.magnolia.jcr.node2bean.TypeDescriptor;
44 import info.magnolia.jcr.node2bean.TypeMapping;
45 import info.magnolia.jcr.predicate.AbstractPredicate;
46 import info.magnolia.jcr.util.NodeTypes;
47 import info.magnolia.objectfactory.Classes;
48 import info.magnolia.objectfactory.ComponentProvider;
49 import info.magnolia.objectfactory.Components;
50
51 import java.lang.reflect.Array;
52 import java.lang.reflect.Constructor;
53 import java.lang.reflect.InvocationTargetException;
54 import java.lang.reflect.Method;
55 import java.util.ArrayList;
56 import java.util.Collection;
57 import java.util.HashSet;
58 import java.util.Iterator;
59 import java.util.LinkedHashMap;
60 import java.util.LinkedList;
61 import java.util.List;
62 import java.util.Locale;
63 import java.util.Map;
64 import java.util.Queue;
65 import java.util.Set;
66
67 import javax.inject.Inject;
68 import javax.jcr.Node;
69 import javax.jcr.NodeIterator;
70 import javax.jcr.RepositoryException;
71
72 import org.apache.commons.beanutils.BeanUtilsBean;
73 import org.apache.commons.beanutils.Converter;
74 import org.apache.commons.beanutils.MethodUtils;
75 import org.apache.commons.beanutils.PropertyUtils;
76 import org.apache.commons.lang3.LocaleUtils;
77 import org.apache.commons.lang3.StringUtils;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80
81 import com.google.common.collect.Iterables;
82
83
84
85
86 public class Node2BeanTransformerImpl implements Node2BeanTransformer {
87
88 private static final Logger log = LoggerFactory.getLogger(Node2BeanTransformerImpl.class);
89
90 private final BeanUtilsBean beanUtilsBean;
91
92 private final Class<?> defaultListImpl;
93
94 private final Class<?> defaultSetImpl;
95
96 private final Class<?> defaultQueueImpl;
97
98
99
100
101
102 @Deprecated
103 public Node2BeanTransformerImpl() {
104 this(Components.getComponent(PreConfiguredBeanUtils.class));
105 }
106
107 @Inject
108 public Node2BeanTransformerImpl(PreConfiguredBeanUtils beanUtilsBean) {
109 this(beanUtilsBean, LinkedList.class, HashSet.class, LinkedList.class);
110 }
111
112
113
114
115
116 @Deprecated
117 public Node2BeanTransformerImpl(Class<?> defaultListImpl, Class<?> defaultSetImpl, Class<?> defaultQueueImpl) {
118 this(Components.getComponent(PreConfiguredBeanUtils.class), defaultListImpl, defaultSetImpl, defaultQueueImpl);
119 }
120
121 protected Node2BeanTransformerImpl(PreConfiguredBeanUtils beanUtilsBean, Class<?> defaultListImpl, Class<?> defaultSetImpl, Class<?> defaultQueueImpl) {
122 this.beanUtilsBean = beanUtilsBean;
123 this.defaultListImpl = defaultListImpl;
124 this.defaultSetImpl = defaultSetImpl;
125 this.defaultQueueImpl = defaultQueueImpl;
126
127
128 beanUtilsBean.getConvertUtils().deregister(Class.class);
129 }
130
131 @Override
132 public TransformationState newState() {
133 return new TransformationStateImpl();
134 }
135
136 @Override
137 public TypeDescriptor resolveType(TypeMapping typeMapping, TransformationState state, ComponentProvider componentProvider) throws ClassNotFoundException, RepositoryException {
138 TypeDescriptor typeDscr = null;
139 Node node = state.getCurrentNode();
140
141 try {
142 if (node.hasProperty("class")) {
143 String className = StringUtils.trim(node.getProperty("class").getString());
144 if (StringUtils.isBlank(className)) {
145 log.warn("Cannot resolve type for node [{}] because class property has empty value.", node);
146 } else {
147 Class<?> clazz = Classes.getClassFactory().forName(className);
148 typeDscr = typeMapping.getTypeDescriptor(clazz);
149 }
150 }
151 } catch (RepositoryException e) {
152 log.warn("Can't read class property from node [{}]", node.getPath(), e);
153 }
154
155 if (typeDscr == null && state.getLevel() > 1) {
156 TypeDescriptor parentTypeDscr = state.getCurrentType();
157 PropertyTypeDescriptor propDscr;
158
159 if (parentTypeDscr.isMap() || parentTypeDscr.isCollection()) {
160 if (state.getLevel() > 2) {
161
162 String mapProperyName = state.peekNode(1).getName();
163 propDscr = state.peekType(1).getPropertyTypeDescriptor(mapProperyName, typeMapping);
164 if (propDscr != null) {
165 typeDscr = propDscr.getCollectionEntryType();
166 }
167 }
168 } else {
169 propDscr = state.getCurrentType().getPropertyTypeDescriptor(node.getName(), typeMapping);
170 if (propDscr != null) {
171 typeDscr = propDscr.getType();
172 }
173 }
174 }
175
176 typeDscr = onResolveType(typeMapping, state, typeDscr, componentProvider);
177
178 if (typeDscr != null) {
179
180 final Class<?> type = typeDscr.getType();
181 typeDscr = typeMapping.getTypeDescriptor(componentProvider.getImplementation(type));
182
183
184 Node2BeanTransformer customTransformer = typeDscr.getTransformer();
185 if (customTransformer != null && customTransformer != this) {
186 TypeDescriptor typeFoundByCustomTransformer = customTransformer.resolveType(typeMapping, state, componentProvider);
187
188
189
190 if (typeFoundByCustomTransformer != TypeMapping.MAP_TYPE) {
191
192 Class<?> implementation = componentProvider.getImplementation(typeFoundByCustomTransformer.getType());
193 typeDscr = typeMapping.getTypeDescriptor(implementation);
194 }
195 }
196 }
197
198 if (typeDscr == null || typeDscr.needsDefaultMapping()) {
199 if (typeDscr == null) {
200 log.debug("Was not able to resolve type for node [{}] will use a map", node);
201 }
202 typeDscr = TypeMapping.MAP_TYPE;
203 }
204 log.debug("Resolved type [{}] for node [{}]", typeDscr.getType(), node.getPath());
205
206 return typeDscr;
207 }
208
209 @Override
210 public NodeIterator getChildren(Node node) throws RepositoryException {
211
212 return new FilteringNodeIterator(node.getNodes(), new AbstractPredicate<Node>() {
213 @Override
214 public boolean evaluateTyped(Node t) {
215 try {
216 return !(t.getName().startsWith(NodeTypes.JCR_PREFIX) || t.isNodeType(NodeTypes.MetaData.NAME));
217 } catch (RepositoryException e) {
218 return false;
219 }
220 }
221 });
222 }
223
224 @Override
225 public Object newBeanInstance(TransformationState state, Map<String, Object> values, ComponentProvider componentProvider) throws Node2BeanException {
226
227
228 final Object bean = convertPropertyValue(state.getCurrentType().getType(), values);
229
230 if (bean == values) {
231 try {
232
233 values.remove("class");
234 final Class<?> type = state.getCurrentType().getType();
235 if (LinkedHashMap.class.equals(type)) {
236
237
238 return new LinkedHashMap();
239 } else if (Map.class.isAssignableFrom(type)) {
240
241 log.warn("someone wants another type of map ? {}", type);
242 } else if (Collection.class.isAssignableFrom(type)) {
243
244 return type.newInstance();
245 }
246 return componentProvider.newInstance(type);
247 } catch (Throwable e) {
248 throw new Node2BeanException(e);
249 }
250 }
251 return bean;
252 }
253
254 @Override
255 public void initBean(TransformationState state, Map values) throws Node2BeanException {
256 Object bean = state.getCurrentBean();
257
258 Method init;
259 try {
260 init = bean.getClass().getMethod("init");
261 try {
262 init.invoke(bean);
263 } catch (Exception e) {
264 throw new Node2BeanException("can't call init method", e);
265 }
266 } catch (SecurityException e) {
267 return;
268 } catch (NoSuchMethodException e) {
269 return;
270 }
271 log.debug("{} is initialized", bean);
272
273 }
274
275 @Override
276 public Object convertPropertyValue(Class<?> propertyType, Object value) throws Node2BeanException {
277 if (Class.class.equals(propertyType)) {
278 try {
279 return Classes.getClassFactory().forName(value.toString());
280 } catch (ClassNotFoundException e) {
281 log.error("Can't convert property. Class for type [{}] not found.", propertyType);
282 throw new Node2BeanException(e);
283 }
284 }
285
286 if (Locale.class.equals(propertyType)) {
287 if (value instanceof String) {
288 String localeStr = (String) value;
289 if (StringUtils.isNotEmpty(localeStr)) {
290 return LocaleUtils.toLocale(localeStr);
291 }
292 }
293 }
294
295 if (Collection.class.equals(propertyType) && value instanceof Map) {
296
297 return ((Map) value).values();
298 }
299
300
301 if (String.class.equals(propertyType) && value instanceof Map && ((Map) value).size() == 1) {
302 return ((Map) value).values().iterator().next();
303 }
304
305 return value;
306 }
307
308
309
310
311
312 protected TypeDescriptor onResolveType(TypeMapping typeMapping, TransformationState state, TypeDescriptor resolvedType, ComponentProvider componentProvider) {
313 return resolvedType;
314 }
315
316 @Override
317 public void setProperty(TypeMapping mapping, TransformationState state, PropertyTypeDescriptor descriptor, Map<String, Object> values) throws RepositoryException {
318 String propertyName = descriptor.getName();
319 if (propertyName.equals("class")) {
320 return;
321 }
322 Object value = values.get(propertyName);
323 Object bean = state.getCurrentBean();
324
325 if (propertyName.equals("content") && value == null) {
326 PropertyTypeDescriptor dscr = mapping.getPropertyTypeDescriptor(bean.getClass(), propertyName);
327
328
329 if (dscr.getType().getType().isAssignableFrom(SystemContentWrapper.class)) {
330 value = new SystemContentWrapper(ContentUtil.asContent(state.getCurrentNode()));
331 } else if (dscr.getType().getType().isAssignableFrom(Node.class)) {
332 value = state.getCurrentNode();
333 }
334
335 } else if (propertyName.equals("name") && value == null) {
336 value = state.getCurrentNode().getName();
337 } else if (propertyName.equals("className") && value == null) {
338 value = values.get("class");
339 }
340
341
342 if (value == null) {
343 return;
344 }
345
346 log.debug("try to set {}.{} with value {}", bean, propertyName, value);
347
348 if (!(bean instanceof Map)) {
349 try {
350 PropertyTypeDescriptor dscr = mapping.getPropertyTypeDescriptor(bean.getClass(), propertyName);
351 if (dscr.getType() != null) {
352
353
354 if (dscr.isCollection() || dscr.isMap() || dscr.isArray()) {
355 log.debug("{} is of type collection, map or array", propertyName);
356 if (dscr.getWriteMethod() != null) {
357 Method method = dscr.getWriteMethod();
358 clearCollection(bean, propertyName);
359 filterOrConvertCollectionsAndMaps(dscr, value);
360 if (dscr.isMap()) {
361 method.invoke(bean, value);
362 } else if (dscr.isArray()) {
363 Class<?> entryClass = dscr.getCollectionEntryType().getType();
364 Collection<Object> list = new LinkedList<Object>(((Map<Object, Object>) value).values());
365
366 Object[] arr = (Object[]) Array.newInstance(entryClass, list.size());
367 for (int i = 0; i < arr.length; i++) {
368 arr[i] = Iterables.get(list, i);
369 }
370 method.invoke(bean, new Object[]{arr});
371 } else if (dscr.isCollection()) {
372 if (value instanceof Map) {
373 value = createCollectionFromMap((Map<Object, Object>) value, dscr.getType().getType());
374 }
375 method.invoke(bean, value);
376 }
377 return;
378 } else if (dscr.getAddMethod() != null) {
379 Method method = dscr.getAddMethod();
380 clearCollection(bean, propertyName);
381 Class<?> entryClass = dscr.getCollectionEntryType().getType();
382
383 log.warn("Will use deprecated add method [{}] to populate [{}] in bean class [{}].", method.getName(), propertyName, bean.getClass().getName());
384 for (Iterator<Object> iter = ((Map<Object, Object>) value).keySet().iterator(); iter.hasNext(); ) {
385 Object key = iter.next();
386 Object entryValue = ((Map<Object, Object>) value).get(key);
387 entryValue = convertPropertyValue(entryClass, entryValue);
388 if (entryClass.isAssignableFrom(entryValue.getClass())) {
389 if (dscr.isCollection() || dscr.isArray()) {
390 log.debug("will add value {}", entryValue);
391 method.invoke(bean, entryValue);
392 }
393
394 else {
395 log.debug("will add key {} with value {}", key, entryValue);
396 method.invoke(bean, key, entryValue);
397 }
398 }
399 }
400 return;
401 }
402 if (dscr.isCollection()) {
403 log.debug("transform the values to a collection", propertyName);
404 value = ((Map<Object, Object>) value).values();
405 }
406 } else {
407 value = convertPropertyValue(dscr.getType().getType(), value);
408 }
409 }
410 } catch (Exception e) {
411 handleSetPropertyException(state, propertyName, value, bean, e);
412 }
413 }
414
415 try {
416
417
418
419
420
421
422 beanUtilsBean.setProperty(bean, propertyName, value);
423 } catch (Exception e) {
424 handleSetPropertyException(state, propertyName, value, bean, e);
425 }
426 }
427
428 protected boolean isBeanEnabled(Object bean) {
429 Method method = null;
430 Object isEnabled = null;
431 try {
432 method = bean.getClass().getMethod("isEnabled");
433
434
435 if (method.getReturnType().equals(Boolean.class)) {
436 return true;
437 }
438 isEnabled = method.invoke(bean);
439 } catch (NoSuchMethodException e) {
440
441 return true;
442 } catch (IllegalArgumentException e) {
443
444 return true;
445 } catch (IllegalAccessException e) {
446 log.warn("Can't access method [{}#isEnabled]. Maybe it's private/protected?", bean.getClass());
447 return true;
448 } catch (InvocationTargetException e) {
449 log.error("An exception was thrown by [{}]#isEnabled method.", bean.getClass(), e);
450 return true;
451 }
452 return (Boolean) isEnabled;
453 }
454
455
456
457
458
459
460
461 private void filterOrConvertCollectionsAndMaps(final PropertyTypeDescriptor dscr, final Object value) {
462 if (dscr.getCollectionEntryType() != null) {
463 Class<?> entryClass = dscr.getCollectionEntryType().getType();
464 if (dscr.getType().isCollection() && value instanceof Collection) {
465 final Collection<?> collection = ((Collection) value);
466 final Collection convertedCollection = filterOrConvert(collection, entryClass);
467 collection.clear();
468 collection.addAll(convertedCollection);
469 } else if (value instanceof Map) {
470 final Map map = (Map) value;
471 final Map convertedMap = filterOrConvert(map, entryClass);
472 map.clear();
473 map.putAll(convertedMap);
474 }
475 }
476 }
477
478 private Collection filterOrConvert(final Collection collection, final Class entryClass) {
479 final Collection converted = new ArrayList();
480 final Iterator<?> it = collection.iterator();
481 while (it.hasNext()) {
482 Object obj = it.next();
483 if (isBeanEnabled(obj)) {
484 if (!entryClass.isAssignableFrom(obj.getClass())) {
485 final Converter converter = beanUtilsBean.getConvertUtils().lookup(entryClass);
486 if (converter != null) {
487 converted.add(converter.convert(entryClass, obj));
488 }
489 } else {
490 converted.add(obj);
491 }
492 }
493 }
494 return converted;
495 }
496
497 private Map filterOrConvert(final Map map, final Class entryClass) {
498 final Map converted = new LinkedHashMap();
499 for (Object key : map.keySet()) {
500 Object obj = map.get(key);
501 if (isBeanEnabled(obj)) {
502 if (!entryClass.isAssignableFrom(obj.getClass())) {
503 final Converter converter = beanUtilsBean.getConvertUtils().lookup(entryClass);
504 if (converter != null) {
505 converted.put(key, converter.convert(entryClass, obj));
506 }
507 } else {
508 converted.put(key, obj);
509 }
510 }
511 }
512 return converted;
513 }
514
515 private void clearCollection(Object bean, String propertyName) {
516 log.debug("clearing the current content of the collection/map");
517 try {
518 Object col = PropertyUtils.getProperty(bean, propertyName);
519 if (col != null) {
520 MethodUtils.invokeExactMethod(col, "clear", new Object[]{});
521 }
522 } catch (Exception e) {
523 log.debug("no clear method found on collection {}", propertyName);
524 }
525 }
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542 protected Collection<?> createCollectionFromMap(Map<?, ?> map, Class<?> clazz) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
543 Collection<?> collection = null;
544 Constructor<?> constructor = null;
545 if (clazz.isInterface()) {
546
547 if (List.class.isAssignableFrom(clazz)) {
548 constructor = defaultListImpl.getConstructor(Collection.class);
549 } else if (clazz.isAssignableFrom(Queue.class)) {
550 constructor = defaultQueueImpl.getConstructor(Collection.class);
551 } else if (Set.class.isAssignableFrom(clazz)) {
552 constructor = defaultSetImpl.getConstructor(Collection.class);
553 }
554 } else {
555 if (Collection.class.isAssignableFrom(clazz)) {
556 constructor = clazz.getConstructor(Collection.class);
557 }
558 }
559 if (constructor != null) {
560 collection = (Collection<?>) constructor.newInstance(map.values());
561 }
562 return collection;
563 }
564
565 private void handleSetPropertyException(TransformationState state, String propertyName, Object value, Object bean, Throwable e) throws RepositoryException {
566 if (e instanceof InvocationTargetException) {
567 e = e.getCause();
568 }
569 log.debug("Can't set property [{}] to value [{}] in bean [{}] for node {} due to {}", propertyName, value, bean.getClass().getName(), state.getCurrentNode().getPath(), e.toString(), e);
570 log.error("Can't set property [{}] to value [{}] in bean [{}] for node {} due to {}", propertyName, value, bean.getClass().getName(), state.getCurrentNode().getPath(), e.toString());
571 }
572
573 }