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