
net.java.ao.EntityProxy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activeobjects Show documentation
Show all versions of activeobjects Show documentation
This is the full Active Objects library, if you don't know which one to use, you probably want this one.
/*
* Copyright 2007 Daniel Spiewak
*
* 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 net.java.ao;
import net.java.ao.cache.CacheLayer;
import net.java.ao.schema.FieldNameConverter;
import net.java.ao.schema.NotNull;
import net.java.ao.schema.TableNameConverter;
import net.java.ao.types.TypeInfo;
import net.java.ao.types.TypeManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static net.java.ao.Common.*;
import static net.java.ao.sql.SqlUtils.closeQuietly;
/**
* @author Daniel Spiewak
*/
public class EntityProxy, K> implements InvocationHandler
{
static boolean ignorePreload = false; // hack for testing
private final K key;
private final Method pkAccessor;
private final String pkFieldName;
private final Class type;
private final EntityManager manager;
private CacheLayer layer;
private Map locks;
private final ReadWriteLock locksLock = new ReentrantReadWriteLock();
private ImplementationWrapper implementation;
private List listeners;
public EntityProxy(EntityManager manager, Class type, K key) {
this.key = key;
this.type = type;
this.manager = manager;
pkAccessor = Common.getPrimaryKeyAccessor(type);
pkFieldName = Common.getPrimaryKeyField(type, getFieldNameConverter());
locks = new HashMap();
listeners = new LinkedList();
}
private FieldNameConverter getFieldNameConverter()
{
return this.manager.getNameConverters().getFieldNameConverter();
}
@SuppressWarnings("unchecked")
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("getEntityProxy")) {
return this;
}
if (method.getName().equals("getEntityType")) {
return type;
}
if (implementation == null) {
implementation = new ImplementationWrapper();
implementation.init((T) proxy);
}
MethodImplWrapper methodImpl = implementation.getMethod(method.getName(), 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 (method.getName().equals(pkAccessor.getName())) {
return getKey();
} else if (method.getName().equals("save")) {
save((RawEntity) proxy);
return Void.TYPE;
} else if (method.getName().equals("getEntityManager")) {
return manager;
} else if (method.getName().equals("addPropertyChangeListener")) {
addPropertyChangeListener((PropertyChangeListener) args[0]);
return null;
} else if (method.getName().equals("removePropertyChangeListener")) {
removePropertyChangeListener((PropertyChangeListener) args[0]);
return null;
} else if (method.getName().equals("hashCode")) {
return hashCodeImpl();
} else if (method.getName().equals("equals")) {
return equalsImpl((RawEntity) proxy, args[0]);
} else if (method.getName().equals("toString")) {
return toStringImpl();
} else if (method.getName().equals("init")) {
return null;
}
checkConstraints(method, args);
String tableName = getTableNameConverter().getName(type);
Class> attributeType = Common.getAttributeTypeFromMethod(method);
String polyFieldName = null;
if (attributeType != null) {
polyFieldName = (attributeType.getAnnotation(Polymorphic.class) == null ? null :
getFieldNameConverter().getPolyTypeName(method));
}
OneToOne oneToOneAnnotation = method.getAnnotation(OneToOne.class);
OneToMany oneToManyAnnotation = method.getAnnotation(OneToMany.class);
ManyToMany manyToManyAnnotation = method.getAnnotation(ManyToMany.class);
AnnotationDelegate annotations = Common.getAnnotationDelegate(getFieldNameConverter(), method);
Transient transientAnnotation = annotations.getAnnotation(Transient.class);
// check annotations first, they trump all
if (oneToOneAnnotation != null && Common.interfaceInheritsFrom(method.getReturnType(), RawEntity.class)) {
Class extends RawEntity>> type = (Class extends RawEntity>>) method.getReturnType();
Object[] back = retrieveRelations(new String[0],
new String[] { Common.getPrimaryKeyField(type, getFieldNameConverter()) },
(Class extends RawEntity>) type, Common.where(oneToOneAnnotation, getFieldNameConverter()),
Common.getPolymorphicFieldNames(getFieldNameConverter(), type, this.type));
return back.length == 0 ? null : back[0];
} else if (oneToManyAnnotation != null && method.getReturnType().isArray()
&& Common.interfaceInheritsFrom(method.getReturnType().getComponentType(), RawEntity.class)) {
Class extends RawEntity>> type = (Class extends RawEntity>>) method.getReturnType().getComponentType();
return retrieveRelations(new String[0],
new String[] { Common.getPrimaryKeyField(type, getFieldNameConverter()) },
(Class extends RawEntity>) type, where(oneToManyAnnotation, getFieldNameConverter()),
Common.getPolymorphicFieldNames(getFieldNameConverter(), type, this.type));
} else if (manyToManyAnnotation != null && method.getReturnType().isArray()
&& Common.interfaceInheritsFrom(method.getReturnType().getComponentType(), RawEntity.class)) {
Class extends RawEntity>> throughType = manyToManyAnnotation.value();
Class extends RawEntity>> type = (Class extends RawEntity>>) method.getReturnType().getComponentType();
return retrieveRelations(null,
Common.getMappingFields(getFieldNameConverter(),
throughType, type), throughType, (Class extends RawEntity>) type,
Common.where(manyToManyAnnotation, getFieldNameConverter()),
Common.getPolymorphicFieldNames(getFieldNameConverter(), throughType, this.type),
Common.getPolymorphicFieldNames(getFieldNameConverter(), throughType, type));
} else if (Common.isAccessor(method)) {
return invokeGetter((RawEntity>) proxy, getKey(), tableName, getFieldNameConverter().getName(method),
polyFieldName, method.getReturnType(), transientAnnotation == null);
} else if (Common.isMutator(method)) {
invokeSetter((T) proxy, getFieldNameConverter().getName(method), args[0], polyFieldName);
return Void.TYPE;
}
throw new RuntimeException("Cannot handle method with signature: " + method.toString());
}
private TableNameConverter getTableNameConverter()
{
return manager.getNameConverters().getTableNameConverter();
}
public K getKey() {
return key;
}
@SuppressWarnings("unchecked")
public void save(RawEntity entity) throws SQLException {
CacheLayer cacheLayer = getCacheLayer(entity);
String[] dirtyFields = cacheLayer.getDirtyFields();
if (dirtyFields.length == 0) {
return;
}
String table = getTableNameConverter().getName(type);
final DatabaseProvider provider = this.manager.getProvider();
final TypeManager typeManager = provider.getTypeManager();
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = provider.getConnection();
StringBuilder sql = new StringBuilder("UPDATE " + provider.withSchema(table) + " SET ");
for (String field : dirtyFields) {
sql.append(provider.processID(field));
if (cacheLayer.contains(field)) {
sql.append(" = ?,");
} else {
sql.append(" = NULL,");
}
}
if (sql.charAt(sql.length() - 1) == ',') {
sql.setLength(sql.length() - 1);
}
sql.append(" WHERE ").append(provider.processID(pkFieldName)).append(" = ?");
stmt = provider.preparedStatement(conn, sql);
List events = new LinkedList();
int index = 1;
for (String field : dirtyFields) {
if (!cacheLayer.contains(field)) {
continue;
}
Object value = cacheLayer.get(field);
events.add(new PropertyChangeEvent(entity, field, null, value));
if (value == null) {
this.manager.getProvider().putNull(stmt, index++);
} else {
Class javaType = value.getClass();
if (value instanceof RawEntity) {
javaType = ((RawEntity) value).getEntityType();
}
TypeInfo dbType = typeManager.getType(javaType);
dbType.getLogicalType().validate(value);
dbType.getLogicalType().putToDatabase(this.manager, stmt, index++, value, dbType.getJdbcWriteType());
if (!dbType.getLogicalType().shouldCache(javaType)) {
cacheLayer.remove(field);
}
}
}
TypeInfo pkType = Common.getPrimaryKeyType(provider.getTypeManager(), type);
pkType.getLogicalType().putToDatabase(this.manager, stmt, index, key, pkType.getJdbcWriteType());
cacheLayer.clearFlush();
stmt.executeUpdate();
for (PropertyChangeListener l : listeners) {
for (PropertyChangeEvent evt : events) {
l.propertyChange(evt);
}
}
cacheLayer.clearDirty();
}
finally
{
closeQuietly(stmt);
closeQuietly(conn);
}
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.add(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
listeners.remove(listener);
}
public int hashCodeImpl() {
return (key.hashCode() + type.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());
return Common.getPrimaryKeyValue(entity).equals(key) && theirTableName.equals(ourTableName);
}
return false;
}
public String toStringImpl() {
return getTableNameConverter().getName(type) + " {" + pkFieldName + " = " + key.toString() + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof EntityProxy, ?>) {
EntityProxy, ?> proxy = (EntityProxy, ?>) obj;
if (proxy.type.equals(type) && proxy.key.equals(key)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return hashCodeImpl();
}
CacheLayer getCacheLayer(RawEntity> entity) {
// not atomic, but throughput is more important in this case
if (layer == null) {
layer = manager.getCache().createCacheLayer(entity);
}
return layer;
}
Class getType() {
return type;
}
// any dirty fields are kept in the cache, since they have yet to be saved
void flushCache(RawEntity> entity) {
getCacheLayer(entity).clear();
}
private ReadWriteLock getLock(String field) {
locksLock.writeLock().lock();
try {
if (locks.containsKey(field)) {
return locks.get(field);
}
ReentrantReadWriteLock back = new ReentrantReadWriteLock();
locks.put(field, back);
return back;
} finally {
locksLock.writeLock().unlock();
}
}
private V invokeGetter(RawEntity> entity, K key, String table, String name, String polyName, Class type, boolean shouldCache) throws Throwable {
V back = null;
CacheLayer cacheLayer = getCacheLayer(entity);
shouldCache = shouldCache && getTypeManager().getType(type).getLogicalType().shouldCache(type);
getLock(name).writeLock().lock();
try {
if (!shouldCache && cacheLayer.dirtyContains(name)) {
return handleNullReturn(null, type);
} else if (shouldCache && cacheLayer.contains(name)) {
Object value = cacheLayer.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);
} else if (Common.interfaceInheritsFrom(type, RawEntity.class)
&& instanceOf(value, Common.getPrimaryKeyClassType((Class extends RawEntity>) type))) {
value = manager.peer((Class extends RawEntity
© 2015 - 2025 Weber Informatics LLC | Privacy Policy