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