1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.config.map2bean;
35
36 import info.magnolia.jcr.node2bean.PropertyTypeDescriptor;
37 import info.magnolia.jcr.node2bean.TypeDescriptor;
38 import info.magnolia.jcr.node2bean.TypeMapping;
39 import info.magnolia.jcr.node2bean.impl.PreConfiguredBeanUtils;
40 import info.magnolia.objectfactory.ClassFactory;
41 import info.magnolia.objectfactory.Classes;
42 import info.magnolia.objectfactory.ComponentProvider;
43
44 import java.lang.reflect.InvocationTargetException;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.LinkedHashMap;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Map;
53
54 import javax.inject.Inject;
55
56 import org.apache.commons.beanutils.Converter;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60
61
62
63
64 public class Map2BeanTransformer {
65
66 private static final Map<Class, Class> DEFAULT_TYPES = new HashMap<>();
67
68 static {
69 DEFAULT_TYPES.put(Object.class, LinkedHashMap.class);
70 DEFAULT_TYPES.put(Map.class, LinkedHashMap.class);
71 DEFAULT_TYPES.put(Collection.class, LinkedList.class);
72
73 }
74
75 private static final Logger log = LoggerFactory.getLogger(Map2BeanTransformer.class);
76
77 private final ComponentProvider componentProvider;
78 private final TypeMapping mapping;
79 private final PreConfiguredBeanUtils beanUtils;
80
81 @Inject
82 public Map2BeanTransformer(ComponentProvider componentProvider, TypeMapping mapping, PreConfiguredBeanUtils beanUtils) {
83 this.componentProvider = componentProvider;
84 this.mapping = mapping;
85 this.beanUtils = beanUtils;
86 }
87
88 public <T> T toBean(Map<String, Object> map, Class<T> defaultRootType) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, ConfigurationParsingException {
89 if (map == null) {
90 log.debug("toBean was invoked with a null map, will try to create bean out of an empty map.");
91 map = Collections.emptyMap();
92 }
93 TypeDescriptor defaultRootTypeDescriptor = mapping.getTypeDescriptor(defaultRootType);
94
95 return readValue(map, defaultRootTypeDescriptor);
96 }
97
98 private <T> T readValue(Object source, TypeDescriptor defaultTargetTypeDescriptor) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, ConfigurationParsingException {
99 if (source == null) {
100 return null;
101 }
102
103 TypeDescriptor sourceTypeDescriptor = mapping.getTypeDescriptor(source.getClass());
104
105 if (needSimpleMapping(sourceTypeDescriptor, defaultTargetTypeDescriptor)) {
106 return readSimpleValue(source, defaultTargetTypeDescriptor);
107 } else {
108 return readComplexValue(source, defaultTargetTypeDescriptor);
109 }
110 }
111
112 private boolean needSimpleMapping(TypeDescriptor sourceTypeDescriptor, TypeDescriptor defaultTargetTypeDescriptor) {
113 Converter converter = beanUtils.getConvertUtils().lookup(sourceTypeDescriptor.getType(), defaultTargetTypeDescriptor.getType());
114
115 if (converter != null) {
116
117 return true;
118 } else {
119 if (defaultTargetTypeDescriptor.getType() == Object.class) {
120
121 return beanUtils.getConvertUtils().lookup(sourceTypeDescriptor.getType()) != null;
122 }
123
124 return false;
125 }
126 }
127
128 private <T> T readSimpleValue(Object source, TypeDescriptor defaultTargetTypeDescriptor) {
129 return (T) beanUtils.getConvertUtils().convert(source, defaultTargetTypeDescriptor.getType());
130 }
131
132 private <T> T readComplexValue(Object source, TypeDescriptor defaultTargetTypeDescriptor) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ConfigurationParsingException {
133 if (source instanceof Collection<?>) {
134 return (T) readOutList((Collection<Object>) source, mapping.getTypeDescriptor(Object.class));
135 }
136 Map<String, Object> sourceMap = (Map<String, Object>) source;
137 TypeDescriptor targetTypeDescriptor = resolveType(sourceMap, defaultTargetTypeDescriptor);
138 targetTypeDescriptor = resolveType(sourceMap, targetTypeDescriptor);
139 if (targetTypeDescriptor.getType() == Object.class || targetTypeDescriptor.isMap()) {
140 return (T) readOutMap(sourceMap, mapping.getTypeDescriptor(Object.class));
141 } else {
142 return (T) readOutObject(sourceMap, targetTypeDescriptor);
143 }
144 }
145
146 private <T> T readOutObject(Map<String, Object> sourceMap, TypeDescriptor targetType) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, ConfigurationParsingException {
147 T targetObj = (T) componentProvider.newInstance(targetType.getType());
148
149 Map<String, String> targetDescription = beanUtils.describe(targetObj);
150
151 for (Map.Entry<String, Object> sourceEntry : sourceMap.entrySet()) {
152 String sourcePropertyName = sourceEntry.getKey();
153 Object sourcePropertyValue = sourceEntry.getValue();
154
155 if ("class".equals(sourcePropertyName)) {
156 continue;
157 }
158
159 if (!targetDescription.containsKey(sourcePropertyName)) {
160
161 log.warn("Property " + sourcePropertyName + " not found in class " + targetObj.getClass());
162 continue;
163 }
164
165 PropertyTypeDescriptor propertyTypeDescriptor = mapping.getPropertyTypeDescriptor(targetType.getType(), sourcePropertyName);
166
167 Object value;
168 if (propertyTypeDescriptor.isCollection()) {
169 value = readOutList((List<Object>) sourcePropertyValue, propertyTypeDescriptor.getCollectionEntryType());
170 } else if (propertyTypeDescriptor.isMap()) {
171 value = readOutMap((Map<String, Object>) sourcePropertyValue, propertyTypeDescriptor.getCollectionEntryType());
172 } else {
173 value = readValue(sourcePropertyValue, propertyTypeDescriptor.getType());
174 }
175
176 beanUtils.setProperty(targetObj, sourcePropertyName, value);
177 }
178
179 return targetObj;
180 }
181
182 private Object readOutList(Collection<?> sourceList, TypeDescriptor mapValueTypeDescriptor) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, ConfigurationParsingException {
183 if (sourceList == null) {
184 return null;
185 }
186
187 List<Object> targetList = new ArrayList<>(sourceList.size());
188
189 for (Object sourceElement : sourceList) {
190 targetList.add(readValue(sourceElement, mapValueTypeDescriptor));
191 }
192
193 return targetList;
194 }
195
196 private Object readOutMap(Map<String, Object> sourceMap, TypeDescriptor listElementTypeDescriptor) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ConfigurationParsingException {
197 if (sourceMap == null) {
198 return null;
199 }
200
201 Map<String, Object> targetMap = new LinkedHashMap<>(sourceMap.size());
202
203 for (Map.Entry<String, Object> sourceEntry : sourceMap.entrySet()) {
204 Object value = readValue(sourceEntry.getValue(), listElementTypeDescriptor);
205
206
207 Map<String, String> valueDescription = beanUtils.describe(value);
208 if (valueDescription.containsKey("name") && beanUtils.getProperty(value, "name") == null) {
209 beanUtils.setProperty(value, "name", sourceEntry.getKey());
210 }
211
212 targetMap.put(sourceEntry.getKey(), value);
213 }
214 return targetMap;
215 }
216
217 private TypeDescriptor resolveType(Map<String, Object> map, TypeDescriptor defaultType) throws ConfigurationParsingException {
218 final TypeDescriptor targetType;
219
220 if (map.containsKey("class")) {
221 String className = (String) map.get("class");
222 ClassFactory classFactory = Classes.getClassFactory();
223 try {
224 Class<?> loadClass = classFactory.forName(className);
225 targetType = mapping.getTypeDescriptor(loadClass);
226 } catch (ClassNotFoundException e) {
227 throw new ConfigurationParsingException("The classname [" + className + "] in the class attribute could not be found in the classpath", e);
228 }
229 } else {
230 targetType = defaultType;
231 }
232
233
234 final TypeDescriptor mappedType;
235 try {
236
237
238 final Class<?> implClass = componentProvider.getImplementation(targetType.getType());
239 mappedType = mapping.getTypeDescriptor(implClass);
240 } catch (ClassNotFoundException e) {
241 throw new ConfigurationParsingException("We don't know the implementation type to be used for [" + targetType.getType() + "]", e);
242 }
243
244
245 if (DEFAULT_TYPES.containsKey(mappedType.getType())) {
246 return mapping.getTypeDescriptor(DEFAULT_TYPES.get(mappedType.getType()));
247 }
248
249 return mappedType;
250 }
251 }