Package org.xorm

Source Code of org.xorm.InterfaceInvocationHandler

/*
  $Header: /cvsroot/xorm/xorm/src/org/xorm/InterfaceInvocationHandler.java,v 1.78 2004/04/27 23:18:35 wbiggs Exp $

  This file is part of XORM.

  XORM is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  XORM is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with XORM; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package org.xorm;

import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import java.util.logging.Level;

import org.xorm.datastore.Column;
import org.xorm.datastore.DataFetchGroup;
import org.xorm.datastore.Row;
import org.xorm.util.FieldDescriptor;
import org.xorm.util.TypeConverter;

import javax.jdo.InstanceCallbacks;
import javax.jdo.PersistenceManager;
import javax.jdo.JDOUserException;
import javax.jdo.spi.PersistenceCapable;

import net.sf.cglib.Enhancer;
import net.sf.cglib.Factory;
import net.sf.cglib.MethodFilter;
import net.sf.cglib.MethodInterceptor;
import net.sf.cglib.MethodProxy;


/**
* Handles calls to an interface of the object model.  The interface may
* be defined as a Java interface or a Java abstract class.
*
* This class ties the notion of an object with state (ObjectState)
* with the concept of the XORM Datastore Row.
*
*/
// TODO: Factor out JDO dependencies (make a subclass)
public class InterfaceInvocationHandler extends ObjectState implements MethodInterceptor {
    /** The logger that this class will output to. */
    private static Logger logger = Logger.getLogger("org.xorm.InterfaceInvocationHandler");

    /**
     * Because calls to hashCode() and equals() are handled by this
     * class, the static initializer performs reflection on the
     * java.lang.Object class to get instances of the Method objects
     * for these two methods so that the reflection API does not need
     * to be called each time.
     */
    private static final Method HASH_CODE, EQUALS, TO_STRING;
    private static MethodFilter METHOD_FILTER;

    /**
     * Initializes the HASH_CODE and EQUALS Method references.
     */
    static {
        Method hashCode = null, equals = null, toString = null;
        try {
            hashCode = Object.class.getDeclaredMethod("hashCode", null);
            equals = Object.class.getDeclaredMethod("equals", new Class[] { Object.class });
            toString = Object.class.getDeclaredMethod("toString", null);
        } catch (NoSuchMethodException willNeverHappen) { }
        HASH_CODE = hashCode;
        EQUALS = equals;
        TO_STRING = toString;

        METHOD_FILTER = new MethodFilter() {
                /**
                 * Implements the CGLIB MethodFilter interface and
                 * indicates which methods are eligible for
                 * enhancement.
                 */
                public boolean accept(Member member) {
                    return Modifier.isAbstract(member.getModifiers())
                        || HASH_CODE.equals(member)
                        || EQUALS.equals(member)
                        || TO_STRING.equals(member)
                        || "clone".equals(member.getName());
                }
            };
    }
   
    /**
     * Gets the InterfaceInvocationHandler associated with the object.
     */
    public static InterfaceInvocationHandler getHandler(Object object) {
        try {
            if (object instanceof InterfaceInvocationHandler) {
                return (InterfaceInvocationHandler) object;
            } else if (object instanceof Factory) {
                return (InterfaceInvocationHandler)
                    ((Factory) object).interceptor();
            }
            return null;
        } catch (Throwable t) {
            t.printStackTrace();
            throw new Error( t.getClass().getName() + ":" +
                             object.getClass().getName() + ":" +
                             t.getMessage()
                             );
        }
    }

    // The InterfaceManagerFactory that created this instance.
    private InterfaceManagerFactory factory;

    // The transaction this object is operating within, or null
    private TransactionImpl txn;

    // The working copy of the Row, containing transactional values
    private Row row;

    // The datastore's version of the Row at the beginning of the transaction,
    // or perhaps null.
    private Row snapshotRow;

    private ClassMapping mapping;

    // The ObjectId for this instance
    private Object primaryKey;

