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.io.Serializable;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.PersistenceBrokerException;
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.ObjectReferenceDescriptor;
import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
import org.apache.ojb.broker.util.ObjectModification;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import org.apache.ojb.odmg.states.ModificationState;
import org.odmg.ODMGRuntimeException;
/**
* ObjectEnvelope is used during ODMG transactions as a wrapper for a persistent objects declaration
*
*/
public class ObjectEnvelope implements ObjectModification, Serializable
{
static final long serialVersionUID = -829177767933340522L;
/**
* The objects modification state, e.g. Old and Clean
*/
private ModificationState modificationState = null;
private Identity oid;
private Boolean hasChanged;
/**
* myObj holds the object we are wrapping.
*/
private transient 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 transient Map beforeImage;
private transient TransactionImpl tx;
/**
*
* Create a wrapper by providing an Object.
*/
public ObjectEnvelope(Object obj, TransactionImpl aTx)
{
tx = aTx;
myObj = ProxyHelper.getRealObject(obj);
refreshObjectImage();
prepareInitialState();
}
public void close()
{
myObj = null;
beforeImage = null;
tx = null;
}
public void refreshObjectImage()
{
try
{
hasChanged = null;
oid = new Identity(myObj, tx.getBroker());
beforeImage = getMap();
}
catch(Exception ex)
{
beforeImage = null;
oid = null;
throw new org.odmg.ClassNotPersistenceCapableException(ex.toString());
}
}
public Identity getIdentity()
{
return oid;
}
/**
* returns the managed object.
*/
public Object getObject()
{
return myObj;
}
/**
* 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();
}
}
/**
* getMap() will return the image of the Object.
*/
private Map getMap() throws PersistenceBrokerException
{
Map fieldValues = new HashMap();
ClassDescriptor mif = tx.getBroker().getClassDescriptor(getObject().getClass());
/**
* MBAIRD
* 1. register all fields of object that aren't collections or references
*/
FieldDescriptor[] fieldDescs = mif.getFieldDescriptions();
for(int i = 0; i < fieldDescs.length; i++)
{
FieldDescriptor fd = fieldDescs[i];
PersistentField f = fd.getPersistentField();
fieldValues.put(fd.getColumnName(), f.get(myObj));
}
/**
* MBAIRD
* 2. 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;
Object temp = null;
while(iter.hasNext())
{
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)
{
temp = 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.
*/
IndirectionHandler handler = ProxyHelper.getIndirectionHandler(temp);
if(handler != null)
{
/**
* only register if the proxy has been materialized
* if it's materialized later and the map is compared, it will
* trigger the update
*/
fieldValues.put(rds, handler.getIdentity());
}
else
{
fieldValues.put(rds, temp);
}
}
/**
* 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 collectionDescriptor = null;
while(collections.hasNext())
{
collectionDescriptor = (CollectionDescriptor) collections.next();
Object collectionOrArray = collectionDescriptor.getPersistentField().get(myObj);
if(collectionOrArray != null)
{
int referencesId = 0;
// no special treatment for CollectionProxies required,
// their size() method does not materialize the elements.
/**
* MBAIRD
* size isn't the safest thing to use as the dirty bit. This will
* need to be fixed.
*/
if(collectionOrArray instanceof Collection)
{
referencesId = ((Collection) collectionOrArray).size();
}
else if(collectionOrArray.getClass().isArray())
{
referencesId = Array.getLength(collectionOrArray);
}
else
{
referencesId = collectionOrArray.hashCode();
}
fieldValues.put(collectionDescriptor, new Integer(referencesId));
}
}
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()
{
// determine appropriate modification state
ModificationState initialState = null;
boolean needsInsert;
try
{
// try to lookup the object.
needsInsert = tx.getBroker().serviceObjectCache().lookup(oid) == null
&& !tx.getBroker().serviceBrokerHelper().doesExist(
tx.getBroker().getClassDescriptor(myObj.getClass()), oid, myObj);
}
catch(PersistenceBrokerException ex)
{
LoggerFactory.getDefaultLogger().error("ObjectEnvelope: ", ex);
throw new ODMGRuntimeException("Unexpected error while check existence of "
+ myObj + ", exception was "+ ex);
}
if(needsInsert)
{
// 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 = org.apache.ojb.odmg.states.StateNewDirty.getInstance();
}
else if(tx.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 = org.apache.ojb.odmg.states.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 = org.apache.ojb.odmg.states.StateOldClean.getInstance();
}
// remember it:
modificationState = initialState;
}
/**
* 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("transition: " + new Identity(this.myObj, tx.getBroker()) + " ("
+ modificationState + " --> " + newModificationState + ")");
}
modificationState = newModificationState;
}
}
/**
* returns a String representation.
* @return java.lang.String
*/
public String toString()
{
String obj = null;
if(myObj == null)
{
obj = "null";
}
else
{
obj = new Identity(myObj, tx.getBroker()).toString();
}
return obj + "(" + modificationState + ")";
}
/**
* checks whether object and internal clone differ and returns true if so, returns false else.
* @return boolean
*/
public boolean hasChanged()
{
Map currentImage = null;
try
{
currentImage = getMap();
}
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();
}
/*
arminw: we can't really restore object state with all dependencies and fields
without having a deep copy of the clean object. To avoid side-effects disable this
feature
*/
// public void rollback()
// {
// if(myObj != null && beforeImage != null)
// {
// ClassDescriptor mif = tx.getBroker().getClassDescriptor(getObject().getClass());
// /**
// * Set all the field values back.
// */
// FieldDescriptor[] fieldDescs = mif.getFieldDescriptions();
// for (int i = 0; i < fieldDescs.length; i++)
// {
// FieldDescriptor fd = fieldDescs[i];
// PersistentField f = fd.getPersistentField();
//
// Object value = beforeImage.get(fd.getColumnName());
//
// f.set(myObj, value);
// }
//
// /**
// * Set all the Object Reference values back
// * Don't know exactly what to do when the are identiy objects
// * (VirtualProxy or proxy classes)
// */
// Iterator iter = mif.getObjectReferenceDescriptors().iterator();
// while (iter.hasNext())
// {
// ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) iter.next();
// Object temp = beforeImage.get(rds);
//
// if(temp instanceof Identity)
// {
// // TODO what to do now? It was a VirtualProxy instance or a Proxy class.
// }
// else
// {
// rds.getPersistentField().set(myObj, temp);
// }
// }
//
// /**
// * Now set all collections back. But we only stored DirtyMarks.
// * So what can be rolledback.?
// */
// // Iterator collections = mif.getCollectionDescriptors().iterator();
// }
// }
}