View Javadoc

1   /**
2    * This file Copyright (c) 2012-2013 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
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.reflect.Method;
46  import java.lang.reflect.ParameterizedType;
47  import java.lang.reflect.Type;
48  import java.util.ArrayList;
49  import java.util.Arrays;
50  import java.util.Collection;
51  import java.util.HashMap;
52  import java.util.List;
53  import java.util.Map;
54  
55  import org.apache.commons.beanutils.PropertyUtils;
56  import org.apache.commons.lang.StringUtils;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  /**
61   * Basic type mapping implementation.
62   */
63  public class TypeMappingImpl implements TypeMapping {
64  
65      private static Logger log = LoggerFactory.getLogger(TypeMappingImpl.class);
66  
67      private final Map<String, PropertyTypeDescriptor> propertyTypes = new HashMap<String, PropertyTypeDescriptor>();
68      private final Map<Class<?>, TypeDescriptor> types = new HashMap<Class<?>, TypeDescriptor>();
69  
70      @Override
71      public PropertyTypeDescriptor getPropertyTypeDescriptor(Class<?> beanClass, String propName) {
72          PropertyTypeDescriptor dscr = null;
73          String key = beanClass.getName() + "." + propName;
74  
75          dscr = propertyTypes.get(key);
76  
77          if (dscr != null) {
78              return dscr;
79          }
80  
81          dscr = new PropertyTypeDescriptor();
82          dscr.setName(propName);
83  
84          PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(beanClass);
85          Method writeMethod = null;
86          for (PropertyDescriptor descriptor : descriptors) {
87              if (descriptor.getName().equals(propName)) {
88                  // may be null for indexed properties
89                  Class<?> propertyType = descriptor.getPropertyType();
90                  writeMethod = descriptor.getWriteMethod();
91                  if (propertyType != null) {
92                      dscr.setType(getTypeDescriptor(propertyType, writeMethod));
93                  }
94                  // set write method
95                  dscr.setWriteMethod(writeMethod);
96                  // set add method
97                  int numberOfParameters = dscr.isMap() ? 2 : 1;
98                  dscr.setAddMethod(getAddMethod(beanClass, propName, numberOfParameters));
99  
100                 break;
101             }
102         }
103 
104         if (dscr.getType() != null) {
105             // we have discovered type for property
106             if (dscr.isMap() || dscr.isCollection()) {
107                 List<Class<?>> parameterTypes = new ArrayList<Class<?>>(); // this will contain collection types (for map key/value type, for collection value type)
108                 if (dscr.getWriteMethod() != null) {
109                     parameterTypes = inferGenericTypes(dscr.getWriteMethod());
110                 }
111                 if (dscr.getAddMethod() != null && parameterTypes.size() == 0) {
112                     // here we know we don't have setter or setter doesn't have parameterized type
113                     // but we have add method so we take parameters from it
114                     parameterTypes = Arrays.asList(dscr.getAddMethod().getParameterTypes());
115                     // rather set it to null because when we are here we will use add method
116                     dscr.setWriteMethod(null);
117                 }
118                 if (parameterTypes.size() > 0) {
119                     // we resolved types
120                     if (dscr.isMap()) {
121                         dscr.setCollectionKeyType(getTypeDescriptor(parameterTypes.get(0)));
122                         dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(1)));
123                     } else {
124                         // collection
125                         dscr.setCollectionEntryType(getTypeDescriptor(parameterTypes.get(0)));
126                     }
127                 }
128             } else if (dscr.isArray()) {
129                 // for arrays we don't need to discover its parameter from set/add method
130                 // we just take it via Class#getComponentType() method
131                 dscr.setCollectionEntryType(getTypeDescriptor(dscr.getType().getType().getComponentType()));
132             }
133         }
134         propertyTypes.put(key, dscr);
135 
136         return dscr;
137     }
138 
139     private List<Class<?>> inferGenericTypes(Method method) {
140         List<Class<?>> inferredTypes = new ArrayList<Class<?>>();
141         Type[] parameterTypes = method.getGenericParameterTypes();
142         for (Type parameterType : parameterTypes) {
143             if (parameterType instanceof ParameterizedType) {
144                 ParameterizedType type = (ParameterizedType) parameterType;
145                 for (Type t : type.getActualTypeArguments()) {
146                     if (t instanceof ParameterizedType) {
147                         // this the case when parameterized type looks like this: Collection<List<String>>
148                         // we care only for raw type List
149                         inferredTypes.add((Class<?>) ((ParameterizedType) t).getRawType());
150                     } else {
151                         inferredTypes.add((Class<?>) t);
152                     }
153                 }
154             }
155         }
156         return inferredTypes;
157     }
158 
159     /**
160      * Resolves transformer from bean class or setter.
161      */
162     private Node2BeanTransformer resolveTransformer(Class<?> beanClass, Method writeMethod) throws Node2BeanException {
163         if (!beanClass.isArray() && !beanClass.isPrimitive()) { // don't bother looking for a transformer if the property is an array or a primitive type
164             Class<Node2BeanTransformer> transformerClass = null;
165             Node2BeanTransformer transformer = null;
166             if (writeMethod != null) {
167                 TransformedBy transformerAnnotation = writeMethod.getAnnotation(TransformedBy.class);
168                 transformerClass = transformerAnnotation == null ? null : (Class<Node2BeanTransformer>) transformerAnnotation.value();
169                 transformer = transformerClass == null ? null : Components.getComponentProvider().newInstance(transformerClass);
170             }
171             if (transformer == null) {
172                 try {
173                     transformerClass = (Class<Node2BeanTransformer>) Class.forName(beanClass.getName() + "Transformer");
174                     transformer = Components.getComponent(transformerClass);
175                 } catch (ClassNotFoundException e) {
176                     log.debug("No transformer found for bean [{}]", beanClass);
177                 }
178             }
179             return transformer;
180         }
181         return null;
182     }
183 
184     /**
185      * Gets type descriptor from bean class.
186      */
187     private TypeDescriptor getTypeDescriptor(Class<?> beanClass, Method method) {
188         TypeDescriptor dscr = types.get(beanClass);
189         // eh, we know about this type, don't bother resolving any further.
190         if(dscr != null){
191             return dscr;
192         }
193         dscr = new TypeDescriptor();
194         dscr.setType(beanClass);
195         dscr.setMap(Map.class.isAssignableFrom(beanClass));
196         dscr.setCollection(Collection.class.isAssignableFrom(beanClass));
197         dscr.setArray(beanClass.isArray());
198         try {
199             dscr.setTransformer(resolveTransformer(beanClass, method));
200         } catch (Node2BeanException e) {
201             log.error("Can't create transformer for bean [" + beanClass + "]", e);
202         }
203 
204         types.put(beanClass, dscr);
205 
206         return dscr;
207     }
208 
209     @Override
210     public TypeDescriptor getTypeDescriptor(Class<?> beanClass) {
211         return getTypeDescriptor(beanClass, null);
212     }
213 
214     /**
215      * Get a adder method. Transforms name to singular.
216      * @deprecated since 5.0 - use setters
217      */
218     public Method getAddMethod(Class<?> type, String name, int numberOfParameters) {
219         name = StringUtils.capitalize(name);
220         Method method = getExactMethod(type, "add" + name, numberOfParameters);
221         if (method == null) {
222             method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "s"), numberOfParameters);
223         }
224 
225         if (method == null) {
226             method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "es"), numberOfParameters);
227         }
228 
229         if (method == null) {
230             method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ren"), numberOfParameters);
231         }
232 
233         if (method == null) {
234             method = getExactMethod(type, "add" + StringUtils.removeEnd(name, "ies") + "y", numberOfParameters);
235         }
236         return method;
237     }
238 
239     /**
240      * Find a method.
241      *
242      * @param numberOfParameters
243      * @deprecated since 5.0 - use setters
244      */
245     protected Method getExactMethod(Class<?> type, String name, int numberOfParameters) {
246         Method[] methods = type.getMethods();
247         for (int i = 0; i < methods.length; i++) {
248             Method method = methods[i];
249             if (method.getName().equals(name)) {
250                 // TODO - CAUTION: in case there's several methods with the same
251                 // name and the same numberOfParameters
252                 // this method might pick the "wrong" one. We should think about
253                 // adding a check and throw an exceptions
254                 // if there's more than one match!
255                 if (method.getParameterTypes().length == numberOfParameters) {
256                     return method;
257                 }
258             }
259         }
260         return null;
261     }
262 
263 }