Store.java :  » Database-ORM » beankeeper » hu » netmind » persistence » Java Open Source

Java Open Source » Database ORM » beankeeper 
beankeeper » hu » netmind » persistence » Store.java
/**
 * 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 &gt; 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>: &lt;, &gt;, =, !=, 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);
      }
   }

}



java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.