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