/**
* Copyright (C) 2006 NetMind Consulting Bt.
*
* 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 3 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package hu.netmind.persistence;
import org.apache.log4j.Logger;
import java.util.*;
import hu.netmind.persistence.parser.*;
import hu.netmind.persistence.node.*;
import hu.netmind.persistence.event.*;
import javax.sql.DataSource;
/**
* This store class is the entry point to the persistence library. To
* store, remove or select given objects, just use the appropriate
* method.
* @author Brautigam Robert
* @version Revision: $Revision$
*/
public class Store
{
private static Logger logger = Logger.getLogger(Store.class);
private StoreContext context;
private Thread shutdownHook;
/**
* Get context.
*/
protected StoreContext getContext()
{
return context;
}
/**
* Instantiate a store. Note that you should only make one
* store instance for each datasource you might want to use, all
* methods are thread-safe, so you can use the single instance
* in more threads.
* @param driverClass The driver class to register.
* @param url The driver's jdbc url (including username and password).
*/
public Store(String driverClass, String url)
{
// {{{ Constructor
this(new DriverDataSource(driverClass,url));
// }}}
}
/**
* Instantiate a store. Note that you should only make one
* store instance for each datasource you might want to use, all
* methods are thread-safe, so you can use the single instance
* in more threads.
*/
public Store(DataSource dataSource)
{
// {{{ Create store context
// Note that initialization order is important!
context = new StoreContext();
context.setEventDispatcher(new EventDispatcher());
context.setStore(this);
context.setSerialTracker(new SerialTracker());
context.setDatabase(DatabaseFactory.getDatabase(dataSource));
context.setTypeHandlerTracker(new TypeHandlerTracker(context));
context.setTransactionTracker(new TransactionTracker(context));
context.setLockTracker(new LockTracker(context));
context.setNodeManager(new NodeManager(context));
context.setClassTracker(new ClassTracker(context));
context.setObjectTracker(new ObjectTracker(context));
context.setCache(new ResultsCache(context));
// Start node as soon as all subsystems run
try
{
context.getNodeManager().ensureState(NodeManager.STATE_CONNECTED); // Init NodeManager
} catch ( Exception e ) {
logger.warn("initialization failed, will try to establish contact later.",e);
}
// Proper shutdown
shutdownHook = new Thread(new ShutdownProcess());
Runtime.getRuntime().addShutdownHook(shutdownHook);
/// }}}
}
/**
* Close the store, and release all resources. This method is automatically
* invoked as a JVM shutdown hook, so it does not have to be called at all.
*/
public void close()
{
close(false);
}
/**
* Internal close method.
*/
private void close(boolean shutdown)
{
logger.debug("store shutdown running");
context.getNodeManager().close();
context.getDatabase().release();
if ( ! shutdown )
Runtime.getRuntime().removeShutdownHook(shutdownHook); // Do not run twice
}
/**
* Get the transaction tracker associated with this store. The transaction
* tracker can be used to create transactions, and listen for transactional
* events.
* @return The transaction tracker.
*/
public TransactionTracker getTransactionTracker()
{
return context.getTransactionTracker();
}
/**
* Get the lock tracker. This tracker can be used to lock and unlock
* objects for exclusive operations.
*/
public LockTracker getLockTracker()
{
return context.getLockTracker();
}
/**
* Get the event dispatcher in which the caller may register
* listeners.
*/
public EventDispatcher getEventDispatcher()
{
return context.getEventDispatcher();
}
/**
* Get the persistence id for an object. This method always returns
* a valid id, even if the object is not saved, or otherwise has
* no persistence id. If the object is later saved, this persistence id
* is always preserved.<br>
* Same as: <code>getPersistenceMetaData(obj).getPersistenceId()</code>.
* @return A persistence id, and never null.
*/
public Long getPersistenceId(Object obj)
{
return getPersistenceMetaData(obj).getPersistenceId();
}
/**
* Get the persistence meta-data object for a given object. Metadata
* is always available, even for non-saved objects.
*/
public PersistenceMetaData getPersistenceMetaData(Object obj)
{
return context.getObjectTracker().getMetaData(obj);
}
/**
* Save the given object to the store. The given object's all private
* non-transient fields will be saved. If the object was not selected
* from the store, and not yet saved, it will be created in the store,
* and a unique id will be assigned, so all subsequent calls to save
* the given object will only modify the already existing instance in
* store. A few tips:<br>
* <ul>
* <li>Use simple beans. Although this library does not scan
* methods to determine the attributes to save, it is a good
* idea to simplify work with them.</li>
* <li>If you do not use simple beans, watch out that your
* object does not reference unnecessary objects, because
* if it does, all will be saved/inserted and tracked.</li>
* <li>You CAN use objects which reference other beans though.
* But beware, that all objects which are directly referenced
* will be loaded when the parent object loads.</li>
* <li>You CAN use Map, and List types in your
* beans. Check the documentation.</li>
* </ul>
* <i>Note:</i>A save operation is not recursive. It does not traverse
* the object hierarchy, only saves the object given, and does not save
* objects referenced, except when referenced object does not exist
* yet. If a referenced object does not yet exist, it will be inserted
* into database (and recursively all referenced objects of that object).
* <i>Implementation note:</i>This class is the intelligent part of
* the framework, an intentionally so. This is the class that coordinates
* all other classes, trackers and functions together.
* @param obj The object to save.
* @throws StoreException If save is not successfull.
*/
public void save(Object obj)
{
// {{{ Save
// Transaction
Transaction transaction = context.getTransactionTracker().getTransaction(TransactionTracker.TX_REQUIRED);
transaction.begin();
transaction.setLastOperation("saving object: "+obj);
try
{
Long currentSerial = context.getNodeManager().getNextSerial();
Long currentTxSerial = transaction.getSerial(currentSerial);
logger.debug("save called, using serial: "+currentSerial+", tx serial: "+currentTxSerial);
// Save object or insert, possibly recursively.
// The waitingObjects list is an ordered list which
// contains all objects currently waiting for saving.
// First it contains the object to be saved, then if
// it referenced more objects, those will be appended to the
// list. An object can only be saved, when all referencing objects
// are all at least created.
LinkedList events = new LinkedList();
LinkedList savedHandledObjects = new LinkedList();
HashSet waitingObjects = new HashSet();
waitingObjects.add(context.getObjectTracker().getWrapper(obj));
while ( waitingObjects.size() > 0 )
{
if ( logger.isDebugEnabled() )
logger.debug("saving object, waiting list: "+waitingObjects.size()+", memory: "+Runtime.getRuntime().freeMemory());
// {{{ Selecting objects which wait for saving
// Get the last object (the bottom of dependency tree)
ObjectTracker.ObjectWrapper currentWrapper = (ObjectTracker.ObjectWrapper) waitingObjects.iterator().next();
Object current = currentWrapper.getObject();
// If object does not exists, register it with object
// tracker. Note, that object tracker only leases an id
// and exists() will return false until object commited.
// Also, if object already has an id, this will do nothing.
context.getObjectTracker().registerObject(current,0);
long currentId = context.getObjectTracker().getIdentifier(current);
logger.debug("saving object with id: "+currentId);
// Lock object
context.getLockTracker().lock(current);
transaction.addSavedObject(currentWrapper);
// Now get object's class info. This method does not
// return null. If the class info is not available, it
// creates it, if it is available it checks whether to
// update the info in database (new attibutes, etc)
ClassInfo classInfo = context.getClassTracker().getClassInfo(current.getClass(),current);
logger.debug("object class info is: "+classInfo);
// Assemble changed attributes of this object into a Map.
// If an attribute is not a primitive type it is checked,
// if it exists. If it does not, it is appended to waitingObjects
// and we start from beginning with this object. If it exists,
// then the reference id will be inserted into the change Map.
Map changedAttributes = new HashMap();
Map updatedAttributes = new HashMap();
// }}}
// {{{ Assemble previous state of object
Map nonchangedAttributes = new HashMap();
if ( context.getObjectTracker().exists(current) )
{
// First, get all original attributes from the database,
// because we'll have to create a full row, even when a
// single attribute changed.
ClassEntry selectClassEntry = classInfo.getSourceEntry();
TableTerm selectTerm = new TableTerm(classInfo.getTableName(selectClassEntry),null);
List relatedClassEntries = context.getClassTracker().getRelatedClassEntries(selectClassEntry);
TimeControl timeControl = new TimeControl(currentSerial,currentTxSerial,true);
for ( int u=0; u<relatedClassEntries.size(); u++ )
{
ClassEntry relatedClassEntry = (ClassEntry) relatedClassEntries.get(u);
ClassInfo relatedClassInfo = context.getClassTracker().getClassInfo(relatedClassEntry);
if ( relatedClassInfo == null )
throw new ParserException(ParserException.ABORT,"object class not found for loading: '"+relatedClassEntry+"'");
// Create the term and add to mainterm
TableTerm leftTerm = new TableTerm(relatedClassInfo.getTableName(relatedClassEntry),null);
selectTerm.getLeftTableTerms().add(leftTerm);
}
Expression expr = new Expression();
expr.add(new ReferenceTerm(selectTerm,"persistence_id"));
expr.add("=");
expr.add(new ConstantTerm(new Long(currentId)));
expr.add("and");
timeControl.apply(expr,selectTerm);
for ( int u=0; u<selectTerm.getLeftTableTerms().size(); u++ )
{
expr.add("and");
timeControl.applyToLeftTable(expr,(TableTerm) selectTerm.getLeftTableTerms().get(u));
}
QueryStatement referredStatement = new QueryStatement(selectTerm,expr,null);
referredStatement.setStaticRepresentation("FIND "+selectClassEntry+" where persistence_id = "+currentId);
referredStatement.setAllLeftTableTerms(selectTerm.getLeftTableTerms());
referredStatement.setTimeControl(timeControl);
SearchResult referredResult = find(transaction,referredStatement,null);
if ( referredResult.getResultSize() > 1 )
{
throw new StoreException("object's last state was ambigous, results for object id '"+currentId+"' was: "+referredResult.getResult());
} else if ( referredResult.getResultSize() == 1 ) {
// Set nonchanged attributes from this last state of object
nonchangedAttributes = (Map) referredResult.getResult().get(0);
if ( logger.isDebugEnabled() )
logger.debug("object nonchanged attributes: "+nonchangedAttributes);
} else {
// Object was not found, it most likely was deleted
// so mark object as non-existent. This is a trick to
// again save all attributes to database. For example,
// when selecting an old instance, it is possible, the
// instance is deleted in present time, so we must save
// all attributes.
context.getObjectTracker().makeUnexist(current);
}
} else {
// Object does not exists, there are no nonchanged attributes
logger.debug("object did not exist, no nonchanged attributes.");
}
// }}}
// {{{ Assembling changed attributes
List attributeNames = classInfo.getAttributeNames();
logger.debug("class tracker reported object to save has following attributes: "+attributeNames+", it has id: "+currentId+", object tracker says it exists: "+context.getObjectTracker().exists(current));
for ( int i=0; i<attributeNames.size(); i++ )
{
String attributeName = (String) attributeNames.get(i);
String attributeNameLowerCase = attributeName.toLowerCase();
// Do not handle special attributes
if ( ("persistence_id".equals(attributeNameLowerCase)) ||
("persistenceid".equals(attributeNameLowerCase)) )
{
// We found a persistence id, fill it with id
classInfo.setAttributeValue(current,attributeName,new Long(currentId));
continue;
}
// Skip reserved prefix'd attributes
if ( attributeNameLowerCase.startsWith("persistence") )
continue;
// Handle non-special attributes
Object attributeValue = classInfo.getAttributeValue(current,attributeName);
logger.debug("saving attribute: "+attributeName+", if changed.");
if ( context.getObjectTracker().hasChanged(classInfo,current,attributeName,nonchangedAttributes) )
{
// Current object's current attribute has changed,
// so arrange it's save.
// This is a switch for the type, this should be
// replaced with something more adequate. For
// example a type manager who will handle types
// and is extensible.
Class attributeType = classInfo.getAttributeType(attributeName);
switch ( context.getClassTracker().getType(attributeType) )
{
case ClassTracker.TYPE_PRIMITIVE:
// Primitive type, just add to attributes
changedAttributes.put(attributeName,attributeValue);
// Set object tracker attribute
updatedAttributes.put(attributeName,attributeValue);
break;
case ClassTracker.TYPE_HANDLED:
logger.debug("changing handled attribute: "+attributeName+", type: "+attributeType);
Object oldValue = context.getObjectTracker().getAttributeValue(current,attributeName);
TypeHandler handler = context.getTypeHandlerTracker().getHandler(attributeType);
Object newValue = handler.save(classInfo,current,attributeName,
transaction,currentSerial,
oldValue,attributeValue,waitingObjects,events,
changedAttributes,nonchangedAttributes);
updatedAttributes.put(attributeName,newValue);
if ( newValue != null )
{
savedHandledObjects.add(handler);
savedHandledObjects.add(newValue);
}
break;
case ClassTracker.TYPE_OBJECT:
logger.debug("attribute decided as custom object '"+attributeName+"', class was: "+classInfo.getAttributeType(attributeName));
// Object type
if ( attributeValue == null )
{
// Object type, but null
changedAttributes.put(attributeName,null);
updatedAttributes.put(attributeName,null);
} else {
if ( ! context.getObjectTracker().exists(attributeValue) )
{
// Object referred does not exists, so it will
// have to be created after we're done
waitingObjects.add(context.getObjectTracker().getWrapper(attributeValue));
}
context.getObjectTracker().registerObject(attributeValue,0);
long objectId = context.getObjectTracker().getIdentifier(attributeValue);
// Got id, so give it to the man
changedAttributes.put(attributeName,new Long(objectId));
// Set object tracker attribute
updatedAttributes.put(attributeName,new Long(objectId));
}
break;
default:
throw new StoreException("unknown type in attribute: "+attributeName+", value was: "+attributeValue);
}
}
} // End of iterating over attributes
// }}}
// {{{ Saving the object's changed attributes
// Now do the database thing on the assembled changed
// attributes, since the waitingObjects list did not
// change, meaning no new dependencies were discovered.
// All changes are saved in multiple save/inserts according
// to class structure.
Iterator strictClassEntriesIterator = classInfo.getStrictClassEntries().iterator();
while ( strictClassEntriesIterator.hasNext() )
{
// Assemble changes for this strict class
ClassEntry entry = (ClassEntry) strictClassEntriesIterator.next();
HashMap strictChanges = new HashMap();
HashMap strictNonChanges = new HashMap();
List strictAttributeNames = classInfo.getStrictAttributeNames(entry);
if ( logger.isDebugEnabled() )
logger.debug("assembling changed for class: "+entry+", strict attributes: "+strictAttributeNames);
for ( int i=0; i<strictAttributeNames.size() ; i++ )
{
String attributeName = (String) strictAttributeNames.get(i);
Class attributeType = classInfo.getAttributeType(attributeName);
TypeHandler handler = context.getTypeHandlerTracker().getHandler(attributeType);
if ( handler == null )
{
// No handler, this is a simple attribute
if ( changedAttributes.containsKey(attributeName) )
strictChanges.put(attributeName,changedAttributes.remove(attributeName));
if ( nonchangedAttributes.containsKey(attributeName) )
strictNonChanges.put(attributeName,nonchangedAttributes.remove(attributeName));
} else {
// Got handler, then iterate on it's attributes
Map embeddedAttributes = handler.getAttributeTypes(attributeName);
if ( logger.isDebugEnabled() )
logger.debug("attribute: "+attributeName+", was an embedded attribute, adding: "+embeddedAttributes);
Iterator embeddedAttributeNamesIterator = embeddedAttributes.keySet().iterator();
while ( embeddedAttributeNamesIterator.hasNext() )
{
attributeName = (String) embeddedAttributeNamesIterator.next();
if ( changedAttributes.containsKey(attributeName) )
strictChanges.put(attributeName,changedAttributes.remove(attributeName));
if ( nonchangedAttributes.containsKey(attributeName) )
strictNonChanges.put(attributeName,nonchangedAttributes.remove(attributeName));
}
}
}
// Make changes
if ( context.getObjectTracker().exists(current) )
{
// Save
logger.debug("changing object with following attributes: "+strictChanges+", not changed: "+strictNonChanges);
if ( strictChanges.size() > 0 )
{
// First set enddate on used entry
HashMap removeChanges = new HashMap();
HashMap keys = new HashMap();
keys.put("persistence_id",new Long(currentId));
keys.put("persistence_end",new Long(DateSerialUtil.getMaxSerial()));
keys.put("persistence_txend",new Long(DateSerialUtil.getMaxSerial()));
removeChanges.put("persistence_txend",currentSerial);
removeChanges.put("persistence_txendid",currentTxSerial);
context.getDatabase().save(transaction,classInfo.getTableName(entry),
keys, removeChanges);
transaction.addRemoveTable(classInfo.getTableName(entry));
// Create new entry
strictNonChanges.putAll(strictChanges);
strictNonChanges.put("persistence_id", new Long(currentId));
strictNonChanges.put("persistence_start", new Long(DateSerialUtil.getMaxSerial()));
strictNonChanges.put("persistence_end", new Long(DateSerialUtil.getMaxSerial()));
strictNonChanges.put("persistence_txendid",new Long(0));
strictNonChanges.put("persistence_txstart", currentSerial);
strictNonChanges.put("persistence_txstartid", currentTxSerial);
strictNonChanges.put("persistence_txend", new Long(DateSerialUtil.getMaxSerial()));
context.getDatabase().insert(transaction,classInfo.getTableName(entry),
strictNonChanges);
// Add to modified tables list
transaction.addSaveTable(classInfo.getTableName(entry));
}
} else {
// Insert
logger.debug("inserting object with following attributes: "+strictChanges);
strictChanges.put("persistence_id", new Long(currentId));
strictChanges.put("persistence_start", new Long(DateSerialUtil.getMaxSerial()));
strictChanges.put("persistence_end", new Long(DateSerialUtil.getMaxSerial()));
strictChanges.put("persistence_txendid",new Long(0));
strictChanges.put("persistence_txstart", currentSerial);
strictChanges.put("persistence_txstartid", currentTxSerial);
strictChanges.put("persistence_txend", new Long(DateSerialUtil.getMaxSerial()));
context.getDatabase().insert(transaction,classInfo.getTableName(entry),
strictChanges);
// Add to modified tables list
transaction.addSaveTable(classInfo.getTableName(entry));
}
}
if ( changedAttributes.size() != 0 )
logger.warn("there are attributes that do not belong in any superclass of object to be saved, classinfo: "+classInfo+", attributes: "+changedAttributes);
logger.debug("saving object with id: "+currentId+" updating meta-data.");
// Remove object from waiting list
waitingObjects.remove(currentWrapper);
// Notify event listeners
if ( context.getObjectTracker().exists(current) )
{
// Modify
events.add(new ModifyObjectEvent(current));
} else {
// Create
events.add(new CreateObjectEvent(current));
}
// Update object tracker
context.getObjectTracker().updateObject(current,updatedAttributes);
context.getObjectTracker().makeExist(current); // Exists for this transaction
// }}}
logger.debug("saving object with id: "+currentId+" finished.");
}
// {{{ Go through handled objects, and notify that save ended
for ( int i=0; i<savedHandledObjects.size(); )
{
TypeHandler handler = (TypeHandler) savedHandledObjects.get(i++);
Object value = (Object) savedHandledObjects.get(i++);
handler.postSave(value);
}
// }}}
// {{{ Notify dispatcher of events occured during save
for ( int i=0; i<events.size(); i++ )
context.getEventDispatcher().notify((PersistenceEvent) events.get(i));
// }}}
// "Happy, happy, joy, joy". Object saved.
} catch ( StoreException e ) {
transaction.rollback();
logger.error("throwing store exception",e);
throw e;
} catch ( Throwable e ) {
transaction.rollback();
logger.error("throwing unexpected exception",e);
throw new StoreException("unexpected exception",e);
}
transaction.commit();
/// }}}
}
/**
* Remove the object given. If the object is not stored yet, no
* operation will take place.
* @param obj The object to remove.
* @throws StoreException If remove is not successfull.
*/
public void remove(Object obj)
{
// {{{ Remove object
// Transaction
Transaction transaction = context.getTransactionTracker().getTransaction(TransactionTracker.TX_REQUIRED);
transaction.begin();
transaction.setLastOperation("removing object: "+obj);
try
{
// Check id. If id is not given, this object does not exists,
// do nothing.
long id = context.getObjectTracker().getIdentifier(obj);
if ( id == 0 )
return;
Long currentSerial = context.getNodeManager().getNextSerial();
Long currentTxSerial = transaction.getSerial(currentSerial);
// Lock object
context.getLockTracker().lock(obj);
transaction.addRemovedObject(context.getObjectTracker().getWrapper(obj));
// Get class info and assemble attributes identifying
// the object
ClassInfo classInfo = context.getClassTracker().getClassInfo(obj.getClass(),obj);
Map removeChanges = new HashMap();
logger.debug("remove called, using serial: "+currentSerial);
removeChanges.put("persistence_txend", currentSerial);
removeChanges.put("persistence_txendid", currentTxSerial);
Map keys = new HashMap();
keys.put("persistence_id",new Long(id));
keys.put("persistence_end",new Long(DateSerialUtil.getMaxSerial()));
keys.put("persistence_txend",new Long(DateSerialUtil.getMaxSerial()));
// Execute remove on class and all superclasses, et voila'
for ( int i=0; i<classInfo.getStrictClassEntries().size(); i++ )
{
ClassEntry entry = (ClassEntry) classInfo.getStrictClassEntries().get(i);
context.getDatabase().save(transaction,classInfo.getTableName(entry),keys , removeChanges);
// Add changed table
transaction.addRemoveTable(classInfo.getTableName(entry));
}
// Notify event listeners
context.getEventDispatcher().notify(new DeleteObjectEvent(obj));
} catch ( StoreException e ) {
transaction.rollback();
logger.error("throwing store exception",e);
throw e;
} catch ( Throwable e ) {
transaction.rollback();
logger.error("throwing unexpected exception",e);
throw new StoreException("unexpected exception",e);
}
transaction.commit();
/// }}}
}
/**
* Query an object from the datastore. The List returned is
* a lazy list, the implementation tries to limit the communication
* with the database layer, as much as possible and pratical. Only
* parts of the list will be loaded when an item is referenced, not
* the whole list. Some features of the query language:<br>
* <pre>find book where book.name='Snow Crash'</pre>
* The statement always starts with the keyword 'find', and all
* keywords and parts of the statement not between apostrophs are
* case in-sensitive.<br>
* The second word of the statement determines
* the class you are trying to find. You can abbreviate the classname
* (strip the package) if it is unique, but you can use the full
* name (com.acme.book) if you wish, but then you MUST provide an
* alias name (see below)<br>
* The following parts are all optional. First, there can be
* a select statement, to specify which objects to select which are
* instances of given class. If you want to have a where part, the
* third word should be 'where'. After it there should be an
* expression almost as in SQL. Parts of the expression can be:<br>
* <ul>
* <li><strong>Member attributes of classes.</strong> The class given previously
* (the target of statement) is always available as declared. If
* you wish to use other classes, they can be referenced by
* class (abbreviated or fully declared). For example
* <pre>find book where book.author=author and author.birthdate=1959</pre>
* Of course Book, and Author classes must exits in the store with
* given attributes.
* Note also, that the above statement can be simply written:
* <pre>find book where book.author.birthdate=1959</pre>
* You can name the classes you are referencing for later
* use (handy if you must "join" the class with itself):
* <pre>find book where book.author=otherbook(book).author and otherbook.name='Snow Crash'</pre>
* Here, the "otherbook" does not refer to a class, it is only another
* name for the class book, and means, that is should be not the same
* instance as the one referenced with "book".
* </li>
* <li><strong>"Now" constant</strong>: For easy use the current date/time
* is represented with the special word 'now'. So you can write:
* <pre>find movie where movie.startdate > now</pre>
* This also means, that attributes named 'now' must be escaped (prefixed
* with it's table name).
* <li><strong>Constants</strong>: Numbers and strings, see examples above.
* Dates and objects can be given by using the question mark (?), and adding the object
* as parameters (same as in jdbc).</li>
* <li><strong>The operators</strong>: <, >, =, !=, like, is null, not null, is not null.
* Note however, that not all database backends are required to support
* all of these. If they are not supported, an exception will be thrown.</li>
* <li><strong>Logical operators</strong>: or, and, not. See examples above.</li>
* <li><strong>Grouping with parenthesis.</strong> As usual in expressions,
* you can use grouping:
* <pre>find book where ((book.author.firstname='Neal') and (book.author.lastname='Stephenson'))</pre>
* </li>
* <li><strong>Special container operator</strong>: contains.
* These are used in conjunction with container types such as Map and List:
* <pre>find book where book.genres contains genre and genre.name='postcyberpunk'</pre>
* <pre>find author where author.books contains book and book.name='Snow Crash'</pre>
* A container operator can not be negated, an exception will be thrown,
* if the expression would try to do that.
* </li>
* <li><strong>Special map operator</strong>: [, ]. These are used when
* referencing a Map. Note also, that [] can only contain strings.
* <pre>find book where book.metadata['author']=author and author.name='Neal Stephenson</pre>
* However, if after a map operator an attribute is referenced,
* there is a mandatory class specifier:
* <pre>find book where book.metadata['author'](author).name='Neal Stephenson</pre>
* </li>
* </ul><br>
* You can also sort the result list with the 'order by' command:
* <pre>find book order by book.name asc</pre>
* The order by command takes attributes as aguments.
* You can give more than one attribute separated by commas.
* Also you can append 'asc' (ascending) or 'desc' (descending) to
* mark the direction of sort.
* <pre>find book order by book.author.name asc, book.name desc</pre>
* Note, that method will silently return an empty list, if the
* specified table or one specified in where clause does not exist.<br>
* For more detailed information, check the documentation.
* @param statement The query statement to select.
*/
public List find(String statement)
{
return find(statement,null,null,null);
}
/**
* Same as <code>find(statement)</code>. When a statement contains
* the question mark (?), the object which should be in the place of
* the mark should be given as parameters (parameters are usually of
* Date, or custom classes).
* @param statement The query statement to execute.
* @param parameters The parameters.
*/
public List find(String statement, Object[] parameters)
{
return find(statement,parameters,null,null);
}
/**
* This method in addition to all usual parameters can define the
* exact time of the query with the timeControl parameter. If the
* query is a historical query, this control will be overridden.
* @param statement The query statement to execute.
* @param parameters The parameters.
* @param timeControl The exact default time of the query.
*/
List find(String statement, Object[] parameters, TimeControl timeControl, Map unmarshalledObjects)
{
// {{{ Parse statement, and create result list
// Convert object parameters. If they contain
// objects, substitute with object id
Transaction transaction = context.getTransactionTracker().getTransaction(TransactionTracker.TX_REQUIRED);
transaction.begin();
transaction.setLastOperation(statement);
try
{
Object[] realParameters = null;
if ( parameters != null )
{
realParameters = new Object[parameters.length];
for ( int i=0; i<parameters.length; i++ )
{
if ( parameters[i] == null )
{
// Handle null parameters
realParameters[i]=null;
} else {
// If parameter is not null, translate object parameters
// to persistent id, leave others
int type = context.getClassTracker().getType(parameters[i].getClass());
if ( type == ClassTracker.TYPE_RESERVED )
throw new StoreException("parameter at position: "+i+", value: "+parameters[i]+" is of unsupported type.");
if ( (type == ClassTracker.TYPE_OBJECT) && (!(parameters[i] instanceof Collection)) )
{
// If object has no id, that's not a problem. It will
// receive an id of 0, and no object should match
// that id anyway.
realParameters[i]=new Identifier(new Long(context.getObjectTracker().getIdentifier(parameters[i])));
} else if ( parameters[i] instanceof Collection ) {
// Check, whether the collection's items are objects,
// in which case translate them to their ids
Vector result = new Vector();
Iterator itemIterator = ((Collection) parameters[i]).iterator();
while ( itemIterator.hasNext() )
{
Object item = itemIterator.next();
if ( context.getClassTracker().getType(item.getClass()) == ClassTracker.TYPE_OBJECT )
result.add(new Long(context.getObjectTracker().getIdentifier(item)));
else
result.add(item);
}
realParameters[i]=result;
} else {
realParameters[i]=parameters[i];
}
}
if ( logger.isDebugEnabled() )
logger.debug("parameter: "+parameters[i]+" -> real parameter #"+i+":"+realParameters[i]);
}
}
// Process it, get expression
QueryStatementList stmts = null;
try
{
// Only apply in-transaction search conditions if there is a transaction
// and something changed during transaction. Only calculate if
// no 'default default' is given.
HashSet modifiedTables = new HashSet();
modifiedTables.addAll(transaction.getSaveTables());
modifiedTables.addAll(transaction.getRemoveTables());
if ( timeControl == null )
{
Long serial = context.getNodeManager().getNextSerial();
Long txSerial = transaction.getSerial(serial);
timeControl = new TimeControl(serial,txSerial,modifiedTables.size()>0);
}
// Parse statement
if ( logger.isDebugEnabled() )
logger.debug("executing parser, serial: "+timeControl.getSerial()+", tx serial: "+timeControl.getTxSerial());
stmts = Parser.parse(statement,new WhereResolver(context),
realParameters,timeControl,modifiedTables);
// Wait now for all commits() before the given serial to finish.
// If this would not be the case, the lazy list might not
// contain the data from previously initiated commits. Which
// would mean, once those finished, the lazy list would change.
context.getNodeManager().waitForQuery(timeControl.getSerial());
} catch ( ParserException e ) {
if ( e.getCode() == ParserException.ABORT )
{
logger.error("aborting query, because of parser exception.");
throw new StoreException(e.getMessage(),e);
} else {
logger.info("returning empty result list because of non-fatal symbol error. Parser said: "+e.getMessage()+", statement was: "+statement);
return new LinkedList(); // Return empty list on non-fatal symbol errors
}
} catch ( StoreException e ) {
throw e;
} catch ( Exception e ) {
throw new StoreException("unknown exception while select",e);
}
// Return list
return new LazyList(context,stmts,unmarshalledObjects);
} catch ( StoreException e ) {
transaction.markRollbackOnly();
throw e;
} catch ( Throwable e ) {
transaction.markRollbackOnly();
throw new StoreException("unexpected exception",e);
} finally {
transaction.commit();
}
// }}}
}
/**
* Same as <code>find(statement,parameters)</code>, but the result should be
* a single object.
* @param statement The query statement to execute.
* @param parameters The parameters to the statement.
* @return The object selected, or null if no such object exists. If
* the result contains more objects, an arbitrary one is selected.
*/
public Object findSingle(String statement, Object[] parameters)
{
// {{{ Call normal find and evaluate result
Iterator iterator = find(statement,parameters).iterator();
if ( iterator.hasNext() )
return iterator.next();
return null;
// }}}
}
/**
* Same as <code>find(statement)</code>, but the result should be
* a single object.
* @param statement The query statement to execute.
* @return The object selected, or null if no such object exists. If
* the result contains more objects, an arbitrary one is selected.
*/
public Object findSingle(String statement)
{
return findSingle(statement,null);
}
/**
* Internal raw loading. All finder methods sooner or later call into
* this method to get real results in form of attribute name-value maps.
*/
SearchResult find(Transaction transaction, QueryStatement stmt, Limits limits)
{
// {{{ Do physical query
// First, check if statement is visible from this transaction
TimeControl timeControl = stmt.getTimeControl();
if ( (timeControl.isApplyTransaction()) && (timeControl.getTxSerial()!=null) &&
(!timeControl.getTxSerial().equals(transaction.getSerial())) &&
(context.getTransactionTracker().hasOpenTransaction(timeControl.getTxSerial())) )
throw new StoreException("tried to do a query which was outside it's transaction '"+timeControl.getTxSerial()+"', which was still open. Current tx was: "+transaction.getSerial());
// Get resultset from cache or database
SearchResult result = context.getCache().getEntry(stmt,limits);
if ( result == null )
{
result = context.getDatabase().search(transaction,stmt,limits);
context.getCache().addEntry(stmt,limits,result);
}
return result;
// }}}
}
/**
* Unmarshall an object. This means create an object of given class
* and set attributes from a given map of attributes. All referred
* objects are assumed to be in the already allocated list, indexed
* by object id.
* @param classInfo The class info of the object that needs to be instantiated.
* @param marshalledValues The attributes values.
* @param unmarshalledObjects The already unmarshalled objects.
*/
private Object unmarshallObject(ClassInfo classInfo, Map marshalledValues,
Map unmarshalledObjects, Map missingAttributes, QueryStatement stmt)
throws InstantiationException, IllegalAccessException
{
// {{{ Umarshall object
Object obj = classInfo.newInstance(marshalledValues);
if ( obj == null )
return null;
// Important! Register object into tracker!
// Note: all attributes are updated into the object tracker.
// Previously this was not done, because object's had a shared
// state (instances of the same database row), but now no shared
// state exists (at least, not with attributes).
context.getObjectTracker().registerObject(obj,((Long) marshalledValues.get("persistence_id")).longValue());
context.getObjectTracker().updateObject(obj,marshalledValues);
PersistenceMetaData metaData =context.getObjectTracker().getMetaData(obj);
metaData.setPersistenceStart(((Long) marshalledValues.get("persistence_start")));
metaData.setPersistenceEnd(((Long) marshalledValues.get("persistence_end")));
metaData.setQuerySerial(stmt.getTimeControl().getSerial());
unmarshalledObjects.put(marshalledValues.get("persistence_id"),obj);
// Set properties in obj, go through object attributes
// (except is the object is of primitive type)
List attributeNames = classInfo.getAttributeNames();
for ( int o=0; (o<attributeNames.size()) &&
(!classInfo.getSourceEntry().isPrimitive()); o++ )
{
String attributeName = attributeNames.get(o).toString();
// Handle peristence_id specially. If this is an attribute
// named something like persistenceId, then fill in the id.
if ( ("persistence_id".equalsIgnoreCase(attributeName)) ||
("persistenceid".equalsIgnoreCase(attributeName)) )
{
// Fill attribute with persistence id
classInfo.setAttributeValue(obj,attributeName,marshalledValues.get("persistence_id"));
continue;
}
// Handle other (normal) attributes
Object attributeValue = marshalledValues.get(attributeName.toLowerCase());
if ( logger.isDebugEnabled() )
logger.debug("setting object property: "+attributeName+", value: "+attributeValue);
Class attributeClass = classInfo.getAttributeType(attributeName);
if ( attributeClass == null )
{
logger.error("object property '"+attributeName+"' cannot set, object has no such property.");
continue;
}
switch ( context.getClassTracker().getType(attributeClass) )
{
case ClassTracker.TYPE_PRIMITIVE:
classInfo.setAttributeValue(obj,attributeName,attributeValue);
break;
case ClassTracker.TYPE_HANDLED:
TypeHandler handler = context.getTypeHandlerTracker().getHandler(attributeClass);
Object value = handler.unmarshallType(
classInfo,obj,attributeName,marshalledValues, stmt.getTimeControl());
classInfo.setAttributeValue(obj,attributeName,value);
context.getObjectTracker().updateAttribute(obj,attributeName,value);
break;
case ClassTracker.TYPE_OBJECT:
// Handle null
if ( attributeValue == null )
{
classInfo.setAttributeValue(obj,attributeName,null);
break;
}
// Get object from list
Object relatedObj = unmarshalledObjects.get(attributeValue);
classInfo.setAttributeValue(obj,attributeName,relatedObj);
if ( relatedObj == null )
{
if ( logger.isDebugEnabled() )
logger.debug("referred object of id: "+attributeValue+" not found, currently unmarshalled objects: "+unmarshalledObjects);
// Remember with ids entry. An entry holds all necessary
// information for a single attribute to reconstruct
// it's objects for all referrers:
// - classinfo: of attribute declared type (for select)
// - ids: all ids of referred objects
// - objects: indexed by referred object id, hold a set
// of objects which need the specified referred object
IdsEntry idsEntry = (IdsEntry) missingAttributes.get(attributeName);
if ( idsEntry == null )
{
idsEntry = new IdsEntry(new HashMap(),new HashSet(),classInfo);
missingAttributes.put(attributeName,idsEntry);
}
idsEntry.ids.add(attributeValue);
List objectsSet = (List) idsEntry.objects.get(attributeValue);
if ( objectsSet == null )
{
objectsSet = new Vector();
idsEntry.objects.put(attributeValue,objectsSet);
}
objectsSet.add(obj);
}
break;
default:
throw new StoreException("attribute: "+attributeName+"'s type was not valid.");
}
} // Iteration over attributes
return obj;
// }}}
}
/**
* Internal loading.
*/
SearchResult find(QueryStatement rawStmt, Limits limits, Map unmarshalledObjects )
{
// {{{ Internal loading code
if ( unmarshalledObjects == null )
unmarshalledObjects = new HashMap();
Transaction transaction = context.getTransactionTracker().getTransaction(TransactionTracker.TX_REQUIRED);
transaction.begin();
logger.debug("called store internal find.");
try
{
// If the query is a view query, then return raw map format.
// Else get the main term.
if ( rawStmt.getMode() == QueryStatement.MODE_VIEW )
return find(transaction,rawStmt,limits);
TableTerm mainTerm = (TableTerm) rawStmt.getSelectTerms().get(0);
// Modify the query statement, so the statement should return
// the object's table in question (so the object instance can be
// created)
ClassInfo classInfo = context.getClassTracker().getTableClassInfo(mainTerm.getTableName());
if ( classInfo == null )
throw new StoreException("no class found for table name: "+mainTerm.getTableName());
QueryStatement stmt = new QueryStatement(rawStmt);
if ( logger.isDebugEnabled() )
logger.debug("main term is: "+mainTerm+", left terms: "+mainTerm.getLeftTableTerms());
if ( mainTerm.getLeftTableTerms().size() > 0 )
{
// Modify main term to include object table information for
// un-marshalling
Vector mainTerms = new Vector();
TableTerm mainTermCopy = new TableTerm(mainTerm.getTableName(),mainTerm.getAlias(),
new Vector(mainTerm.getLeftTableTerms()));
mainTermCopy.getLeftTableTerms().add(new TableTerm("persistence_object_ids",null));
mainTerms.add(mainTermCopy);
mainTerms.addAll(stmt.getSelectTerms().subList(1,stmt.getSelectTerms().size()));
stmt.setSelectTerms(mainTerms);
stmt.setStaticRepresentation(stmt.getStaticRepresentation()+"-ids");
}
// Run the real database search
logger.debug("find running real select statement.");
SearchResult rawResult = find(transaction,stmt,limits);
// Take the raw data and umarshall them into objects
logger.debug("find unmarshalling objects.");
HashMap missingAttributes = new HashMap();
SearchResult cookedResult = new SearchResult();
cookedResult.setResultSize(rawResult.getResultSize());
Vector cookedResultList = new Vector();
cookedResult.setResult(cookedResultList);
for ( int i=0; i<rawResult.getResult().size(); i++ )
{
// Get values
Map marshalledValues = (Map) rawResult.getResult().get(i);
// If object already unmarshalled, then get from list,
// else instantiate
Object obj = unmarshalledObjects.get(marshalledValues.get("persistence_id"));
if ( obj == null )
{
if ( logger.isDebugEnabled() )
logger.debug("got marshalled values: "+marshalledValues);
// Instantiate and unmarshall object
ClassInfo localClassInfo = classInfo;
if ( mainTerm.getLeftTableTerms().size() > 0 )
{
// If there were left join tables, try to get the
// correct class. This means, that the object
// can be the subclass of queried class, and not
// exactly that. We can determine the exact class
// from the table name.
String objectTable = (String) marshalledValues.get("object_table");
localClassInfo = context.getClassTracker().getTableClassInfo(objectTable);
}
// Unmarshall, with the exact class info given
obj = unmarshallObject(localClassInfo,marshalledValues,
unmarshalledObjects,missingAttributes,rawStmt);
}
// Add object to result list. The 'obj' is an umarshalled full
// object, which is gethered from the main table of the query.
// How ever, if there are other referenced attributes the
// query should return, then we do the whole thing into a Map.
// The object itself will have the key 'object' in this case.
if ( rawStmt.getSelectTerms().size() > 1 )
{
// There are other attributes the caller wants, so
// do the whole thing into a Map. Insert all wanted attributes,
// and the unmarshalled main object too.
HashMap resultObj = new HashMap();
for ( int o=1; o<rawStmt.getSelectTerms().size(); o++ )
{
ReferenceTerm refTerm = (ReferenceTerm) rawStmt.getSelectTerms().get(o);
resultObj.put(refTerm.getColumnFinalName(),marshalledValues.get(refTerm.getColumnFinalName().toLowerCase()));
}
resultObj.put("object",obj);
cookedResultList.add(resultObj);
} else {
// There was just the object, so insert it into the result
// list, just in itself.
cookedResultList.add(obj);
}
}
// Load all referred objects for all classes which were unmarshalled,
// the missing list was assembled in the unmarshall code.
Iterator missingAttributesIterator = missingAttributes.entrySet().iterator();
while ( missingAttributesIterator.hasNext() )
{
// Get all necessary meta-data
Map.Entry entry = (Map.Entry) missingAttributesIterator.next();
String attributeName = entry.getKey().toString();
IdsEntry idsEntry = (IdsEntry) entry.getValue();
Class selectClass = idsEntry.classInfo.getAttributeType(attributeName);
// We got the class of the object, so we select all objects to
// this attribute into a map keyed with the persistence id.
HashMap referredObjects = new HashMap();
if ( logger.isDebugEnabled() )
logger.debug("getting member attribute: "+selectClass+", for ids: "+idsEntry.ids);
List referredObjectList = find("find member("+selectClass.getName()+") where member in ?",new Object[] {idsEntry.ids},null,unmarshalledObjects);
for ( int i=0; i<referredObjectList.size(); i++ )
{
Object referredObject = referredObjectList.get(i);
referredObjects.put(new Long(context.getObjectTracker().getIdentifier(referredObject)),referredObject);
}
// Now fill in this attribute with the ready referred objects
Iterator objectEntryIterator = idsEntry.objects.entrySet().iterator();
while ( objectEntryIterator.hasNext() )
{
Map.Entry objectEntry = (Map.Entry) objectEntryIterator.next();
Object referredObject = referredObjects.get(objectEntry.getKey());
// Now set to all referring objects
Iterator objectIterator = ((List) objectEntry.getValue()).iterator();
while ( objectIterator.hasNext() )
{
Object referrerObject = objectIterator.next();
ClassInfo referrerClassInfo = context.getClassTracker().getClassInfo(referrerObject.getClass(),referrerObject);
referrerClassInfo.setAttributeValue(referrerObject,attributeName,referredObject);
}
}
}
// Return result
logger.debug("find returning result list");
return cookedResult;
} catch ( StoreException e ) {
transaction.markRollbackOnly();
logger.error("throwing store exception",e);
throw e;
} catch ( Exception e ) {
transaction.markRollbackOnly();
logger.error("throwing unexpected exception",e);
throw new StoreException("unexpected exception",e);
} finally {
transaction.commit();
}
// }}}
}
/**
* Unlock all objects in transaction.
*/
private void unlockObjects(Transaction transaction)
{
// {{{ Unlock all objects in transaction
List removedObjects = transaction.getRemovedObjects();
for ( int i=0; i<removedObjects.size(); i++ )
context.getLockTracker().unlock(((ObjectTracker.ObjectWrapper)removedObjects.get(i)).getObject());
List addedObjects = transaction.getSavedObjects();
for ( int i=0; i<addedObjects.size(); i++ )
context.getLockTracker().unlock(((ObjectTracker.ObjectWrapper)addedObjects.get(i)).getObject());
// }}}
}
/**
* Notify server of all objects that changed. Server keeps track of the last modifications
* to know which object versions are current. To do this, we notify the server, that
* this transaction is about to be commited. The object modifications will be finalized,
* if the server receives the endCommit() call. If that event fails to be delivered,
* the server will mark the changes as unknown, and will check from the database to be
* sure.
*/
private void notifyServerOfChanges(Transaction transaction)
{
// {{{ Notify server of object changes inside transaction
Vector objects = new Vector();
objects.addAll(transaction.getRemovedObjects());
objects.addAll(transaction.getSavedObjects());
List metas = new Vector();
for ( int i=0; i<objects.size(); i++ )
metas.add(getPersistenceMetaData(((ObjectTracker.ObjectWrapper)objects.get(i)).getObject()));
context.getNodeManager().notifyChange(metas, transaction.getSerial(), transaction.getEndSerial());
// }}}
}
/**
* Modify object tracker's metadata for the transaction's changes.
*/
private void modifyMetaData(Transaction transaction)
{
// {{{ Modify transaction objects' metadata
for ( int i=0; i<transaction.getRemovedObjects().size(); i++ )
{
Object obj = ((ObjectTracker.ObjectWrapper)transaction.getRemovedObjects().get(i)).getObject();
PersistenceMetaData metaData = getPersistenceMetaData(obj);
metaData.setPersistenceEnd(transaction.getEndSerial());
}
for ( int i=0; i<transaction.getSavedObjects().size(); i++ )
{
Object obj = ((ObjectTracker.ObjectWrapper)transaction.getSavedObjects().get(i)).getObject();
PersistenceMetaData metaData = getPersistenceMetaData(obj);
metaData.setQuerySerial(transaction.getEndSerial());
metaData.setPersistenceStart(transaction.getEndSerial());
// If object was saved, it also was removed if it existed, so
// if the object still exists, clear the end serial
if ( context.getObjectTracker().exists(obj) )
metaData.setPersistenceEnd(null);
}
// }}}
}
/**
* Rollback a transaction. Rollback has to only unlock objects
* the transaction carries, and call the database rollback.
*/
void rollback(Transaction transaction)
{
// {{{ Rollback transaction
try
{
logger.debug("store rollback runs on transaction: "+transaction);
// Call transaction tracker rollback (which also calls notify threads)
context.getTransactionTracker().internalRollback(transaction);
} finally {
// Unlock objects
unlockObjects(transaction);
}
// }}}
}
/**
* Commit a transaction. This method is necessary, because on the
* end of a transaction, the store must set all persistence_start
* and persistence_end date/serials to the actual close-serial
* of transaction.
*/
void commit(Transaction transaction)
{
// {{{ Commit transaction
logger.debug("store commit runs on transaction: "+transaction);
Long endSerial = null;
boolean success = true;
try
{
Throwable failureCause = null;
if ( transaction.isRollbackOnly() )
success=false;
// We need a serial for the transaction to end. This serial
// will denote the commit itself. The beginning of a commit
// must be marked, because while the commit is running, no
// query can execute which has a higher serial, because this
// would mean the query will change once this commit finishes.
endSerial = context.getNodeManager().startCommit(
context.getNodeManager().getNodeIndex());
transaction.setEndSerial(endSerial);
// All operations done now must finish, if an exception occurs,
// roll it back manually and throw exception.
try
{
// Save: set startdates where startdate is maxdate
List saveTables = transaction.getSaveTables();
HashMap keys = new HashMap();
keys.put("persistence_txstartid",transaction.getSerial());
HashMap changes = new HashMap();
changes.put("persistence_start",endSerial);
for ( int i=0; i<saveTables.size(); i++ )
{
String tableName = (String) saveTables.get(i);
logger.debug("fixing save table: "+tableName);
context.getDatabase().save(transaction,tableName,keys,changes);
// Notify cache, that table changed for everyone
context.getCache().updateEntries(tableName,endSerial);
context.getNodeManager().updateEntries(tableName,endSerial);
}
// Remove: set enddates where txenddate is not maxdate
List removeTables = transaction.getRemoveTables();
keys = new HashMap();
keys.put("persistence_txendid",transaction.getSerial());
changes = new HashMap();
changes.put("persistence_end",endSerial);
for ( int i=0; i<removeTables.size(); i++ )
{
String tableName = (String) removeTables.get(i);
logger.debug("fixing remove table: "+tableName);
context.getDatabase().save(transaction,tableName,keys,changes);
// Notify cache, that table changed for everyone
context.getCache().updateEntries(tableName,endSerial);
context.getNodeManager().updateEntries(tableName,endSerial);
}
// Notify the server of all objects that changed. This operation
// must be before the commit physically occurs, because this notification
// will cause the server to know which objects are modified.
notifyServerOfChanges(transaction);
} catch ( Exception e ) {
failureCause=e;
success=false;
}
// Call transaction tracker commit (which also calls notify threads)
if ( success )
{
success = false;
logger.debug("store calls internal commit on transaction: "+transaction);
context.getTransactionTracker().internalCommit(transaction);
success = true; // Commit really ran
} else {
// Rollback and throw exception
logger.debug("store commit calls rollback on transaction: "+transaction);
context.getTransactionTracker().internalRollback(transaction);
throw new StoreException("exception while commiting transaction.",failureCause);
}
} finally {
// If the commit was successfull, we must update objects'
// metadata to reflect changes.
if ( success )
modifyMetaData(transaction);
// Exit commit semaphore. If the end of the commit is reached,
// queries can be initiated with this serial, because all information
// is readyly commited.
if ( endSerial != null )
context.getNodeManager().endCommit(
context.getNodeManager().getNodeIndex(),endSerial);
// Unlock objects. Unlock event must come after the commit has been
// closed. If this event does not reach the server, the communication
// error will cause the server to unlock all objects anyway.
unlockObjects(transaction);
// Log end
logger.debug("store commit ended on transaction: "+transaction);
}
// }}}
}
private class IdsEntry
{
public Set ids;
public ClassInfo classInfo;
public Map objects;
public IdsEntry(Map objects, Set ids, ClassInfo classInfo)
{
this.objects=objects;
this.ids=ids;
this.classInfo=classInfo;
}
}
/**
* This is a shutdown logic, which simply calls <code>close()</code>
* when the JVM exists.
*/
private class ShutdownProcess implements Runnable
{
public void run()
{
close(true);
}
}
}
|