--- /dev/null
+/*
+ * Copyright 2005-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wamblee.persistence;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.persistence.Id;
+import javax.persistence.Version;
+
+import org.wamblee.reflection.ReflectionUtils;
+
+/**
+ * Factory which creates a {@link Persistent} object for a given entity for
+ * interfacing with the primary key and version of the entity.
+ *
+ * This utility only treats primary keys and fields that are annotated with @Id
+ * and @Version. In case ORM files are used for the definition of primary key
+ * and or version, then those fields are ignored.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class PersistentFactory {
+
+ /**
+ * Cache of a mapping of class names to entity accessors.
+ */
+ private static Map<String, EntityAccessor> CACHE = new ConcurrentHashMap<String, EntityAccessor>();
+
+ static interface Accessor<T> {
+ void set(Object aEntity, T aValue);
+
+ T get(Object aEntity);
+ }
+
+ static class FieldAccessor<T> implements Accessor<T> {
+ private Field field;
+
+ public FieldAccessor(Field aField) {
+ field = aField;
+ field.setAccessible(true);
+ }
+
+ @Override
+ public T get(Object aEntity) {
+ try {
+ return (T) field.get(aEntity);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void set(Object aEntity, T aValue) {
+ try {
+ field.set(aEntity, aValue);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ public Field getField() {
+ return field;
+ }
+ }
+
+ static class PropertyAccessor<T> implements Accessor<T> {
+ private Method getter;
+ private Method setter;
+
+ public PropertyAccessor(Method aGetter, Method aSetter) {
+ getter = aGetter;
+ setter = aSetter;
+ getter.setAccessible(true);
+ setter.setAccessible(true);
+ }
+
+ @Override
+ public T get(Object aEntity) {
+ try {
+ return (T) getter.invoke(aEntity);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void set(Object aEntity, T aValue) {
+ try {
+ setter.invoke(aEntity, aValue);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Method getGetter() {
+ return getter;
+ }
+
+ public Method getSetter() {
+ return setter;
+ }
+ }
+
+ static class EntityAccessor {
+ private Accessor pk;
+ private Accessor version;
+
+ public EntityAccessor(Accessor<?> aPk, Accessor<?> aVersion) {
+ pk = aPk;
+ version = aVersion;
+ }
+
+ public Accessor getPk() {
+ return pk;
+ }
+
+ public Accessor getVersion() {
+ return version;
+ }
+ }
+
+ public static class EntityObjectAccessor implements Persistent {
+ private EntityAccessor accessor;
+ private Object entity;
+
+ public EntityObjectAccessor(Object aEntity, EntityAccessor aAccessor) {
+ accessor = aAccessor;
+ entity = aEntity;
+ }
+
+ public EntityAccessor getAccessor() {
+ return accessor;
+ };
+
+ @Override
+ public Serializable getPrimaryKey() {
+ return (Serializable)accessor.getPk().get(entity);
+ }
+
+ @Override
+ public void setPrimaryKey(Serializable aKey) {
+ accessor.getPk().set(entity, aKey);
+ }
+
+ @Override
+ public Number getPersistedVersion() {
+ return (Number)accessor.getVersion().get(entity);
+ }
+
+ @Override
+ public void setPersistedVersion(Number aVersion) {
+ accessor.getVersion().set(entity, aVersion);
+ }
+ }
+
+ /**
+ * Create the entity accessor for a given class or returns a cached instance
+ * if one already exists.
+ *
+ * @param aClass
+ * Class.
+ * @return Entity accessor for the given class or null of the given object
+ * is not an entity.
+ */
+ public static EntityAccessor createEntityAccessor(Class aClass) {
+ EntityAccessor accessor = CACHE.get(aClass.getName());
+ if (accessor == null) {
+ accessor = analyse(aClass);
+ if (accessor != null) {
+ CACHE.put(aClass.getName(), accessor);
+ }
+ }
+ return accessor;
+ }
+
+ private static EntityAccessor analyse(Class aClass) {
+ Accessor<Serializable> pk = analyse(aClass, Id.class);
+ Accessor<Integer> version = analyse(aClass, Version.class);
+ if (pk != null || version != null) {
+ return new EntityAccessor(pk, version);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the accessor for a given annotation.
+ *
+ * @param aClass
+ * Class to analyse.
+ * @param aAnnotation
+ * Annotation that must be present.
+ * @return Accessor to use or null if the annotation is not present.
+ */
+ private static Accessor analyse(Class aClass,
+ Class<? extends Annotation> aAnnotation) {
+ List<Field> fields = ReflectionUtils.getAllFields(aClass);
+ for (Field field : fields) {
+ if (field.isAnnotationPresent(aAnnotation)) {
+ return new FieldAccessor(field);
+ }
+ }
+ List<Method> methods = ReflectionUtils.getAllMethods(aClass,
+ Object.class);
+ for (Method method : methods) {
+ if (method.isAnnotationPresent(aAnnotation)) {
+ String setterName = null;
+ if (method.getName().startsWith("get")) {
+ setterName = method.getName().replaceFirst("get", "set");
+ } else if (method.getName().startsWith("is")) {
+ setterName = method.getName().replaceFirst("is", "set");
+ }
+ try {
+ Class returnType = method.getReturnType();
+ Method setter = method.getDeclaringClass().getDeclaredMethod(setterName, returnType);
+ return new PropertyAccessor(method, setter);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Error obtaining setter for " +
+ method.getName() + " in class " + aClass.getName(), e);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates the {@link Persistent} wrapper for interfacing with primary key and
+ * version of the entity.
+ * @param aEntity Entity to use.
+ * @return Persistent object or null if this is not an entity.
+ */
+ public static Persistent create(Object aEntity) {
+ EntityAccessor accessor = createEntityAccessor(aEntity.getClass());
+ if ( accessor == null ) {
+ return null;
+ }
+ return new EntityObjectAccessor(aEntity, accessor);
+ }
+}