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

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

Go to download

This is the full Active Objects library, if you don't know which one to use, you probably want this one.

The newest version!
/*
 * 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 com.google.common.base.Defaults;
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.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.HashSet;
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.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.util.stream.Collectors.joining;
import static net.java.ao.Common.getAttributeTypeFromMethod;
import static net.java.ao.Common.preloadValue;
import static net.java.ao.Common.where;
import static net.java.ao.sql.SqlUtils.closeQuietly;

/**
 * @author Daniel Spiewak
 */
public class EntityProxy, K> implements InvocationHandler {
    private final K key;
    private final EntityInfo entityInfo;

    private final EntityManager manager;

    private final Map values = new HashMap();
    private final Set dirty = new HashSet();
    private final Lock lockValuesDirty = new ReentrantLock();

    private ImplementationWrapper implementation;
    private List listeners;

    EntityProxy(EntityManager manager, EntityInfo entityInfo, K key) {
        this.key = key;
        this.entityInfo = entityInfo;
        this.manager = manager;

        listeners = new LinkedList();
    }

    private FieldNameConverter getFieldNameConverter() {
        return manager.getNameConverters().getFieldNameConverter();
    }

    @SuppressWarnings("unchecked")
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        final String methodName = method.getName();

        if (methodName.equals("getEntityProxy")) {
            return this;
        }

        if (methodName.equals("getEntityType")) {
            return getType();
        }

        if (implementation == null) {
            implementation = new ImplementationWrapper();
            implementation.init((T) proxy);
        }

        final 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 (entityInfo.hasAccessor(method) && entityInfo.getField(method).equals(entityInfo.getPrimaryKey())) {
            return getKey();
        }
        if (methodName.equals("save")) {
            save((RawEntity) proxy);
            return Void.TYPE;
        }
        if (methodName.equals("getEntityManager")) {
            return manager;
        }
        if (methodName.equals("addPropertyChangeListener")) {
            addPropertyChangeListener((PropertyChangeListener) args[0]);
            return null;
        }
        if (methodName.equals("removePropertyChangeListener")) {
            removePropertyChangeListener((PropertyChangeListener) args[0]);
            return null;
        }
        if (methodName.equals("hashCode")) {
            return hashCodeImpl();
        }
        if (methodName.equals("equals")) {
            return equalsImpl((RawEntity) proxy, args[0]);
        }
        if (methodName.equals("toString")) {
            return toStringImpl();
        }
        if (methodName.equals("init")) {
            return null;
        }

        final FieldInfo fieldInfo = entityInfo.getField(method);

        if (fieldInfo != null && !fieldInfo.isNullable() && args != null && args.length > 0) {
            if (args[0] == null) {
                throw new IllegalArgumentException("Field '" + getFieldNameConverter().getName(method) + "' does not accept null values");
            }
        }

        // TODO move this information into EntityInfo and friends
        final OneToOne oneToOneAnnotation = method.getAnnotation(OneToOne.class);
        final boolean isOneToOne = oneToOneAnnotation != null && RawEntity.class.isAssignableFrom(method.getReturnType());
        final OneToMany oneToManyAnnotation = method.getAnnotation(OneToMany.class);
        final boolean isOneToMany = oneToManyAnnotation != null && method.getReturnType().isArray() && RawEntity.class.isAssignableFrom(method.getReturnType().getComponentType());
        final ManyToMany manyToManyAnnotation = method.getAnnotation(ManyToMany.class);
        final boolean isManyToMany = manyToManyAnnotation != null && method.getReturnType().isArray() && RawEntity.class.isAssignableFrom(method.getReturnType().getComponentType());
        if (isOneToOne || isOneToMany || isManyToMany) {
            final Object ret;
            lockValuesDirty.lock();
            try {
                if (values.containsKey(methodName)) {
                    ret = values.get(methodName);
                } else if (isOneToOne) {
                    if (oneToOneAnnotation.reverse().isEmpty()) {
                        ret = legacyFetchOneToOne((RawEntity) proxy, method, oneToOneAnnotation);
                    } else {
                        ret = fetchOneToOne(method, oneToOneAnnotation);
                    }
                    values.put(methodName, ret);
                } else if (isOneToMany) {
                    if (oneToManyAnnotation.reverse().isEmpty()) {
                        ret = legacyFetchOneToMany((RawEntity) proxy, method, oneToManyAnnotation);
                    } else {
                        ret = fetchOneToMany(method, oneToManyAnnotation);
                    }
                    values.put(methodName, ret);
                } else if (isManyToMany) {
                    if (manyToManyAnnotation.reverse().isEmpty() || manyToManyAnnotation.through().isEmpty()) {
                        ret = legacyFetchManyToMany((RawEntity) proxy, method, manyToManyAnnotation);
                    } else {
                        ret = fetchManyToMany(method, manyToManyAnnotation);
                    }
                    values.put(methodName, ret);
                } else {
                    ret = null;
                }
            } finally {
                lockValuesDirty.unlock();
            }
            return ret;
        }

