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