net.java.ao.ReadOnlyEntityProxy Maven / Gradle / Ivy
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 extends RawEntity>> entityType = (Class extends RawEntity>>) 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 - 2025 Weber Informatics LLC | Privacy Policy