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.content2bean.impl;
35
36 import info.magnolia.cms.core.Content;
37 import info.magnolia.cms.util.ContentUtil;
38 import info.magnolia.cms.util.SystemContentWrapper;
39 import info.magnolia.content2bean.Content2BeanException;
40 import info.magnolia.content2bean.Content2BeanTransformer;
41 import info.magnolia.content2bean.PropertyTypeDescriptor;
42 import info.magnolia.content2bean.TransformationState;
43 import info.magnolia.content2bean.TypeDescriptor;
44 import info.magnolia.content2bean.TypeMapping;
45 import info.magnolia.objectfactory.Classes;
46 import info.magnolia.objectfactory.ComponentProvider;
47
48 import java.lang.reflect.Method;
49 import java.util.Collection;
50 import java.util.Iterator;
51 import java.util.LinkedHashMap;
52 import java.util.Locale;
53 import java.util.Map;
54
55 import javax.inject.Inject;
56 import javax.inject.Singleton;
57 import javax.jcr.RepositoryException;
58
59 import org.apache.commons.beanutils.BeanUtilsBean;
60 import org.apache.commons.beanutils.MethodUtils;
61 import org.apache.commons.beanutils.PropertyUtils;
62 import org.apache.commons.beanutils.PropertyUtilsBean;
63 import org.apache.commons.lang.LocaleUtils;
64 import org.apache.commons.lang.StringUtils;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68
69
70
71 @Singleton
72 public class Content2BeanTransformerImpl implements Content2BeanTransformer, Content.ContentFilter {
73
74 private static final Logger log = LoggerFactory.getLogger(Content2BeanTransformerImpl.class);
75
76 private final BeanUtilsBean beanUtilsBean;
77
78
79
80
81 @Inject
82 private TypeMapping typeMapping;
83
84 public Content2BeanTransformerImpl() {
85 super();
86
87
88
89
90
91 final EnumAwareConvertUtilsBean convertUtilsBean = new EnumAwareConvertUtilsBean();
92
93
94 convertUtilsBean.deregister(Class.class);
95
96 this.beanUtilsBean = new BeanUtilsBean(convertUtilsBean, new PropertyUtilsBean());
97 }
98
99 @Override
100 @Deprecated
101 public TypeDescriptor resolveType(TransformationState state) throws ClassNotFoundException {
102 throw new UnsupportedOperationException();
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116 @Override
117 public TypeDescriptor resolveType(TypeMapping typeMapping, TransformationState state, ComponentProvider componentProvider) throws ClassNotFoundException {
118 TypeDescriptor typeDscr = null;
119 Content node = state.getCurrentContent();
120
121 try {
122 if (node.hasNodeData("class")) {
123 String className = node.getNodeData("class").getString();
124 if (StringUtils.isBlank(className)) {
125 throw new ClassNotFoundException("(no value for class property)");
126 }
127 Class<?> clazz = Classes.getClassFactory().forName(className);
128 typeDscr = typeMapping.getTypeDescriptor(clazz);
129 }
130 } catch (RepositoryException e) {
131
132 log.warn("can't read class property", e);
133 }
134
135 if (typeDscr == null && state.getLevel() > 1) {
136 TypeDescriptor parentTypeDscr = state.getCurrentType();
137 PropertyTypeDescriptor propDscr;
138
139 if (parentTypeDscr.isMap() || parentTypeDscr.isCollection()) {
140 if (state.getLevel() > 2) {
141
142 String mapProperyName = state.peekContent(1).getName();
143 propDscr = state.peekType(1).getPropertyTypeDescriptor(mapProperyName, typeMapping);
144 if (propDscr != null) {
145 typeDscr = propDscr.getCollectionEntryType();
146 }
147 }
148 } else {
149 propDscr = state.getCurrentType().getPropertyTypeDescriptor(node.getName(), typeMapping);
150 if (propDscr != null) {
151 typeDscr = propDscr.getType();
152 }
153 }
154 }
155
156 typeDscr = onResolveType(typeMapping, state, typeDscr, componentProvider);
157
158 if (typeDscr != null) {
159
160 final Class<?> type = typeDscr.getType();
161 typeDscr = typeMapping.getTypeDescriptor(componentProvider.getImplementation(type));
162
163
164 Content2BeanTransformer customTransformer = typeDscr.getTransformer();
165 if (customTransformer != null && customTransformer != this) {
166 TypeDescriptor typeFoundByCustomTransformer = customTransformer.resolveType(typeMapping, state, componentProvider);
167
168
169 if (typeFoundByCustomTransformer != TypeMapping.MAP_TYPE) {
170
171 Class<?> implementation = componentProvider.getImplementation(typeFoundByCustomTransformer.getType());
172 typeDscr = typeMapping.getTypeDescriptor(implementation);
173 }
174 }
175 }
176
177 if (typeDscr == null || typeDscr.needsDefaultMapping()) {
178 if (typeDscr == null) {
179 log.debug("was not able to resolve type for node [{}] will use a map", node);
180 }
181 typeDscr = TypeMapping.MAP_TYPE;
182 }
183
184 log.debug("{} --> {}", node.getHandle(), typeDscr.getType());
185
186 return typeDscr;
187 }
188
189
190
191
192
193 protected TypeDescriptor onResolveType(TypeMapping typeMapping, TransformationState state, TypeDescriptor resolvedType, ComponentProvider componentProvider) {
194 return resolvedType;
195 }
196
197
198
199
200 protected TypeDescriptor onResolveType(TransformationState state, TypeDescriptor resolvedType, ComponentProvider componentProvider) {
201 return onResolveType(getTypeMapping(), state, resolvedType, componentProvider);
202 }
203
204 @Override
205 public Collection<Content> getChildren(Content node) {
206 return node.getChildren(this);
207 }
208
209
210
211
212 @Override
213 public boolean accept(Content content) {
214 return ContentUtil.EXCLUDE_META_DATA_CONTENT_FILTER.accept(content);
215 }
216
217 @Override
218 public void setProperty(TransformationState state, PropertyTypeDescriptor descriptor, Map<String, Object> values) {
219 throw new UnsupportedOperationException();
220 }
221
222
223
224
225 @Override
226 public void setProperty(TypeMapping mapping, TransformationState state, PropertyTypeDescriptor descriptor, Map<String, Object> values) {
227 String propertyName = descriptor.getName();
228 if (propertyName.equals("class")) {
229 return;
230 }
231 Object value = values.get(propertyName);
232 Object bean = state.getCurrentBean();
233
234 if (propertyName.equals("content") && value == null) {
235 value = new SystemContentWrapper(state.getCurrentContent());
236 } else if (propertyName.equals("name") && value == null) {
237 value = state.getCurrentContent().getName();
238 } else if (propertyName.equals("className") && value == null) {
239 value = values.get("class");
240 }
241
242
243
244 if (value == null) {
245 return;
246 }
247
248 log.debug("try to set {}.{} with value {}", new Object[] { bean, propertyName, value });
249
250
251 if (!(bean instanceof Map)) {
252 try {
253 PropertyTypeDescriptor dscr = mapping.getPropertyTypeDescriptor(bean.getClass(), propertyName);
254 if (dscr.getType() != null) {
255
256
257 if (dscr.isCollection() || dscr.isMap()) {
258 log.debug("{} is of type collection, map or /array", propertyName);
259 Method method = dscr.getAddMethod();
260
261 if (method != null) {
262 log.debug("clearing the current content of the collection/map");
263 try {
264 Object col = PropertyUtils.getProperty(bean, propertyName);
265 if (col != null) {
266 MethodUtils.invokeExactMethod(col, "clear", new Object[] {});
267 }
268 } catch (Exception e) {
269 log.debug("no clear method found on collection {}", propertyName);
270 }
271
272 Class<?> entryClass = dscr.getCollectionEntryType().getType();
273
274 log.debug("will add values by using adder method {}", method.getName());
275 for (Iterator<Object> iter = ((Map<Object, Object>) value).keySet().iterator(); iter
276 .hasNext();) {
277 Object key = iter.next();
278 Object entryValue = ((Map<Object, Object>) value).get(key);
279 entryValue = convertPropertyValue(entryClass, entryValue);
280 if (entryClass.isAssignableFrom(entryValue.getClass())) {
281 if (dscr.isCollection()) {
282 log.debug("will add value {}", entryValue);
283 method.invoke(bean, new Object[] { entryValue });
284 }
285
286 else {
287 log.debug("will add key {} with value {}", key, entryValue);
288 method.invoke(bean, new Object[] { key, entryValue });
289 }
290 }
291 }
292
293 return;
294 }
295 log.debug("no add method found for property {}", propertyName);
296 if (dscr.isCollection()) {
297 log.debug("transform the values to a collection", propertyName);
298 value = ((Map<Object, Object>) value).values();
299 }
300 } else {
301 value = convertPropertyValue(dscr.getType().getType(), value);
302 }
303 }
304 } catch (Exception e) {
305
306 log.error("Can't set property [{}] to value [{}] in bean [{}] for node {} due to {}",
307 new Object[] { propertyName, value, bean.getClass().getName(),
308 state.getCurrentContent().getHandle(), e.toString() });
309 log.debug("stacktrace", e);
310 }
311 }
312
313 try {
314
315
316
317
318
319
320 beanUtilsBean.setProperty(bean, propertyName, value);
321
322
323
324 } catch (Exception e) {
325
326 log.error("Can't set property [{}] to value [{}] in bean [{}] for node {} due to {}",
327 new Object[] { propertyName, value, bean.getClass().getName(),
328 state.getCurrentContent().getHandle(), e.toString() });
329 log.debug("stacktrace", e);
330 }
331
332 }
333
334
335
336
337
338 @Override
339 public Object convertPropertyValue(Class<?> propertyType, Object value) throws Content2BeanException {
340 if (Class.class.equals(propertyType)) {
341 try {
342 return Classes.getClassFactory().forName(value.toString());
343 } catch (ClassNotFoundException e) {
344 log.error(e.getMessage());
345 throw new Content2BeanException(e);
346 }
347 }
348
349 if (Locale.class.equals(propertyType)) {
350 if (value instanceof String) {
351 String localeStr = (String) value;
352 if (StringUtils.isNotEmpty(localeStr)) {
353 return LocaleUtils.toLocale(localeStr);
354 }
355 }
356 }
357
358 if (Collection.class.equals(propertyType) && value instanceof Map) {
359
360 return ((Map) value).values();
361 }
362
363
364 if (String.class.equals(propertyType) && value instanceof Map && ((Map) value).size() == 1) {
365 return ((Map) value).values().iterator().next();
366 }
367
368 return value;
369 }
370
371
372
373
374 @Override
375 public Object newBeanInstance(TransformationState state, Map properties, ComponentProvider componentProvider) throws Content2BeanException {
376
377
378 final Object bean = convertPropertyValue(state.getCurrentType().getType(), properties);
379
380 if (bean == properties) {
381 try {
382
383
384 final Class<?> type = state.getCurrentType().getType();
385 if (LinkedHashMap.class.equals(type)) {
386
387
388 return new LinkedHashMap();
389 } else if (Map.class.isAssignableFrom(type)) {
390
391 log.warn("someone wants another type of map ? " + type);
392 }
393 return componentProvider.newInstance(type);
394 } catch (Throwable e) {
395 throw new Content2BeanException(e);
396 }
397 }
398 return bean;
399 }
400
401
402
403
404 @Override
405 public void initBean(TransformationState state, Map properties) throws Content2BeanException {
406 Object bean = state.getCurrentBean();
407
408 Method init;
409 try {
410 init = bean.getClass().getMethod("init", new Class[] {});
411 try {
412 init.invoke(bean);
413 } catch (Exception e) {
414 throw new Content2BeanException("can't call init method", e);
415 }
416 } catch (SecurityException e) {
417 return;
418 } catch (NoSuchMethodException e) {
419 return;
420 }
421 log.debug("{} is initialized", bean);
422 }
423
424 @Override
425 public TransformationState newState() {
426 return new TransformationStateImpl();
427
428
429
430 }
431
432
433
434
435
436
437 @Override
438 public TypeMapping getTypeMapping() {
439 return typeMapping;
440 }
441
442 }