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