Thanks for replying Andrus. I cam up with a solution to the problem that I
will detail below. the problem only seemed to manifest itself when I was
using Tapestry. It may have to do with Tapestry holding on to objects in
memory ot manipulating them in some way that led to Cayenne assuming they
were in the same context. I can't be certain as it has been quite erratic
but consistently broken.
To get around it, I am creating a new context and I have implemented my own
version of localObject as such
public static DataObject localDataObject(DataContext
dataContext,DataObject dataObject) {
DataObject localDataObject = dataObject;
if (dataContext != null dataObject != null
!dataObject.getDataContext().equals(dataContext)) {
// in most cases this just works
localDataObject = (DataObject)dataContext.localObject(
dataObject.getObjectId(),null);
//but for new objects in nested contexts it does not work and
adding a prototype to the call above returns
//a committed dataObject which fails on a fetch as it is a
temporary id
// so deep merge with the object we are trying to localise from
if (dataObject.getObjectId().isTemporary()
localDataObject.getPersistenceState() == PersistenceState.HOLLOW) {
CayenneUtil.deepMerge(dataObject,localDataObject);
}
//don't really need to do this but it did cause issues elsewhere
for some odd reason
if (localDataObject.getPersistenceState() ==
PersistenceState.HOLLOW !dataObject.getObjectId().isTemporary()) {
dataContext.getObjectStore().resolveHollow(localDataObject);
}
}
return localDataObject;
}
And for some reason as you can see in the code above I was getting hollow
new dataObjects which would cause a fetch to the database and this is
completely wrong. So I have had to force a deepMerge of the object I am
trying to localise and these are the details of the method below
/**
* visit every property of fromDataObject and copy the values to
toDataContext making sure the values are in the proper dataContext
*
* @param fromDataObject
* @param toDataObject
*/
@SuppressWarnings(unchecked)
public static void deepMerge(final DataObject fromDataObject, final
DataObject toDataObject) {
ClassDescriptor descriptor = toDataObject.getDataContext
().getEntityResolver().getClassDescriptor(toDataObject.getObjectId
().getEntityName());
//we want the localised object to have the same state as the other
toDataObject.setPersistenceState(fromDataObject.getPersistenceState
());
descriptor.injectValueHolders(toDataObject);
descriptor.visitProperties(new PropertyVisitor() {
public boolean visitCollectionArc(CollectionProperty property) {
PropertyAccessor accessor = new DataObjectAccessor(
property.getName());
ToManyList oldList =
(ToManyList)property.readProperty(fromDataObject);
//lists are annoying. we need to make sure the owner of the
list is the new object which requires a copy
ToManyList newList = new ToManyList(toDataObject,
oldList.getRelationship());
IteratorDataObject it = oldList.iterator();
//then we need to localise every object in the list as well
otherwise a real mess
while (it.hasNext()) {
newList.add(CayenneUtil.localDataObject(
toDataObject.getDataContext(),it.next()));
}
accessor.writePropertyDirectly(toDataObject,
property.readProperty(toDataObject), newList);
return true;
}
public boolean visitSingleObjectArc(SingleObjectArcProperty
property) {
PropertyAccessor accessor = new DataObjectAccessor(
property.getName());
Object newValue = property.readProperty(fromDataObject);
//we need to to make sure relationship pull in objects from
the same context so recurse here
if (newValue instanceof DataObject) {
newValue = CayenneUtil.localDataObject(
toDataObject.getDataContext(),(DataObject)newValue);
}
accessor.writePropertyDirectly(toDataObject,
property.readProperty(toDataObject), newValue);
return true;
}
public boolean visitProperty(Property property) {
PropertyAccessor accessor = new DataObjectAccessor(
property.getName());
accessor.writePropertyDirectly(toDataObject,
property.readProperty(toDataObject), property.readProperty(fromDataObject));
return true;
}
});
}
And finally, because I am using a new dataContext, i want to merge the
changes back into the threaded context so I have done this
newObject().getDataContext().setChannel(this.getDataContext()); //
this.getDataContext() is the threaded context