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.jcr.util.PropertyUtil;
50  import info.magnolia.objectfactory.Classes;
51  import info.magnolia.objectfactory.ComponentProvider;
52  import info.magnolia.objectfactory.Components;
53  import info.magnolia.transformer.BeanTypeResolver;
54  
55  import java.lang.reflect.Array;
56  import java.lang.reflect.Constructor;
57  import java.lang.reflect.InvocationTargetException;
58  import java.lang.reflect.Method;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.Collection;
62  import java.util.HashSet;
63  import java.util.LinkedHashMap;
64  import java.util.LinkedList;
65  import java.util.List;
66  import java.util.Locale;
67  import java.util.Map;
68  import java.util.Map.Entry;
69  import java.util.Queue;
70  import java.util.Set;
71  import java.util.stream.Collectors;
72  
73  import javax.inject.Inject;
74  import javax.jcr.Node;
75  import javax.jcr.NodeIterator;
76  import javax.jcr.Property;
77  import javax.jcr.PropertyIterator;
78  import javax.jcr.RepositoryException;
79  
80  import org.apache.commons.beanutils.BeanUtilsBean;
81  import org.apache.commons.beanutils.Converter;
82  import org.apache.commons.beanutils.MethodUtils;
83  import org.apache.commons.beanutils.PropertyUtils;
84  import org.apache.commons.lang3.LocaleUtils;
85  import org.apache.commons.lang3.StringUtils;
86  import org.slf4j.Logger;
87  import org.slf4j.LoggerFactory;
88  
89  import com.google.common.collect.Iterables;
90  import com.google.common.collect.Maps;
91  
92  /**
93   * Concrete implementation using reflection, generics and setter methods.
94   */
95  public class Node2BeanTransformerImpl implements Node2BeanTransformer {
96  
97      private static final Logger log = LoggerFactory.getLogger(Node2BeanTransformerImpl.class);
98  
99      private final BeanUtilsBean beanUtilsBean;
100 
101     private final Class<?> defaultListImpl;
102 
103     private final Class<?> defaultSetImpl;
104 
105     private final Class<?> defaultQueueImpl;
106 
107     private final BeanTypeResolver beanTypeResolver;
108 
109     @Inject
110     public Node2BeanTransformerImpl(PreConfiguredBeanUtils beanUtilsBean, BeanTypeResolver beanTypeResolver) {
111         this(beanUtilsBean, LinkedList.class, HashSet.class, LinkedList.class, beanTypeResolver);
112     }
113 
114     /**
115      * @deprecated since 5.4 - use IoC, don't instantiate directly. You should not have to instantiate this if you
116      * use magnolia-configuration mechanisms introduced in 5.4.
117      */
118     @Deprecated
119     public Node2BeanTransformerImpl() {
120         this(Components.getComponent(PreConfiguredBeanUtils.class), new BeanTypeResolver());
121     }
122 
123     /**
124      * @deprecated since 5.5.1 - use {@link #Node2BeanTransformerImpl(PreConfiguredBeanUtils, BeanTypeResolver)} instead.
125      */
126     @Deprecated
127     public Node2BeanTransformerImpl(PreConfiguredBeanUtils beanUtilsBean) {
128         this(beanUtilsBean, LinkedList.class, HashSet.class, LinkedList.class, new BeanTypeResolver());
129     }
130 
131     /**
132      * @deprecated since 5.4 - use IoC, don't instantiate directly. You should not have to instantiate this if you
133      * use magnolia-configuration mechanisms introduced in 5.4.
134      */
135     @Deprecated
136     public Node2BeanTransformerImpl(Class<?> defaultListImpl, Class<?> defaultSetImpl, Class<?> defaultQueueImpl) {
137         this(Components.getComponent(PreConfiguredBeanUtils.class), defaultListImpl, defaultSetImpl, defaultQueueImpl, Components.getComponent(BeanTypeResolver.class));
138     }
139 
140     protected Node2BeanTransformerImpl(PreConfiguredBeanUtils beanUtilsBean, Class<?> defaultListImpl, Class<?> defaultSetImpl, Class<?> defaultQueueImpl, BeanTypeResolver beanTypeResolver) {
141         this.beanUtilsBean = beanUtilsBean;
142         this.defaultListImpl = defaultListImpl;
143         this.defaultSetImpl = defaultSetImpl;
144         this.defaultQueueImpl = defaultQueueImpl;
145         this.beanTypeResolver = beanTypeResolver;
146 
147         beanUtilsBean.getConvertUtils().deregister(Class.class);
148     }
149 
150     @Override
151     public TransformationState newState() {
152         return new TransformationStateImpl();
153     }
154 
155     @Override
156     public TypeDescriptor resolveType(TypeMapping typeMapping, TransformationState state, ComponentProvider componentProvider) throws ClassNotFoundException, RepositoryException {
157         Node node = state.getCurrentNode();
158 
159         Map<String, Object> properties = convertPropertiesToMap(node.getProperties());
160 
161         TypeDescriptor typeDscr = null;
162         if (state.getLevel() > 1) {
163             TypeDescriptor parentTypeDscr = state.getCurrentType();
164             PropertyTypeDescriptor propDscr;
165 
166             if (parentTypeDscr.isMap() || parentTypeDscr.isCollection()) {
167                 if (state.getLevel() > 2) {
168                     // this is not necessarily the parent node of the current
169                     String mapProperyName = state.peekNode(1).getName();
170                     propDscr = state.peekType(1).getPropertyTypeDescriptor(mapProperyName, typeMapping);
171                     if (propDscr != null) {
172                         typeDscr = propDscr.getCollectionEntryType();
173                     }
174                 }
175             } else {
176                 propDscr = state.getCurrentType().getPropertyTypeDescriptor(node.getName(), typeMapping);
177                 if (propDscr != null) {
178                     typeDscr = propDscr.getType();
179                 }
180             }
181         }
182 
183         typeDscr = beanTypeResolver.resolve(typeDscr, properties).map(typeMapping::getTypeDescriptor).orElse(typeDscr);
184         typeDscr = onResolveType(typeMapping, state, typeDscr, componentProvider);
185 
186         if (typeDscr != null) {
187             // might be that the factory util defines a default implementation for interfaces
188             final Class<?> type = typeDscr.getType();
189             typeDscr = typeMapping.getTypeDescriptor(componentProvider.getImplementation(type));
190 
191             // now that we know the property type we should delegate to the custom transformer if any defined
192             Node2BeanTransformer customTransformer = typeDscr.getTransformer();
193             if (customTransformer != null && customTransformer != this) {
194                 TypeDescriptor typeFoundByCustomTransformer = customTransformer.resolveType(typeMapping, state, componentProvider);
195                 // if no specific type has been provided by the
196                 // TODO - TypeDescriptor - equals and hashCode impl and use
197                 // not equals instead of !=
198                 if (typeFoundByCustomTransformer != TypeMapping.MAP_TYPE) {
199                     // might be that the factory util defines a default implementation for interfaces
200                     Class<?> implementation = componentProvider.getImplementation(typeFoundByCustomTransformer.getType());
201                     typeDscr = typeMapping.getTypeDescriptor(implementation);
202                 }
203             }
204         }
205 
206         if (typeDscr == null || typeDscr.needsDefaultMapping()) {
207             if (typeDscr == null) {
208                 log.debug("Was not able to resolve type for node [{}] will use a map", node);
209             }
210             typeDscr = TypeMapping.MAP_TYPE;
211         }
212         log.debug("Resolved type [{}] for node [{}]", typeDscr.getType(), node.getPath());
213 
214         return typeDscr;
215     }
216 
217     private Map<String, Object> convertPropertiesToMap(PropertyIterator iterator) throws RepositoryException {
218         Map<String, Object> propertyMap = Maps.newHashMap();
219         while (iterator.hasNext()) {
220             Property property = iterator.nextProperty();
221             final Object propertyValue;
222             if (property.isMultiple()) {
223                propertyValue = Arrays.stream(property.getValues()).map(PropertyUtil::getValueObject).collect(Collectors.toList());
224             } else {
225                propertyValue = PropertyUtil.getValueObject(property.getValue());
226             }
227             propertyMap.put(property.getName(), propertyValue);
228         }
229         return propertyMap;
230     }
231 
232     @Override
233     public NodeIterator getChildren(Node node) throws RepositoryException {
234         // TODO create predicate into separate class, <? extends Item> ItemHidingPredicate (regexp)
235         return new FilteringNodeIterator(node.getNodes(), new AbstractPredicate<Node>() {
236             @Override
237             public boolean evaluateTyped(Node t) {
238                 try {
239                     return !(t.getName().startsWith(NodeTypes.JCR_PREFIX) || t.isNodeType(NodeTypes.MetaData.NAME));
240                 } catch (RepositoryException e) {
241                     return false;
242                 }
243             }
244         });
245     }
246 
247     @Override
248     public Object newBeanInstance(TransformationState state, Map<String, Object> values, ComponentProvider componentProvider) throws Node2BeanException {
249         // we try first to use conversion (Map --> primitive type)
250         // this is the case when we flattening the hierarchy?
251         final Object bean = convertPropertyValue(state.getCurrentType().getType(), values);
252         // were the properties transformed?
253         if (bean == values) {
254             try {
255                 // is this property remove necessary?
256                 values.remove("class");
257                 final Class<?> type = state.getCurrentType().getType();
258                 if (LinkedHashMap.class.equals(type)) {
259                     // TODO - as far as I can tell, "bean" and "properties" are already the same instance of a
260                     // LinkedHashMap, so what are we doing in here ?
261                     return new LinkedHashMap();
262                 } else if (Map.class.isAssignableFrom(type)) {
263                     // TODO ?
264                     log.warn("someone wants another type of map ? {}", type);
265                 } else if (Collection.class.isAssignableFrom(type)) {
266                     // someone wants specific collection
267                     return type.newInstance();
268                 }
269                 return componentProvider.newInstance(type);
270             } catch (Exception e) {
271                 // This error is not tracked here because by current design the N2B conversion is supossed to blow up.
272                 // 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)
273                 // or, in case of the old API - we don't care about error tracking that much and will just let exception fly.
274                 throw new Node2BeanException(
275                         String.format("Failed to instantiate a bean of type [%s] due to: [%s]", state.getCurrentType().getType().getName(), e.getMessage()), e);
276             }
277         }
278         return bean;
279     }
280 
281     @Override
282     public void initBean(TransformationState state, Map values) throws Node2BeanException {
283         Object bean = state.getCurrentBean();
284 
285         Method init;
286         try {
287             init = bean.getClass().getMethod("init");
288             try {
289                 init.invoke(bean); // no parameters
290             } catch (Exception e) {
291                 throw new Node2BeanException("can't call init method", e);
292             }
293         } catch (SecurityException | NoSuchMethodException e) {
294             return;
295         }
296         log.debug("{} is initialized", bean);
297 
298     }
299 
300     @Override
301     public Object convertPropertyValue(Class<?> propertyType, Object value) throws Node2BeanException {
302         if (Class.class.equals(propertyType)) {
303             try {
304                 return Classes.getClassFactory().forName(value.toString());
305             } catch (ClassNotFoundException e) {
306                 // we let this error to be tracked by handleSetPropertyException, which is a common funnel for property-related failures
307                 throw new Node2BeanException(String.format("Can't convert property. Class for type [%s] not found.", propertyType), e);
308             }
309         }
310 
311         if (Locale.class.equals(propertyType)) {
312             if (value instanceof String) {
313                 String localeStr = (String) value;
314                 if (StringUtils.isNotEmpty(localeStr)) {
315                     return LocaleUtils.toLocale(localeStr);
316                 }
317             }
318         }
319 
320         if (Collection.class.equals(propertyType) && value instanceof Map) {
321             // TODO never used ?
322             return ((Map) value).values();
323         }
324 
325         // this is mainly the case when we are flattening node hierarchies
326         if (String.class.equals(propertyType) && value instanceof Map && ((Map) value).size() == 1) {
327             return ((Map) value).values().iterator().next();
328         }
329 
330         return value;
331     }
332 
333     /**
334      * Called once the type should have been resolved. The resolvedType might be
335      * null if no type has been resolved. Every subclass should override this method.
336      */
337     protected TypeDescriptor onResolveType(TypeMapping typeMapping, TransformationState state, TypeDescriptor resolvedType, ComponentProvider componentProvider) {
338         return resolvedType;
339     }
340 
341     @Override
342     public void setProperty(TypeMapping mapping, TransformationState state, PropertyTypeDescriptor descriptor, Map<String, Object> values) throws RepositoryException {
343         String propertyName = descriptor.getName();
344         if (propertyName.equals("class")) {
345             return;
346         }
347         Object value = values.get(propertyName);
348         Object bean = state.getCurrentBean();
349 
350         if (propertyName.equals("content") && value == null) {
351             PropertyTypeDescriptor dscr = mapping.getPropertyTypeDescriptor(bean.getClass(), propertyName);
352             // For backwards compatibility with before this type check was present we support all the base classes of
353             // SystemContentWrapper and all the interfaces it implements
354             if (dscr.getType().getType().isAssignableFrom(SystemContentWrapper.class)) {
355                 value = new SystemContentWrapper(ContentUtil.asContent(state.getCurrentNode()));
356             } else  if (dscr.getType().getType().isAssignableFrom(Node.class)) {
357                 value = state.getCurrentNode();
358             }
359 
360         } else if (propertyName.equals("name") && value == null) {
361             value = state.getCurrentNode().getName();
362         } else if (propertyName.equals("className") && value == null) {
363             value = values.get("class");
364         }
365 
366         // do no try to set a bean-property that has no corresponding node-property
367         if (value == null) {
368             return;
369         }
370 
371         log.debug("try to set {}.{} with value {}", bean, propertyName, value);
372         // if the parent bean is a map, we can't guess the types.
373         if (!(bean instanceof Map)) {
374             try {
375                 PropertyTypeDescriptor dscr = mapping.getPropertyTypeDescriptor(bean.getClass(), propertyName);
376                 if (dscr.getType() != null) {
377                     // try to use a setter method for a Collection property of
378                     // the bean
379                     if (dscr.isCollection() || dscr.isMap() || dscr.isArray()) {
380                         log.debug("{} is of type collection, map or array", propertyName);
381                         if (dscr.getWriteMethod() != null) {
382                             Method method = dscr.getWriteMethod();
383                             clearCollection(bean, propertyName);
384                             filterOrConvertCollectionsAndMaps(dscr, value);
385                             if (dscr.isMap()) {
386                                 method.invoke(bean, value);
387                             } else if (dscr.isArray()) {
388                                 Class<?> entryClass = dscr.getCollectionEntryType().getType();
389                                 Collection<Object> list = new LinkedList<>(((Map<Object, Object>) value).values());
390 
391                                 Object[] arr = (Object[]) Array.newInstance(entryClass, list.size());
392                                 for (int i = 0; i < arr.length; i++) {
393                                     arr[i] = Iterables.get(list, i);
394                                 }
395                                 method.invoke(bean, new Object[]{arr});
396                             } else if (dscr.isCollection()) {
397                                 if (value instanceof Map) {
398                                     value = createCollectionFromMap((Map<Object, Object>) value, dscr.getType().getType());
399                                 }
400                                 method.invoke(bean, value);
401                             }
402                             return;
403                         } else if (dscr.getAddMethod() != null) {
404                             Method method = dscr.getAddMethod();
405                             clearCollection(bean, propertyName);
406                             Class<?> entryClass = dscr.getCollectionEntryType().getType();
407 
408                             log.warn("Will use deprecated add method [{}] to populate [{}] in bean class [{}].", method.getName(), propertyName, bean.getClass().getName());
409                             for (Object key : ((Map<Object, Object>) value).keySet()) {
410                                 Object entryValue = ((Map<Object, Object>) value).get(key);
411                                 entryValue = convertPropertyValue(entryClass, entryValue);
412                                 if (entryClass.isAssignableFrom(entryValue.getClass())) {
413                                     if (dscr.isCollection() || dscr.isArray()) {
414                                         log.debug("will add value {}", entryValue);
415                                         method.invoke(bean, entryValue);
416                                     }
417                                     // is a map
418                                     else {
419                                         log.debug("will add key {} with value {}", key, entryValue);
420                                         method.invoke(bean, key, entryValue);
421                                     }
422                                 }
423                             }
424                             return;
425                         }
426                         if (dscr.isCollection()) {
427                             log.debug("transform the values to a collection", propertyName);
428                             value = ((Map<Object, Object>) value).values();
429                         }
430                     } else {
431                         value = convertPropertyValue(dscr.getType().getType(), value);
432                     }
433                 }
434             } catch (Exception e) {
435                 handleSetPropertyException(state, propertyName, value, bean, e);
436                 return;
437             }
438         }
439 
440         try {
441             // This uses the converters registered in beanUtilsBean.convertUtilsBean (see constructor of this class)
442             // If a converter is registered, beanutils will determineBestMatch value.toString(), not the value object as-is.
443             // If no converter is registered, then the value Object is set as-is.
444             // If convertPropertyValue() already converted this value, you'll probably want to unregister the beanutils
445             // converter.
446             // some conversions like string to class. Performance of PropertyUtils.setProperty() would be better
447             beanUtilsBean.setProperty(bean, propertyName, value);
448         } catch (Exception e) {
449             handleSetPropertyException(state, propertyName, value, bean, e);
450         }
451     }
452 
453     @Override
454     public boolean canHandleValue(TypeMapping typeMapping, TransformationState state, PropertyTypeDescriptor descriptor, Entry<String, Object> value) {
455         return descriptor.getName().equals(value.getKey())
456                 || isCollectionOfStrings(typeMapping, state, value);
457     }
458 
459     private boolean isCollectionOfStrings(TypeMapping typeMapping, TransformationState state, Entry<String, Object> value) {
460         if (state.getCurrentBean() instanceof String && state.getLevel() > 2) {
461             try {
462                 PropertyTypeDescriptor propertyTypeDescriptor = state.peekType(2).getPropertyTypeDescriptor(state.peekNode(1).getName(), typeMapping);
463                 if (propertyTypeDescriptor != null) {
464                     TypeDescriptor entryType = propertyTypeDescriptor.getCollectionEntryType();
465                     if (entryType != null) {
466                         return entryType.getType().isAssignableFrom(value.getValue().getClass());
467                     }
468                 }
469             } catch (RepositoryException e) {
470                 log.error("Could not resolve property type for node [{}]", state.getCurrentNode(), e);
471             }
472         }
473         return false;
474     }
475 
476     @Override
477     public boolean supportsValueClaims() {
478         // sub-classes should opt-in explicitly
479         return getClass().equals(Node2BeanTransformerImpl.class);
480     }
481 
482     protected boolean isBeanEnabled(Object bean) {
483         Method method;
484         Object isEnabled;
485         try {
486             method = bean.getClass().getMethod("isEnabled");
487             // Check if return type is *not primitive* Boolean and if so return always true
488             // See ConfiguredAreaDefinition
489             if (method.getReturnType().equals(Boolean.class)) {
490                 return true;
491             }
492             isEnabled = method.invoke(bean);
493         } catch (NoSuchMethodException e) {
494             // this is ok, enabled property is optional
495             return true;
496         } catch (IllegalArgumentException e) {
497             // this should never happen
498             return true;
499         } catch (IllegalAccessException e) {
500             log.warn("Can't access method [{}#isEnabled]. Maybe it's private/protected?", bean.getClass());
501             return true;
502         } catch (InvocationTargetException e) {
503             log.error("An exception was thrown by [{}]#isEnabled method.", bean.getClass(), e);
504             return true;
505         }
506         return (Boolean) isEnabled;
507     }
508 
509     /**
510      * Filters out not matching values and converts values if required.
511      *
512      * @param dscr descriptor keeping information about the properties
513      * @param value object from which some element might be filtered or converted
514      */
515     private void filterOrConvertCollectionsAndMaps(final PropertyTypeDescriptor dscr, final Object value) {
516         if (dscr.getCollectionEntryType() != null) {
517             Class<?> entryClass = dscr.getCollectionEntryType().getType();
518             if (dscr.getType().isCollection() && value instanceof Collection) {
519                 final Collection<?> collection = ((Collection) value);
520                 final Collection convertedCollection = filterOrConvert(collection, entryClass);
521                 collection.clear();
522                 collection.addAll(convertedCollection);
523             } else if (value instanceof Map) {
524                 final Map map = (Map) value;
525                 final Map convertedMap = filterOrConvert(map, entryClass);
526                 map.clear();
527                 map.putAll(convertedMap);
528             }
529         }
530     }
531 
532     private Collection filterOrConvert(final Collection collection, final Class entryClass) {
533         final Collection converted = new ArrayList();
534         for (Object obj : collection) {
535             if (isBeanEnabled(obj)) {
536                 if (!entryClass.isAssignableFrom(obj.getClass())) {
537                     final Converter converter = beanUtilsBean.getConvertUtils().lookup(entryClass);
538                     if (converter != null) {
539                         converted.add(converter.convert(entryClass, obj));
540                     }
541                 } else {
542                     converted.add(obj);
543                 }
544             }
545         }
546         return converted;
547     }
548 
549     private Map filterOrConvert(final Map map, final Class entryClass) {
550         final Map converted = new LinkedHashMap();
551         for (Object key : map.keySet()) {
552             Object obj = map.get(key);
553             if (isBeanEnabled(obj)) {
554                 if (!entryClass.isAssignableFrom(obj.getClass())) {
555                     final Converter converter = beanUtilsBean.getConvertUtils().lookup(entryClass);
556                     if (converter != null) {
557                         converted.put(key, converter.convert(entryClass, obj));
558                     }
559                 } else {
560                     converted.put(key, obj);
561                 }
562             }
563         }
564         return converted;
565     }
566 
567     private void clearCollection(Object bean, String propertyName) {
568         log.debug("clearing the current content of the collection/map");
569         try {
570             Object col = PropertyUtils.getProperty(bean, propertyName);
571             if (col != null) {
572                 MethodUtils.invokeExactMethod(col, "clear", new Object[]{});
573             }
574         } catch (Exception e) {
575             log.debug("no clear method found on collection {}", propertyName);
576         }
577     }
578 
579     /**
580      * Creates collection from map. Collection type depends on passed class parameter. If passed class parameter is
581      * interface, then default implementation will be used for creating collection.<br/>
582      * By default
583      * <ul>
584      * <li>{@link LinkedList} is used for creating List and Queue collections.</li>
585      * <li>{@link HashSet} is used for creating Set collection.</li>
586      * </ul>
587      * If passed class parameter is an implementation of any collection type, then this method will create
588      * this implementation and returns it.
589      *
590      * @param map a map which values will be converted to a collection
591      * @param clazz collection type
592      * @return Collection of elements or null.
593      */
594     protected Collection<?> createCollectionFromMap(Map<?, ?> map, Class<?> clazz) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
595         Collection<?> collection = null;
596         Constructor<?> constructor = null;
597         if (clazz.isInterface()) {
598             // class is an interface, we need to decide which implementation of interface we will use
599             if (List.class.isAssignableFrom(clazz)) {
600                 constructor = defaultListImpl.getConstructor(Collection.class);
601             } else if (clazz.isAssignableFrom(Queue.class)) {
602                 constructor = defaultQueueImpl.getConstructor(Collection.class);
603             } else if (Set.class.isAssignableFrom(clazz)) {
604                 constructor = defaultSetImpl.getConstructor(Collection.class);
605             }
606         } else {
607             if (Collection.class.isAssignableFrom(clazz)) {
608                 constructor = clazz.getConstructor(Collection.class);
609             }
610         }
611         if (constructor != null) {
612             collection = (Collection<?>) constructor.newInstance(map.values());
613         }
614         return collection;
615     }
616 
617     private void handleSetPropertyException(TransformationState state, String propertyName, Object value, Object bean, Exception e) throws RepositoryException /* See MAGNOLIA-5890, Node2BeanException */ {
618         if (e instanceof InvocationTargetException) {
619             e = (Exception) e.getCause();
620         }
621         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());
622 
623         state.trackProblem(error(errorMessage).withException(e));
624     }
625 
626 }