(no commit message)
[utils] / support / general / src / main / java / org / wamblee / persistence / PersistentFactory.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.persistence;
17
18 import java.io.Serializable;
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentHashMap;
26
27 import javax.persistence.Id;
28 import javax.persistence.Version;
29
30 import org.wamblee.reflection.ReflectionUtils;
31
32 /**
33  * Factory which creates a {@link Persistent} object for a given entity for
34  * interfacing with the primary key and version of the entity.
35  * 
36  * This utility only treats primary keys and fields that are annotated with @Id
37  * and @Version. In case ORM files are used for the definition of primary key
38  * and or version, then those fields are ignored.
39  * 
40  * @author Erik Brakkee
41  * 
42  */
43 public class PersistentFactory {
44
45     /**
46      * Cache of a mapping of class names to entity accessors.
47      */
48     private static Map<String, EntityAccessor> CACHE = new ConcurrentHashMap<String, EntityAccessor>();
49
50     static interface Accessor<T> {
51         void set(Object aEntity, T aValue);
52
53         T get(Object aEntity);
54     }
55
56     static class FieldAccessor<T> implements Accessor<T> {
57         private Field field;
58
59         public FieldAccessor(Field aField) {
60             field = aField;
61             field.setAccessible(true);
62         }
63
64         @Override
65         public T get(Object aEntity) {
66             try {
67                 return (T) field.get(aEntity);
68             } catch (Exception e) {
69                 throw new RuntimeException(e);
70             } 
71         }
72
73         @Override
74         public void set(Object aEntity, T aValue) {
75             try {
76                 field.set(aEntity, aValue);
77             } catch (IllegalAccessException e) {
78                 throw new RuntimeException(e.getMessage(), e);
79             }
80         }
81
82         public Field getField() {
83             return field;
84         }
85     }
86
87     static class PropertyAccessor<T> implements Accessor<T> {
88         private Method getter;
89         private Method setter;
90
91         public PropertyAccessor(Method aGetter, Method aSetter) {
92             getter = aGetter;
93             setter = aSetter;
94             getter.setAccessible(true);
95             setter.setAccessible(true);
96         }
97
98         @Override
99         public T get(Object aEntity) {
100             try {
101                 return (T) getter.invoke(aEntity);
102             } catch (Exception e) {
103                 throw new RuntimeException(e);
104             }
105         }
106
107         @Override
108         public void set(Object aEntity, T aValue) {
109             try {
110                 setter.invoke(aEntity, aValue);
111             } catch (Exception e) {
112                 throw new RuntimeException(e);
113             } 
114         }
115
116         public Method getGetter() {
117             return getter;
118         }
119
120         public Method getSetter() {
121             return setter;
122         }
123     }
124
125     static class EntityAccessor {
126         private Accessor pk;
127         private Accessor version;
128
129         public EntityAccessor(Accessor<?> aPk, Accessor<?> aVersion) {
130             pk = aPk;
131             version = aVersion;
132         }
133
134         public Accessor getPk() {
135             return pk;
136         }
137
138         public Accessor getVersion() {
139             return version;
140         }
141     }
142
143     public static class EntityObjectAccessor implements Persistent {
144         private EntityAccessor accessor;
145         private Object entity;
146
147         public EntityObjectAccessor(Object aEntity, EntityAccessor aAccessor) {
148             accessor = aAccessor;
149             entity = aEntity;
150         }
151
152         public EntityAccessor getAccessor() {
153             return accessor;
154         };
155
156         @Override
157         public Serializable getPrimaryKey() {
158             return (Serializable)accessor.getPk().get(entity);
159         }
160
161         @Override
162         public void setPrimaryKey(Serializable aKey) {
163             accessor.getPk().set(entity, aKey);
164         } 
165
166         @Override
167         public Number getPersistedVersion() {
168             return (Number)accessor.getVersion().get(entity);
169         }
170
171         @Override
172         public void setPersistedVersion(Number aVersion) {
173             accessor.getVersion().set(entity, aVersion);
174         }
175     }
176
177     /**
178      * Create the entity accessor for a given class or returns a cached instance
179      * if one already exists.
180      * 
181      * @param aClass
182      *            Class.
183      * @return Entity accessor for the given class or null of the given object
184      *         is not an entity.
185      */
186     public static EntityAccessor createEntityAccessor(Class aClass) {
187         EntityAccessor accessor = CACHE.get(aClass.getName());
188         if (accessor == null) {
189             accessor = analyse(aClass);
190             if (accessor != null) {
191                 CACHE.put(aClass.getName(), accessor);
192             }
193         }
194         return accessor;
195     }
196
197     private static EntityAccessor analyse(Class aClass) {
198         Accessor<Serializable> pk = analyse(aClass, Id.class);
199         Accessor<Integer> version = analyse(aClass, Version.class);
200         if (pk != null || version != null) {
201             return new EntityAccessor(pk, version);
202         }
203         return null;
204     }
205
206     /**
207      * Returns the accessor for a given annotation.
208      * 
209      * @param aClass
210      *            Class to analyse.
211      * @param aAnnotation
212      *            Annotation that must be present.
213      * @return Accessor to use or null if the annotation is not present.
214      */
215     private static Accessor analyse(Class aClass,
216         Class<? extends Annotation> aAnnotation) {
217         List<Field> fields = ReflectionUtils.getAllFields(aClass);
218         for (Field field : fields) {
219             if (field.isAnnotationPresent(aAnnotation)) {
220                 return new FieldAccessor(field);
221             }
222         }
223         List<Method> methods = ReflectionUtils.getAllMethods(aClass,
224             Object.class);
225         for (Method method : methods) {
226             if (method.isAnnotationPresent(aAnnotation)) {
227                 String setterName = null;
228                 if (method.getName().startsWith("get")) {
229                     setterName = method.getName().replaceFirst("get", "set");
230                 } else if (method.getName().startsWith("is")) {
231                     setterName = method.getName().replaceFirst("is", "set");
232                 }
233                 try {
234                     Class returnType = method.getReturnType();
235                     Method setter = method.getDeclaringClass().getDeclaredMethod(setterName, returnType);
236                     return new PropertyAccessor(method, setter);
237                 } catch (NoSuchMethodException e) {
238                     throw new RuntimeException("Error obtaining setter for " +
239                         method.getName() + " in class " + aClass.getName(), e);
240                 }
241             }
242         }
243         return null;
244     }
245
246     /**
247      * Creates the {@link Persistent} wrapper for interfacing with primary key and 
248      * version of the entity. 
249      * @param aEntity Entity to use. 
250      * @return Persistent object or null if this is not an entity. 
251      */
252     public static Persistent create(Object aEntity) { 
253         EntityAccessor accessor = createEntityAccessor(aEntity.getClass());
254         if ( accessor == null ) { 
255             return null; 
256         }
257         return new EntityObjectAccessor(aEntity, accessor);
258     }
259 }