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