/* * 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 CACHE = new ConcurrentHashMap(); static interface Accessor { void set(Object aEntity, T aValue); T get(Object aEntity); } static class FieldAccessor implements Accessor { private Field field; public FieldAccessor(Field aField) { field = aField; field.setAccessible(true); } @Override public T get(Object aEntity) { try { T value = (T) field.get(aEntity); return value; } 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 implements Accessor { 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() { if (accessor == null || accessor.getPk() == null) { return null; } return (Serializable) accessor.getPk().get(entity); } @Override public void setPrimaryKey(Serializable aKey) { if (accessor == null || accessor.getPk() == null) { return; } accessor.getPk().set(entity, aKey); } @Override public Number getPersistedVersion() { if ( accessor == null || accessor.getVersion() == null) { return null; } return (Number) accessor.getVersion().get(entity); } @Override public void setPersistedVersion(Number aVersion) { if ( accessor == null || accessor.getVersion() == null) { return; } 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 pk = analyse(aClass, Id.class); Accessor 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 aAnnotation) { List fields = ReflectionUtils.getAllFields(aClass); for (Field field : fields) { if (field.isAnnotationPresent(aAnnotation)) { return new FieldAccessor(field); } } List 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); } }