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.jcr.node2bean.impl;
35
36 import info.magnolia.jcr.node2bean.Node2BeanException;
37 import info.magnolia.jcr.node2bean.Node2BeanTransformer;
38 import info.magnolia.jcr.node2bean.PropertyTypeDescriptor;
39 import info.magnolia.jcr.node2bean.TransformedBy;
40 import info.magnolia.jcr.node2bean.TypeDescriptor;
41 import info.magnolia.jcr.node2bean.TypeMapping;
42 import info.magnolia.objectfactory.Components;
43
44 import java.beans.PropertyDescriptor;
45 import java.lang.annotation.Annotation;
46 import java.lang.reflect.AnnotatedElement;
47 import java.lang.reflect.Method;
48 import java.lang.reflect.ParameterizedType;
49 import java.lang.reflect.Type;
50 import java.lang.reflect.WildcardType;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collection;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Map;
57
58 import org.apache.commons.beanutils.PropertyUtils;
59 import org.apache.commons.lang3.StringUtils;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63
64
65
66 public class TypeMappingImpl implements TypeMapping {
67
68 private static Logger log = LoggerFactory.getLogger(TypeMappingImpl.class);
69
70 private final Map<String, PropertyTypeDescriptor> propertyTypes = new HashMap<String, PropertyTypeDescriptor>();
71 private final Map<Class<?>, TypeDescriptor> types = new HashMap<Class<?>, TypeDescriptor>();
72
73 @Override
74 public PropertyTypeDescriptor getPropertyTypeDescriptor(Class<?> beanClass, String propName) {
75 PropertyTypeDescriptor dscr = null;
76 String key = beanClass.getName() + "." + propName;
77
78 dscr = propertyTypes.get(key);
79
80 if (dscr != null) {
81 return dscr;
82 }
83
84 dscr = new PropertyTypeDescriptor();
85 dscr.setName(propName);
86
87 PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(beanClass);
88 for (PropertyDescriptor javaBeanDescriptor : descriptors) {
89 if (javaBeanDescriptor.getName().equals(propName)) {
90
91
92
93
94
95
96
97
98
99 Class<?> propertyType = javaBeanDescriptor.getPropertyType();
100 if (propertyType == null && javaBeanDescriptor.getReadMethod() != null) {
101 log.warn("Property type for {}#{} was {}, using readMethod instead to get {}. Please check {} and make sure getters/setters for {} are consistent.", simpleName(beanClass), propName, javaBeanDescriptor.getPropertyType(), simpleName(javaBeanDescriptor.getReadMethod().getReturnType()), beanClass, propName);
102 propertyType = javaBeanDescriptor.getReadMethod().getReturnType();
103 }
104
105 if (propertyType == null) {
106 log.warn("Can't determine property type for {}#{}. Skipping.");
107 break;
108 }
109
110 final Method writeMethod = javaBeanDescriptor.getWriteMethod();
111 final TypeDescriptor typeDescriptor = getTypeDescriptor(propertyType, writeMethod);
112
113 dscr.setType(typeDescriptor);
114 dscr.setWriteMethod(writeMethod);
115
116
117 int numberOfParameters = dscr.isMap() ? 2 : 1;
118 final Method addMethod = getAddMethod(beanClass, propName, numberOfParameters);
119 dscr.setAddMethod(addMethod);
120
121 break;
122 }
123 }
124
125 if (dscr.getType() != null) {
126
127 if (dscr.isMap() || dscr.isCollection()) {
128 List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
129 if (dscr.getWriteMethod() != null) {
130 parameterTypes = inferGenericTypes(dscr.getWriteMethod());
131 }
132 if (dscr.getAddMethod() != null && parameterTypes.size() == 0) {
133
134
135 parameterTypes = Arrays.asList(dscr.getAddMethod().getParameterTypes());
136
137 dscr.setWriteMethod(null);
138 }
139 if (parameterTypes.size() > 0) {
140
141 if (dscr.isMap()) {
142 dscr.setCollectionKeyType(getTypeDescriptor(parameterTypes.get(0)));
143 dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(1)));
144 } else {
145
146 dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(0)));
147 }
148 }
149 } else if (dscr.isArray()) {
150
151
152 dscr.setCollectionEntryType(getTypeDescriptor(dscr.getType().getType().getComponentType()));
153 }
154 }
155 propertyTypes.put(key, dscr);
156
157 return dscr;
158 }
159
160 private List<Class<?>> inferGenericTypes(Method method) {
161 List<Class<?>> inferredTypes = new ArrayList<Class<?>>();
162 Type[] parameterTypes = method.getGenericParameterTypes();
163 for (Type parameterType : parameterTypes) {
164 if (parameterType instanceof ParameterizedType) {
165 ParameterizedType type = (ParameterizedType) parameterType;
166 for (Type t : type.getActualTypeArguments()) {
167 if (t instanceof ParameterizedType) {
168
169
170 inferredTypes.add((Class<?>) ((ParameterizedType) t).getRawType());
171 } else if (t instanceof WildcardType) {
172 WildcardType wildcardType = (WildcardType) t;
173 if (wildcardType.getUpperBounds().length > 0) {
174
175 inferredTypes.add((Class<?>) wildcardType.getUpperBounds()[0]);
176 } else {
177
178 inferredTypes.add((Class<?>) wildcardType.getLowerBounds()[0]);
179 }
180 } else {
181 inferredTypes.add((Class<?>) t);
182 }
183 }
184 }
185 }
186 return inferredTypes;
187 }
188
189
190
191
192 private Node2BeanTransformer resolveTransformer(Class<?> beanClass, Method writeMethod) throws Node2BeanException {
193 if (!beanClass.isArray() && !beanClass.isPrimitive()) {
194 Class<Node2BeanTransformer> transformerClass = null;
195 Node2BeanTransformer transformer = null;
196 if (writeMethod != null) {
197 transformer = getTransformerFromAnnotation(writeMethod);
198 if (transformer != null) {
199
200 log.warn("You have a @{} annotation on {}. This is currently causing potential issues if you have other properties of the same type. " +
201 "If possible, add the annotation on a type instead. See MAGNOLIA-5865 for details.", TransformedBy.class.getSimpleName(), writeMethod);
202 }
203 }
204 if (transformer == null) {
205 transformer = getTransformerFromAnnotation(beanClass);
206 }
207 if (transformer == null) {
208
209 final Annotation[] annotations = beanClass.getAnnotations();
210 for (Annotation annotation : annotations) {
211 transformer = getTransformerFromAnnotation(annotation.annotationType());
212 }
213 }
214 if (transformer == null) {
215 try {
216 transformerClass = (Class<Node2BeanTransformer>) Class.forName(beanClass.getName() + "Transformer");
217
218 transformer = Components.getComponent(transformerClass);
219 } catch (ClassNotFoundException e) {
220 log.debug("No transformer found for bean [{}]", beanClass);
221 }
222 }
223
224 return transformer;
225 }
226 return null;
227 }
228
229 protected Node2BeanTransformer getTransformerFromAnnotation(AnnotatedElement writeMethod) {
230 final TransformedBy transformerAnnotation = writeMethod.getAnnotation(TransformedBy.class);
231 final Class<Node2BeanTransformer> transformerClass = transformerAnnotation == null ? null : (Class<Node2BeanTransformer>) transformerAnnotation.value();
232 return transformerClass == null ? null : Components.getComponentProvider().newInstance(transformerClass);
233 }
234
235
236
237
238 private TypeDescriptor getTypeDescriptor(Class<?> beanClass, Method writeMethod) {
239 TypeDescriptor dscr = types.get(beanClass);
240
241 if(dscr != null){
242 return dscr;
243 }
244 dscr = new TypeDescriptor();
245 dscr.setType(beanClass);
246 dscr.setMap(Map.class.isAssignableFrom(beanClass));
247 dscr.setCollection(Collection.class.isAssignableFrom(beanClass));
248 dscr.setArray(beanClass.isArray());
249 try {
250 dscr.setTransformer(resolveTransformer(beanClass, writeMethod));
251 } catch (Node2BeanException e) {
252 log.error("Can't create transformer for bean [{}]", beanClass, e);
253 }
254
255 types.put(beanClass, dscr);
256
257 return dscr;
258 }
259
260 @Override
261 public TypeDescriptor getTypeDescriptor(Class<?> beanClass) {
262 return getTypeDescriptor(beanClass, null);
263 }
264
265
266
267
268
269 public Method getAddMethod(Class<?> type, String name, int numberOfParameters) {
270 name = StringUtils.capitalize(name);
271 Method method = getExactMethod(type, "add" + name, numberOfParameters);
272 if (method == null) {
273 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "s"), numberOfParameters);
274 }
275
276 if (method == null) {
277 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "es"), numberOfParameters);
278 }
279
280 if (method == null) {
281 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ren"), numberOfParameters);
282 }
283
284 if (method == null) {
285 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ies") + "y", numberOfParameters);
286 }
287 return method;
288 }
289
290
291
292
293
294
295 protected Method getExactMethod(Class<?> type, String name, int numberOfParameters) {
296 Method[] methods = type.getMethods();
297 for (int i = 0; i < methods.length; i++) {
298 Method method = methods[i];
299 if (method.getName().equals(name)) {
300
301
302
303
304
305 if (method.getParameterTypes().length == numberOfParameters) {
306 return method;
307 }
308 }
309 }
310 return null;
311 }
312
313 private static String simpleName(Class<?> c) {
314 return c != null ? c.getSimpleName() : "<undetermined>";
315 }
316
317 }