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