/*
$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;
}
}