/*==========================================================================*\
| $Id: CachingEOManager.java,v 1.2 2011/12/25 02:24:54 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2011 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT 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 Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.core;
import com.webobjects.foundation.*;
import com.webobjects.eocontrol.*;
import org.webcat.core.CachingEOManager;
import org.webcat.core.EOManager;
import org.apache.log4j.Logger;
//-------------------------------------------------------------------------
/**
* This implementation of EOManager provides a "write through" cache of
* an EO's state, where every change to every attribute or relationship is
* immediately committed to the database, and all attributes/relationships
* are locally cached as well.
*
* @author stedwar2
* @author latest changes by: $Author: stedwar2 $
* @version $Revision: 1.2 $ $Date: 2011/12/25 02:24:54 $
*/
public class CachingEOManager
implements EOManager,
NSKeyValueCoding.ErrorHandling,
Cloneable
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Creates a new manager for the given EO. The EO must exist and be
* within an existing editing context, already stored in the database.
* After giving control of an EO to this manager, no other code should
* directly modify the EO's state.
* @param eo the object to manage
* @param manager the (probably shared) editing context manager to use
* for independent saving of the given eo
*/
public CachingEOManager(EOEnterpriseObject eo, ECManager manager)
{
ecm = manager;
snapshot = eo.snapshot().mutableClone();
// Now convert all NSArrays in snapshot so they are mutable
for (String key : eo.toManyRelationshipKeys())
{
snapshot.takeValueForKey(
((NSArray<?>)snapshot.valueForKey(key)).mutableClone(),
key);
}
NSArray<String> attributeKeyArray = eo.attributeKeys();
attributeKeys = new NSDictionary<String, String>(
attributeKeyArray, attributeKeyArray);
// Now create a mirror in a new EC
mirror = ecm.localize(eo);
}
//~ Public Methods ........................................................
// ----------------------------------------------------------
@SuppressWarnings("unchecked")
public Object clone()
{
try
{
CachingEOManager result = (CachingEOManager)super.clone();
result.snapshot =
(NSMutableDictionary<String, Object>)snapshot.clone();
return result;
}
catch (CloneNotSupportedException e)
{
// never happens
return null;
}
}
// ----------------------------------------------------------
public Object valueForKey(String key)
{
return NSKeyValueCoding.DefaultImplementation.valueForKey(this, key);
}
// ----------------------------------------------------------
public void takeValueForKey( Object value, String key )
{
NSKeyValueCoding.DefaultImplementation.takeValueForKey(
this, value, key);
}
// ----------------------------------------------------------
@SuppressWarnings("unchecked")
public void addObjectToBothSidesOfRelationshipWithKey(
EORelationshipManipulation eo, String key)
{
Object current = snapshot.valueForKey(key);
if (current != null
&& current != NullValue
&& current instanceof NSArray)
{
NSMutableArray<Object> currentTargets =
(NSMutableArray<Object>)current;
if (currentTargets.contains(eo))
{
return;
}
currentTargets.add(eo);
}
else
{
snapshot.takeValueForKey(eo, key);
}
mirror.addObjectToBothSidesOfRelationshipWithKey(
ecm.localize(eo), key);
EOEnterpriseObject oldMirror = mirror;
mirror = ecm.saveChanges(mirror);
if (mirror != oldMirror)
{
// retry it once if the save forced an abort and a new
// EC was created instead
mirror.addObjectToBothSidesOfRelationshipWithKey(
ecm.localize(eo), key);
mirror = ecm.saveChanges(mirror);
}
}
// ----------------------------------------------------------
@SuppressWarnings("unchecked")
public void addObjectToPropertyWithKey(Object eo, String key)
{
Object current = snapshot.valueForKey(key);
if (current != null
&& current != NullValue
&& current instanceof NSArray)
{
NSMutableArray<Object> currentTargets =
(NSMutableArray<Object>)current;
if (currentTargets.contains(eo))
{
return;
}
currentTargets.add(eo);
}
else
{
snapshot.takeValueForKey(eo, key);
}
mirror.addObjectToPropertyWithKey(ecm.localize(eo), key);
EOEnterpriseObject oldMirror = mirror;
mirror = ecm.saveChanges(mirror);
if (mirror != oldMirror)
{
// retry it once if the save forced an abort and a new
// EC was created instead
mirror.addObjectToPropertyWithKey(ecm.localize(eo), key);
mirror = ecm.saveChanges(mirror);
}
}
// ----------------------------------------------------------
public void removeObjectFromBothSidesOfRelationshipWithKey(
EORelationshipManipulation eo, String key)
{
Object current = snapshot.valueForKey(key);
if (current != null
&& current != NullValue
&& current instanceof NSArray)
{
NSMutableArray<?> currentTargets =
(NSMutableArray<?>)current;
currentTargets.remove(eo);
}
else if (current == eo)
{
snapshot.takeValueForKey(NullValue, key);
}
mirror.removeObjectFromBothSidesOfRelationshipWithKey(
ecm.localize(eo), key);
EOEnterpriseObject oldMirror = mirror;
mirror = ecm.saveChanges(mirror);
if (mirror != oldMirror)
{
// retry it once if the save forced an abort and a new
// EC was created instead
mirror.removeObjectFromBothSidesOfRelationshipWithKey(
ecm.localize(eo), key);
mirror = ecm.saveChanges(mirror);
}
}
// ----------------------------------------------------------
public void removeObjectFromPropertyWithKey(Object eo, String key)
{
Object current = snapshot.valueForKey(key);
if (current != null
&& current != NullValue
&& current instanceof NSArray)
{
NSMutableArray<?> currentTargets = (NSMutableArray<?>)current;
currentTargets.remove(eo);
}
else if (current == eo)
{
snapshot.takeValueForKey(NullValue, key);
}
mirror.removeObjectFromPropertyWithKey(ecm.localize(eo), key);
EOEnterpriseObject oldMirror = mirror;
mirror = ecm.saveChanges(mirror);
if (mirror != oldMirror)
{
// retry it once if the save forced an abort and a new
// EC was created instead
mirror.removeObjectFromPropertyWithKey(ecm.localize(eo), key);
mirror = ecm.saveChanges(mirror);
}
}
// ----------------------------------------------------------
public Object handleQueryWithUnboundKey(String key)
{
Object result = snapshot.valueForKey(key);
if (result == NullValue)
{
result = null;
}
return result;
}
// ----------------------------------------------------------
public void handleTakeValueForUnboundKey(Object value, String key)
{
Object current = snapshot.valueForKey(key);
if (current != null && current != NullValue && current instanceof Null)
{
log.error("non-unique KVC.Null found in snapshot for key " + key);
log.error("snapshot = " + snapshot);
}
if (attributeKeys.valueForKey(key) == null)
{
// Then this is a relationship, not a plain attribute
if (value == null)
{
if (current == null || current == NullValue)
{
return;
}
if (current instanceof NSArray)
{
NSArray<?> currents = (NSArray<?>)current;
if (currents.count() == 1)
{
current = currents.objectAtIndex(0);
}
else
{
throw new IllegalArgumentException("takeValueForKey("
+ value + ", " + key + ") called on to-many "
+ "relationship with current value of "
+ currents);
}
}
removeObjectFromBothSidesOfRelationshipWithKey(
(EORelationshipManipulation)current, key);
}
else if (value instanceof NSArray)
{
NSArray<?> currents = (NSArray<?>)current;
for (int i = 0; i < currents.count(); i++)
{
removeObjectFromBothSidesOfRelationshipWithKey(
(EORelationshipManipulation)currents.objectAtIndex(i),
key);
}
NSArray<?> newOnes = (NSArray<?>)value;
for (int i = 0; i < newOnes.count(); i++)
{
addObjectToBothSidesOfRelationshipWithKey(
(EORelationshipManipulation)newOnes.objectAtIndex(i),
key);
}
}
else
{
if (current != null && current != NullValue)
{
removeObjectFromBothSidesOfRelationshipWithKey(
(EORelationshipManipulation)current, key);
}
addObjectToBothSidesOfRelationshipWithKey(
(EORelationshipManipulation)value, key);
}
return;
}
if (current == value
|| (value == null && current == NullValue))
{
return;
}
Object newValue = value;
if (value == null)
{
snapshot.takeValueForKey(NullValue, key);
}
else if (value instanceof NSArray)
{
snapshot.takeValueForKey(
((NSArray<?>)value).mutableClone(), key);
newValue = ecm.localize(value);
}
else
{
snapshot.takeValueForKey(value, key);
newValue = ecm.localize(value);
}
mirror.takeValueForKey(newValue, key);
EOEnterpriseObject oldMirror = mirror;
mirror = ecm.saveChanges(mirror);
if (mirror != oldMirror)
{
// retry it once if the save forced an abort and a new
// EC was created instead
mirror.takeValueForKey(ecm.localize(newValue), key);
mirror = ecm.saveChanges(mirror);
}
}
// ----------------------------------------------------------
public void unableToSetNullForKey(String key)
{
NSKeyValueCoding.DefaultImplementation.unableToSetNullForKey(this, key);
}
//~ Instance/static variables .............................................
private ECManager ecm;
// copy of EO internal ec context
private EOEnterpriseObject mirror;
// snapshot of managed EO's state
private NSMutableDictionary<String, Object> snapshot;
private NSDictionary<String, String> attributeKeys;
static Logger log = Logger.getLogger(CachingEOManager.class);
}