    // Initially starts off empty, gets filled in as references
    // are resolved, gets cleared when objects becomes hollow
    // The references held are proxy objects
    // or possibly RelationshipProxies, not native types.
    private HashMap fieldToValue = new HashMap();

    public InterfaceInvocationHandler(InterfaceManagerFactory factory, ClassMapping mapping, Row row) {
        super(null);
        this.factory = factory;
        this.mapping = mapping;
        this.row = row;
        if (row == null) {
            primaryKey = TransientKey.next();
            setStatus(STATUS_TRANSIENT);
        } else {
            primaryKey = row.getPrimaryKeyValue();
            setStatus(row.isHollow() ? STATUS_HOLLOW : STATUS_PERSISTENT_NONTRANSACTIONAL);
        }
    }

    /** Accessor for persistence manager factory. */
    InterfaceManagerFactory getFactory() {
        return factory;
    }

    /** Called during a refresh operation. */
    void resetRow(Row row) {
        this.row = row;
        fieldToValue.clear();
        primaryKey = row.getPrimaryKeyValue();
        if (txn != null) {
            setStatus(STATUS_PERSISTENT_CLEAN);
        } else {
            setStatus(STATUS_PERSISTENT_NONTRANSACTIONAL);
        }
    }

    /** The InterfaceManager that is managing this proxy. */
    public InterfaceManager getInterfaceManager() {
        return (txn == null) ? null : (InterfaceManager) txn.getPersistenceManager();
    }

    /**
     * The ClassMapping that describes how the proxy data translates
     * into the datastore Row.
     */
    public ClassMapping getClassMapping() { return mapping; }

    // TODO: combine makeTransactional and enterTransaction
    /**
     * Called only from InterfaceManager.makeTransactional(obj)
     */
    void makeTransactional(InterfaceManager mgr) {
        TransactionImpl t = (TransactionImpl) mgr.currentTransaction();
        enterTransaction(t);

        if (isHollow()) {
            refresh(mgr);
        }
        setTransactional(true);
    }

    /**
     * This is the only method where the txn field gets set.
     * All reachable collection references become transactional
     * under the same transaction.  Additionally,
     * persistent-non-transactional objects become persistent-clean.
     */
    public void enterTransaction(TransactionImpl txn) {
        this.txn = txn;
        txn.attach(proxy);
        if (row != null) {
            row.clean();
        }

        // Make transactional via reachability (FIXME)
        Iterator i = fieldToValue.values().iterator();
        while (i.hasNext()) {
            Object o = i.next();
            if (o instanceof RelationshipProxy) {
                txn.attachRelationship((RelationshipProxy) o);
            }
        }

        switch (getStatus()) {
        case STATUS_PERSISTENT_NONTRANSACTIONAL:
            if (txn.isActive()) {
                setStatus(STATUS_PERSISTENT_CLEAN);
            }
            break;
        }
    }

    /**
     * @return true if the handler should be detached from the transaction.
     */
    public boolean exitTransaction(boolean commit) {
        boolean retainValues = txn.getRetainValues();
        boolean detach = false;

        // On commit, the following state transitions occur:
        //   TRANSIENT --> TRANSIENT
        //   HOLLOW --> HOLLOW
        //   TRANSIENT_CLEAN --> TRANSIENT_CLEAN
        //   TRANSIENT_DIRTY --> TRANSIENT_CLEAN
        //   PERSISTENT_(NEW_)?DELETED --> TRANSIENT
        //   PERSISTENT_NONTRANSACTIONAL --> PERSISTENT_NONTRANSACTIONAL

        // commit with retainValues == true:
        //   PERSISTENT_(NEW|CLEAN|DIRTY) --> PERSISTENT_NONTRANSACTIONAL
 
        // commit with retainValues == false
        //   PERSISTENT_(NEW|CLEAN|DIRTY) --> HOLLOW

        // rollback differs:
        //   PERSISTENT_NEW --> TRANSIENT
        //   PERSISTENT_DELETED --> HOLLOW if retainValues == false
        //   PERSISTENT_DELETED --> PERSISTENT_NONTRANSACTIONAL if true

        if (commit) {
            switch (status) {
            case STATUS_TRANSIENT_DIRTY:
                setStatus(STATUS_TRANSIENT_CLEAN);
                break;
            case STATUS_PERSISTENT_NEW_DELETED:
            case STATUS_PERSISTENT_DELETED:
                setStatus(STATUS_TRANSIENT);
                detach = true;
                txn = null;
                break;
            case STATUS_PERSISTENT_NEW:
            case STATUS_PERSISTENT_CLEAN:
            case STATUS_PERSISTENT_DIRTY:
                if (retainValues) {
                    setStatus(STATUS_PERSISTENT_NONTRANSACTIONAL);
                } else {
                    setStatus(STATUS_HOLLOW);
                }
                break;
            }
        } else {
            // Rollback
            if (snapshotRow != null) {
                row = snapshotRow;
            }
            // Cached object references may be invalid
            fieldToValue = new HashMap();
            switch (status) {
            case STATUS_TRANSIENT_DIRTY:
                setStatus(STATUS_TRANSIENT_CLEAN);
                break;
            case STATUS_PERSISTENT_NEW:
            case STATUS_PERSISTENT_NEW_DELETED:
                setStatus(STATUS_TRANSIENT);
                detach = true;
                txn = null;
                break;
            case STATUS_PERSISTENT_CLEAN:
            case STATUS_PERSISTENT_DIRTY:
            case STATUS_PERSISTENT_DELETED:
                if (retainValues) {
                    setStatus(STATUS_PERSISTENT_NONTRANSACTIONAL);
                } else {
                    setStatus(STATUS_HOLLOW);
                }
                break;
            }
        }
        snapshotRow = null;
        return detach;
    }

    // TODO: This is currently unused.
    private void makeHollow() {
        if (proxy instanceof InstanceCallbacks) {
            ((InstanceCallbacks) proxy).jdoPreClear();
        }

        primaryKey = getRow().getPrimaryKeyValue();
        fieldToValue = new HashMap();
        status = STATUS_HOLLOW;
        row = null;
    }

    // Causes a state change
    // This is only called from invokeSet(), so we can be assured
    // that the object has been entered into the current transaction
    // if necessary.
    public void makeDirty() {
        switch (status) {
        case STATUS_PERSISTENT_CLEAN:
            status = STATUS_PERSISTENT_DIRTY;
            break;
        case STATUS_TRANSIENT_CLEAN:
            if (txn != null && txn.isActive()) {
                // Enlist in transaction
                enterTransaction(txn);
                status = STATUS_TRANSIENT_DIRTY;
            }
            break;
        case STATUS_PERSISTENT_DELETED:
        case STATUS_PERSISTENT_NEW_DELETED:
            throw new JDOUserException("cannot change field of deleted object");
        }
        if (txn != null) {
            snapshot();
        }
    }

    /**
     * Marks this object and all the objects it contains as persistent
     * (persistence by reachability).  Any newly persistent objects will
     * be inserted into the database when the transaction is committed.
     */
    public void makePersistent(InterfaceManager mgr) {
        if (txn != null) {
            // Object was transactional already
            if (mgr == txn.getInterfaceManager()) return;
            else throw new JDOUserException("Object " + toString() + " managed by other PersistenceManager");
        }
        if (!isPersistent()) {
            enterTransaction((TransactionImpl) mgr.currentTransaction());
            status = STATUS_PERSISTENT_NEW;

            // Follow any references and mark those as persistent
            Iterator i = mapping.getRelationships().keySet().iterator();
            while (i.hasNext()) {
                String field = (String) i.next();
                RelationshipMapping rm = mapping.getRelationship(field);
                if (rm.getTarget() != null) {
                    // It's a to-many relationship
                    Collection c = invokeCollectionGet(field, rm, null, null);
                    Iterator j = c.iterator();
                    while (j.hasNext()) {
                        Object o = j.next();
                        InterfaceInvocationHandler other = getHandler(o);
                        other.makePersistent(mgr);
                    }
                } else {
                    Class returnType = rm.getSource().getElementClass();
                    ClassMapping returnTypeMapping = factory.getModelMapping()
                        .getClassMapping(returnType);
                    Object o = invokeGet(field, returnTypeMapping, returnType);
                    if (o != null) {
                        // Get the handler for o
                        InterfaceInvocationHandler other = getHandler(o);
                        other.makePersistent(mgr);
                    }
                }
            }
        }
    }

    // Another object, which might be one this references, has had
    // its ID changed (possibly through an insert)
    public void notifyIDChanged(Object oldID, Object newID) {
        // See if this has a transient reference that gets to other
        Iterator i = mapping.getRelationships().keySet().iterator();
        while (i.hasNext()) {
            String field = (String) i.next();
            Object proxy = fieldToValue.get(field);
            if (proxy == null) continue;
            if (proxy instanceof RelationshipProxy) {
                RelationshipProxy rp = (RelationshipProxy) fieldToValue.get(field);
                rp.notifyIDChanged(oldID, newID);
            } else {
                Column column = mapping.getColumn(field);
                Object value = getRow().getValue(column);
                if (oldID.equals(value)) {
                    getRow().setValue(column, newID);
                }
            }
        }
    }

    public int compareTo(InterfaceInvocationHandler other) {
        // For convenient sorting, sort first by mapped class,
        // then by object id
        if (other.mapping.equals(mapping)) {
            if (!((other.primaryKey instanceof TransientKey) ^ (primaryKey instanceof TransientKey))) {
                return ((Comparable) primaryKey).compareTo(other.primaryKey);
            } else {
                if (other.primaryKey instanceof TransientKey) return 1;
                return -1;
            }
        }
        return mapping.getMappedClass().getName()
            .compareTo(other.mapping.getMappedClass().getName());
    }

    /**
     * Returns true if any of the references from this object resolve
     * to the object supplied as a parameter.
     */
    public boolean dependsOn(InterfaceInvocationHandler other) {
        if (other == this) return true;

        // See if this has a reference that gets to other
        Iterator i = mapping.getRelationships().keySet().iterator();
        while (i.hasNext()) {
            String field = (String) i.next();
            Column column = mapping.getColumn(field);
            if (column != null) {
                Object value = getRow().getValue(column);
                if (other.primaryKey.equals(value)) {
                    return true;
                } else if (value instanceof TransientKey) {
                        // Transient key to something else
                        Class returnType = mapping.getRelationship(field).getSource().getElementClass();
                        ClassMapping returnTypeMapping = factory
                            .getModelMapping()
                            .getClassMapping(returnType);
                        Object o = invokeGet(field, returnTypeMapping, returnType);
                        if (o != null) {
                            // Get the handler for o
                            InterfaceInvocationHandler o2 = getHandler(o);
                            if (o2.dependsOn(other)) return true;
                        }
                } // value instanceof TransientKey
            } // column != null
            /* THIS SECTION NOT USED-- column dependence only
               else {
               // Relationship without a column (-to-many?)
               Object value = fieldToValue.get(field);
               if (value instanceof RelationshipProxy) {
               if (((RelationshipProxy) value).dependsOn(other)) {
               return true;
               }
               }
               }
            */
        } // for each relationship identified in class mapping
        return false;
    } // dependsOn

    public Object getObjectId() {
        return primaryKey;
    }

    public void refreshObjectId() {
        primaryKey = getRow().getPrimaryKeyValue();
    }

    public void refresh(InterfaceManager mgr) {
        row = mgr.lookupRow(mapping, primaryKey);

        if (proxy instanceof InstanceCallbacks) {
            ((InstanceCallbacks) proxy).jdoPostLoad();
        }
        if (status == STATUS_HOLLOW) {
            // Not currently in a transaction
            if (mgr.currentTransaction().isActive()) {
                enterTransaction((TransactionImpl) mgr.currentTransaction());
            } else {
                // this was a nontransactional read
                status = STATUS_PERSISTENT_NONTRANSACTIONAL;
                return;
            }
        }
        status = STATUS_PERSISTENT_CLEAN;
    }