        if (fieldInfo != null) {

            if (method.equals(fieldInfo.getAccessor())) {
                return invokeGetter(fieldInfo);
            }

            if (method.equals(fieldInfo.getMutator())) {
                invokeSetter(getFieldNameConverter().getName(method), args[0], fieldInfo.getPolymorphicName());
                return Void.TYPE;
            }
        }

        throw new IllegalArgumentException("Cannot handle method. It is not a valid getter or setter and does not have an implementation supplied. " +
                "Signature: " + method.toString());
    }

    private RawEntity[] fetchManyToMany(final Method method, final ManyToMany annotation) throws SQLException, NoSuchMethodException {
        final Class remoteType = method.getReturnType().getComponentType();
        final Class throughType = annotation.value();
        final String whereClause = Common.where(annotation, getFieldNameConverter());
        final Preload preloadAnnotation = (Preload) remoteType.getAnnotation(Preload.class);
        final Method reverseMethod = throughType.getMethod(annotation.reverse());
        final Method throughMethod = throughType.getMethod(annotation.through());
        final String reversePolymorphicTypeFieldName = getAttributeTypeFromMethod(reverseMethod).isAnnotationPresent(Polymorphic.class) ? getFieldNameConverter().getPolyTypeName(reverseMethod) : null;
        final String remotePolymorphicTypeFieldName = getAttributeTypeFromMethod(throughMethod).isAnnotationPresent(Polymorphic.class) ? getFieldNameConverter().getPolyTypeName(throughMethod) : null;
        final String returnField = getFieldNameConverter().getName(throughMethod);
        final Set selectFields = new LinkedHashSet();
        final DatabaseProvider provider = manager.getProvider();
        final StringBuilder sql = new StringBuilder("SELECT t.").append(provider.processID(returnField));

        if (remotePolymorphicTypeFieldName != null) {
            sql.append(", t.").append(provider.processID(remotePolymorphicTypeFieldName));
        } else {
            if (preloadAnnotation != null) {
                selectFields.addAll(preloadValue(preloadAnnotation, getFieldNameConverter()));
            } else {
                selectFields.addAll(Common.getValueFieldsNames(manager.resolveEntityInfo(remoteType), getFieldNameConverter()));
            }
            if (selectFields.contains(Preload.ALL)) {
                sql.append(", r.*");
            } else {
                for (final String field : selectFields) {
                    sql.append(", r.").append(manager.getProvider().processID(field));
                }
            }
        }

        final String throughTable = provider.withSchema(getTableNameConverter().getName(throughType));
        sql.append(" FROM ").append(throughTable).append(" t ");
        final String remotePrimaryKeyField = Common.getPrimaryKeyField(remoteType, getFieldNameConverter());
        if (!selectFields.isEmpty()) {
            final String remoteTable = provider.withSchema(getTableNameConverter().getName(remoteType));
            sql.append(" INNER JOIN ").append(remoteTable).append(" r ON t.").append(provider.processID(returnField)).append(" = r.").append(provider.processID(remotePrimaryKeyField));
        }
        final String reverseField = provider.processID(getFieldNameConverter().getName(reverseMethod));
        sql.append(" WHERE ");
        if (!selectFields.isEmpty()) {
            sql.append("t.");
        }
        sql.append(reverseField).append(" = ?");
        if (reversePolymorphicTypeFieldName != null) {
            sql.append(" AND ");
            if (!selectFields.isEmpty()) {
                sql.append("t.");
            }
            sql.append(provider.processID(reversePolymorphicTypeFieldName)).append(" = ?");
        }
        if (!whereClause.trim().equals("")) {
            sql.append(" AND (").append(provider.processWhereClause(whereClause)).append(")");
        }
        final List back = new ArrayList();
        final Connection conn = provider.getConnection();
        try {
            final PreparedStatement stmt = provider.preparedStatement(conn, sql);
            try {
                final TypeInfo dbType = getTypeManager().getType(getClass(key));
                dbType.getLogicalType().putToDatabase(manager, stmt, 1, key, dbType.getJdbcWriteType());
                if (reversePolymorphicTypeFieldName != null) {
                    stmt.setString(2, manager.getPolymorphicTypeMapper().convert(entityInfo.getEntityType()));
                }
                final TypeInfo primaryKeyType = Common.getPrimaryKeyType(provider.getTypeManager(), (Class>) remoteType);
                final ResultSet res = stmt.executeQuery();
                try {
                    while (res.next()) {
                        EntityInfo entityInfo = manager.resolveEntityInfo((remotePolymorphicTypeFieldName == null ? remoteType : manager.getPolymorphicTypeMapper().invert(remoteType, res.getString(remotePolymorphicTypeFieldName))));
                        if (selectFields.remove(Preload.ALL)) {
                            selectFields.addAll(entityInfo.getFieldNames());
                        }
                        final RawEntity returnValueEntity = manager.peer(entityInfo, primaryKeyType.getLogicalType().pullFromDatabase(manager, res, throughType, returnField));
                        final EntityProxy proxy = manager.getProxyForEntity(returnValueEntity);
                        proxy.lockValuesDirty.lock();
                        try {
                            for (final String field : selectFields) {
                                proxy.values.put(field, res.getObject(field));
                            }
                        } finally {
                            proxy.lockValuesDirty.unlock();
                        }
                        back.add(returnValueEntity);
                    }
                } finally {
                    res.close();
                }
            } finally {
                stmt.close();
            }
        } finally {
            conn.close();
        }
        return back.toArray((RawEntity[]) Array.newInstance(remoteType, back.size()));
    }

    private RawEntity[] fetchOneToMany(final Method method, final OneToMany annotation) throws SQLException, NoSuchMethodException {
        final Class remoteType = method.getReturnType().getComponentType();
        final EntityInfo entityInfo = manager.resolveEntityInfo(remoteType);
        final String remotePrimaryKeyFieldName = Common.getPrimaryKeyField(remoteType, getFieldNameConverter());
        final String whereClause = where(annotation, getFieldNameConverter());
        final Preload preloadAnnotation = (Preload) remoteType.getAnnotation(Preload.class);
        final Method remoteMethod = remoteType.getMethod(annotation.reverse());
        final String remotePolymorphicTypeFieldName = getPolymorphicTypeFieldName(remoteMethod);
        final StringBuilder sql = new StringBuilder("SELECT ");
        final Set selectFields = new LinkedHashSet();

        selectFields.add(remotePrimaryKeyFieldName);
        if (remotePolymorphicTypeFieldName == null) {
            if (preloadAnnotation != null) {
                selectFields.addAll(preloadValue(preloadAnnotation, getFieldNameConverter()));
            } else {
                selectFields.addAll(Common.getValueFieldsNames(entityInfo, getFieldNameConverter()));
            }
        }
        if (selectFields.contains(Preload.ALL)) {
            sql.append(Preload.ALL);
        } else {
            for (final String field : selectFields) {
                sql.append(manager.getProvider().processID(field)).append(',');
            }
            sql.setLength(sql.length() - 1);
        }

        sql.append(" FROM ").append(manager.getProvider().withSchema(getTableNameConverter().getName(remoteType)));
        sql.append(" WHERE ").append(manager.getProvider().processID(getFieldNameConverter().getName(remoteMethod))).append(" = ?");
        if (!whereClause.trim().equals("")) {
            sql.append(" AND (").append(manager.getProvider().processWhereClause(whereClause)).append(")");
        }
        if (remotePolymorphicTypeFieldName != null) {
            sql.append(" AND ").append(manager.getProvider().processID(remotePolymorphicTypeFieldName)).append(" = ?");
        }
        final Connection conn = manager.getProvider().getConnection();
        try {
            final PreparedStatement stmt = manager.getProvider().preparedStatement(conn, sql);
            try {
                final TypeInfo dbType = getTypeManager().getType(getClass(key));
                dbType.getLogicalType().putToDatabase(manager, stmt, 1, key, dbType.getJdbcWriteType());
                if (remotePolymorphicTypeFieldName != null) {
                    stmt.setString(2, manager.getPolymorphicTypeMapper().convert(entityInfo.getEntityType()));
                }
                final ResultSet res = stmt.executeQuery();
                try {
                    final List> result = new ArrayList>();
                    while (res.next()) {
                        final Object returnValue = Common.getPrimaryKeyType(getTypeManager(), (Class) remoteType).getLogicalType().pullFromDatabase(manager, res, (Class) remoteType, remotePrimaryKeyFieldName);
                        final RawEntity returnValueEntity = manager.peer(entityInfo, returnValue);
                        final EntityProxy proxy = manager.getProxyForEntity(returnValueEntity);
                        if (selectFields.remove(Preload.ALL)) {
                            selectFields.addAll(entityInfo.getFieldNames());
                        }
                        proxy.lockValuesDirty.lock();
                        try {
                            for (final String field : selectFields) {
                                proxy.values.put(field, res.getObject(field));
                            }
                        } finally {
                            proxy.lockValuesDirty.unlock();
                        }
                        result.add(returnValueEntity);
                    }
                    return result.toArray((RawEntity[]) Array.newInstance(remoteType, result.size()));
                } finally {
                    res.close();
                }
            } finally {
                stmt.close();
            }
        } finally {
            conn.close();
        }
    }

    private String getPolymorphicTypeFieldName(final Method remoteMethod) {
        final Class attributeType = getAttributeTypeFromMethod(remoteMethod);
        return attributeType != null && attributeType.isAssignableFrom(getType())
                && attributeType.isAnnotationPresent(Polymorphic.class) ? getFieldNameConverter().getPolyTypeName(remoteMethod) : null;
    }

    private RawEntity fetchOneToOne(final Method method, final OneToOne annotation) throws SQLException, NoSuchMethodException {
        final Class remoteType = method.getReturnType();
        final EntityInfo entityInfo = manager.resolveEntityInfo(remoteType);
        final String remotePrimaryKeyFieldName = Common.getPrimaryKeyField(remoteType, getFieldNameConverter());
        final String whereClause = Common.where(annotation, getFieldNameConverter());
        final Method remoteMethod = remoteType.getMethod(annotation.reverse());
        final String remotePolymorphicTypeFieldName = getPolymorphicTypeFieldName(remoteMethod);
        final Preload preloadAnnotation = (Preload) remoteType.getAnnotation(Preload.class);
        final StringBuilder sql = new StringBuilder("SELECT ");
        final Set selectFields = new LinkedHashSet();

        selectFields.add(remotePrimaryKeyFieldName);
        if (remotePolymorphicTypeFieldName == null) {
            if (preloadAnnotation != null) {
                selectFields.addAll(preloadValue(preloadAnnotation, getFieldNameConverter()));
            } else {
                selectFields.addAll(Common.getValueFieldsNames(entityInfo, getFieldNameConverter()));
            }
        }
        if (selectFields.contains(Preload.ALL)) {
            sql.append(Preload.ALL);
        } else {
            for (final String field : selectFields) {
                sql.append(manager.getProvider().processID(field)).append(',');
            }
            sql.setLength(sql.length() - 1);
        }

        sql.append(" FROM ").append(manager.getProvider().withSchema(getTableNameConverter().getName(remoteType)));
        sql.append(" WHERE ").append(manager.getProvider().processID(getFieldNameConverter().getName(remoteMethod))).append(" = ?");
        if (whereClause.trim().length() != 0) {
            sql.append(" AND (").append(manager.getProvider().processWhereClause(whereClause)).append(")");
        }
        if (remotePolymorphicTypeFieldName != null) {
            sql.append(" AND ").append(manager.getProvider().processID(remotePolymorphicTypeFieldName)).append(" = ?");
        }
        final Connection conn = manager.getProvider().getConnection();
        try {
            final PreparedStatement stmt = manager.getProvider().preparedStatement(conn, sql);
            try {
                final TypeInfo dbType = getTypeManager().getType(getClass(key));
                dbType.getLogicalType().putToDatabase(manager, stmt, 1, key, dbType.getJdbcWriteType());
                if (remotePolymorphicTypeFieldName != null) {
                    stmt.setString(2, manager.getPolymorphicTypeMapper().convert(entityInfo.getEntityType()));
                }
                final ResultSet res = stmt.executeQuery();
                try {
                    if (res.next()) {
                        final RawEntity returnValueEntity = manager.peer(entityInfo, Common.getPrimaryKeyType(getTypeManager(), (Class>) remoteType).getLogicalType().pullFromDatabase(manager, res, (Class) remoteType, remotePrimaryKeyFieldName));
                        if (selectFields.remove(Preload.ALL)) {
                            selectFields.addAll(entityInfo.getFieldNames());
                        }
                        final EntityProxy proxy = manager.getProxyForEntity(returnValueEntity);
                        proxy.lockValuesDirty.lock();
                        try {
                            for (final String field : selectFields) {
                                proxy.values.put(field, res.getObject(field));
                            }
                        } finally {
                            proxy.lockValuesDirty.unlock();
                        }
                        return returnValueEntity;
                    } else {
                        return null;
                    }
                } finally {
                    res.close();
                }
            } finally {
                stmt.close();
            }
        } finally {
            conn.close();
        }
    }

    /**
     * @see AO-325
     */
    @Deprecated
    private RawEntity[] legacyFetchManyToMany(final RawEntity proxy, final Method method, final ManyToMany manyToManyAnnotation) throws SQLException {
        final Class> throughType = manyToManyAnnotation.value();
        final Class> type = (Class>) method.getReturnType().getComponentType();
        return retrieveRelations(proxy, null,
                Common.getMappingFields(getFieldNameConverter(),
                        throughType, type), throughType, (Class) type,
                Common.where(manyToManyAnnotation, getFieldNameConverter()),
                Common.getPolymorphicFieldNames(getFieldNameConverter(), throughType, entityInfo.getEntityType()),
                Common.getPolymorphicFieldNames(getFieldNameConverter(), throughType, type));
    }

    /**
     * @see AO-325
     */
    @Deprecated
    private RawEntity[] legacyFetchOneToMany(final RawEntity proxy, final Method method, final OneToMany oneToManyAnnotation) throws SQLException {
        final Class> type = (Class>) method.getReturnType().getComponentType();
        return retrieveRelations(proxy, new String[0],
                new String[]{Common.getPrimaryKeyField(type, getFieldNameConverter())},
                (Class) type, where(oneToManyAnnotation, getFieldNameConverter()),
                Common.getPolymorphicFieldNames(getFieldNameConverter(), type, entityInfo.getEntityType()));
    }

    /**
     * @see AO-325
     */
    @Deprecated
    private RawEntity legacyFetchOneToOne(final RawEntity proxy, final Method method, final OneToOne oneToOneAnnotation) throws SQLException {
        Class> type = (Class>) method.getReturnType();
        final RawEntity[] back = retrieveRelations(proxy, new String[0],
                new String[]{Common.getPrimaryKeyField(type, getFieldNameConverter())},
                (Class) type, Common.where(oneToOneAnnotation, getFieldNameConverter()),
                Common.getPolymorphicFieldNames(getFieldNameConverter(), type, entityInfo.getEntityType()));
        return back.length == 0 ? null : back[0];
    }

    private TableNameConverter getTableNameConverter() {
        return manager.getNameConverters().getTableNameConverter();
    }

    public K getKey() {
        return key;
    }

    @SuppressWarnings("unchecked")
    public void save(RawEntity entity) throws SQLException {
        lockValuesDirty.lock();
        try {
            if (dirty.isEmpty()) {
                return;
            }

            String table = entityInfo.getName();
            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 ");

                final String paramList = dirty.stream()
                                                 .map(this::prepareParam)
                                                 // The sqljdbc driver fails to handle queries properly if
                                                 // parameters are not separated by space.
                                                 // See also: AO-3518
                                                 .collect(joining(", "));
                sql.append(paramList);

                sql.append(" WHERE ").append(provider.processID(entityInfo.getPrimaryKey().getName())).append(" = ?");

                stmt = provider.preparedStatement(conn, sql);

                List events = new LinkedList();
                int index = 1;
                for (String name : dirty) {
                    if (!values.containsKey(name)) {
                        continue;
                    }

                    Object value = values.get(name);
                    FieldInfo fieldInfo = entityInfo.getField(name);

                    events.add(new PropertyChangeEvent(entity, name, 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 (!fieldInfo.isStorable()) {
                            values.remove(name);
                        }
                    }
                }
                TypeInfo pkType = entityInfo.getPrimaryKey().getTypeInfo();
                pkType.getLogicalType().putToDatabase(this.manager, stmt, index, key, pkType.getJdbcWriteType());
                stmt.executeUpdate();
                dirty.clear();

                for (PropertyChangeListener l : listeners) {
                    for (PropertyChangeEvent evt : events) {
                        l.propertyChange(evt);
                    }
                }
            } finally {
                closeQuietly(stmt);
                closeQuietly(conn);
            }
        } finally {
            lockValuesDirty.unlock();
        }
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        listeners.add(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        listeners.remove(listener);
    }

    public int hashCodeImpl() {
        return (key.hashCode() + 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());

            return Common.getPrimaryKeyValue(entity).equals(key) && theirTableName.equals(ourTableName);
        }

        return false;
    }

    public String toStringImpl() {
        return entityInfo.getName() + " {" + entityInfo.getPrimaryKey().getName() + " = " + key.toString() + "}";
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }

        if (obj instanceof EntityProxy) {
            EntityProxy proxy = (EntityProxy) obj;

            if (proxy.entityInfo.equals(entityInfo) && proxy.key.equals(key)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public int hashCode() {
        return hashCodeImpl();
    }

    /**
     * Thread safely update the values with those provided.
     * This is only used by {@link net.java.ao.EntityManager#find(Class, String, Query)}, which should really move
     * the leave the population of this class to itself.
     *
     * @param updatedValues mandatory
     */
    protected void updateValues(Map updatedValues) {
        lockValuesDirty.lock();
        try {
            for (Map.Entry updatedValue : updatedValues.entrySet()) {
                values.put(updatedValue.getKey(), updatedValue.getValue());
            }
        } finally {
            lockValuesDirty.unlock();
        }
    }

    Class getType() {
        return entityInfo.getEntityType();
    }

    private  V invokeGetter(FieldInfo fieldInfo) throws Throwable {
        final Class type = fieldInfo.getJavaType();
        final String name = fieldInfo.getName();
        final boolean isStorable = fieldInfo.isStorable();

        lockValuesDirty.lock();
        try {
            if (values.containsKey(name) && isStorable) {
                Object value = values.get(name);
                if (instanceOf(value, type)) {
                    //noinspection unchecked
                    return handleNullReturn((V) value, type);
                } else if (isBigDecimal(value, type)) { // Oracle for example returns BigDecimal when we expect doubles
                    //noinspection unchecked
                    return (V) handleBigDecimal(value, type);
                } else if (RawEntity.class.isAssignableFrom(type)) {
                    //noinspection unchecked
                    EntityInfo remoteEntityInfo = manager.resolveEntityInfo((Class) type);
                    if (instanceOf(value, remoteEntityInfo.getPrimaryKey().getJavaType())) {
                        value = manager.peer(remoteEntityInfo, value);

                        values.put(name, value);
                        //noinspection unchecked
                        return handleNullReturn((V) value, type);
                    }
                }
            }

            final V back = pullFromDatabase(fieldInfo);

            if (isStorable) {
                values.put(name, back);
            }

            return handleNullReturn(back, type);
        } finally {
            lockValuesDirty.unlock();
        }
    }

    private  V pullFromDatabase(FieldInfo fieldInfo) throws SQLException {
        Class type = fieldInfo.getJavaType();
        String name = fieldInfo.getName();
        final DatabaseProvider provider = manager.getProvider();
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet res = null;
        V back = null;
        try {
            conn = provider.getConnection();
            StringBuilder sql = new StringBuilder("SELECT ");

            sql.append(provider.processID(name));
            String polyName = fieldInfo.getPolymorphicName();
            if (polyName != null) {
                sql.append(',').append(provider.processID(polyName));
            }

            sql.append(" FROM ").append(provider.withSchema(entityInfo.getName())).append(" WHERE ");
            sql.append(provider.processID(entityInfo.getPrimaryKey().getName())).append(" = ?");

            stmt = provider.preparedStatement(conn, sql);
            TypeInfo pkType = entityInfo.getPrimaryKey().getTypeInfo();
            pkType.getLogicalType().putToDatabase(manager, stmt, 1, getKey(), pkType.getJdbcWriteType());

            res = stmt.executeQuery();
            if (res.next()) {
                back = convertValue(res, provider.shorten(name), provider.shorten(polyName), type);
            }
        } finally {
            closeQuietly(res, stmt, conn);
        }
        return back;
    }

    private  V handleNullReturn(V back, Class type) {
        return back != null ? back : Defaults.defaultValue(type);
    }

    private void invokeSetter(String name, Object value, String polyName) throws Throwable {
        lockValuesDirty.lock();
        try {
            values.put(name, value);
            dirty.add(name);

            if (polyName != null) {
                String strValue = null;

                if (value != null) {
                    strValue = manager.getPolymorphicTypeMapper().convert(((RawEntity) value).getEntityType());
                }

                values.put(polyName, strValue);
                dirty.add(polyName);
            }
        } finally {
            lockValuesDirty.unlock();
        }
    }

    /**
     * @see AO-325
     */
    @Deprecated
    private > V[] retrieveRelations(RawEntity entity, String[] inMapFields,
                                                           String[] outMapFields, Class type, String where, String[] thisPolyNames) throws SQLException {
        return retrieveRelations(entity, inMapFields, outMapFields, type, type, where, thisPolyNames, null);
    }

    /**
     * @see AO-325
     */
    @Deprecated
    private > V[] retrieveRelations(final RawEntity entity,
                                                           String[] inMapFields,
                                                           final String[] outMapFields,
                                                           final Class> type,
                                                           final Class finalType,
                                                           final String where,
                                                           final String[] thisPolyNames,
                                                           final String[] thatPolyNames) throws SQLException {
        if (inMapFields == null || inMapFields.length == 0) {
            inMapFields = Common.getMappingFields(getFieldNameConverter(), type, entityInfo.getEntityType());
        }
        List back = new ArrayList();
        List resPolyNames = new ArrayList(thatPolyNames == null ? 0 : thatPolyNames.length);

        String table = getTableNameConverter().getName(type);
        boolean oneToMany = type.equals(finalType);
        final Preload preloadAnnotation = finalType.getAnnotation(Preload.class);

        final DatabaseProvider provider = manager.getProvider();
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet res = null;
        try {
            conn = provider.getConnection();
            StringBuilder sql = new StringBuilder();
            String returnField;
            String throughField;
            int numParams = 0;

            Set selectFields = new LinkedHashSet();

            if (oneToMany && inMapFields.length == 1 && outMapFields.length == 1 && (thatPolyNames == null || thatPolyNames.length == 0)) {
                // one-to-one, one-to-many non polymorphic relation
                sql.append("SELECT ");

                selectFields.add(outMapFields[0]);
                if (preloadAnnotation != null) {
                    selectFields.addAll(preloadValue(preloadAnnotation, getFieldNameConverter()));
                } else {
                    selectFields.addAll(Common.getValueFieldsNames(manager.resolveEntityInfo(finalType), getFieldNameConverter()));
                }

                if (selectFields.contains(Preload.ALL)) {
                    sql.append(Preload.ALL);
                } else {
                    for (String field : selectFields) {
                        sql.append(provider.processID(field)).append(',');
                    }
                    sql.setLength(sql.length() - 1);
                }

                sql.append(" FROM ").append(provider.withSchema(table));

                sql.append(" WHERE ").append(provider.processID(inMapFields[0])).append(" = ?");

                if (!where.trim().equals("")) {
                    sql.append(" AND (").append(manager.getProvider().processWhereClause(where)).append(")");
                }

                if (thisPolyNames != null) {
                    for (String name : thisPolyNames) {
                        sql.append(" AND ").append(provider.processID(name)).append(" = ?");
                    }
                }

                numParams++;
                returnField = outMapFields[0];
            } else if (!oneToMany && inMapFields.length == 1 && outMapFields.length == 1 && (thatPolyNames == null || thatPolyNames.length == 0)) {
                // many-to-many non polymorphic relation
                final String finalTable = getTableNameConverter().getName(finalType);
                final String finalTableAlias = "f";
                final String tableAlias = "t";

                returnField = manager.getProvider().shorten(finalTable + "__aointernal__id");
                throughField = manager.getProvider().shorten(table + "__aointernal__id");

                sql.append("SELECT ");

                String finalPKField = Common.getPrimaryKeyField(finalType, getFieldNameConverter());

                selectFields.add(finalPKField);
                if (preloadAnnotation != null) {
                    selectFields.addAll(preloadValue(preloadAnnotation, getFieldNameConverter()));
                } else {
                    selectFields.addAll(Common.getValueFieldsNames(manager.resolveEntityInfo(finalType), getFieldNameConverter()));
                }

                if (selectFields.contains(Preload.ALL)) {
                    selectFields.remove(Preload.ALL);
                    selectFields.addAll(Common.getValueFieldsNames(manager.resolveEntityInfo(finalType), getFieldNameConverter()));
                }

                sql.append(finalTableAlias).append('.').append(provider.processID(finalPKField));
                sql.append(" AS ").append(provider.quote(returnField)).append(',');

                selectFields.remove(finalPKField);

                sql.append(tableAlias).append('.').append(provider.processID(Common.getPrimaryKeyField(type, getFieldNameConverter())));
                sql.append(" AS ").append(provider.quote(throughField)).append(',');

                for (String field : selectFields) {
                    sql.append(finalTableAlias).append('.').append(provider.processID(field)).append(',');
                }
                sql.setLength(sql.length() - 1);

                sql.append(" FROM ").append(provider.withSchema(table)).append(" ").append(tableAlias).append(" INNER JOIN ");
                sql.append(provider.withSchema(finalTable)).append(" ").append(finalTableAlias).append(" ON ");
                sql.append(tableAlias).append('.').append(provider.processID(outMapFields[0]));
                sql.append(" = ").append(finalTableAlias).append('.').append(provider.processID(finalPKField));

                sql.append(" WHERE ").append(tableAlias).append('.').append(
                        provider.processID(inMapFields[0])).append(" = ?");

                if (!where.trim().equals("")) {
                    sql.append(" AND (").append(manager.getProvider().processWhereClause(where)).append(")");
                }

                if (thisPolyNames != null) {
                    for (String name : thisPolyNames) {
                        sql.append(" AND ").append(provider.processID(name)).append(" = ?");
                    }
                }

                numParams++;
            } else if (inMapFields.length == 1 && outMapFields.length == 1) {
                // one-to-one, one-to-many or many-to-many polymorphic relation
                sql.append("SELECT ").append(provider.processID(outMapFields[0]));
                selectFields.add(outMapFields[0]);

                if (!oneToMany) {
                    throughField = Common.getPrimaryKeyField(type, getFieldNameConverter());

                    sql.append(',').append(provider.processID(throughField));
                    selectFields.add(throughField);
                }

                if (thatPolyNames != null) {
                    for (String name : thatPolyNames) {
                        resPolyNames.add(name);
                        sql.append(',').append(provider.processID(name));
                        selectFields.add(name);
                    }
                }

                sql.append(" FROM ").append(provider.withSchema(table));
                sql.append(" WHERE ").append(provider.processID(inMapFields[0])).append(" = ?");

                if (!where.trim().equals("")) {
                    sql.append(" AND (").append(manager.getProvider().processWhereClause(where)).append(")");
                }

                if (thisPolyNames != null) {
                    for (String name : thisPolyNames) {
                        sql.append(" AND ").append(provider.processID(name)).append(" = ?");
                    }
                }

                numParams++;
                returnField = outMapFields[0];
            } else {
                sql.append("SELECT DISTINCT a.outMap AS outMap");
                selectFields.add("outMap");

                if (thatPolyNames != null) {
                    for (String name : thatPolyNames) {
                        resPolyNames.add(name);
                        sql.append(',').append("a.").append(provider.processID(name)).append(" AS ").append(
                                provider.processID(name));
                        selectFields.add(name);
                    }
                }

                sql.append(" FROM (");
                returnField = "outMap";

                for (String outMap : outMapFields) {
                    for (String inMap : inMapFields) {
                        sql.append("SELECT ");
                        sql.append(provider.processID(outMap));
                        sql.append(" AS outMap,");
                        sql.append(provider.processID(inMap));
                        sql.append(" AS inMap");

                        if (thatPolyNames != null) {
                            for (String name : thatPolyNames) {
                                sql.append(',').append(provider.processID(name));
                            }
                        }

                        if (thisPolyNames != null) {
                            for (String name : thisPolyNames) {
                                sql.append(',').append(provider.processID(name));
                            }
                        }

                        sql.append(" FROM ").append(provider.withSchema(table));
                        sql.append(" WHERE ");
                        sql.append(provider.processID(inMap)).append(" = ?");

                        if (!where.trim().equals("")) {
                            sql.append(" AND (").append(manager.getProvider().processWhereClause(where)).append(")");
                        }

                        sql.append(" UNION ");

                        numParams++;
                    }
                }

                sql.setLength(sql.length() - " UNION ".length());
                sql.append(") a");

                if (thatPolyNames != null) {
                    if (thatPolyNames.length > 0) {
                        sql.append(" WHERE (");
                    }

                    for (String name : thatPolyNames) {
                        sql.append("a.").append(provider.processID(name)).append(" = ?").append(" OR ");
                    }

                    if (thatPolyNames.length > 0) {
                        sql.setLength(sql.length() - " OR ".length());
                        sql.append(')');
                    }
                }

                if (thisPolyNames != null) {
                    if (thisPolyNames.length > 0) {
                        if (thatPolyNames == null) {
                            sql.append(" WHERE (");
                        } else {
                            sql.append(" AND (");
                        }
                    }

                    for (String name : thisPolyNames) {
                        sql.append("a.").append(provider.processID(name)).append(" = ?").append(" OR ");
                    }

                    if (thisPolyNames.length > 0) {
                        sql.setLength(sql.length() - " OR ".length());
                        sql.append(')');
                    }
                }
            }

            stmt = provider.preparedStatement(conn, sql);

            TypeInfo dbType = getTypeManager().getType(getClass(key));
            int index = 0;
            for (; index < numParams; index++) {
                dbType.getLogicalType().putToDatabase(manager, stmt, index + 1, key, dbType.getJdbcWriteType());
            }

            int newLength = numParams + (thisPolyNames == null ? 0 : thisPolyNames.length);
            String typeValue = manager.getPolymorphicTypeMapper().convert(entityInfo.getEntityType());
            for (; index < newLength; index++) {
                stmt.setString(index + 1, typeValue);
            }

            dbType = Common.getPrimaryKeyType(provider.getTypeManager(), finalType);
            res = stmt.executeQuery();
            while (res.next()) {
                K returnValue = dbType.getLogicalType().pullFromDatabase(manager, res, (Class) type, returnField);
                Class backType = finalType;

                for (String polyName : resPolyNames) {
                    if ((typeValue = res.getString(polyName)) != null) {
                        backType = (Class) manager.getPolymorphicTypeMapper().invert(finalType, typeValue);
                        break;
                    }
                }

                if (backType.equals(entityInfo.getEntityType()) && returnValue.equals(key)) {
                    continue;
                }
                final V returnValueEntity = manager.peer(manager.resolveEntityInfo(backType), returnValue);
                final EntityProxy proxy = manager.getProxyForEntity(returnValueEntity);
                if (selectFields.contains(Preload.ALL)) {
                    selectFields.remove(Preload.ALL);
                    selectFields.addAll(Common.getValueFieldsNames(manager.resolveEntityInfo(finalType), getFieldNameConverter()));
                }
                proxy.lockValuesDirty.lock();
                try {
                    for (String field : selectFields) {
                        if (!resPolyNames.contains(field)) {
                            proxy.values.put(field, res.getObject(field));
                        }
                    }
                } finally {
                    proxy.lockValuesDirty.unlock();
                }

                back.add(returnValueEntity);
            }
        } finally {
            closeQuietly(res, stmt, conn);
        }
        return back.toArray((V[]) Array.newInstance(finalType, back.size()));
    }

    private TypeManager getTypeManager() {
        return manager.getProvider().getTypeManager();
    }

    /**
     * Gets the generic class of the given type
     *
     * @param object the type for which to get the class
     * @return the generic class
     */
    @SuppressWarnings("unchecked")
    private static  Class getClass(O object) {
        return (Class) object.getClass();
    }

    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 = manager.getPolymorphicTypeMapper().invert(entityType, res.getString(polyName));

            type = (Class) entityType;        // avoiding Java cast oddities with generics
        }

        final TypeInfo databaseType = getTypeManager().getType(type);

        if (databaseType == null) {
            throw new RuntimeException("UnrecognizedType: " + type.toString());
        }

        return databaseType.getLogicalType().pullFromDatabase(this.manager, res, type, field);
    }

    private String prepareParam(String name) {
        final DatabaseProvider provider = manager.getProvider();

        final String paramName = provider.processID(name);
        final String paramValue = values.containsKey(name) ? "?" : "NULL";

        return String.format("%s = %s", paramName, paramValue);
    }

    private boolean isNull(ResultSet res, String field) throws SQLException {
        res.getObject(field);
        return res.wasNull();
    }

    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) {
        return value instanceof BigDecimal && (isInteger(type) || isLong(type) || isFloat(type) || isDouble(type));
    }

    private Object handleBigDecimal(Object value, Class type) {
        final BigDecimal bd = (BigDecimal) value;
        if (isInteger(type)) {
            return bd.intValue();
        } else if (isLong(type)) {
            return bd.longValue();
        } else if (isFloat(type)) {
            return bd.floatValue();
        } else if (isDouble(type)) {
            return bd.doubleValue();
        } else {
            throw new RuntimeException("Could not resolve actual type for object :" + value + ", expected type is " + type);
        }
    }

    private boolean isDouble(Class type) {
        return type.equals(double.class) || type.equals(Double.class);
    }

    private boolean isFloat(Class type) {
        return type.equals(float.class) || type.equals(Float.class);
    }

    private boolean isLong(Class type) {
        return type.equals(long.class) || type.equals(Long.class);
    }

    private boolean isInteger(Class type) {
        return type.equals(int.class) || type.equals(Integer.class);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy