package org.apache.ojb.odmg;
/* Copyright 2002-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
* @author <a href="mailto:thma@apache.org">Thomas Mahler</a>
* @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
*
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.OJBRuntimeException;
import org.apache.ojb.broker.core.proxy.CollectionProxy;
import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
import org.apache.ojb.broker.core.proxy.IndirectionHandler;
import org.apache.ojb.broker.core.proxy.ProxyHelper;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.CollectionDescriptor;
import org.apache.ojb.broker.metadata.FieldDescriptor;
import org.apache.ojb.broker.metadata.FieldType;
import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
import org.apache.ojb.broker.util.BrokerHelper;
import org.apache.ojb.broker.util.ObjectModification;
import org.apache.ojb.broker.util.ObjectModificationDefaultImpl;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import org.apache.ojb.odmg.states.ModificationState;
import org.apache.ojb.odmg.states.StateNewDirty;
import org.apache.ojb.odmg.states.StateOldClean;
import org.apache.ojb.odmg.states.StateOldDirty;
import org.apache.ojb.odmg.link.LinkEntry;
import org.apache.ojb.odmg.link.LinkEntryOneToOne;
import org.apache.ojb.odmg.link.LinkEntryOneToN;
/**
* ObjectEnvelope is used during ODMG transactions as a wrapper for a
* persistent objects declaration
*
*/
public class ObjectEnvelope implements ObjectModification
{
static final long serialVersionUID = -829177767933340522L;
static final int IS_MATERIALIZED_OBJECT = 11;
static final int IS_MATERIALIZED_PROXY = 13;
static final int IS_UNMATERIALIZED_PROXY = 17;
private Logger log = LoggerFactory.getLogger(ObjectEnvelope.class);
/**
* The objects modification state, e.g. Old and Clean
*/
private ModificationState modificationState = null;
private Identity oid;
private Boolean hasChanged;
private boolean writeLocked;
/**
* myObj holds the object we are wrapping.
*/
private Object myObj;
/**
* beforeImage holds a mapping between field
* names and values at the start of the transaction.
* afterImage holds the mapping at the
* end of the transaction.
*/
private Map beforeImage;
private Map currentImage;
private ObjectEnvelopeTable buffer;
// list of all LinkEntry's
private List linkEntryList;
/**
*
* Create a wrapper by providing an Object.
*/
public ObjectEnvelope(ObjectEnvelopeTable buffer, Identity oid, Object obj, boolean isNewObject)
{
this.linkEntryList = new ArrayList();
this.buffer = buffer;
this.oid = oid;
// TODO: do we really need to materialize??
myObj = ProxyHelper.getRealObject(obj);
/*
if object is new don't make an image, because we know it needs insert
*/
if(!isNewObject) refreshObjectImage();
prepareInitialState(isNewObject);
}
public PersistenceBroker getBroker()
{
return buffer.getTransaction().getBroker();
}
TransactionImpl getTx()
{
return buffer.getTransaction();
}
ObjectEnvelopeTable getEnvelopeTable()
{
return buffer;
}
public Map getBeforeImage()
{
if(beforeImage == null)
{
beforeImage = buildObjectImage(getBroker());
//prepareToCompare();
}
return beforeImage;
}
public Map getCurrentImage()
{
if(currentImage == null)
{
currentImage = buildObjectImage(getBroker());
//prepareToCompare();
}
return currentImage;
}
public void close()
{
if(currentImage != null)
{
Iterator iterator = currentImage.values().iterator();
while(iterator.hasNext())
{
EqualsBase base = (EqualsBase) iterator.next();
if(base != null) base.close();
}
}
if(beforeImage != null)
{
Iterator iterator = beforeImage.values().iterator();
while(iterator.hasNext())
{
EqualsBase base = (EqualsBase) iterator.next();
if(base != null) base.close();
}
}
myObj = null;
}
public void refreshObjectImage()
{
PersistenceBroker broker = getBroker();
try
{
// if an image already exists we
// replace the Identity too, maybe a temporary
// used PK value was replaced by the real one,
// see in docs SequenceManagerNativeImpl
if(beforeImage != null)
{
oid = broker.serviceIdentity().buildIdentity(myObj);
}
if(currentImage != null)
{
beforeImage = currentImage;
}
else
{
if(beforeImage == null)
{
beforeImage = buildObjectImage(getBroker());
}
}
currentImage = null;
hasChanged = null;
}
catch(Exception ex)
{
beforeImage = null;
currentImage = null;
hasChanged = null;
log.error("Can't refresh object image", ex);
throw new org.odmg.ClassNotPersistenceCapableException(ex.toString());
}
}
public Identity getIdentity()
{
return oid;
}
/**
* returns the managed object.
*/
public Object getObject()
{
return myObj;
}
public void refreshObjectIfNeeded(Object obj)
{
if(this.myObj != obj)
{
this.myObj = obj;
}
}
/**
* We need to implement the Two-Phase Commit
* protocol.
*
* beginCommit is where we say if we can or cannot
* commit the transaction. At the begining however,
* we need to attain the after image so we can isolate
* everything.
*
* We should issue the call against the database
* at this point. If we get a SQL Exception, we
* should throw the org.odmg.TransactionAbortedException.
*
* We should also check to see if the object is
* TransactionAware. If so, we should give it a chance
* to kill the transaction before we toss it to the
* database.
*/
public void beforeCommit()
{
if(myObj instanceof TransactionAware)
{
TransactionAware ta = (TransactionAware) myObj;
ta.beforeCommit();
}
}
/**
* Method declaration
*/
public void afterCommit()
{
if(myObj instanceof TransactionAware)
{
TransactionAware ta = (TransactionAware) myObj;
ta.afterCommit();
}
}
/**
* Method declaration
*/
public void beforeAbort()
{
if(myObj instanceof TransactionAware)
{
TransactionAware ta = (TransactionAware) myObj;
ta.beforeAbort();
}
}
/**
* Method declaration
*/
public void afterAbort()
{
if(myObj instanceof TransactionAware)
{
TransactionAware ta = (TransactionAware) myObj;
ta.afterAbort();
}
}
/**
* buildObjectImage() will return the image of the Object.
*/
private Map buildObjectImage(PersistenceBroker broker) throws PersistenceBrokerException
{
Map fieldValues = new HashMap();
ClassDescriptor mif = broker.getClassDescriptor(getObject().getClass());
//System.out.println("++++ build image: " + getObject());
/**
* MBAIRD
* 1. register all 1:1 references
* field changes to 1:1 mapped objects should also be registered in the map,
* so that alterations to those related objects will trigger an object to be
* marked "dirty", otherwise attaching or detaching a 1:1 referenced object will
* not be updated in ODMG.
*/
Iterator iter = mif.getObjectReferenceDescriptors().iterator();
ObjectReferenceDescriptor rds = null;
while(iter.hasNext())
{
Object referenceObject = null;
EqualsRefHelper erh;
rds = (ObjectReferenceDescriptor) iter.next();
/*
* synchronize on myObj so the ODMG-layer can take a snapshot only of
* fully cached (i.e. with all references + collections) objects
*/
synchronized(myObj)
{
referenceObject = rds.getPersistentField().get(myObj);
}
/**
* MBAIRD
* In the case of a proxy, we check if it has been materialized
* if it's been materialized, we put it in the map, because it could change.
* if it hasn't been materialized, it hasn't changed.
*
* Also handles virtual proxies.
*
* arminw:
* wrap Object or Identity with a helper class. The main object will get
* dirty when the 1:1 reference change: add or replaced by another object or deleted
*/
IndirectionHandler handler = ProxyHelper.getIndirectionHandler(referenceObject);
// if it is a not materialized proxy, use the Identity
if(handler != null)
{
erh = handler.alreadyMaterialized()
? new EqualsRefHelper(handler.getRealSubject())
: new EqualsRefHelper(handler.getIdentity());
}
else
{
erh = new EqualsRefHelper(referenceObject);
}
/*
arminw:
if object was serialized and anonymous FK are used in the main object, the FK
values are null, we have to refresh (re-assign) this values before building field images
*/
if(handler == null && referenceObject != null && BrokerHelper.hasAnonymousKeyReference(mif, rds))
{
getBroker().serviceBrokerHelper().link(myObj, rds, false);
}
/*
register the Identity for 1:1 relations only, if change we have
to update the main object
*/
fieldValues.put(rds, erh);
}
/**
* MBAIRD
* 2. register all fields of object that aren't collections or references
*/
FieldDescriptor[] fieldDescs = mif.getFieldDescriptions();
for(int i = 0; i < fieldDescs.length; i++)
{
FieldDescriptor fld = fieldDescs[i];
// map copies of all field values
Object value = fld.getPersistentField().get(myObj);
// get the real sql type value
value = fld.getFieldConversion().javaToSql(value);
// make copy of the sql type value
value = fld.getJdbcType().getFieldType().copy(value);
// buffer in image the field name and the sql type value
// wrapped by a helper class
fieldValues.put(fld.getPersistentField().getName(), new EqualsFieldHelper(fld.getJdbcType().getFieldType(), value));
}
/**
* MBAIRD
* 3. now let's register the collection descriptors
* How do we handle proxied collections and collections of proxies
*/
Iterator collections = mif.getCollectionDescriptors().iterator();
CollectionDescriptor cds = null;
while(collections.hasNext())
{
cds = (CollectionDescriptor) collections.next();
Object collectionOrArray = cds.getPersistentField().get(myObj);
EqualsColHelper ech = new EqualsColHelper(cds, collectionOrArray);
fieldValues.put(cds, ech);
}
return fieldValues;
}
/**
* returns the Modification-state.
* @return org.apache.ojb.server.states.ModificationState
*/
public ModificationState getModificationState()
{
return modificationState;
}
/**
* returns true if the underlying Object needs an INSERT statement, else returns false.
*/
public boolean needsInsert()
{
return this.getModificationState().needsInsert();
}
/**
* returns true if the underlying Object needs an UPDATE statement, else returns false.
*/
public boolean needsUpdate()
{
return this.getModificationState().needsUpdate();
}
/**
* returns true if the underlying Object needs an UPDATE statement, else returns false.
*/
public boolean needsDelete()
{
return this.getModificationState().needsDelete();
}
/**
* sets the initial MoificationState of the wrapped object myObj. The initial state will be StateNewDirty if myObj
* is not persisten already. The state will be set to StateOldClean if the object is already persistent.
*/
private void prepareInitialState(boolean isNewObject)
{
// determine appropriate modification state
ModificationState initialState = null;
if(isNewObject)
{
// if object is not already persistent it must be marked as new
// it must be marked as dirty because it must be stored even if it will not modified during tx
initialState = StateNewDirty.getInstance();
}
else if(isDeleted(oid))
{
// if object is already persistent it will be marked as old.
// it is marked as dirty as it has been deleted during tx and now it is inserted again,
// possibly with new field values.
initialState = StateOldDirty.getInstance();
}
else
{
// if object is already persistent it will be marked as old.
// it is marked as clean as it has not been modified during tx already
initialState = StateOldClean.getInstance();
}
// remember it:
modificationState = initialState;
}
/**
* Checks if the object with the given identity has been deleted
* within the transaction.
* @param id The identity
* @return true if the object has been deleted
* @throws PersistenceBrokerException
*/
public boolean isDeleted(Identity id)
{
ObjectEnvelope envelope = buffer.getByIdentity(id);
return (envelope == null ? false : envelope.needsDelete());
}
/**
* set the Modification state to a new value. Used during state transitions.
* @param newModificationState org.apache.ojb.server.states.ModificationState
*/
public void setModificationState(ModificationState newModificationState)
{
if(newModificationState != modificationState)
{
if(LoggerFactory.getDefaultLogger().isDebugEnabled())
{
LoggerFactory.getDefaultLogger().debug("object state transition for object " + this.oid + " ("
+ modificationState + " --> " + newModificationState + ")");
}
modificationState = newModificationState;
}
}
/**
* returns a String representation.
* @return java.lang.String
*/
public String toString()
{
ToStringBuilder buf = new ToStringBuilder(this);
buf.append("Identity", oid)
.append("ModificationState", modificationState.toString());
return buf.toString();
}
/**
* checks whether object and internal clone differ and returns true if so, returns false else.
* @return boolean
*/
public boolean hasChanged(PersistenceBroker broker)
{
try
{
currentImage = getCurrentImage();
}
catch(Exception e)
{
LoggerFactory.getDefaultLogger().warn("Could not verify object changes, return hasChanged 'true'", e);
}
hasChanged = (beforeImage != null && beforeImage.equals(currentImage) ? Boolean.FALSE : Boolean.TRUE);
return hasChanged.booleanValue();
}
/**
* Mark new or deleted reference elements
* @param broker
*/
void markReferenceElements(PersistenceBroker broker)
{
// these cases will be handled by ObjectEnvelopeTable#cascadingDependents()
if(getModificationState().needsInsert() || getModificationState().needsDelete()) return;
Map oldImage = getBeforeImage();
Map newImage = getCurrentImage();
Iterator iter = newImage.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
// CollectionDescriptor extends ObjectReferenceDescriptor
// we have to search for XXXDescriptor
if(entry.getKey() instanceof ObjectReferenceDescriptor)
{
if (entry.getKey() instanceof CollectionDescriptor)
{
CollectionDescriptor cds = (CollectionDescriptor) entry.getKey();
EqualsColHelper oldEch = (EqualsColHelper) oldImage.get(cds);
EqualsColHelper newEch = (EqualsColHelper) entry.getValue();
//System.out.println("");
//System.out.println("mark-oldEch: " + oldEch);
//System.out.println("mark-newEch: " + newEch);
markDelete(cds, oldEch, newEch);
markNew(cds, oldEch, newEch);
}
else
{
/*
check for new 1:1 reference object
*/
ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) entry.getKey();
EqualsRefHelper oldEh = (EqualsRefHelper) oldImage.get(rds);
EqualsRefHelper newEh = (EqualsRefHelper) newImage.get(rds);
if(!oldEh.equals(newEh))
{
// the main objects needs link/unlink of the FK to 1:1 reference,
// so mark this dirty
setModificationState(getModificationState().markDirty());
// if the new reference helper value is not null
// Lock the object, because it can be a new unregistered object.
// in other cases it can be an unmaterialized proxy and we
// don't need to materialize
if(newEh.value != null)
{
// if the object is already registered, OJB knows about
// else lock and register object, get read lock, because we
// don't know if the object is new or moved from an existing other object
ObjectEnvelope oe = buffer.getByIdentity(newEh.oid);
if(oe == null)
{
RuntimeObject rt = new RuntimeObject(newEh.value, getTx());
getTx().lockAndRegister(rt, TransactionExt.READ, false);
}
// in any case we need to link the main object
addLinkOneToOne(rds, false);
}
// if the new image doesn't contain a reference object, the
// reference is deleted, so lookup the old reference object
// and mark for delete
else if(newEh.isNull())
{
ObjectEnvelope oldRefMod = buffer.getByIdentity(oldEh.oid);
// only delete when the reference wasn't assigned with another object
if(!buffer.isNewAssociatedObject(oldEh.oid))
{
// if cascading delete is enabled, remove the 1:1 reference
// because it was removed from the main object
if(buffer.getTransaction().cascadeDeleteFor(rds))
{
oldRefMod.setModificationState(oldRefMod.getModificationState().markDelete());
}
// in any case we have to unlink the main object
addLinkOneToOne(rds, true);
}
}
}
}
}
}
}
/**
* Mark object for delete if it's not available in the new Collection.
*/
private void markDelete(CollectionDescriptor cds, EqualsColHelper oldEch, EqualsColHelper newEch)
{
if (oldEch.references.size() == 0)
{
return;
}
Iterator oldIter = oldEch.references.entrySet().iterator();
Map newRefs = newEch.references;
while (oldIter.hasNext())
{
Map.Entry entry = (Map.Entry) oldIter.next();
Identity oldOid = (Identity) entry.getKey();
if (!newRefs.containsKey(oldOid) && newEch.status != IS_UNMATERIALIZED_PROXY)
{
ObjectEnvelope mod = buffer.getByIdentity(oldOid);
// if this object is associated with another object it's
// not allowed to remove it
if(!buffer.isNewAssociatedObject(oldOid))
{
if(mod != null)
{
boolean cascade = buffer.getTransaction().cascadeDeleteFor(cds);
if(cascade)
{
mod.setModificationState(mod.getModificationState().markDelete());
buffer.addForDeletionDependent(mod);
}
if(cds.isMtoNRelation())
{
buffer.addM2NUnlinkEntry(cds, getObject(), entry.getValue());
}
else
{
// when cascade 'true' we remove all dependent objects, so no need to unlink
if(!cascade)
{
mod.setModificationState(mod.getModificationState().markDirty());
mod.addLinkOneToN(cds, getObject(), true);
}
}
}
else
{
throw new ImageExcetion("Unexpected behaviour, unregistered object to delete: "
+ oldOid + ", main object is " + getIdentity());
}
}
}
}
}
/**
* Mark object for insert if it's not available in the old Collection.
*/
private void markNew(CollectionDescriptor cds, EqualsColHelper oldEch, EqualsColHelper newEch)
{
if (newEch.references.size() == 0)
{
return;
}
Iterator newIter = newEch.references.entrySet().iterator();
Map oldRefs = oldEch.references;
while (newIter.hasNext())
{
Map.Entry entry = (Map.Entry) newIter.next();
Identity newOid = (Identity) entry.getKey();
Object newObj = entry.getValue();
/*
search for new objects: if in the old reference collection an object
of the new reference collection is not contained
*/
if ((oldRefs == null) || !oldRefs.containsKey(newOid))
{
ObjectEnvelope mod = buffer.getByIdentity(newOid);
if(mod == null) mod = buffer.get(newObj, true);
// if the object was deleted in an previous action, mark as new
// to avoid deletion, else mark object as dirty to assign the FK of
// the main object
if(mod.needsDelete())
{
mod.setModificationState(mod.getModificationState().markNew());
}
else
{
mod.setModificationState(mod.getModificationState().markDirty());
}
// buffer this object as "new" in a list to prevent deletion
// when object was moved from one collection to another
buffer.addNewAssociatedIdentity(newOid);
// new referenced object found, so register all m:n relation for "linking"
if(cds.isMtoNRelation())
{
buffer.addM2NLinkEntry(cds, getObject(), newObj);
}
else
{
// we have to link the new object
mod.addLinkOneToN(cds, getObject(), false);
}
if(mod.needsInsert())
{
buffer.addForInsertDependent(mod);
}
}
}
}
public void doUpdate()
{
performLinkEntries();
getBroker().store(getObject(), ObjectModificationDefaultImpl.UPDATE);
}
public void doInsert()
{
performLinkEntries();
getBroker().store(getObject(), ObjectModificationDefaultImpl.INSERT);
}
public void doDelete()
{
getBroker().delete(getObject());
}
public void doEvictFromCache()
{
getBroker().removeFromCache(getIdentity());
}
public boolean isWriteLocked()
{
return writeLocked;
}
public void setWriteLocked(boolean writeLocked)
{
this.writeLocked = writeLocked;
}
ClassDescriptor getClassDescriptor()
{
return getBroker().getClassDescriptor(ProxyHelper.getRealClass(getObject()));
}
void addLinkOneToOne(ObjectReferenceDescriptor ord, boolean unlink)
{
LinkEntry entry = new LinkEntryOneToOne(ord, getObject(), unlink);
linkEntryList.add(entry);
//setModificationState(getModificationState().markDirty());
}
void addLinkOneToN(CollectionDescriptor col, Object source, boolean unlink)
{
if(col.isMtoNRelation()) throw new OJBRuntimeException("Expected an 1:n relation, but specified a m:n");
LinkEntry entry = new LinkEntryOneToN(source, col, getObject(), unlink);
linkEntryList.add(entry);
//setModificationState(getModificationState().markDirty());
}
private void performLinkEntries()
{
PersistenceBroker broker = getBroker();
for(int i = 0; i < linkEntryList.size(); i++)
{
LinkEntry linkEntry = (LinkEntry) linkEntryList.get(i);
linkEntry.execute(broker);
}
}
//====================================================
// inner class
//====================================================
/**
* Help to compare field values.
*/
abstract class EqualsBase
{
abstract void close();
abstract void prepareForCompare();
}
//====================================================
// inner class
//====================================================
/**
* Help to compare field values.
*/
class EqualsFieldHelper extends EqualsBase
{
FieldType type;
Object value;
public EqualsFieldHelper(FieldType type, Object value)
{
this.type = type;
this.value = value;
}
void close()
{
}
void prepareForCompare()
{
}
public boolean equals(Object valueNew)
{
boolean result = false;
if(this==valueNew)
{
result = true;
}
else
{
if(valueNew instanceof EqualsFieldHelper)
{
result = type.equals(value, ((EqualsFieldHelper) valueNew).value);
}
}
// if(!result)
// {
// System.out.println("** changed field: " + getIdentity() + ", this="+this + ", other=" + valueNew);
// }
return result;
}
public String toString()
{
return "EqualsFieldHelper[type=" + type + "->value=" + value + "]";
}
}
//====================================================
// inner class
//====================================================
/**
* Help to compare 1:1 references of the main object.
*/
class EqualsRefHelper extends EqualsBase
{
Identity oid;
Object value;
public EqualsRefHelper(Object refObject)
{
this.value = refObject;
}
public EqualsRefHelper(Identity oid)
{
this.oid = oid;
}
void close()
{
}
void prepareForCompare()
{
}
/**
* The reference is <em>null</em> when both Identity and Object attribute
* is null.
*/
public boolean isNull()
{
return oid == null && value == null;
}
public boolean equals(Object toCompare)
{
boolean result = false;
if(this==toCompare)
{
result = true;
}
else
{
if(toCompare instanceof EqualsRefHelper)
{
EqualsRefHelper other = (EqualsRefHelper) toCompare;
if(oid == null)
{
if(value != null)
{
oid = getBroker().serviceIdentity().buildIdentity(value);
}
}
if(other.oid == null)
{
if(other.value != null)
{
other.oid = getBroker().serviceIdentity().buildIdentity(other.value);
}
}
// return oid != null ? oid.equals(other.oid) : other.oid == null;
result = oid != null ? oid.equals(other.oid) : other.oid == null;
}
}
// if(!result)
// {
// System.out.println("** changed 1:1: " + getIdentity() + ", ref: " + oid);
// }
return result;
}
}
//====================================================
// inner class
//====================================================
/**
* Help to compare 1:1 references of the main object.
*/
class EqualsColHelper extends EqualsBase implements CollectionProxyListener
{
private CollectionDescriptor des;
private CollectionProxy collectionHandler;
private int status;
private Map references;
// private Collection proxyData;
public EqualsColHelper(CollectionDescriptor des, Object collOrArray)
{
this.des = des;
references = new HashMap();
assignReferenceObject(collOrArray);
//System.out.println("** Create " + this);
}
public String toString()
{
return new ToStringBuilder(this)
.append("main class", des.getClassDescriptor().getClassNameOfObject())
.append("reference name", des.getPersistentField().getName())
.append("reference class", des.getItemClassName())
.append("status", status)
.append("references", references)
.toString();
}
/**
* Always returns true, because changes in 1:n or m:n relations
* do not influence main object, no need for update main object
* thus return always true.
*/
public boolean equals(Object obj)
{
return (obj instanceof EqualsColHelper) ? true : false;
}
void prepareForCompare()
{
}
void close()
{
if(collectionHandler != null)
{
collectionHandler.removeListener(this);
collectionHandler = null;
}
}
void assignReferenceObject(Object collOrArray)
{
CollectionProxy colProxy = ProxyHelper.getCollectionProxy(collOrArray);
if(colProxy != null)
{
if(colProxy.isLoaded())
{
status = IS_MATERIALIZED_PROXY;
/*
TODO: avoid dependency to CollectionProxyDefaultImpl
e.g. change CollectionProxy interface - CollectionProxy should
extend Collection to support Iterator
*/
handleCollectionOrArray(((CollectionProxyDefaultImpl) colProxy).getData());
}
else
{
status = IS_UNMATERIALIZED_PROXY;
// TODO: ObjectEnvelopeTable#register take care of proxy objects/collection, no need to handle proxy objects??
colProxy.addListener(this);
collectionHandler = colProxy;
}
}
else
{
status = IS_MATERIALIZED_OBJECT;
handleCollectionOrArray(collOrArray);
}
}
public void beforeLoading(CollectionProxyDefaultImpl colProxy)
{
//noop
}
public void afterLoading(CollectionProxyDefaultImpl colProxy)
{
if(status == IS_UNMATERIALIZED_PROXY)
{
//System.out.println("**** materialize " + this + " ->> data: " + colProxy.getData());
// reference to the proxy data
handleMaterializedCollectionProxy(colProxy);
status = IS_MATERIALIZED_PROXY;
colProxy.removeListener(this);
collectionHandler = null;
}
}
void addReference(Identity oid, Object obj)
{
references.put(oid, obj);
}
void handleCollectionOrArray(Object collOrArray)
{
if(collOrArray == null) return;
Iterator it = BrokerHelper.getCollectionIterator(collOrArray);
Object obj;
while(it.hasNext())
{
obj = it.next();
addReference(getBroker().serviceIdentity().buildIdentity(obj), obj);
}
}
void handleMaterializedCollectionProxy(CollectionProxyDefaultImpl colProxy)
{
if(colProxy.getData() == null || colProxy.getData().size() == 0)
{
// nothing to do
return;
}
// if the object materialize outside of a running tx (should not happen), we have to lookup
// a broker instance itself, so use PBCapsule to handle this
PBCapsule capsule = new PBCapsule(colProxy.getBrokerKey(), getTx());
try
{
PersistenceBroker broker = capsule.getBroker();
Iterator it = colProxy.ojbIterator();
Object tempObj;
IndirectionHandler tempHandler;
while(it.hasNext())
{
tempObj = it.next();
// the referenced objects can be proxy objects too
tempHandler = ProxyHelper.getIndirectionHandler(tempObj);
if(tempHandler != null)
{
addReference(tempHandler.getIdentity(), tempObj);
}
else
{
addReference(broker.serviceIdentity().buildIdentity(tempObj), tempObj);
}
}
}
finally
{
if(capsule != null) capsule.destroy();
}
}
}
//====================================================
// inner class
//====================================================
/**
* Thrown if something unexpected is happen when handling the
* object images for state detection.
*/
public static class ImageExcetion extends OJBRuntimeException
{
public ImageExcetion()
{
}
public ImageExcetion(String msg)
{
super(msg);
}
public ImageExcetion(Throwable cause)
{
super(cause);
}
public ImageExcetion(String msg, Throwable cause)
{
super(msg, cause);
}
}
}