bd1217e61cb7b2ae66dc2a7b0749228c26362da7
[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.Method;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.concurrent.ConcurrentHashMap;
25
26 import javax.persistence.Id;
27 import javax.persistence.Version;
28
29 import org.wamblee.reflection.ReflectionUtils;
30
31 /**
32  * Factory which creates a {@link Persistent} object for a given JPA entity for
33  * interfacing with the primary key and version of the entity.
34  * 
35  * This utility only treats primary keys and fields that are annotated with @Id
36  * and @Version. In case ORM files are used for the definition of primary key
37  * and or version, then those fields are ignored.
38  * 
39  * @author Erik Brakkee
40  * 
41  */
42 public class PersistentFactory {
43
44     /**
45      * Cache of a mapping of class names to entity accessors.
46      */
47     private static Map<String, EntityAccessor> CACHE = new ConcurrentHashMap<String, EntityAccessor>();
48
49     static class EntityAccessor {
50         private Accessor pk;
51         private Accessor version;
52
53         public EntityAccessor(Accessor<?> aPk, Accessor<?> aVersion) {
54             pk = aPk;
55             version = aVersion;
56         }
57
58         public Accessor getPk() {
59             return pk;
60         }
61
62         public Accessor getVersion() {
63             return version;
64         }
65     }
66
67     public static class EntityObjectAccessor implements Persistent {
68         private EntityAccessor accessor;
69         private Object entity;
70
71         public EntityObjectAccessor(Object aEntity, EntityAccessor aAccessor) {
72             accessor = aAccessor;
73             entity = aEntity;
74         }
75
76         public EntityAccessor getAccessor() {
77             return accessor;
78         };
79
80         @Override
81         public Serializable getPrimaryKey() {
82             if (accessor == null || accessor.getPk() == null) {
83                 return null;
84             }
85             return (Serializable) accessor.getPk().get(entity);
86         }
87
88         @Override
89         public void setPrimaryKey(Serializable aKey) {
90             if (accessor == null || accessor.getPk() == null) {
91                 return;
92             }
93             accessor.getPk().set(entity, aKey);
94         }
95
96         @Override
97         public Number getPersistedVersion() {
98             if (accessor == null || accessor.getVersion() == null) {
99                 return null;
100             }
101             return (Number) accessor.getVersion().get(entity);
102         }
103
104         @Override
105         public void setPersistedVersion(Number aVersion) {
106             if (accessor == null || accessor.getVersion() == null) {
107                 return;
108             }
109             accessor.getVersion().set(entity, aVersion);
110         }
111     }
112
113     /**
114      * Create the entity accessor for a given class or returns a cached instance
115      * if one already exists.
116      * 
117      * @param aClass
118      *            Class.
119      * @return Entity accessor for the given class or null of the given object
120      *         is not an entity.
121      */
122     public static EntityAccessor createEntityAccessor(Class aClass) {
123         EntityAccessor accessor = CACHE.get(aClass.getName());
124         if (accessor == null) {
125             accessor = analyse(aClass);
126             if (accessor != null) {
127                 CACHE.put(aClass.getName(), accessor);
128             }
129         }
130         return accessor;
131     }
132
133     private static EntityAccessor analyse(Class aClass) {
134         Accessor<Serializable> pk = analyse(aClass, Id.class);
135         Accessor<Integer> version = analyse(aClass, Version.class);
136         if (pk != null || version != null) {
137             return new EntityAccessor(pk, version);
138         }
139         return null;
140     }
141
142     /**
143      * Returns the accessor for a given annotation.
144      * 
145      * @param aClass
146      *            Class to analyse.
147      * @param aAnnotation
148      *            Annotation that must be present.
149      * @return Accessor to use or null if the annotation is not present.
150      */
151     // TODO move generic analysis part to the reflection package.
152     public static Accessor analyse(Class aClass,
153         Class<? extends Annotation> aAnnotation) {
154         List<Field> fields = ReflectionUtils.getAllFields(aClass);
155         for (Field field : fields) {
156             if (field.isAnnotationPresent(aAnnotation)) {
157                 return new FieldAccessor(field);
158             }
159         }
160         List<Method> methods = ReflectionUtils.getAllMethods(aClass,
161             Object.class);
162         for (Method method : methods) {
163             if (method.isAnnotationPresent(aAnnotation)) {
164                 String setterName = null;
165                 if (method.getName().startsWith("get")) {
166                     setterName = method.getName().replaceFirst("get", "set");
167                 } else if (method.getName().startsWith("is")) {
168                     setterName = method.getName().replaceFirst("is", "set");
169                 }
170                 try {
171                     Class returnType = method.getReturnType();
172                     Method setter = method.getDeclaringClass()
173                         .getDeclaredMethod(setterName, returnType);
174                     return new PropertyAccessor(method, setter);
175                 } catch (NoSuchMethodException e) {
176                     throw new RuntimeException("Error obtaining setter for " +
177                         method.getName() + " in class " + aClass.getName(), e);
178                 }
179             }
180         }
181         return null;
182     }
183
184     /**
185      * Creates the {@link Persistent} wrapper for interfacing with primary key
186      * and version of the entity.
187      * 
188      * @param aEntity
189      *            Entity to use.
190      * @return Persistent object or null if this is not an entity.
191      */
192     public static Persistent create(Object aEntity) {
193         EntityAccessor accessor = createEntityAccessor(aEntity.getClass());
194         if (accessor == null) {
195             return null;
196         }
197         return new EntityObjectAccessor(aEntity, accessor);
198     }
199 }