001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2013 GRANITE DATA SERVICES S.A.S.
004
005 This file is part of Granite Data Services.
006
007 Granite Data Services is free software; you can redistribute it and/or modify
008 it under the terms of the GNU Library General Public License as published by
009 the Free Software Foundation; either version 2 of the License, or (at your
010 option) any later version.
011
012 Granite Data Services is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 for more details.
016
017 You should have received a copy of the GNU Library General Public License
018 along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020
021 package org.granite.client.persistence;
022
023 import java.lang.annotation.Annotation;
024 import java.lang.annotation.ElementType;
025 import java.lang.annotation.Target;
026 import java.lang.reflect.Field;
027 import java.lang.reflect.Method;
028 import java.lang.reflect.Modifier;
029 import java.util.concurrent.ConcurrentHashMap;
030 import java.util.concurrent.ConcurrentMap;
031
032 import org.granite.client.persistence.collection.PersistentCollection;
033
034 /**
035 * @author Franck WOLFF
036 */
037 public class Persistence {
038
039 private static final String INITIALIZED_FIELD_NAME = "__initialized__";
040 private static final String DETACHED_STATE_FIELD_NAME = "__detachedState__";
041
042 private static final PropertyAccessor NULL_PROPERTY_ACCESSOR = new MethodAccessor(null, null, null);
043
044 private static final ConcurrentMap<Class<?>, PropertyAccessor> idAccessors = new ConcurrentHashMap<Class<?>, PropertyAccessor>();
045 private static final ConcurrentMap<Class<?>, PropertyAccessor> uidAccessors = new ConcurrentHashMap<Class<?>, PropertyAccessor>();
046 private static final ConcurrentMap<Class<?>, PropertyAccessor> versionAccessors = new ConcurrentHashMap<Class<?>, PropertyAccessor>();
047
048 private static final Field NULL_FIELD;
049 static {
050 try {
051 NULL_FIELD = Persistence.class.getDeclaredField("NULL_FIELD");
052 }
053 catch (Exception e) {
054 throw new ExceptionInInitializerError(e);
055 }
056 }
057
058 private static final ConcurrentMap<Class<?>, Field> initializedFields = new ConcurrentHashMap<Class<?>, Field>();
059 private static final ConcurrentMap<Class<?>, Field> detachedStateFields = new ConcurrentHashMap<Class<?>, Field>();
060
061 public static Field getInitializedField(Class<?> cls) {
062 return findField(cls, initializedFields, INITIALIZED_FIELD_NAME, Boolean.TYPE);
063 }
064
065 public static Field getDetachedStateField(Class<?> cls) {
066 return findField(cls, detachedStateFields, DETACHED_STATE_FIELD_NAME, String.class);
067 }
068
069 public static boolean isInitialized(Object o) {
070 if (o instanceof PersistentCollection)
071 return ((PersistentCollection)o).wasInitialized();
072
073 Class<?> cls = o.getClass();
074 if (!cls.isAnnotationPresent(Entity.class))
075 return true;
076
077 Field field = findField(cls, initializedFields, INITIALIZED_FIELD_NAME, Boolean.TYPE);
078 if (field == null)
079 return true;
080
081 try {
082 return field.getBoolean(o);
083 }
084 catch (Exception e) {
085 throw new RuntimeException("Could not get field " + field + " value on object " + o, e);
086 }
087 }
088
089 public static void setInitialized(Object o, boolean value) throws IllegalAccessException {
090 Class<?> cls = o.getClass();
091 if (!cls.isAnnotationPresent(Entity.class))
092 throw new IllegalAccessException("Not annotated with @Entity: " + cls);
093
094 Field field = findField(cls, initializedFields, INITIALIZED_FIELD_NAME, Boolean.TYPE);
095 if (field == null)
096 throw new IllegalAccessException("Could not find field " + INITIALIZED_FIELD_NAME + " in " + o);
097
098 try {
099 field.setBoolean(o, value);
100 }
101 catch (Exception e) {
102 throw new RuntimeException("Could not set field " + field + " on object " + o + " to value " + value, e);
103 }
104 }
105
106 public static String getDetachedState(Object o) throws IllegalAccessException {
107 Class<?> cls = o.getClass();
108 if (!cls.isAnnotationPresent(Entity.class))
109 return null;
110
111 Field field = findField(cls, detachedStateFields, DETACHED_STATE_FIELD_NAME, String.class);
112 if (field == null)
113 throw new IllegalAccessException("Could not find field " + DETACHED_STATE_FIELD_NAME + " in " + o);
114
115 return (String)field.get(o);
116 }
117
118 public static void setDetachedState(Object o, String value) throws IllegalAccessException {
119 Class<?> cls = o.getClass();
120 if (!cls.isAnnotationPresent(Entity.class))
121 throw new IllegalAccessException("Not annotated with @Entity: " + cls);
122
123 Field field = findField(cls, detachedStateFields, DETACHED_STATE_FIELD_NAME, String.class);
124 if (field == null)
125 throw new IllegalAccessException("Could not find field " + DETACHED_STATE_FIELD_NAME + " in " + o);
126
127 try {
128 field.set(o, value);
129 }
130 catch (Exception e) {
131 throw new RuntimeException("Could not set field " + field + " on object " + o + " to value " + value, e);
132 }
133 }
134
135 @SuppressWarnings("unchecked")
136 public static <T> T getId(Object entity) throws IllegalAccessException {
137 return (T)getIdProperty(entity).getValue();
138 }
139
140 public static void setId(Object entity, Object value) throws IllegalAccessException {
141 getIdProperty(entity).setValue(value);
142 }
143
144 public static String getUid(Object entity) throws IllegalAccessException {
145 return (String)getUidProperty(entity).getValue();
146 }
147
148 public static void setUid(Object entity, String value) throws IllegalAccessException {
149 getUidProperty(entity).setValue(value);
150 }
151
152 @SuppressWarnings("unchecked")
153 public static <T> T getVersion(Object entity) throws IllegalAccessException {
154 return (T)getVersionProperty(entity).getValue();
155 }
156
157 public static Property getIdProperty(Object entity) throws IllegalAccessException {
158 return getProperty(entity, idAccessors, Id.class);
159 }
160
161 public static Property getUidProperty(Object entity) throws IllegalAccessException {
162 return getProperty(entity, uidAccessors, Uid.class);
163 }
164
165 public static Property getVersionProperty(Object entity) throws IllegalAccessException {
166 return getProperty(entity, versionAccessors, Version.class);
167 }
168
169 private static Property getProperty(Object entity, ConcurrentMap<Class<?>, PropertyAccessor> cache, Class<? extends Annotation> annotationClass) throws IllegalAccessException {
170 PropertyAccessor accessor = findPropertyAccessor(entity.getClass(), cache, annotationClass);
171 if (accessor == null)
172 throw newIllegalAccessException(entity.getClass(), annotationClass);
173 return new Property(entity, accessor);
174 }
175
176 private static IllegalAccessException newIllegalAccessException(Class<?> cls, Class<? extends Annotation> annotationClass) {
177 return new IllegalAccessException("Could not find property annotated with " + annotationClass + " in " + cls);
178 }
179
180 private static Field findField(Class<?> cls, ConcurrentMap<Class<?>, Field> cache, String name, Class<?> type) {
181
182 Field field = cache.get(cls);
183
184 if (field == null) {
185 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
186 try {
187 field = c.getDeclaredField(name);
188 }
189 catch (Exception e) {
190 continue;
191 }
192
193 if (field.getType() != type)
194 continue;
195
196 field.setAccessible(true);
197 break;
198 }
199
200 if (field == null)
201 field = NULL_FIELD;
202
203 Field previous = cache.putIfAbsent(cls, field);
204 if (previous != null)
205 field = previous;
206 }
207
208 return (field != NULL_FIELD ? field : null);
209 }
210
211 private static PropertyAccessor findPropertyAccessor(Class<?> cls, ConcurrentMap<Class<?>, PropertyAccessor> cache, Class<? extends Annotation> annotationClass) {
212
213 PropertyAccessor accessor = cache.get(cls);
214
215 if (accessor == null) {
216 boolean searchFields = false;
217 boolean searchMethods = false;
218
219 if (!annotationClass.isAnnotationPresent(Target.class))
220 searchFields = searchMethods = true;
221 else {
222 Target target = annotationClass.getAnnotation(Target.class);
223 for (ElementType targetType : target.value()) {
224 if (targetType == ElementType.FIELD)
225 searchFields = true;
226 else if (targetType == ElementType.METHOD)
227 searchMethods = true;
228 }
229 }
230
231 if (searchFields == false && searchMethods == false)
232 return null;
233
234 final int modifierMask = Modifier.PUBLIC | Modifier.STATIC;
235
236 classLoop:
237 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
238 if (searchMethods) {
239 for (Method method : c.getDeclaredMethods()) {
240 if ((method.getModifiers() & modifierMask) != Modifier.PUBLIC ||
241 !method.isAnnotationPresent(annotationClass))
242 continue;
243
244 if (method.getReturnType() == Void.TYPE) {
245 if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
246 String name = method.getName().substring(3);
247
248 if (name.length() == 0)
249 continue;
250
251 Method getter = null;
252 try {
253 getter = cls.getMethod("get" + name);
254 }
255 catch (Exception e) {
256 try {
257 getter = cls.getMethod("is" + name);
258 }
259 catch (Exception f) {
260 }
261 }
262
263 if (getter != null && (getter.getModifiers() & Modifier.STATIC) != 0 &&
264 getter.getReturnType() != method.getParameterTypes()[0])
265 getter = null;
266
267 accessor = new MethodAccessor(getter, method, uncapitalizePropertyName(name));
268 break classLoop;
269 }
270 }
271 else if (method.getParameterTypes().length == 0 && (method.getName().startsWith("get") || method.getName().startsWith("is"))) {
272 String name;
273 if (method.getName().startsWith("get"))
274 name = method.getName().substring(3);
275 else
276 name = method.getName().substring(2);
277
278 if (name.length() == 0)
279 continue;
280
281 Method setter = null;
282 try {
283 setter = cls.getMethod("set" + name);
284 }
285 catch (Exception e) {
286 }
287
288 if (setter != null && (setter.getModifiers() & Modifier.STATIC) != 0 &&
289 method.getReturnType() != setter.getParameterTypes()[0])
290 setter = null;
291
292 accessor = new MethodAccessor(method, setter, uncapitalizePropertyName(name));
293 break classLoop;
294 }
295 }
296 }
297
298 if (searchFields) {
299 for (Field field : c.getDeclaredFields()) {
300 if ((field.getModifiers() & Modifier.STATIC) == 0 && field.isAnnotationPresent(annotationClass)) {
301 accessor = new FieldAccessor(field);
302 break classLoop;
303 }
304 }
305 }
306 }
307
308 if (accessor == null)
309 accessor = NULL_PROPERTY_ACCESSOR;
310
311 PropertyAccessor previous = cache.putIfAbsent(cls, accessor);
312 if (previous != null)
313 accessor = previous;
314 }
315
316 return (accessor != NULL_PROPERTY_ACCESSOR ? accessor : null);
317 }
318
319 private static String uncapitalizePropertyName(String name) {
320 if (name.length() > 0 && Character.isUpperCase(name.charAt(0)))
321 name = name.substring(0, 1).toLowerCase() + name.substring(1);
322 return name;
323 }
324
325 static interface PropertyAccessor {
326
327 String getName();
328 Class<?> getType();
329
330 boolean isReadable();
331 boolean isWritable();
332
333 Object get(Object obj) throws IllegalAccessException;
334 void set(Object obj, Object value) throws IllegalAccessException;
335 }
336
337 static class FieldAccessor implements PropertyAccessor {
338
339 private final Field field;
340
341 public FieldAccessor(Field field) {
342 field.setAccessible(true);
343
344 this.field = field;
345 }
346
347 public Field getField() {
348 return field;
349 }
350
351 public String getName() {
352 return field.getName();
353 }
354
355 public Class<?> getType() {
356 return field.getType();
357 }
358
359 public boolean isReadable() {
360 return true;
361 }
362
363 public boolean isWritable() {
364 return true;
365 }
366
367 public Object get(Object obj) throws IllegalAccessException {
368 try {
369 return field.get(obj);
370 }
371 catch (IllegalAccessException e) {
372 throw e;
373 }
374 catch (Exception e) {
375 throw new IllegalAccessException("Could not get field " + field + " value on object " + obj + ": " + e.toString());
376 }
377 }
378
379 public void set(Object obj, Object value) throws IllegalAccessException {
380 try {
381 field.set(obj, value);
382 }
383 catch (IllegalAccessException e) {
384 throw e;
385 }
386 catch (Exception e) {
387 throw new IllegalAccessException("Could not set field " + field + " on object " + obj + " to value " + value + ": " + e.toString());
388 }
389 }
390 }
391
392 static class MethodAccessor implements PropertyAccessor {
393
394 private final Method getter;
395 private final Method setter;
396
397 private final String name;
398
399 public MethodAccessor(Method getter, Method setter, String name) {
400
401 if (getter != null)
402 getter.setAccessible(true);
403 if (setter != null)
404 setter.setAccessible(true);
405
406 this.getter = getter;
407 this.setter = setter;
408
409 this.name = name;
410 }
411
412 public Method getGetter() {
413 return getter;
414 }
415
416 public Method getSetter() {
417 return setter;
418 }
419
420 public String getName() {
421 return name;
422 }
423
424 public Class<?> getType() {
425 return (getter != null ? getter.getReturnType() : setter.getParameterTypes()[0]);
426 }
427
428 public boolean isReadable() {
429 return getter != null;
430 }
431
432 public boolean isWritable() {
433 return setter != null;
434 }
435
436 public Object get(Object obj) throws IllegalAccessException {
437 try {
438 return getter.invoke(obj);
439 }
440 catch (IllegalAccessException e) {
441 throw e;
442 }
443 catch (Exception e) {
444 throw new IllegalAccessException("Could not invoke getter " + getter + " on object " + obj + ": " + e.toString());
445 }
446 }
447
448 public void set(Object obj, Object value) throws IllegalAccessException {
449 try {
450 setter.invoke(obj, value);
451 }
452 catch (IllegalAccessException e) {
453 throw e;
454 }
455 catch (Exception e) {
456 throw new IllegalAccessException("Could not invoke setter " + setter + " on object " + obj + " with " + value + ": " + e.toString());
457 }
458 }
459 }
460
461 public static class Property {
462
463 private final Object obj;
464 private final PropertyAccessor accessor;
465
466 public Property(Object obj, PropertyAccessor accessor) {
467 this.obj = obj;
468 this.accessor = accessor;
469 }
470
471 public String getName() {
472 return accessor.getName();
473 }
474
475 public Class<?> getType() {
476 return accessor.getType();
477 }
478
479 public boolean isReadable() {
480 return accessor.isReadable();
481 }
482
483 public boolean isWritable() {
484 return accessor.isWritable();
485 }
486
487 public Object getValue() throws IllegalAccessException {
488 return accessor.get(obj);
489 }
490
491 public void setValue(Object value) throws IllegalAccessException {
492 accessor.set(obj, value);
493 }
494 }
495 }