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 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
/*
 * 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