001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/InterfaceInvocationHandler.java,v 1.78 2004/04/27 23:18:35 wbiggs Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm;
021:
022: import java.lang.reflect.Member;
023: import java.lang.reflect.Method;
024: import java.lang.reflect.Modifier;
025: import java.util.Collection;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.logging.Logger;
030: import java.util.logging.Level;
031:
032: import org.xorm.datastore.Column;
033: import org.xorm.datastore.DataFetchGroup;
034: import org.xorm.datastore.Row;
035: import org.xorm.util.FieldDescriptor;
036: import org.xorm.util.TypeConverter;
037:
038: import javax.jdo.InstanceCallbacks;
039: import javax.jdo.PersistenceManager;
040: import javax.jdo.JDOUserException;
041: import javax.jdo.spi.PersistenceCapable;
042:
043: import net.sf.cglib.Enhancer;
044: import net.sf.cglib.Factory;
045: import net.sf.cglib.MethodFilter;
046: import net.sf.cglib.MethodInterceptor;
047: import net.sf.cglib.MethodProxy;
048:
049: /**
050: * Handles calls to an interface of the object model. The interface may
051: * be defined as a Java interface or a Java abstract class.
052: *
053: * This class ties the notion of an object with state (ObjectState)
054: * with the concept of the XORM Datastore Row.
055: *
056: */
057: // TODO: Factor out JDO dependencies (make a subclass)
058: public class InterfaceInvocationHandler extends ObjectState implements
059: MethodInterceptor {
060: /** The logger that this class will output to. */
061: private static Logger logger = Logger
062: .getLogger("org.xorm.InterfaceInvocationHandler");
063:
064: /**
065: * Because calls to hashCode() and equals() are handled by this
066: * class, the static initializer performs reflection on the
067: * java.lang.Object class to get instances of the Method objects
068: * for these two methods so that the reflection API does not need
069: * to be called each time.
070: */
071: private static final Method HASH_CODE, EQUALS, TO_STRING;
072: private static MethodFilter METHOD_FILTER;
073:
074: /**
075: * Initializes the HASH_CODE and EQUALS Method references.
076: */
077: static {
078: Method hashCode = null, equals = null, toString = null;
079: try {
080: hashCode = Object.class.getDeclaredMethod("hashCode", null);
081: equals = Object.class.getDeclaredMethod("equals",
082: new Class[] { Object.class });
083: toString = Object.class.getDeclaredMethod("toString", null);
084: } catch (NoSuchMethodException willNeverHappen) {
085: }
086: HASH_CODE = hashCode;
087: EQUALS = equals;
088: TO_STRING = toString;
089:
090: METHOD_FILTER = new MethodFilter() {
091: /**
092: * Implements the CGLIB MethodFilter interface and
093: * indicates which methods are eligible for
094: * enhancement.
095: */
096: public boolean accept(Member member) {
097: return Modifier.isAbstract(member.getModifiers())
098: || HASH_CODE.equals(member)
099: || EQUALS.equals(member)
100: || TO_STRING.equals(member)
101: || "clone".equals(member.getName());
102: }
103: };
104: }
105:
106: /**
107: * Gets the InterfaceInvocationHandler associated with the object.
108: */
109: public static InterfaceInvocationHandler getHandler(Object object) {
110: try {
111: if (object instanceof InterfaceInvocationHandler) {
112: return (InterfaceInvocationHandler) object;
113: } else if (object instanceof Factory) {
114: return (InterfaceInvocationHandler) ((Factory) object)
115: .interceptor();
116: }
117: return null;
118: } catch (Throwable t) {
119: t.printStackTrace();
120: throw new Error(t.getClass().getName() + ":"
121: + object.getClass().getName() + ":"
122: + t.getMessage());
123: }
124: }
125:
126: // The InterfaceManagerFactory that created this instance.
127: private InterfaceManagerFactory factory;
128:
129: // The transaction this object is operating within, or null
130: private TransactionImpl txn;
131:
132: // The working copy of the Row, containing transactional values
133: private Row row;
134:
135: // The datastore's version of the Row at the beginning of the transaction,
136: // or perhaps null.
137: private Row snapshotRow;
138:
139: private ClassMapping mapping;
140:
141: // The ObjectId for this instance
142: private Object primaryKey;
143:
144: // Initially starts off empty, gets filled in as references
145: // are resolved, gets cleared when objects becomes hollow
146: // The references held are proxy objects
147: // or possibly RelationshipProxies, not native types.
148: private HashMap fieldToValue = new HashMap();
149:
150: public InterfaceInvocationHandler(InterfaceManagerFactory factory,
151: ClassMapping mapping, Row row) {
152: super (null);
153: this .factory = factory;
154: this .mapping = mapping;
155: this .row = row;
156: if (row == null) {
157: primaryKey = TransientKey.next();
158: setStatus(STATUS_TRANSIENT);
159: } else {
160: primaryKey = row.getPrimaryKeyValue();
161: setStatus(row.isHollow() ? STATUS_HOLLOW
162: : STATUS_PERSISTENT_NONTRANSACTIONAL);
163: }
164: }
165:
166: /** Accessor for persistence manager factory. */
167: InterfaceManagerFactory getFactory() {
168: return factory;
169: }
170:
171: /** Called during a refresh operation. */
172: void resetRow(Row row) {
173: this .row = row;
174: fieldToValue.clear();
175: primaryKey = row.getPrimaryKeyValue();
176: if (txn != null) {
177: setStatus(STATUS_PERSISTENT_CLEAN);
178: } else {
179: setStatus(STATUS_PERSISTENT_NONTRANSACTIONAL);
180: }
181: }
182:
183: /** The InterfaceManager that is managing this proxy. */
184: public InterfaceManager getInterfaceManager() {
185: return (txn == null) ? null : (InterfaceManager) txn
186: .getPersistenceManager();
187: }
188:
189: /**
190: * The ClassMapping that describes how the proxy data translates
191: * into the datastore Row.
192: */
193: public ClassMapping getClassMapping() {
194: return mapping;
195: }
196:
197: // TODO: combine makeTransactional and enterTransaction
198: /**
199: * Called only from InterfaceManager.makeTransactional(obj)
200: */
201: void makeTransactional(InterfaceManager mgr) {
202: TransactionImpl t = (TransactionImpl) mgr.currentTransaction();
203: enterTransaction(t);
204:
205: if (isHollow()) {
206: refresh(mgr);
207: }
208: setTransactional(true);
209: }
210:
211: /**
212: * This is the only method where the txn field gets set.
213: * All reachable collection references become transactional
214: * under the same transaction. Additionally,
215: * persistent-non-transactional objects become persistent-clean.
216: */
217: public void enterTransaction(TransactionImpl txn) {
218: this .txn = txn;
219: txn.attach(proxy);
220: if (row != null) {
221: row.clean();
222: }
223:
224: // Make transactional via reachability (FIXME)
225: Iterator i = fieldToValue.values().iterator();
226: while (i.hasNext()) {
227: Object o = i.next();
228: if (o instanceof RelationshipProxy) {
229: txn.attachRelationship((RelationshipProxy) o);
230: }
231: }
232:
233: switch (getStatus()) {
234: case STATUS_PERSISTENT_NONTRANSACTIONAL:
235: if (txn.isActive()) {
236: setStatus(STATUS_PERSISTENT_CLEAN);
237: }
238: break;
239: }
240: }
241:
242: /**
243: * @return true if the handler should be detached from the transaction.
244: */
245: public boolean exitTransaction(boolean commit) {
246: boolean retainValues = txn.getRetainValues();
247: boolean detach = false;
248:
249: // On commit, the following state transitions occur:
250: // TRANSIENT --> TRANSIENT
251: // HOLLOW --> HOLLOW
252: // TRANSIENT_CLEAN --> TRANSIENT_CLEAN
253: // TRANSIENT_DIRTY --> TRANSIENT_CLEAN
254: // PERSISTENT_(NEW_)?DELETED --> TRANSIENT
255: // PERSISTENT_NONTRANSACTIONAL --> PERSISTENT_NONTRANSACTIONAL
256:
257: // commit with retainValues == true:
258: // PERSISTENT_(NEW|CLEAN|DIRTY) --> PERSISTENT_NONTRANSACTIONAL
259:
260: // commit with retainValues == false
261: // PERSISTENT_(NEW|CLEAN|DIRTY) --> HOLLOW
262:
263: // rollback differs:
264: // PERSISTENT_NEW --> TRANSIENT
265: // PERSISTENT_DELETED --> HOLLOW if retainValues == false
266: // PERSISTENT_DELETED --> PERSISTENT_NONTRANSACTIONAL if true
267:
268: if (commit) {
269: switch (status) {
270: case STATUS_TRANSIENT_DIRTY:
271: setStatus(STATUS_TRANSIENT_CLEAN);
272: break;
273: case STATUS_PERSISTENT_NEW_DELETED:
274: case STATUS_PERSISTENT_DELETED:
275: setStatus(STATUS_TRANSIENT);
276: detach = true;
277: txn = null;
278: break;
279: case STATUS_PERSISTENT_NEW:
280: case STATUS_PERSISTENT_CLEAN:
281: case STATUS_PERSISTENT_DIRTY:
282: if (retainValues) {
283: setStatus(STATUS_PERSISTENT_NONTRANSACTIONAL);
284: } else {
285: setStatus(STATUS_HOLLOW);
286: }
287: break;
288: }
289: } else {
290: // Rollback
291: if (snapshotRow != null) {
292: row = snapshotRow;
293: }
294: // Cached object references may be invalid
295: fieldToValue = new HashMap();
296: switch (status) {
297: case STATUS_TRANSIENT_DIRTY:
298: setStatus(STATUS_TRANSIENT_CLEAN);
299: break;
300: case STATUS_PERSISTENT_NEW:
301: case STATUS_PERSISTENT_NEW_DELETED:
302: setStatus(STATUS_TRANSIENT);
303: detach = true;
304: txn = null;
305: break;
306: case STATUS_PERSISTENT_CLEAN:
307: case STATUS_PERSISTENT_DIRTY:
308: case STATUS_PERSISTENT_DELETED:
309: if (retainValues) {
310: setStatus(STATUS_PERSISTENT_NONTRANSACTIONAL);
311: } else {
312: setStatus(STATUS_HOLLOW);
313: }
314: break;
315: }
316: }
317: snapshotRow = null;
318: return detach;
319: }
320:
321: // TODO: This is currently unused.
322: private void makeHollow() {
323: if (proxy instanceof InstanceCallbacks) {
324: ((InstanceCallbacks) proxy).jdoPreClear();
325: }
326:
327: primaryKey = getRow().getPrimaryKeyValue();
328: fieldToValue = new HashMap();
329: status = STATUS_HOLLOW;
330: row = null;
331: }
332:
333: // Causes a state change
334: // This is only called from invokeSet(), so we can be assured
335: // that the object has been entered into the current transaction
336: // if necessary.
337: public void makeDirty() {
338: switch (status) {
339: case STATUS_PERSISTENT_CLEAN:
340: status = STATUS_PERSISTENT_DIRTY;
341: break;
342: case STATUS_TRANSIENT_CLEAN:
343: if (txn != null && txn.isActive()) {
344: // Enlist in transaction
345: enterTransaction(txn);
346: status = STATUS_TRANSIENT_DIRTY;
347: }
348: break;
349: case STATUS_PERSISTENT_DELETED:
350: case STATUS_PERSISTENT_NEW_DELETED:
351: throw new JDOUserException(
352: "cannot change field of deleted object");
353: }
354: if (txn != null) {
355: snapshot();
356: }
357: }
358:
359: /**
360: * Marks this object and all the objects it contains as persistent
361: * (persistence by reachability). Any newly persistent objects will
362: * be inserted into the database when the transaction is committed.
363: */
364: public void makePersistent(InterfaceManager mgr) {
365: if (txn != null) {
366: // Object was transactional already
367: if (mgr == txn.getInterfaceManager())
368: return;
369: else
370: throw new JDOUserException("Object " + toString()
371: + " managed by other PersistenceManager");
372: }
373: if (!isPersistent()) {
374: enterTransaction((TransactionImpl) mgr.currentTransaction());
375: status = STATUS_PERSISTENT_NEW;
376:
377: // Follow any references and mark those as persistent
378: Iterator i = mapping.getRelationships().keySet().iterator();
379: while (i.hasNext()) {
380: String field = (String) i.next();
381: RelationshipMapping rm = mapping.getRelationship(field);
382: if (rm.getTarget() != null) {
383: // It's a to-many relationship
384: Collection c = invokeCollectionGet(field, rm, null,
385: null);
386: Iterator j = c.iterator();
387: while (j.hasNext()) {
388: Object o = j.next();
389: InterfaceInvocationHandler other = getHandler(o);
390: other.makePersistent(mgr);
391: }
392: } else {
393: Class returnType = rm.getSource().getElementClass();
394: ClassMapping returnTypeMapping = factory
395: .getModelMapping().getClassMapping(
396: returnType);
397: Object o = invokeGet(field, returnTypeMapping,
398: returnType);
399: if (o != null) {
400: // Get the handler for o
401: InterfaceInvocationHandler other = getHandler(o);
402: other.makePersistent(mgr);
403: }
404: }
405: }
406: }
407: }
408:
409: // Another object, which might be one this references, has had
410: // its ID changed (possibly through an insert)
411: public void notifyIDChanged(Object oldID, Object newID) {
412: // See if this has a transient reference that gets to other
413: Iterator i = mapping.getRelationships().keySet().iterator();
414: while (i.hasNext()) {
415: String field = (String) i.next();
416: Object proxy = fieldToValue.get(field);
417: if (proxy == null)
418: continue;
419: if (proxy instanceof RelationshipProxy) {
420: RelationshipProxy rp = (RelationshipProxy) fieldToValue
421: .get(field);
422: rp.notifyIDChanged(oldID, newID);
423: } else {
424: Column column = mapping.getColumn(field);
425: Object value = getRow().getValue(column);
426: if (oldID.equals(value)) {
427: getRow().setValue(column, newID);
428: }
429: }
430: }
431: }
432:
433: public int compareTo(InterfaceInvocationHandler other) {
434: // For convenient sorting, sort first by mapped class,
435: // then by object id
436: if (other.mapping.equals(mapping)) {
437: if (!((other.primaryKey instanceof TransientKey) ^ (primaryKey instanceof TransientKey))) {
438: return ((Comparable) primaryKey)
439: .compareTo(other.primaryKey);
440: } else {
441: if (other.primaryKey instanceof TransientKey)
442: return 1;
443: return -1;
444: }
445: }
446: return mapping.getMappedClass().getName().compareTo(
447: other.mapping.getMappedClass().getName());
448: }
449:
450: /**
451: * Returns true if any of the references from this object resolve
452: * to the object supplied as a parameter.
453: */
454: public boolean dependsOn(InterfaceInvocationHandler other) {
455: if (other == this )
456: return true;
457:
458: // See if this has a reference that gets to other
459: Iterator i = mapping.getRelationships().keySet().iterator();
460: while (i.hasNext()) {
461: String field = (String) i.next();
462: Column column = mapping.getColumn(field);
463: if (column != null) {
464: Object value = getRow().getValue(column);
465: if (other.primaryKey.equals(value)) {
466: return true;
467: } else if (value instanceof TransientKey) {
468: // Transient key to something else
469: Class returnType = mapping.getRelationship(field)
470: .getSource().getElementClass();
471: ClassMapping returnTypeMapping = factory
472: .getModelMapping().getClassMapping(
473: returnType);
474: Object o = invokeGet(field, returnTypeMapping,
475: returnType);
476: if (o != null) {
477: // Get the handler for o
478: InterfaceInvocationHandler o2 = getHandler(o);
479: if (o2.dependsOn(other))
480: return true;
481: }
482: } // value instanceof TransientKey
483: } // column != null
484: /* THIS SECTION NOT USED-- column dependence only
485: else {
486: // Relationship without a column (-to-many?)
487: Object value = fieldToValue.get(field);
488: if (value instanceof RelationshipProxy) {
489: if (((RelationshipProxy) value).dependsOn(other)) {
490: return true;
491: }
492: }
493: }
494: */
495: } // for each relationship identified in class mapping
496: return false;
497: } // dependsOn
498:
499: public Object getObjectId() {
500: return primaryKey;
501: }
502:
503: public void refreshObjectId() {
504: primaryKey = getRow().getPrimaryKeyValue();
505: }
506:
507: public void refresh(InterfaceManager mgr) {
508: row = mgr.lookupRow(mapping, primaryKey);
509:
510: if (proxy instanceof InstanceCallbacks) {
511: ((InstanceCallbacks) proxy).jdoPostLoad();
512: }
513: if (status == STATUS_HOLLOW) {
514: // Not currently in a transaction
515: if (mgr.currentTransaction().isActive()) {
516: enterTransaction((TransactionImpl) mgr
517: .currentTransaction());
518: } else {
519: // this was a nontransactional read
520: status = STATUS_PERSISTENT_NONTRANSACTIONAL;
521: return;
522: }
523: }
524: status = STATUS_PERSISTENT_CLEAN;
525: }
526:
527: /** Clones the current backing row for use in case of rollback. */
528: public void snapshot() {
529: snapshotRow = (Row) getRow().clone();
530: }
531:
532: /** Initializes or retrieves the working Row. */
533: public Row getRow() {
534: if (row == null) {
535: row = new Row(mapping.getTable());
536: initDefaultValues();
537: }
538: return row;
539: }
540:
541: /** Sets the working Row. */
542: public void setRow(Row newRow) {
543: row = newRow;
544: }
545:
546: /**
547: * Initialize any primitive values that should be populated
548: * in the row when newly created.
549: */
550: private void initDefaultValues() {
551: Iterator i = mapping.getMappedFieldDescriptors().iterator();
552: while (i.hasNext()) {
553: FieldDescriptor fd = (FieldDescriptor) i.next();
554: Column c = mapping.getColumn(fd.name);
555: if (!c.isReadOnly()) {
556: row.setValue(c,
557: fd.type.isPrimitive() ? defaultValue(fd.type)
558: : null);
559: }
560: }
561: }
562:
563: /**
564: * Returns a debug representation of this handler, in the format
565: * [org.xorm.InterfaceInvocationHandler@xxxxxx; interface com.xyz.model.MyClass, primaryKey: {id}, status: PERSISTENT_CLEAN]
566: */
567: public String toString() {
568: return new StringBuffer("[").append(super .toString()).append(
569: "; ").append(mapping.getMappedClass()).append(
570: ", primaryKey: ").append(primaryKey).append(
571: ", status: ").append(getStatusName()).append(']')
572: .toString();
573: }
574:
575: private boolean checkEquals(Object me, Object other) {
576: if (me == other) {
577: return true;
578: } else if (other == null) {
579: return false;
580: } else {
581: if (me.getClass().equals(other.getClass())) {
582: InterfaceInvocationHandler otherHandler = getHandler(other);
583: if (primaryKey.equals(otherHandler.primaryKey)) {
584: // Added these hollow checks (Dan)
585: // Otherwise, a hollow object would fail checkEquals
586: // against a non-hollow object representing the same
587: // row with the same values. Gotta refresh to make
588: // sure the Row's values will be populated.
589: if (isHollow()) {
590: refresh(txn.getInterfaceManager());
591: }
592: if (otherHandler.isHollow()) {
593: otherHandler.refresh(otherHandler.txn
594: .getInterfaceManager());
595: }
596:
597: Row myRow = row;
598: Row otherRow = otherHandler.row;
599: if (!row.equals(otherHandler.row)) {
600: return false;
601: }
602: // TODO check relationships?
603: return true;
604: }
605: }
606: }
607: return false;
608: }
609:
610: /**
611: * Constructs a new proxy instance for the specified interface.
612: * The proxy will implement or extend the specified class and
613: * also implement javax.jdo.spi.PersistenceCapable.
614: */
615: Object newProxy() {
616: Class clazz = mapping.getMappedClass();
617: Factory sample = (Factory) factory.getSample(clazz);
618: if (sample != null) {
619: proxy = sample.newInstance(this );
620: setProxy(proxy);
621: return proxy;
622: }
623: Class[] interfaces;
624: ClassLoader loader = clazz.getClassLoader();
625:
626: if (clazz.isInterface()) {
627: interfaces = new Class[] { clazz, PersistenceCapable.class };
628: clazz = null;
629: } else {
630: interfaces = new Class[] { PersistenceCapable.class };
631: }
632:
633: Object proxy;
634: try {
635: proxy = Enhancer.enhance(clazz, interfaces, this , loader,
636: null, METHOD_FILTER);
637: } catch (Error e) {
638: throw (Error) e.fillInStackTrace();
639: } catch (Throwable t) {
640: t.printStackTrace();
641: throw new Error(t.getMessage());
642: }
643: setProxy(proxy);
644: factory.putSample(mapping.getMappedClass(), proxy);
645: return proxy;
646: }
647:
648: /**
649: * This method is invoked after execution, or in the case of
650: * abstract or interface methods, instead of.
651: *
652: * @param proxy this
653: * @param method Method
654: * @param args Arg array
655: * @param methodProxy the MethodProxy
656: * @throws Throwable any exception
657: * @return value to return from generated method
658: */
659: public Object intercept(Object proxy, Method method, Object[] args,
660: MethodProxy methodProxy) throws Throwable {
661: if (method.equals(HASH_CODE)) {
662: return new Integer(primaryKey.hashCode());
663: } else if (method.equals(EQUALS)) {
664: return Boolean.valueOf(checkEquals(proxy, args[0]));
665: } else if (method.equals(TO_STRING)) {
666: return toString();
667: }
668:
669: if ("clone".equals(method.getName())) {
670: InterfaceInvocationHandler handler = new InterfaceInvocationHandler(
671: factory, mapping, null);
672: handler.row = this .row;
673: handler.row.setPrimaryKeyValue(null);
674: return handler.newProxy();
675: }
676:
677: // Calls to methods of PersistenceCapable require us to
678: // make a suitable PersistenceCapableImpl.
679: if (method.getDeclaringClass().equals(PersistenceCapable.class)) {
680: PersistenceCapableImpl pc = new PersistenceCapableImpl(this );
681: return method.invoke(pc, args);
682: }
683:
684: // Force HOLLOW instances to be read
685: if (isHollow()) {
686: refresh(txn.getInterfaceManager());
687: } else if (getStatus() == STATUS_PERSISTENT_NONTRANSACTIONAL) {
688: // Account for thread-local transactions if necessary
689: txn = (TransactionImpl) txn.getInterfaceManager()
690: .currentTransaction();
691: if (txn.isActive()) {
692: // nontransactional instances become transactional if
693: // there is an active transaction.
694: // TODO: This should also refresh() the instance
695: enterTransaction(txn);
696: }
697: }
698:
699: String field = mapping.getFieldForMethod(method);
700: Column c = mapping.getColumnForMethod(method);
701: if (c == null) {
702: RelationshipMapping rm = mapping
703: .getRelationshipForMethod(method);
704: if (rm == null)
705: return null;
706: if (args == null || args.length == 0) {
707: // Relationship read method
708: return invokeCollectionGet(field, rm, method
709: .getReturnType(), null);
710: } else if (method.getName().startsWith("set")) {
711: invokeCollectionSet(field, rm, method.getReturnType(),
712: (Collection) args[0]);
713: return null;
714: } else {
715: // Relationship read method with arguments...this would
716: // be the case when a filtered relationship is being invoked,
717: // i.e. when the relationship collection has been configured
718: // with a filter and possibly parameters and variables.
719: // Most likely since parameters are being passed that's the
720: // case here.
721: return invokeCollectionGet(field, rm, method
722: .getReturnType(), args);
723: }
724: }
725:
726: // Method maps to get/set on a column
727: if (args == null || args.length == 0) {
728: // Get method
729: Class returnType = method.getReturnType();
730: ClassMapping returnTypeMapping = null;
731: if (ClassMapping.isUserType(returnType)) {
732: returnTypeMapping = factory.getModelMapping()
733: .getClassMapping(returnType);
734: }
735: return invokeGet(field, returnTypeMapping, returnType);
736: } else {
737: // Write method
738: Object value = args[0];
739: invokeSet(field, c, value);
740: }
741: return null;
742: }
743:
744: private void invokeSet(String field, Column c, Object value) {
745:
746: // TO THINK ABOUT: should a set of a new parent detach the
747: // pre-existing relationship if one is there? If so, need
748: // to check for that old value first.
749:
750: if (value != null) {
751: if (value instanceof PersistenceCapable) { // TODO: use cglib interface?
752: fieldToValue.put(field, value);
753: // Convert value to primary key:
754: InterfaceInvocationHandler other = getHandler(value);
755: // If this is persistent, anything attached
756: // becomes persistent too
757: if (isPersistent()) {
758: other.makePersistent(txn.getInterfaceManager());
759: }
760: value = other.primaryKey;
761:
762: String inverse = mapping.getInverse(field);
763: if (inverse != null) {
764: logger.info("Examining inverse relationship");
765: // Is it a collection or a single item?
766: RelationshipMapping rm = other.mapping
767: .getRelationship(inverse);
768: int type = rm.getSource().getCollectionType();
769: if (type == RelationshipMapping.Endpoint.SET) {
770: // Add this object to the relationship
771: // TODO
772: } else {
773: // Call set on other object
774: // TODO
775: }
776: }
777: }
778: }
779:
780: Row theRow = getRow();
781: // Gotta see if we're dealing with a cached instance of the Row
782: if (theRow.isCached()) {
783: // Yep, let's not modify that instance...now we need to clone.
784: theRow = (Row) theRow.clone();
785: // Make sure the cached flag is set to false on our copy
786: theRow.setCached(false);
787: // Update our instance variable or whatever
788: setRow(theRow);
789: }
790:
791: if (txn != null && txn.isActive() && !isDirty()) {
792: makeDirty();
793: }
794: theRow.setValue(c, value);
795: }
796:
797: private Collection invokeCollectionGet(String field,
798: RelationshipMapping mapping, Class returnType, Object[] args) {
799: //logger.info("invokeCollectionGet: I am " + toString());
800: RelationshipProxy rp = null;
801: if (fieldToValue.containsKey(field)) {
802: rp = (RelationshipProxy) fieldToValue.get(field);
803: //logger.info("Using existing value for " + field + " with rp " + rp);
804: } else {
805: /*
806: if ((returnType != null) && List.class.isAssignableFrom(returnType)) {
807: rp = new ListProxy(mgr, mapping, this, field, args);
808: } else {
809: */
810: InterfaceManager mgr = null;
811: if (txn != null) {
812: mgr = txn.getInterfaceManager();
813: }
814: rp = new RelationshipProxy(mgr, mapping, this , factory
815: .getModelMapping().getClassMapping(
816: mapping.getSource().getElementClass()),
817: args);
818: /*
819: }
820: */
821: if (isPersistent()) {
822: if (txn != null && txn.isActive()) {
823: txn.attachRelationship(rp);
824: }
825: }
826: //logger.info("Calling fieldToValue.put " + field + " with rp " + rp);
827:
828: // Do not cache filtered relationships locally
829: if (mapping.getFilter() == null) {
830: fieldToValue.put(field, rp);
831: }
832: }
833: return rp;
834: }
835:
836: private void invokeCollectionSet(String field,
837: RelationshipMapping mapping, Class returnType,
838: Collection coll) {
839: // Sanity checking
840: if (coll == null) {
841: throw new NullPointerException(
842: "Setting collection to null is bad idea, "
843: + "if you need to clear this relationschip try to make this collection empty.");
844: }
845: if (coll instanceof RelationshipProxy) {
846: // check if it is already set
847: Object obj = fieldToValue.get(field);
848: if (coll.equals(obj)) {
849: // we do not need to set back the same collection
850: return;
851: } else {
852: // import relationship if allowed
853: if (mapping.isMToN()) {
854: //is many to many, add all
855: RelationshipProxy rp = (RelationshipProxy) invokeCollectionGet(
856: field, mapping, returnType, null);
857: rp.clear();
858: rp.addAll(coll);
859: return;
860: } else {
861: throw new JDOUserException(
862: "by one to many relation you can not share referenced objects");
863: }
864: }
865: } else {
866: //collection is not backed in datastore
867: //TODO: try to optimize adding collection, replace addAll(Collection) with something better
868: RelationshipProxy rp = (RelationshipProxy) invokeCollectionGet(
869: field, mapping, returnType, null);
870: rp.clear();
871: rp.addAll(coll);
872: }
873: }
874:
875: /**
876: * Returns the object associated with the given field that
877: * is of the given return type.
878: */
879: public Object invokeGet(String field,
880: ClassMapping returnTypeMapping, Class returnType) {
881: Column c = mapping.getColumn(field);
882: if (c == null)
883: throw new JDOUserException("No column for field " + field
884: + " of class " + mapping.getMappedClass().getName());
885: if (!getRow().containsValue(c)) {
886: // Column fault: column was not in default fetch group
887: DataFetchGroup dfg = new DataFetchGroup();
888: dfg.addColumn(c);
889: if (getRow().isCached()) {
890: // We need to clone the row so we don't modify the cached Row
891: Row theRow = (Row) getRow().clone();
892: theRow.setCached(false);
893: setRow(theRow);
894: }
895: txn.getInterfaceManager().refreshColumns(getRow(), dfg);
896: }
897: Object value = getRow().getValue(c);
898: if (value == null) {
899: return null;
900: }
901: if (returnTypeMapping != null) {
902: if (fieldToValue.containsKey(field)) {
903: value = fieldToValue.get(field);
904: } else {
905: value = txn.getInterfaceManager().lookup(
906: returnTypeMapping, value);
907: InterfaceInvocationHandler other = getHandler(value);
908: fieldToValue.put(field, value);
909: }
910: }
911:
912: if (returnType != null) {
913: value = TypeConverter.convertToType(value, returnType);
914: }
915: return value;
916: }
917: }
|