All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.ao.ReadOnlyEntityProxy Maven / Gradle / Ivy

Go to download

This is the core library for Active Objects. It is generic and can be embedded in any environment. As such it is generic and won't contain all connection pooling, etc.

There is a newer version: 6.1.1
Show newest version
package net.java.ao;

import net.java.ao.schema.FieldNameConverter;
import net.java.ao.schema.TableNameConverter;
import net.java.ao.schema.info.EntityInfo;
import net.java.ao.schema.info.FieldInfo;
import net.java.ao.types.TypeInfo;
import net.java.ao.types.TypeManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 

A read-only representation of a database row proxy, mapped to the specified Entity type.

* *

This implementation is used for streaming and intended to be as lightweight as possible. It only supports getters * or designated {@link Accessor} methods. Calling setters or save will result in an exception. Other method calls will * be ignored.

* * *

As much as possible, reflection calls should be kept out of this implementation. If there is information needed about * the implemented type, please use {@link net.java.ao.schema.info.EntityInfo} to cache them.

* *

TODO There is currently some overlap with {@link EntityProxy}. As soon as this is battle-hardened, these can be refactored * into an abstract superclass, for example.

* * @param the type of Entity * @param the primary key type * @author ahennecke */ public class ReadOnlyEntityProxy, K> implements InvocationHandler { private final static String UNDEFINED_COMPARISION_RESULT = "Cannot compare two entities with null key - undefined behaviour"; private final K key; private final EntityInfo entityInfo; private final EntityManager manager; private ImplementationWrapper implementation; private final Map values = new HashMap(); public ReadOnlyEntityProxy( EntityManager manager, EntityInfo entityInfo, K key) { this.manager = manager; this.entityInfo = entityInfo; this.key = key; } @SuppressWarnings("unchecked") public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // optimise reflection call String methodName = method.getName(); if (methodName.equals("getEntityProxy")) { return this; } else if (methodName.equals("getEntityType")) { return getType(); } else if (methodName.equals("save")) { // someone tried to call "save" at a read-only instance. We don't simply ignore that but rather throw an // exception, so the client knows that the call did not do what he expected. throw new RuntimeException("'save' method called on a read-only entity of type " + entityInfo.getEntityType().getSimpleName()); } if (implementation == null) { implementation = new ImplementationWrapper(); implementation.init((T) proxy); } MethodImplWrapper methodImpl = implementation.getMethod(methodName, method.getParameterTypes()); if (methodImpl != null) { final Class declaringClass = methodImpl.getMethod().getDeclaringClass(); if (!Object.class.equals(declaringClass)) { // We don't want to return get the class Class using Class.forName as this doesn't play well // with multiple ClassLoaders (if the AO library has a separate class loader to the AO client) // Instead just compare the classNames final String callingClassName = Common.getCallingClassName(1); if (callingClassName == null || !callingClassName.equals(declaringClass.getName())) { return methodImpl.getMethod().invoke(methodImpl.getInstance(), args); } } } if (methodName.equals("getEntityManager")) { return getManager(); } else if (methodName.equals("hashCode")) { return hashCodeImpl(); } else if (methodName.equals("equals")) { return equalsImpl((RawEntity) proxy, args[0]); } else if (methodName.equals("toString")) { return toStringImpl(); } else if (methodName.equals("init")) { return null; } if (entityInfo.hasAccessor(method)) { return invokeGetter((RawEntity) proxy, getKey(), entityInfo.getField(method).getName(), method.getReturnType()); } else if (entityInfo.hasMutator(method)) { // someone tried to call "save" at a read-only instance. We don't simply ignore that but rather throw an // exception, so the client knows that the call did not do what he expected. throw new RuntimeException("Setter method called on a read-only entity of type " + entityInfo.getEntityType().getSimpleName() + ": " + methodName); } return null; } @SuppressWarnings({"rawtypes", "unchecked"}) public void addValue(String fieldName, ResultSet res) throws SQLException { FieldInfo fieldInfo = entityInfo.getField(fieldName); Class type = fieldInfo.getJavaType(); String polyName = fieldInfo.getPolymorphicName(); Object value = convertValue(res, fieldName, polyName, type); values.put(fieldName, value); } public K getKey() { return key; } public int hashCodeImpl() { return (Objects.hashCode(key) + entityInfo.hashCode()) % (2 << 15); } public boolean equalsImpl(RawEntity proxy, Object obj) { if (proxy == obj) { return true; } if (obj instanceof RawEntity) { RawEntity entity = (RawEntity) obj; String ourTableName = getTableNameConverter().getName(proxy.getEntityType()); String theirTableName = getTableNameConverter().getName(entity.getEntityType()); Object objectKey = Common.getPrimaryKeyValue(entity); if(objectKey == null && key == null){ throw new IllegalArgumentException(UNDEFINED_COMPARISION_RESULT); } return Objects.equals(objectKey, key) && theirTableName.equals(ourTableName); } return false; } private TableNameConverter getTableNameConverter() { return getManager().getNameConverters().getTableNameConverter(); } public String toStringImpl() { return entityInfo.getName() + " {" + entityInfo.getPrimaryKey().getName() + " = " + String.valueOf(key) + "}"; } private FieldNameConverter getFieldNameConverter() { return getManager().getNameConverters().getFieldNameConverter(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof ReadOnlyEntityProxy) { ReadOnlyEntityProxy proxy = (ReadOnlyEntityProxy) obj; if (proxy.entityInfo.equals(entityInfo)) { if(proxy.key == null && key == null){ throw new IllegalArgumentException(UNDEFINED_COMPARISION_RESULT); } return Objects.equals(proxy.key, key); } } return false; } @Override public int hashCode() { return hashCodeImpl(); } Class getType() { return entityInfo.getEntityType(); } private EntityManager getManager() { return manager; } @SuppressWarnings("unchecked") private V invokeGetter(RawEntity entity, K key, String name, Class type) throws Throwable { Object value = values.get(name); if (instanceOf(value, type)) { return handleNullReturn((V) value, type); } else if (isBigDecimal(value, type)) { // Oracle for example returns BigDecimal when we expect doubles return (V) handleBigDecimal(value, type); } return handleNullReturn(null, type); } private V convertValue(ResultSet res, String field, String polyName, Class type) throws SQLException { if (isNull(res, field)) { return null; } if (polyName != null) { Class> entityType = (Class>) type; entityType = getManager().getPolymorphicTypeMapper().invert(entityType, res.getString(polyName)); type = (Class) entityType; // avoiding Java cast oddities with generics } final TypeManager manager = getTypeManager(); final TypeInfo databaseType = manager.getType(type); if (databaseType == null) { throw new RuntimeException("UnrecognizedType: " + type.toString()); } return databaseType.getLogicalType().pullFromDatabase(getManager(), res, type, field); } private TypeManager getTypeManager() { return getManager().getProvider().getTypeManager(); } private boolean isNull(ResultSet res, String field) throws SQLException { res.getObject(field); return res.wasNull(); } @SuppressWarnings("unchecked") private V handleNullReturn(V back, Class type) { if (back != null) { return back; } if (type.isPrimitive()) { if (type.equals(boolean.class)) { return (V) Boolean.FALSE; } else if (type.equals(char.class)) { return (V) new Character(' '); } else if (type.equals(int.class)) { return (V) new Integer(0); } else if (type.equals(short.class)) { return (V) new Short("0"); } else if (type.equals(long.class)) { return (V) new Long("0"); } else if (type.equals(float.class)) { return (V) new Float("0"); } else if (type.equals(double.class)) { return (V) new Double("0"); } else if (type.equals(byte.class)) { return (V) new Byte("0"); } } return null; } private boolean instanceOf(Object value, Class type) { if (value == null) { return true; } if (type.isPrimitive()) { if (type.equals(boolean.class)) { return instanceOf(value, Boolean.class); } else if (type.equals(char.class)) { return instanceOf(value, Character.class); } else if (type.equals(byte.class)) { return instanceOf(value, Byte.class); } else if (type.equals(short.class)) { return instanceOf(value, Short.class); } else if (type.equals(int.class)) { return instanceOf(value, Integer.class); } else if (type.equals(long.class)) { return instanceOf(value, Long.class); } else if (type.equals(float.class)) { return instanceOf(value, Float.class); } else if (type.equals(double.class)) { return instanceOf(value, Double.class); } } else { return type.isInstance(value); } return false; } /** * Some DB (Oracle) return BigDecimal for about any number */ private boolean isBigDecimal(Object value, Class type) { if (!(value instanceof BigDecimal)) { return false; } return type.equals(int.class) || type.equals(long.class) || type.equals(float.class) || type.equals(double.class); } private Object handleBigDecimal(Object value, Class type) { final BigDecimal bd = (BigDecimal) value; if (type.equals(int.class)) { return bd.intValue(); } else if (type.equals(long.class)) { return bd.longValue(); } else if (type.equals(float.class)) { return bd.floatValue(); } else if (type.equals(double.class)) { return bd.doubleValue(); } else { throw new RuntimeException("Could not resolve actual type for object :" + value + ", expected type is " + type); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy