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 final Logger log = LoggerFactory.getLogger(TypeMappingImpl.class);
69
70 private final Map<String, PropertyTypeDescriptor> propertyTypes = new HashMap<>();
71 private final Map<Class<?>, TypeDescriptor> types = new HashMap<>();
72
73 @Override
74 public PropertyTypeDescriptor getPropertyTypeDescriptor(Class<?> beanClass, String propName) {
75 PropertyTypeDescriptor dscr;
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.", simpleName(beanClass), propName);
107 break;
108 }
109
110 final Method writeMethod = javaBeanDescriptor.getWriteMethod();
111 final Method readMethod = javaBeanDescriptor.getReadMethod();
112 final TypeDescriptor typeDescriptor = getTypeDescriptor(propertyType, writeMethod);
113
114 dscr.setType(typeDescriptor);
115 dscr.setWriteMethod(writeMethod);
116 dscr.setReadMethod(readMethod);
117
118
119 int numberOfParameters = dscr.isMap() ? 2 : 1;
120 final Method addMethod = getAddMethod(beanClass, propName, numberOfParameters);
121 dscr.setAddMethod(addMethod);
122
123 break;
124 }
125 }
126
127 if (dscr.getType() != null) {
128
129 if (dscr.isMap() || dscr.isCollection()) {
130 List<Class<?>> parameterTypes = new ArrayList<>();
131 if (dscr.getWriteMethod() != null) {
132 parameterTypes = inferGenericTypes(dscr.getWriteMethod());
133 }
134 if (dscr.getAddMethod() != null && parameterTypes.size() == 0) {
135
136
137 parameterTypes = Arrays.asList(dscr.getAddMethod().getParameterTypes());
138
139 dscr.setWriteMethod(null);
140 }
141 if (parameterTypes.size() > 0) {
142
143 if (dscr.isMap()) {
144 dscr.setCollectionKeyType(getTypeDescriptor(parameterTypes.get(0)));
145 dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(1)));
146 } else {
147
148 dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(0)));
149 }
150 }
151 } else if (dscr.isArray()) {
152
153
154 dscr.setCollectionEntryType(getTypeDescriptor(dscr.getType().getType().getComponentType()));
155 }
156 }
157 propertyTypes.put(key, dscr);
158
159 return dscr;
160 }
161
162 private List<Class<?>> inferGenericTypes(Method method) {
163 List<Class<?>> inferredTypes = new ArrayList<>();
164 Type[] parameterTypes = method.getGenericParameterTypes();
165 for (Type parameterType : parameterTypes) {
166 if (parameterType instanceof ParameterizedType) {
167 ParameterizedType type = (ParameterizedType) parameterType;
168 for (Type t : type.getActualTypeArguments()) {
169 if (t instanceof ParameterizedType) {
170
171
172 inferredTypes.add((Class<?>) ((ParameterizedType) t).getRawType());
173 } else if (t instanceof WildcardType) {
174 WildcardType wildcardType = (WildcardType) t;
175 if (wildcardType.getUpperBounds().length > 0) {
176
177 inferredTypes.add((Class<?>) wildcardType.getUpperBounds()[0]);
178 } else {
179
180 inferredTypes.add((Class<?>) wildcardType.getLowerBounds()[0]);
181 }
182 } else {
183 inferredTypes.add((Class<?>) t);
184 }
185 }
186 }
187 }
188 return inferredTypes;
189 }
190
191
192
193
194 private Node2BeanTransformer resolveTransformer(Class<?> beanClass, Method writeMethod) throws Node2BeanException {
195 if (!beanClass.isArray() && !beanClass.isPrimitive()) {
196 Class<Node2BeanTransformer> transformerClass;
197 Node2BeanTransformer transformer = null;
198 if (writeMethod != null) {
199 transformer = getTransformerFromAnnotation(writeMethod);
200 if (transformer != null) {
201
202 log.warn("You have a @{} annotation on {}. This is currently causing potential issues if you have other properties of the same type. " +
203 "If possible, add the annotation on a type instead. See MAGNOLIA-5865 for details.", TransformedBy.class.getSimpleName(), writeMethod);
204 }
205 }
206 if (transformer == null) {
207 transformer = getTransformerFromAnnotation(beanClass);
208 }
209 if (transformer == null) {
210
211 final Annotation[] annotations = beanClass.getAnnotations();
212 for (Annotation annotation : annotations) {
213 transformer = getTransformerFromAnnotation(annotation.annotationType());
214 }
215 }
216 if (transformer == null) {
217 try {
218 transformerClass = (Class<Node2BeanTransformer>) Class.forName(beanClass.getName() + "Transformer");
219
220 transformer = Components.getComponent(transformerClass);
221 } catch (ClassNotFoundException e) {
222 log.debug("No transformer found for bean [{}]", beanClass);
223 }
224 }
225
226 return transformer;
227 }
228 return null;
229 }
230
231 protected Node2BeanTransformer getTransformerFromAnnotation(AnnotatedElement writeMethod) {
232 final TransformedBy transformerAnnotation = writeMethod.getAnnotation(TransformedBy.class);
233 final Class<Node2BeanTransformer> transformerClass = transformerAnnotation == null ? null : (Class<Node2BeanTransformer>) transformerAnnotation.value();
234 return transformerClass == null ? null : Components.getComponentProvider().newInstance(transformerClass);
235 }
236
237
238
239
240 private TypeDescriptor getTypeDescriptor(Class<?> beanClass, Method writeMethod) {
241 TypeDescriptor dscr = types.get(beanClass);
242
243 if (dscr != null) {
244 return dscr;
245 }
246 dscr = new TypeDescriptor();
247 dscr.setType(beanClass);
248 dscr.setMap(Map.class.isAssignableFrom(beanClass));
249 dscr.setCollection(Collection.class.isAssignableFrom(beanClass));
250 dscr.setArray(beanClass.isArray());
251 try {
252 dscr.setTransformer(resolveTransformer(beanClass, writeMethod));
253 } catch (Node2BeanException e) {
254 log.error("Can't create transformer for bean [{}]", beanClass, e);
255 }
256
257 types.put(beanClass, dscr);
258
259 return dscr;
260 }
261
262 @Override
263 public TypeDescriptor getTypeDescriptor(Class<?> beanClass) {
264 return getTypeDescriptor(beanClass, null);
265 }
266
267
268
269
270
271
272 @Deprecated
273 public Method getAddMethod(Class<?> type, String name, int numberOfParameters) {
274 name = StringUtils.capitalize(name);
275 Method method = getExactMethod(type, "add" + name, numberOfParameters);
276 if (method == null) {
277 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "s"), numberOfParameters);
278 }
279
280 if (method == null) {
281 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "es"), numberOfParameters);
282 }
283
284 if (method == null) {
285 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ren"), numberOfParameters);
286 }
287
288 if (method == null) {
289 method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ies") + "y", numberOfParameters);
290 }
291 return method;
292 }
293
294
295
296
297
298
299 @Deprecated
300 protected Method getExactMethod(Class<?> type, String name, int numberOfParameters) {
301 Method[] methods = type.getMethods();
302 for (Method method : methods) {
303 if (method.getName().equals(name)) {
304
305
306
307
308
309 if (method.getParameterTypes().length == numberOfParameters) {
310 return method;
311 }
312 }
313 }
314 return null;
315 }
316
317 private static String simpleName(Class<?> c) {
318 return c != null ? c.getSimpleName() : "<undetermined>";
319 }
320
321 }