View Javadoc
1   /**
2    * This file Copyright (c) 2012-2017 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.jcr.node2bean.impl;
35  
36  import static info.magnolia.transformer.TransformationProblem.error;
37  
38  import info.magnolia.cms.util.ContentUtil;
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.jcr.util.NodeTypes;
49  import info.magnolia.objectfactory.Classes;
50  import info.magnolia.objectfactory.ComponentProvider;
51  import info.magnolia.objectfactory.Components;
52  
53  import java.lang.reflect.Array;
54  import java.lang.reflect.Constructor;
55  import java.lang.reflect.InvocationTargetException;
56  import java.lang.reflect.Method;
57  import java.util.ArrayList;
58  import java.util.Collection;
59  import java.util.HashSet;
60  import java.util.Iterator;
61  import java.util.LinkedHashMap;
62  import java.util.LinkedList;
63  import java.util.List;
64  import java.util.Locale;
65  import java.util.Map;
66  import java.util.Map.Entry;
67  import java.util.Queue;
68  import java.util.Set;
69  
70  import javax.inject.Inject;
71  import javax.jcr.Node;
72  import javax.jcr.NodeIterator;
73  import javax.jcr.RepositoryException;
74  
75  import org.apache.commons.beanutils.BeanUtilsBean;
76  import org.apache.commons.beanutils.Converter;
77  import org.apache.commons.beanutils.MethodUtils;
78  import org.apache.commons.beanutils.PropertyUtils;
79  import org.apache.commons.lang3.LocaleUtils;
80  import org.apache.commons.lang3.StringUtils;
81  import org.slf4j.Logger;
82  import org.slf4j.LoggerFactory;
83  
84  import com.google.common.collect.Iterables;
85  
86  /**
87   * Concrete implementation using reflection, generics and setter methods.
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     /**
102      * @deprecated since 5.4 - use IoC, don't instantiate directly. You should not have to instantiate this if you
103      * use magnolia-configuration mechanisms introduced in 5.4.
104      */
105     @Deprecated
106     public Node2BeanTransformerImpl() {
107         this(Components.getComponent(PreConfiguredBeanUtils.class));
108     }
109 
110     @Inject
111     public Node2BeanTransformerImpl(PreConfiguredBeanUtils beanUtilsBean) {
112         this(beanUtilsBean, LinkedList.class, HashSet.class, LinkedList.class);
113     }
114 
115     /**
116      * @deprecated since 5.4 - use IoC, don't instantiate directly. You should not have to instantiate this if you
117      * use magnolia-configuration mechanisms introduced in 5.4.
118      */
119     @Deprecated
120     public Node2BeanTransformerImpl(Class<?> defaultListImpl, Class<?> defaultSetImpl, Class<?> defaultQueueImpl) {
121         this(Components.getComponent(PreConfiguredBeanUtils.class), defaultListImpl, defaultSetImpl, defaultQueueImpl);
122     }
123 
124     protected Node2BeanTransformerImpl(PreConfiguredBeanUtils beanUtilsBean, Class<?> defaultListImpl, Class<?> defaultSetImpl, Class<?> defaultQueueImpl) {
125         this.beanUtilsBean = beanUtilsBean;
126         this.defaultListImpl = defaultListImpl;
127         this.defaultSetImpl = defaultSetImpl;
128         this.defaultQueueImpl = defaultQueueImpl;
129 
130         beanUtilsBean.getConvertUtils().deregister(Class.class);
131     }
132 
133     @Override
134     public TransformationState newState() {
135         return new TransformationStateImpl();
136     }
137 
138     @Override
139     public TypeDescriptor resolveType(TypeMapping typeMapping, TransformationState state, ComponentProvider componentProvider) throws ClassNotFoundException, RepositoryException {
140         TypeDescriptor typeDscr = null;
141         Node node = state.getCurrentNode();
142 
143         try {
144             if (node.hasProperty("class")) {
145                 String className = StringUtils.trim(node.getProperty("class").getString());
146                 if (StringUtils.isBlank(className)) {
147                     state.trackProblem(error("Cannot resolve type for node [%s] because class property has empty value", node.getPath()));
148                 } else {
149                     Class<?> clazz = Classes.getClassFactory().forName(className);
150                     typeDscr = typeMapping.getTypeDescriptor(clazz);
151                 }
152             }
153         } catch (RepositoryException e) {
154             state.trackProblem(error("Failed to read a class property value from the node [%d]", node.getPath()));
155         }
156 
157         if (typeDscr == null && state.getLevel() > 1) {
158             TypeDescriptor parentTypeDscr = state.getCurrentType();
159             PropertyTypeDescriptor propDscr;
160 
161             if (parentTypeDscr.isMap() || parentTypeDscr.isCollection()) {
162                 if (state.getLevel() > 2) {
163                     // this is not necessarily the parent node of the current
164                     String mapProperyName = state.peekNode(1).getName();
165                     propDscr = state.peekType(1).getPropertyTypeDescriptor(mapProperyName, typeMapping);
166                     if (propDscr != null) {
167                         typeDscr = propDscr.getCollectionEntryType();
168                     }
169                 }
170             } else {
171                 propDscr = state.getCurrentType().getPropertyTypeDescriptor(node.getName(), typeMapping);
172                 if (propDscr != null) {
173                     typeDscr = propDscr.getType();
174                 }
175             }
176         }
177 
178         typeDscr = onResolveType(typeMapping, state, typeDscr, componentProvider);
179 
180         if (typeDscr != null) {
181             // might be that the factory util defines a default implementation for interfaces
182             final Class<?> type = typeDscr.getType();
183             typeDscr = typeMapping.getTypeDescriptor(componentProvider.getImplementation(type));
184 
185             // now that we know the property type we should delegate to the custom transformer if any defined
186             Node2BeanTransformer customTransformer = typeDscr.getTransformer();
187             if (customTransformer != null && customTransformer != this) {
188                 TypeDescriptor typeFoundByCustomTransformer = customTransformer.resolveType(typeMapping, state, componentProvider);
189                 // if no specific type has been provided by the
190                 // TODO - TypeDescriptor - equals and hashCode impl and use
191                 // not equals instead of !=
192                 if (typeFoundByCustomTransformer != TypeMapping.MAP_TYPE) {
193                     // might be that the factory util defines a default implementation for interfaces
194                     Class<?> implementation = componentProvider.getImplementation(typeFoundByCustomTransformer.getType());
195                     typeDscr = typeMapping.getTypeDescriptor(implementation);
196                 }
197             }
198         }
199 
200         if (typeDscr == null || typeDscr.needsDefaultMapping()) {
201             if (typeDscr == null) {
202                 log.debug("Was not able to resolve type for node [{}] will use a map", node);
203             }
204             typeDscr = TypeMapping.MAP_TYPE;
205         }
206         log.debug("Resolved type [{}] for node [{}]", typeDscr.getType(), node.getPath());
207 
208         return typeDscr;
209     }
210 
211     @Override
212     public NodeIterator getChildren(Node node) throws RepositoryException {
213         // TODO create predicate into separate class, <? extends Item> ItemHidingPredicate (regexp)
214         return new FilteringNodeIterator(node.getNodes(), new AbstractPredicate<Node>() {
215             @Override
216             public boolean evaluateTyped(Node t) {
217                 try {
218                     return !(t.getName().startsWith(NodeTypes.JCR_PREFIX) || t.isNodeType(NodeTypes.MetaData.NAME));
219                 } catch (RepositoryException e) {
220                     return false;
221                 }
222             }
223         });
224     }
225 
226     @Override
227     public Object newBeanInstance(TransformationState state, Map<String, Object> values, ComponentProvider componentProvider) throws Node2BeanException {
228         // we try first to use conversion (Map --> primitive type)
229         // this is the case when we flattening the hierarchy?
230         final Object bean = convertPropertyValue(state.getCurrentType().getType(), values);
231         // were the properties transformed?
232         if (bean == values) {
233             try {
234                 // is this property remove necessary?
235                 values.remove("class");
236                 final Class<?> type = state.getCurrentType().getType();
237                 if (LinkedHashMap.class.equals(type)) {
238                     // TODO - as far as I can tell, "bean" and "properties" are already the same instance of a
239                     // LinkedHashMap, so what are we doing in here ?
240                     return new LinkedHashMap();
241                 } else if (Map.class.isAssignableFrom(type)) {
242                     // TODO ?
243                     log.warn("someone wants another type of map ? {}", type);
244                 } else if (Collection.class.isAssignableFrom(type)) {
245                     // someone wants specific collection
246                     return type.newInstance();
247                 }
248                 return componentProvider.newInstance(type);
249             } catch (Exception e) {
250                 // This error is not tracked here because by current design the N2B conversion is supossed to blow up.
251                 // The N2B exception will bubble upwards to the N2BProcessor where we either handle it (the new API will catch the exception and record the error)
252                 // or, in case of the old API - we don't care about error tracking that much and will just let exception fly.
253                 throw new Node2BeanException(
254                         String.format("Failed to instantiate a bean of type [%s] due to: [%s]", state.getCurrentType().getType().getName(), e.getMessage()), e);
255             }
256         }
257         return bean;
258     }
259 
260     @Override
261     public void initBean(TransformationState state, Map values) throws Node2BeanException {
262         Object bean = state.getCurrentBean();
263 
264         Method init;
265         try {
266             init = bean.getClass().getMethod("init");
267             try {
268                 init.invoke(bean); // no parameters
269             } catch (Exception e) {
270                 throw new Node2BeanException("can't call init method", e);
271             }
272         } catch (SecurityException | 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                 // we let this error to be tracked by handleSetPropertyException, which is a common funnel for property-related failures
286                 throw new Node2BeanException(String.format("Can't convert property. Class for type [%s] not found.", propertyType), 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             // TODO never used ?
301             return ((Map) value).values();
302         }
303 
304         // this is mainly the case when we are flattening node hierarchies
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      * Called once the type should have been resolved. The resolvedType might be
314      * null if no type has been resolved. Every subclass should override this method.
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             PropertyTypeDescriptor dscr = mapping.getPropertyTypeDescriptor(bean.getClass(), propertyName);
331             // For backwards compatibility with before this type check was present we support all the base classes of
332             // SystemContentWrapper and all the interfaces it implements
333             if (dscr.getType().getType().isAssignableFrom(SystemContentWrapper.class)) {
334                 value = new SystemContentWrapper(ContentUtil.asContent(state.getCurrentNode()));
335             } else  if (dscr.getType().getType().isAssignableFrom(Node.class)) {
336                 value = state.getCurrentNode();
337             }
338 
339         } else if (propertyName.equals("name") && value == null) {
340             value = state.getCurrentNode().getName();
341         } else if (propertyName.equals("className") && value == null) {
342             value = values.get("class");
343         }
344 
345         // do no try to set a bean-property that has no corresponding node-property
346         if (value == null) {
347             return;
348         }
349 
350         log.debug("try to set {}.{} with value {}", bean, propertyName, value);
351         // if the parent bean is a map, we can't guess the types.
352         if (!(bean instanceof Map)) {
353             try {
354                 PropertyTypeDescriptor dscr = mapping.getPropertyTypeDescriptor(bean.getClass(), propertyName);
355                 if (dscr.getType() != null) {
356                     // try to use a setter method for a Collection property of
357                     // the bean
358                     if (dscr.isCollection() || dscr.isMap() || dscr.isArray()) {
359                         log.debug("{} is of type collection, map or array", propertyName);
360                         if (dscr.getWriteMethod() != null) {
361                             Method method = dscr.getWriteMethod();
362                             clearCollection(bean, propertyName);
363                             filterOrConvertCollectionsAndMaps(dscr, value);
364                             if (dscr.isMap()) {
365                                 method.invoke(bean, value);
366                             } else if (dscr.isArray()) {
367                                 Class<?> entryClass = dscr.getCollectionEntryType().getType();
368                                 Collection<Object> list = new LinkedList<Object>(((Map<Object, Object>) value).values());
369 
370                                 Object[] arr = (Object[]) Array.newInstance(entryClass, list.size());
371                                 for (int i = 0; i < arr.length; i++) {
372                                     arr[i] = Iterables.get(list, i);
373                                 }
374                                 method.invoke(bean, new Object[]{arr});
375                             } else if (dscr.isCollection()) {
376                                 if (value instanceof Map) {
377                                     value = createCollectionFromMap((Map<Object, Object>) value, dscr.getType().getType());
378                                 }
379                                 method.invoke(bean, value);
380                             }
381                             return;
382                         } else if (dscr.getAddMethod() != null) {
383                             Method method = dscr.getAddMethod();
384                             clearCollection(bean, propertyName);
385                             Class<?> entryClass = dscr.getCollectionEntryType().getType();
386 
387                             log.warn("Will use deprecated add method [{}] to populate [{}] in bean class [{}].", method.getName(), propertyName, bean.getClass().getName());
388                             for (Iterator<Object> iter = ((Map<Object, Object>) value).keySet().iterator(); iter.hasNext(); ) {
389                                 Object key = iter.next();
390                                 Object entryValue = ((Map<Object, Object>) value).get(key);
391                                 entryValue = convertPropertyValue(entryClass, entryValue);
392                                 if (entryClass.isAssignableFrom(entryValue.getClass())) {
393                                     if (dscr.isCollection() || dscr.isArray()) {
394                                         log.debug("will add value {}", entryValue);
395                                         method.invoke(bean, entryValue);
396                                     }
397                                     // is a map
398                                     else {
399                                         log.debug("will add key {} with value {}", key, entryValue);
400                                         method.invoke(bean, key, entryValue);
401                                     }
402                                 }
403                             }
404                             return;
405                         }
406                         if (dscr.isCollection()) {
407                             log.debug("transform the values to a collection", propertyName);
408                             value = ((Map<Object, Object>) value).values();
409                         }
410                     } else {
411                         value = convertPropertyValue(dscr.getType().getType(), value);
412                     }
413                 }
414             } catch (Exception e) {
415                 handleSetPropertyException(state, propertyName, value, bean, e);
416                 return;
417             }
418         }
419 
420         try {
421             // This uses the converters registered in beanUtilsBean.convertUtilsBean (see constructor of this class)
422             // If a converter is registered, beanutils will determineBestMatch value.toString(), not the value object as-is.
423             // If no converter is registered, then the value Object is set as-is.
424             // If convertPropertyValue() already converted this value, you'll probably want to unregister the beanutils
425             // converter.
426             // some conversions like string to class. Performance of PropertyUtils.setProperty() would be better
427             beanUtilsBean.setProperty(bean, propertyName, value);
428         } catch (Exception e) {
429             handleSetPropertyException(state, propertyName, value, bean, e);
430         }
431     }
432 
433     @Override
434     public boolean canHandleValue(TypeMapping typeMapping, TransformationState state, PropertyTypeDescriptor descriptor, Entry<String, Object> value) {
435         return descriptor.getName().equals(value.getKey())
436                 || isCollectionOfStrings(typeMapping, state, value);
437     }
438 
439     private boolean isCollectionOfStrings(TypeMapping typeMapping, TransformationState state, Entry<String, Object> value) {
440         if (state.getCurrentBean() instanceof String && state.getLevel() > 2) {
441             try {
442                 PropertyTypeDescriptor propertyTypeDescriptor = state.peekType(2).getPropertyTypeDescriptor(state.peekNode(1).getName(), typeMapping);
443                 if (propertyTypeDescriptor != null) {
444                     TypeDescriptor entryType = propertyTypeDescriptor.getCollectionEntryType();
445                     if (entryType != null) {
446                         return entryType.getType().isAssignableFrom(value.getValue().getClass());
447                     }
448                 }
449             } catch (RepositoryException e) {
450                 log.error("Could not resolve property type for node [{}]", state.getCurrentNode(), e);
451             }
452         }
453         return false;
454     }
455 
456     @Override
457     public boolean supportsValueClaims() {
458         // sub-classes should opt-in explicitly
459         return getClass().equals(Node2BeanTransformerImpl.class);
460     }
461 
462     protected boolean isBeanEnabled(Object bean) {
463         Method method = null;
464         Object isEnabled = null;
465         try {
466             method = bean.getClass().getMethod("isEnabled");
467             // Check if return type is *not primitive* Boolean and if so return always true
468             // See ConfiguredAreaDefinition
469             if (method.getReturnType().equals(Boolean.class)) {
470                 return true;
471             }
472             isEnabled = method.invoke(bean);
473         } catch (NoSuchMethodException e) {
474             // this is ok, enabled property is optional
475             return true;
476         } catch (IllegalArgumentException e) {
477             // this should never happen
478             return true;
479         } catch (IllegalAccessException e) {
480             log.warn("Can't access method [{}#isEnabled]. Maybe it's private/protected?", bean.getClass());
481             return true;
482         } catch (InvocationTargetException e) {
483             log.error("An exception was thrown by [{}]#isEnabled method.", bean.getClass(), e);
484             return true;
485         }
486         return (Boolean) isEnabled;
487     }
488 
489     /**
490      * Filters out not matching values and converts values if required.
491      *
492      * @param dscr descriptor keeping information about the properties
493      * @param value object from which some element might be filtered or converted
494      */
495     private void filterOrConvertCollectionsAndMaps(final PropertyTypeDescriptor dscr, final Object value) {
496         if (dscr.getCollectionEntryType() != null) {
497             Class<?> entryClass = dscr.getCollectionEntryType().getType();
498             if (dscr.getType().isCollection() && value instanceof Collection) {
499                 final Collection<?> collection = ((Collection) value);
500                 final Collection convertedCollection = filterOrConvert(collection, entryClass);
501                 collection.clear();
502                 collection.addAll(convertedCollection);
503             } else if (value instanceof Map) {
504                 final Map map = (Map) value;
505                 final Map convertedMap = filterOrConvert(map, entryClass);
506                 map.clear();
507                 map.putAll(convertedMap);
508             }
509         }
510     }
511 
512     private Collection filterOrConvert(final Collection collection, final Class entryClass) {
513         final Collection converted = new ArrayList();
514         final Iterator<?> it = collection.iterator();
515         while (it.hasNext()) {
516             Object obj = it.next();
517             if (isBeanEnabled(obj)) {
518                 if (!entryClass.isAssignableFrom(obj.getClass())) {
519                     final Converter converter = beanUtilsBean.getConvertUtils().lookup(entryClass);
520                     if (converter != null) {
521                         converted.add(converter.convert(entryClass, obj));
522                     }
523                 } else {
524                     converted.add(obj);
525                 }
526             }
527         }
528         return converted;
529     }
530 
531     private Map filterOrConvert(final Map map, final Class entryClass) {
532         final Map converted = new LinkedHashMap();
533         for (Object key : map.keySet()) {
534             Object obj = map.get(key);
535             if (isBeanEnabled(obj)) {
536                 if (!entryClass.isAssignableFrom(obj.getClass())) {
537                     final Converter converter = beanUtilsBean.getConvertUtils().lookup(entryClass);
538                     if (converter != null) {
539                         converted.put(key, converter.convert(entryClass, obj));
540                     }
541                 } else {
542                     converted.put(key, obj);
543                 }
544             }
545         }
546         return converted;
547     }
548 
549     private void clearCollection(Object bean, String propertyName) {
550         log.debug("clearing the current content of the collection/map");
551         try {
552             Object col = PropertyUtils.getProperty(bean, propertyName);
553             if (col != null) {
554                 MethodUtils.invokeExactMethod(col, "clear", new Object[]{});
555             }
556         } catch (Exception e) {
557             log.debug("no clear method found on collection {}", propertyName);
558         }
559     }
560 
561     /**
562      * Creates collection from map. Collection type depends on passed class parameter. If passed class parameter is
563      * interface, then default implementation will be used for creating collection.<br/>
564      * By default
565      * <ul>
566      * <li>{@link LinkedList} is used for creating List and Queue collections.</li>
567      * <li>{@link HashSet} is used for creating Set collection.</li>
568      * </ul>
569      * If passed class parameter is an implementation of any collection type, then this method will create
570      * this implementation and returns it.
571      *
572      * @param map a map which values will be converted to a collection
573      * @param clazz collection type
574      * @return Collection of elements or null.
575      */
576     protected Collection<?> createCollectionFromMap(Map<?, ?> map, Class<?> clazz) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
577         Collection<?> collection = null;
578         Constructor<?> constructor = null;
579         if (clazz.isInterface()) {
580             // class is an interface, we need to decide which implementation of interface we will use
581             if (List.class.isAssignableFrom(clazz)) {
582                 constructor = defaultListImpl.getConstructor(Collection.class);
583             } else if (clazz.isAssignableFrom(Queue.class)) {
584                 constructor = defaultQueueImpl.getConstructor(Collection.class);
585             } else if (Set.class.isAssignableFrom(clazz)) {
586                 constructor = defaultSetImpl.getConstructor(Collection.class);
587             }
588         } else {
589             if (Collection.class.isAssignableFrom(clazz)) {
590                 constructor = clazz.getConstructor(Collection.class);
591             }
592         }
593         if (constructor != null) {
594             collection = (Collection<?>) constructor.newInstance(map.values());
595         }
596         return collection;
597     }
598 
599     private void handleSetPropertyException(TransformationState state, String propertyName, Object value, Object bean, Exception e) throws RepositoryException /* See MAGNOLIA-5890, Node2BeanException */ {
600         if (e instanceof InvocationTargetException) {
601             e = (Exception) e.getCause();
602         }
603         final String errorMessage = String.format("Can't set property [%s] to value [%s] in bean [%s] for node [%s] due to: [%s]", propertyName, value, bean.getClass().getName(), state.getCurrentNode().getPath(), e.toString());
604 
605         state.trackProblem(error(errorMessage).withException(e));
606     }
607 
608 }