/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2001 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.access; import java.util.Enumeration; import java.util.List; import java.util.Map; import net.wotonomy.control.EOAndQualifier; import net.wotonomy.control.EOCooperatingObjectStore; import net.wotonomy.control.EOEditingContext; import net.wotonomy.control.EOEnterpriseObject; import net.wotonomy.control.EOFaultHandler; import net.wotonomy.control.EOFaulting; import net.wotonomy.control.EOFetchSpecification; import net.wotonomy.control.EOGlobalID; import net.wotonomy.control.EOKeyGlobalID; import net.wotonomy.control.EOKeyValueCoding; import net.wotonomy.control.EOKeyValueCodingSupport; import net.wotonomy.control.EOKeyValueQualifier; import net.wotonomy.control.EOObjectStoreCoordinator; import net.wotonomy.control.EOQualifier; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSLocking; import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.NSMutableDictionary; /** * * @author ezamudio@nasoft.com * @author $Author: cgruber $ * @version $Revision: 894 $ */ public class EODatabaseContext extends EOCooperatingObjectStore implements NSLocking { private static Class _contextClass; protected EODatabase _database; protected EOAdaptorContext _context; protected NSMutableArray _channels = new NSMutableArray(); protected NSMutableArray _lockedObjects = new NSMutableArray(); protected NSMutableDictionary _simpleSnaps; protected NSMutableDictionary _manySnaps; protected EOObjectStoreCoordinator _coordinator; protected EOEditingContext _currEC; protected int _updateStrategy; public EODatabaseContext(EODatabase database) { super(); _database = database; _context = _database.adaptor().createAdaptorContext(); } public EOAdaptorContext adaptorContext() { return _context; } public EODatabase database() { return _database; } public EODatabaseChannel availableChannel() { for (int i = 0; i < _channels.count(); i++) { EODatabaseChannel c = (EODatabaseChannel) _channels.objectAtIndex(i); if (!c.isFetchInProgress()) return c; } EODatabaseChannel c = new EODatabaseChannel(this); registerChannel(c); return c; } public void batchFetchRelationship(EORelationship rel, NSArray arr, EOEditingContext ec) { } public static void setContextClassToRegister(Class contextClass) { _contextClass = contextClass; } public static Class contextClassToRegister() { if (_contextClass == null) _contextClass = EODatabaseContext.class; return _contextClass; } public EOObjectStoreCoordinator coordinator() { return _coordinator; } public void editingContextDidForgetObjectWithGlobalID(EOEditingContext ec, EOGlobalID gid) { database().decrementSnapshotCountForGlobalID(gid); } public void handleDroppedConnection() { // TODO: unregister channels adaptorContext().handleDroppedConnection(); } /* * (non-Javadoc) * * @see net.wotonomy.control.EOCooperatingObjectStore#ownsGlobalID(net.wotonomy. * control.EOGlobalID) */ public boolean ownsGlobalID(EOGlobalID gid) { if (!(gid instanceof EOKeyGlobalID)) return false; return (database().entityNamed(((EOKeyGlobalID) gid).entityName()) != null); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOCooperatingObjectStore#ownsObject(net.wotonomy.control * .EOEnterpriseObject) */ public boolean ownsObject(EOEnterpriseObject eo) { if (eo.entityName() == null) return false; return (database().entityNamed(eo.entityName()) != null); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOCooperatingObjectStore#handlesFetchSpecification(net. * wotonomy.control.EOFetchSpecification) */ public boolean handlesFetchSpecification(EOFetchSpecification fspec) { String ename = fspec.entityName(); return (database().entityNamed(ename) != null); } public boolean hasBusyChannels() { return adaptorContext().hasBusyChannels(); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOCooperatingObjectStore#prepareForSaveWithCoordinator( * net.wotonomy.control.EOObjectStoreCoordinator, * net.wotonomy.control.EOEditingContext) */ public void prepareForSaveWithCoordinator(EOObjectStoreCoordinator coord, EOEditingContext ec) { // TODO Auto-generated method stub _coordinator = coord; _currEC = ec; } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOCooperatingObjectStore#recordChangesInEditingContext() */ public void recordChangesInEditingContext() { // TODO insert, delete, update } /* * (non-Javadoc) * * @see net.wotonomy.control.EOCooperatingObjectStore#recordUpdateForObject(net. * wotonomy.control.EOEnterpriseObject, net.wotonomy.foundation.NSDictionary) */ public void recordUpdateForObject(EOEnterpriseObject eo, NSDictionary changes) { // TODO Auto-generated method stub } public void recordSnapshotForGlobalID(NSDictionary snap, EOGlobalID gid) { if (_simpleSnaps == null) throw new IllegalArgumentException("Attempt to record a snapshot without a transaction in progress"); _simpleSnaps.setObjectForKey(snap, gid); } public void recordSnapshotForSourceGlobalID(NSArray gids, EOGlobalID gid, String relationName) { if (_manySnaps == null) throw new IllegalArgumentException("Attempt to record a snapshot without a transaction in progress"); NSMutableDictionary d = (NSMutableDictionary) _manySnaps.objectForKey(gid); if (d == null) { d = new NSMutableDictionary(); _manySnaps.setObjectForKey(d, gid); } d.setObjectForKey(gids, relationName); } public void recordSnapshots(NSDictionary snaps) { if (_simpleSnaps == null) throw new IllegalArgumentException("Attempt to record snapshots without a transaction in progress."); _simpleSnaps.addEntriesFromDictionary(snaps); /* * Make sure we don't need to do this instead Enumeration enumeration = * snaps.keyEnumerator(); while (enumeration.hasMoreElements()) { EOGlobalID g = * (EOGlobalID)enumeration.nextElement(); NSDictionary d = * (NSDictionary)snaps.objectForKey(g); recordSnapshotForGlobalID(d, g); } */ } public void recordToManySnapshots(NSDictionary snaps) { if (_manySnaps == null) throw new IllegalArgumentException("Attempt to record snapshots without a transaction in progress."); Enumeration enumeration = snaps.keyEnumerator(); while (enumeration.hasMoreElements()) { Object key = enumeration.nextElement(); NSDictionary d = (NSDictionary) snaps.objectForKey(key); NSMutableDictionary d2 = (NSMutableDictionary) _manySnaps.objectForKey(key); if (d2 == null) { d2 = new NSMutableDictionary(); _manySnaps.setObjectForKey(d2, key); } // this could also be done with many calls to recordSnapshotForSourceGID d2.addEntriesFromDictionary(d); } } /* * (non-Javadoc) * * @see net.wotonomy.control.EOCooperatingObjectStore#performChanges() */ public void performChanges() { // TODO Auto-generated method stub } /* * (non-Javadoc) * * @see net.wotonomy.control.EOCooperatingObjectStore#commitChanges() */ public void commitChanges() { adaptorContext().commitTransaction(); } /* * (non-Javadoc) * * @see net.wotonomy.control.EOCooperatingObjectStore#rollbackChanges() */ public void rollbackChanges() { adaptorContext().rollbackTransaction(); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOCooperatingObjectStore#valuesForKeys(net.wotonomy. * foundation.NSArray, net.wotonomy.control.EOEnterpriseObject) */ public NSDictionary valuesForKeys(NSArray keys, EOEnterpriseObject eo) { // TODO check snapshots; eo could be a fault return eo.valuesForKeys(keys); } /* * (non-Javadoc) * * @see net.wotonomy.foundation.NSLocking#lock() */ public void lock() { EOAccessLock.lock(); } /* * (non-Javadoc) * * @see net.wotonomy.foundation.NSLocking#unlock() */ public void unlock() { EOAccessLock.unlock(); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOObjectStore#arrayFaultWithSourceGlobalID(net.wotonomy. * control.EOGlobalID, java.lang.String, net.wotonomy.control.EOEditingContext) */ public NSArray arrayFaultWithSourceGlobalID(EOGlobalID gid, String relName, EOEditingContext ec) { if (!(gid instanceof EOKeyGlobalID)) throw new IllegalArgumentException("an EOKeyGlobalID is needed."); EOAccessArrayFaultHandler handler = new EOAccessArrayFaultHandler((EOKeyGlobalID) gid, relName, this, ec); return new NSArray(handler); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOObjectStore#faultForGlobalID(net.wotonomy.control. * EOGlobalID, net.wotonomy.control.EOEditingContext) */ public /* EOEnterpriseObject */Object faultForGlobalID(EOGlobalID gid, EOEditingContext ec) { if (!(gid instanceof EOKeyGlobalID)) throw new IllegalArgumentException("Cannot fault an object that doesn't have a key global ID."); EOAccessFaultHandler handler = new EOAccessFaultHandler((EOKeyGlobalID) gid, this, ec); EOEntity e = database().entityNamed(((EOKeyGlobalID) gid).entityName()); Object o = e.classDescriptionForInstances().createInstanceWithEditingContext(ec, gid); EOFaultHandler.makeObjectIntoFault(o, handler); return o; } /* * (non-Javadoc) * * @see net.wotonomy.control.EOObjectStore#faultForRawRow(java.util.Map, * java.lang.String, net.wotonomy.control.EOEditingContext) */ public /* EOEnterpriseObject */ Object faultForRawRow(Map row, String entityName, EOEditingContext ec) { EOEntity e = database().entityNamed(entityName); EOGlobalID gid = e.globalIDForRow(row); return faultForGlobalID(gid, ec); } public void forgetSnapshotForGlobalID(EOGlobalID gid) { if (_simpleSnaps == null) throw new IllegalArgumentException("Attempt to forget snapshot with no transaction in progress."); _simpleSnaps.removeObjectForKey(gid); _manySnaps.removeObjectForKey(gid); } public void forgetSnapshotsForGlobalIDs(List gids) { for (int i = 0; i < gids.size(); i++) { EOGlobalID g = (EOGlobalID) gids.get(i); forgetSnapshotForGlobalID(g); database().forgetSnapshotForGlobalID(g); } } /* * (non-Javadoc) * * @see net.wotonomy.control.EOObjectStore#initializeObject(java.lang.Object, * net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) */ public void initializeObject(/* EOEnterpriseObject */Object eo, EOGlobalID gid, EOEditingContext ec) { if (gid.isTemporary()) return; NSDictionary snap = snapshotForGlobalID(gid); Object obj = ec.objectForGlobalID(gid); EOEntity e = database().entityNamed(((EOKeyGlobalID) gid).entityName()); NSArray props = e.classProperties(); for (int i = 0; i < props.count(); i++) { EOProperty p = (EOProperty) props.objectAtIndex(i); Object val = snap.objectForKey(p.name()); if (p instanceof EOAttribute) { if (eo instanceof EOKeyValueCoding) { ((EOKeyValueCoding) eo).takeValueForKey(val, p.name()); } else { EOKeyValueCodingSupport.takeValueForKey(eo, val, p.name()); } } else if (p instanceof EORelationship) { if (((EORelationship) p).isToMany()) { val = arrayFaultWithSourceGlobalID(gid, p.name(), ec); } else { EOEntity dest = ((EORelationship) p).destinationEntity(); EOKeyGlobalID kgid = (EOKeyGlobalID) dest.globalIDForRow(snap); val = ec.objectForGlobalID(kgid); if (val == null) val = new EOAccessFaultHandler(kgid, this, ec); } if (val == EOKeyValueCoding.NullValue) val = null; if (eo instanceof EOKeyValueCoding) { ((EOKeyValueCoding) eo).takeValueForKey(val, p.name()); } else { EOKeyValueCodingSupport.takeValueForKey(eo, val, p.name()); } } } } /* * (non-Javadoc) * * @see net.wotonomy.control.EOObjectStore#invalidateAllObjects() */ public void invalidateAllObjects() { invalidateObjectsWithGlobalIDs(database().snapshots().allKeys()); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOObjectStore#invalidateObjectsWithGlobalIDs(java.util. * List) */ public void invalidateObjectsWithGlobalIDs(List aList) { forgetSnapshotsForGlobalIDs(aList); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOObjectStore#isObjectLockedWithGlobalID(net.wotonomy. * control.EOGlobalID, net.wotonomy.control.EOEditingContext) */ public boolean isObjectLockedWithGlobalID(EOGlobalID gid, EOEditingContext ec) { return isObjectLockedWithGlobalID(gid); } public boolean isObjectLockedWithGlobalID(EOGlobalID gid) { return _lockedObjects.containsObject(gid); } /* * (non-Javadoc) * * @see net.wotonomy.control.EOObjectStore#lockObjectWithGlobalID(net.wotonomy. * control.EOGlobalID, net.wotonomy.control.EOEditingContext) */ public void lockObjectWithGlobalID(EOGlobalID gid, EOEditingContext ec) { NSDictionary snap = snapshotForGlobalID(gid); if (snap == null) return; if (!(gid instanceof EOKeyGlobalID)) return; EOEntity e = database().entityNamed(((EOKeyGlobalID) gid).entityName()); EOQualifier q = e.qualifierForPrimaryKey(snap); EOFetchSpecification fspec = new EOFetchSpecification(e.name(), q, null); fspec.setLocksObjects(true); NSArray arr = ec.objectsWithFetchSpecification(fspec); if (arr.count() != 1) throw new IllegalStateException("Cannot lock object with Global ID " + gid); } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOObjectStore#objectsForSourceGlobalID(net.wotonomy. * control.EOGlobalID, java.lang.String, net.wotonomy.control.EOEditingContext) */ public NSArray objectsForSourceGlobalID(EOGlobalID gid, String relationName, EOEditingContext ec) { EOEnterpriseObject eo = (EOEnterpriseObject) ec.objectForGlobalID(gid); if (eo == null) throw new IllegalStateException( "Cannot find object for global ID " + gid + " in specified editing context."); // Get the source object EOEnterpriseObject source = (EOEnterpriseObject) faultForGlobalID(gid, ec); if (source == null) throw new IllegalStateException("There is no snapshot for source global ID " + gid); // Check if there is already a value here NSArray value = (NSArray) source.valueForKey(relationName); EOAccessArrayFaultHandler handler = null; if (value != null) { if (EOFaultHandler.isFault(value)) { handler = new EOAccessArrayFaultHandler((EOKeyGlobalID) gid, relationName, this, ec); // TODO: fire the fault an return the value } else return value; } // Get the relationship EOEntity entity = database().entityNamed(eo.entityName()); EORelationship rel = entity.relationshipNamed(relationName); if (rel == null) throw new IllegalStateException( "Cannot find relationship named " + relationName + " in entity " + entity.name()); // create a fetch specification for this EOQualifier q = null; NSArray joins = rel.joins(); NSMutableArray subq = new NSMutableArray(joins.count()); for (int i = 0; i < joins.count(); i++) { EOJoin j = (EOJoin) joins.objectAtIndex(i); String key = j.destinationAttribute().name(); Object val = eo.valueForKey(j.sourceAttribute().name()); subq.addObject(new EOKeyValueQualifier(key, EOQualifier.QualifierOperatorEqual, val)); } if (subq.count() == 1) { q = (EOQualifier) subq.objectAtIndex(0); } else { q = new EOAndQualifier(subq); } EOFetchSpecification fspec = new EOFetchSpecification(rel.destinationEntity().name(), q, null); NSArray res = ec.objectsWithFetchSpecification(fspec, ec); NSMutableArray gids = new NSMutableArray(res.count()); for (int i = 0; i < res.count(); i++) gids.addObject(ec.globalIDForObject(res.objectAtIndex(i))); recordSnapshotForSourceGlobalID(gids, gid, relationName); return res; } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOObjectStore#objectsWithFetchSpecification(net.wotonomy * .control.EOFetchSpecification, net.wotonomy.control.EOEditingContext) */ public NSArray objectsWithFetchSpecification(EOFetchSpecification fspec, EOEditingContext ec) { EODatabaseChannel channel = availableChannel(); channel.selectObjectsWithFetchSpecification(fspec, ec); NSMutableArray arr = new NSMutableArray(); while (channel.isFetchInProgress()) { Object o = channel.fetchObject(); if (o != null) { arr.addObject(o); } } return arr; } /* * (non-Javadoc) * * @see net.wotonomy.control.EOObjectStore#refaultObject(java.lang.Object, * net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) */ public void refaultObject(Object obj, EOGlobalID gid, EOEditingContext ec) { if (!(gid instanceof EOKeyGlobalID)) throw new IllegalArgumentException("GlobalID must be an EOKeyGlobalID"); if (obj instanceof EOFaulting) // check if it's already a fault if (!EOFaultHandler.isFault(obj)) { ((EOFaulting) obj).turnIntoFault(EOFaultHandler.handlerForFault(obj)); } } /* * (non-Javadoc) * * @see * net.wotonomy.control.EOObjectStore#saveChangesInEditingContext(net.wotonomy. * control.EOEditingContext) */ public void saveChangesInEditingContext(EOEditingContext ec) { prepareForSaveWithCoordinator(null, ec); recordChangesInEditingContext(); performChanges(); commitChanges(); } public void registerChannel(EODatabaseChannel channel) { if (channel.databaseContext() != this) throw new IllegalArgumentException("Cannot register a channel on a context other than its own."); if (_channels.containsObject(channel)) throw new IllegalArgumentException("Attempt to register a channel more than once."); _channels.addObject(channel); } public void unregisterChannel(EODatabaseChannel channel) { if (channel.databaseContext() != this) throw new IllegalArgumentException("Attempt to unregister a channel from a context other than its own."); _channels.removeObject(channel); } public NSArray registeredChannels() { return new NSArray(_channels); } public NSDictionary snapshotForGlobalID(EOGlobalID gid) { NSDictionary d = null; if (_simpleSnaps != null) { d = (NSDictionary) _simpleSnaps.objectForKey(gid); } if (d == null) d = database().snapshotForGlobalID(gid); return d; } public NSArray snapshotForSourceGlobalID(EOGlobalID gid, String name) { NSArray a = null; if (_manySnaps != null) { NSDictionary d = (NSDictionary) _manySnaps.objectForKey(gid); a = (NSArray) d.objectForKey(name); } if (a == null) a = database().snapshotForSourceGlobalID(gid, name); return a; } public void setUpdateStrategy(int strategy) { _updateStrategy = strategy; } public int updateStrategy() { return _updateStrategy; } } /* * $Log$ Revision 1.2 2006/02/16 16:47:13 cgruber Move some classes in to * "internal" packages and re-work imports, etc. * * Also use UnsupportedOperationExceptions where appropriate, instead of * WotonomyExceptions. * * Revision 1.1 2006/02/16 13:19:57 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.5 2005/05/11 15:21:53 cgruber Change enum to enumeration, since * enum is now a keyword as of Java 5.0 * * A few other comments in the code. * * Revision 1.4 2003/12/18 15:37:38 mpowers Changes to retain ability to work * with objects that don't necessarily implement EOEnterpriseObject. I would * still like to preserve this case for general usage, however the access * package is free to assume that those objects will be EOs and cast * appropriately. * * Revision 1.3 2003/08/29 21:14:44 chochos implement a couple more methods. * * Revision 1.2 2003/08/20 01:16:22 chochos more methods have code now, but * there's no way to test this yet. The core is still pending. * * Revision 1.1 2003/08/19 01:54:43 chochos The EODatabase layer still needs a * lot of work, but it's on its way... * */