    /** Clones the current backing row for use in case of rollback. */
    public void snapshot() {
        snapshotRow = (Row) getRow().clone();
    }

    /** Initializes or retrieves the working Row. */
    public Row getRow() {
        if (row == null) {
            row = new Row(mapping.getTable());
            initDefaultValues();
        }
        return row;
    }

    /** Sets the working Row. */
    public void setRow(Row newRow) {
        row = newRow;
    }

    /**
     * Initialize any primitive values that should be populated
     * in the row when newly created.
     */
    private void initDefaultValues() {
        Iterator i = mapping.getMappedFieldDescriptors().iterator();
        while (i.hasNext()) {
            FieldDescriptor fd =
                (FieldDescriptor) i.next();
            Column c = mapping.getColumn(fd.name);
            if (!c.isReadOnly()) {
                row.setValue(c, fd.type.isPrimitive() ?
                             defaultValue(fd.type) : null);
            }
        }
    }

    /**
     * Returns a debug representation of this handler, in the format
     * [org.xorm.InterfaceInvocationHandler@xxxxxx; interface com.xyz.model.MyClass, primaryKey: {id}, status: PERSISTENT_CLEAN]
     */
    public String toString() {
        return new StringBuffer("[")
            .append(super.toString())
            .append("; ")
            .append(mapping.getMappedClass())
            .append(", primaryKey: ")
            .append(primaryKey)
            .append(", status: ")
            .append(getStatusName())
            .append(']')
            .toString();
    }

