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