    private boolean checkEquals(Object me, Object other) {
        if(me == other) {
            return true;
        } else if (other == null) {
            return false;
        } else {
            if(me.getClass().equals(other.getClass())) {
                InterfaceInvocationHandler otherHandler = getHandler(other);
                if(primaryKey.equals(otherHandler.primaryKey)) {
                    // Added these hollow checks (Dan)
                    // Otherwise, a hollow object would fail checkEquals
                    // against a non-hollow object representing the same
                    // row with the same values.   Gotta refresh to make
                    // sure the Row's values will be populated.
                    if (isHollow()) {
                        refresh(txn.getInterfaceManager());
                    }
                    if (otherHandler.isHollow()) {
                        otherHandler.refresh(otherHandler.txn.getInterfaceManager());
                    }
                   
                    Row myRow = row;
                    Row otherRow = otherHandler.row;
                    if (!row.equals(otherHandler.row)) {
                        return false;
                    }
                    // TODO check relationships?
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * Constructs a new proxy instance for the specified interface.
     * The proxy will implement or extend the specified class and
     * also implement javax.jdo.spi.PersistenceCapable.
     */
    Object newProxy() {
        Class clazz = mapping.getMappedClass();
        Factory sample = (Factory) factory.getSample(clazz);
        if (sample != null) {
            proxy = sample.newInstance(this);
            setProxy(proxy);
            return proxy;
        }
        Class[] interfaces;
        ClassLoader loader = clazz.getClassLoader();
 
        if (clazz.isInterface()) {
            interfaces = new Class[] { clazz, PersistenceCapable.class };
            clazz = null;
        } else {
            interfaces = new Class[] { PersistenceCapable.class };
        }
 
        Object proxy;
        try {
            proxy = Enhancer.enhance
                (clazz, interfaces, this, loader, null, METHOD_FILTER);
        } catch (Error e) {
            throw (Error) e.fillInStackTrace();
        } catch (Throwable t) {
            t.printStackTrace();
            throw new Error(t.getMessage());
        }
        setProxy(proxy);
        factory.putSample(mapping.getMappedClass(), proxy);
        return proxy;
    }

    /**
     * This method is invoked after execution, or in the case of
     * abstract or interface methods, instead of.
     *
     * @param proxy this
     * @param method Method
     * @param args Arg array
     * @param methodProxy the MethodProxy
     * @throws Throwable any exception
     * @return value to return from generated method
     */
    public Object intercept(Object proxy, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        if (method.equals(HASH_CODE)) {
            return new Integer(primaryKey.hashCode());
        } else if (method.equals(EQUALS)) {
            return Boolean.valueOf(checkEquals(proxy, args[0]));
        } else if (method.equals(TO_STRING)) {
            return toString();
        }

        if ("clone".equals(method.getName())) {
            InterfaceInvocationHandler handler = new InterfaceInvocationHandler(factory, mapping, null);
            handler.row = this.row;
            handler.row.setPrimaryKeyValue(null);
            return handler.newProxy();
        }
 
        // Calls to methods of PersistenceCapable require us to
        // make a suitable PersistenceCapableImpl.
        if (method.getDeclaringClass().equals(PersistenceCapable.class)) {
            PersistenceCapableImpl pc = new PersistenceCapableImpl(this);
            return method.invoke(pc, args);
        }

        // Force HOLLOW instances to be read
        if (isHollow()) {
            refresh(txn.getInterfaceManager());
        } else if (getStatus() == STATUS_PERSISTENT_NONTRANSACTIONAL) {
            // Account for thread-local transactions if necessary
            txn = (TransactionImpl) txn.getInterfaceManager().currentTransaction();
            if (txn.isActive()) {
                // nontransactional instances become transactional if
                // there is an active transaction.
                // TODO: This should also refresh() the instance
                enterTransaction(txn);
            }
        }
 
        String field = mapping.getFieldForMethod(method);
        Column c = mapping.getColumnForMethod(method);
        if (c == null) {
            RelationshipMapping rm = mapping.getRelationshipForMethod(method);
            if (rm == null) return null;
            if (args == null || args.length == 0) {
                // Relationship read method
                return invokeCollectionGet(field, rm, method.getReturnType(), null);
            }
            else if (method.getName().startsWith("set")) {
                invokeCollectionSet(field, rm, method.getReturnType(), (Collection) args[0]);
                return null;
            }
            else {
                // Relationship read method with arguments...this would
                // be the case when a filtered relationship is being invoked,
                // i.e. when the relationship collection has been configured
                // with a filter and possibly parameters and variables.
                // Most likely since parameters are being passed that's the
                // case here.
                return invokeCollectionGet(field, rm, method.getReturnType(), args);
            }
        }
 
        // Method maps to get/set on a column
        if (args == null || args.length == 0) {
            // Get method
            Class returnType = method.getReturnType();
            ClassMapping returnTypeMapping = null;
            if (ClassMapping.isUserType(returnType)) {
                returnTypeMapping = factory
                    .getModelMapping().getClassMapping(returnType);
            }
            return invokeGet(field, returnTypeMapping, returnType);
        } else {
            // Write method
            Object value = args[0];
            invokeSet(field, c, value);
        }
        return null;
    }
   
    private void invokeSet(String field, Column c, Object value) {

        // TO THINK ABOUT: should a set of a new parent detach the
        // pre-existing relationship if one is there?  If so, need
        // to check for that old value first.

        if (value != null) {
            if (value instanceof PersistenceCapable) { // TODO: use cglib interface?
                fieldToValue.put(field, value);
                // Convert value to primary key:
                InterfaceInvocationHandler other = getHandler(value);
                // If this is persistent, anything attached
                // becomes persistent too
                if (isPersistent()) {
                    other.makePersistent(txn.getInterfaceManager());
                }
                value = other.primaryKey;

                String inverse = mapping.getInverse(field);
                if (inverse != null) {
                    logger.info("Examining inverse relationship");
                    // Is it a collection or a single item?
                    RelationshipMapping rm = other.mapping.getRelationship(inverse);
                    int type = rm.getSource().getCollectionType();
                    if (type == RelationshipMapping.Endpoint.SET) {
                        // Add this object to the relationship
                        // TODO
                    } else {
                        // Call set on other object
                        // TODO
                    }
                }
            }
        }

        Row theRow = getRow();
        // Gotta see if we're dealing with a cached instance of the Row
        if (theRow.isCached()) {
            // Yep, let's not modify that instance...now we need to clone.
            theRow = (Row)theRow.clone();
            // Make sure the cached flag is set to false on our copy
            theRow.setCached(false);
            // Update our instance variable or whatever
            setRow(theRow);
        }

        if (txn != null && txn.isActive() && !isDirty()) {
            makeDirty();
        }
        theRow.setValue(c,value);
    }

    private Collection invokeCollectionGet(String field, RelationshipMapping mapping, Class returnType, Object[] args) {
        //logger.info("invokeCollectionGet: I am " + toString());
        RelationshipProxy rp = null;
        if (fieldToValue.containsKey(field)) {
            rp = (RelationshipProxy) fieldToValue.get(field);
            //logger.info("Using existing value for " + field + " with rp " + rp);
        } else {
            /*
              if ((returnType != null) && List.class.isAssignableFrom(returnType)) {
              rp = new ListProxy(mgr, mapping, this, field, args);
              } else {
            */
            InterfaceManager mgr = null;
            if (txn != null) {
                mgr = txn.getInterfaceManager();
            }
            rp = new RelationshipProxy(mgr, mapping, this,
                                       factory.getModelMapping()
                                       .getClassMapping(mapping.getSource()
                                                        .getElementClass()),
                                       args);
            /*
              }
            */
            if (isPersistent()) {
                if (txn != null && txn.isActive()) {
                    txn.attachRelationship(rp);
                }
            }
            //logger.info("Calling fieldToValue.put " + field + " with rp " + rp);
     
            // Do not cache filtered relationships locally
            if (mapping.getFilter() == null) {
                fieldToValue.put(field, rp);
            }
        }
        return rp;
    }

 
    private void invokeCollectionSet(String field, RelationshipMapping mapping, Class returnType, Collection coll) {
        //  Sanity checking
        if(coll == null){
            throw new NullPointerException("Setting collection to null is bad idea, "+
            "if you need to clear this relationschip try to make this collection empty.");
        }
        if(coll instanceof RelationshipProxy){
            // check if it is already set
            Object obj = fieldToValue.get(field);
            if(coll.equals(obj)){
                // we do not need to set back the same collection
                return;
            } else {
                // import relationship if allowed
                if(mapping.isMToN()){
                    //is many to many, add all
                    RelationshipProxy rp = (RelationshipProxy) invokeCollectionGet(field, mapping, returnType, null);
                    rp.clear();
                    rp.addAll(coll);
                    return;
                } else {
                    throw new JDOUserException("by one to many relation you can not share referenced objects")
                }
            }
        } else {
            //collection is not backed in datastore
            //TODO: try to optimize adding collection, replace addAll(Collection) with something better
            RelationshipProxy rp = (RelationshipProxy) invokeCollectionGet(field, mapping, returnType, null);
            rp.clear();
            rp.addAll(coll);
        }
    }

    /**
     * Returns the object associated with the given field that
     * is of the given return type.
     */
    public Object invokeGet(String field, ClassMapping returnTypeMapping, Class returnType) {
        Column c = mapping.getColumn(field);
        if (c == null) throw new JDOUserException("No column for field " + field + " of class " + mapping.getMappedClass().getName());
        if (!getRow().containsValue(c)) {
            // Column fault: column was not in default fetch group
            DataFetchGroup dfg = new DataFetchGroup();
            dfg.addColumn(c);
            if (getRow().isCached()) {
                // We need to clone the row so we don't modify the cached Row
                Row theRow = (Row)getRow().clone();
                theRow.setCached(false);
                setRow(theRow);
            }
            txn.getInterfaceManager().refreshColumns(getRow(), dfg);
        }
        Object value = getRow().getValue(c);
        if (value == null) {
            return null;
        }
        if (returnTypeMapping != null) {
            if (fieldToValue.containsKey(field)) {
                value = fieldToValue.get(field);
            } else {
                value = txn.getInterfaceManager()
                    .lookup(returnTypeMapping, value);
                InterfaceInvocationHandler other = getHandler(value);
                fieldToValue.put(field, value);
            }
        }

        if (returnType != null) {
            value = TypeConverter.convertToType(value, returnType);
        }
        return value;
    }
}
TOP

Related Classes of org.xorm.InterfaceInvocationHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.