0001: /**
0002: * Copyright (C) 2006 NetMind Consulting Bt.
0003: *
0004: * This library is free software; you can redistribute it and/or
0005: * modify it under the terms of the GNU Lesser General Public
0006: * License as published by the Free Software Foundation; either
0007: * version 3 of the License, or (at your option) any later version.
0008: *
0009: * This library is distributed in the hope that it will be useful,
0010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * Lesser General Public License for more details.
0013: *
0014: * You should have received a copy of the GNU Lesser General Public
0015: * License along with this library; if not, write to the Free Software
0016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: */package hu.netmind.persistence;
0018:
0019: import org.apache.log4j.Logger;
0020: import java.util.*;
0021: import hu.netmind.persistence.parser.*;
0022: import hu.netmind.persistence.node.*;
0023: import hu.netmind.persistence.event.*;
0024: import javax.sql.DataSource;
0025:
0026: /**
0027: * This store class is the entry point to the persistence library. To
0028: * store, remove or select given objects, just use the appropriate
0029: * method.
0030: * @author Brautigam Robert
0031: * @version Revision: $Revision$
0032: */
0033: public class Store {
0034: private static Logger logger = Logger.getLogger(Store.class);
0035:
0036: private StoreContext context;
0037: private Thread shutdownHook;
0038:
0039: /**
0040: * Get context.
0041: */
0042: protected StoreContext getContext() {
0043: return context;
0044: }
0045:
0046: /**
0047: * Instantiate a store. Note that you should only make one
0048: * store instance for each datasource you might want to use, all
0049: * methods are thread-safe, so you can use the single instance
0050: * in more threads.
0051: * @param driverClass The driver class to register.
0052: * @param url The driver's jdbc url (including username and password).
0053: */
0054: public Store(String driverClass, String url) {
0055: // {{{ Constructor
0056: this (new DriverDataSource(driverClass, url));
0057: // }}}
0058: }
0059:
0060: /**
0061: * Instantiate a store. Note that you should only make one
0062: * store instance for each datasource you might want to use, all
0063: * methods are thread-safe, so you can use the single instance
0064: * in more threads.
0065: */
0066: public Store(DataSource dataSource) {
0067: // {{{ Create store context
0068: // Note that initialization order is important!
0069: context = new StoreContext();
0070: context.setEventDispatcher(new EventDispatcher());
0071: context.setStore(this );
0072: context.setSerialTracker(new SerialTracker());
0073: context.setDatabase(DatabaseFactory.getDatabase(dataSource));
0074: context.setTypeHandlerTracker(new TypeHandlerTracker(context));
0075: context.setTransactionTracker(new TransactionTracker(context));
0076: context.setLockTracker(new LockTracker(context));
0077: context.setNodeManager(new NodeManager(context));
0078: context.setClassTracker(new ClassTracker(context));
0079: context.setObjectTracker(new ObjectTracker(context));
0080: context.setCache(new ResultsCache(context));
0081: // Start node as soon as all subsystems run
0082: try {
0083: context.getNodeManager().ensureState(
0084: NodeManager.STATE_CONNECTED); // Init NodeManager
0085: } catch (Exception e) {
0086: logger
0087: .warn(
0088: "initialization failed, will try to establish contact later.",
0089: e);
0090: }
0091: // Proper shutdown
0092: shutdownHook = new Thread(new ShutdownProcess());
0093: Runtime.getRuntime().addShutdownHook(shutdownHook);
0094: /// }}}
0095: }
0096:
0097: /**
0098: * Close the store, and release all resources. This method is automatically
0099: * invoked as a JVM shutdown hook, so it does not have to be called at all.
0100: */
0101: public void close() {
0102: close(false);
0103: }
0104:
0105: /**
0106: * Internal close method.
0107: */
0108: private void close(boolean shutdown) {
0109: logger.debug("store shutdown running");
0110: context.getNodeManager().close();
0111: context.getDatabase().release();
0112: if (!shutdown)
0113: Runtime.getRuntime().removeShutdownHook(shutdownHook); // Do not run twice
0114: }
0115:
0116: /**
0117: * Get the transaction tracker associated with this store. The transaction
0118: * tracker can be used to create transactions, and listen for transactional
0119: * events.
0120: * @return The transaction tracker.
0121: */
0122: public TransactionTracker getTransactionTracker() {
0123: return context.getTransactionTracker();
0124: }
0125:
0126: /**
0127: * Get the lock tracker. This tracker can be used to lock and unlock
0128: * objects for exclusive operations.
0129: */
0130: public LockTracker getLockTracker() {
0131: return context.getLockTracker();
0132: }
0133:
0134: /**
0135: * Get the event dispatcher in which the caller may register
0136: * listeners.
0137: */
0138: public EventDispatcher getEventDispatcher() {
0139: return context.getEventDispatcher();
0140: }
0141:
0142: /**
0143: * Get the persistence id for an object. This method always returns
0144: * a valid id, even if the object is not saved, or otherwise has
0145: * no persistence id. If the object is later saved, this persistence id
0146: * is always preserved.<br>
0147: * Same as: <code>getPersistenceMetaData(obj).getPersistenceId()</code>.
0148: * @return A persistence id, and never null.
0149: */
0150: public Long getPersistenceId(Object obj) {
0151: return getPersistenceMetaData(obj).getPersistenceId();
0152: }
0153:
0154: /**
0155: * Get the persistence meta-data object for a given object. Metadata
0156: * is always available, even for non-saved objects.
0157: */
0158: public PersistenceMetaData getPersistenceMetaData(Object obj) {
0159: return context.getObjectTracker().getMetaData(obj);
0160: }
0161:
0162: /**
0163: * Save the given object to the store. The given object's all private
0164: * non-transient fields will be saved. If the object was not selected
0165: * from the store, and not yet saved, it will be created in the store,
0166: * and a unique id will be assigned, so all subsequent calls to save
0167: * the given object will only modify the already existing instance in
0168: * store. A few tips:<br>
0169: * <ul>
0170: * <li>Use simple beans. Although this library does not scan
0171: * methods to determine the attributes to save, it is a good
0172: * idea to simplify work with them.</li>
0173: * <li>If you do not use simple beans, watch out that your
0174: * object does not reference unnecessary objects, because
0175: * if it does, all will be saved/inserted and tracked.</li>
0176: * <li>You CAN use objects which reference other beans though.
0177: * But beware, that all objects which are directly referenced
0178: * will be loaded when the parent object loads.</li>
0179: * <li>You CAN use Map, and List types in your
0180: * beans. Check the documentation.</li>
0181: * </ul>
0182: * <i>Note:</i>A save operation is not recursive. It does not traverse
0183: * the object hierarchy, only saves the object given, and does not save
0184: * objects referenced, except when referenced object does not exist
0185: * yet. If a referenced object does not yet exist, it will be inserted
0186: * into database (and recursively all referenced objects of that object).
0187: * <i>Implementation note:</i>This class is the intelligent part of
0188: * the framework, an intentionally so. This is the class that coordinates
0189: * all other classes, trackers and functions together.
0190: * @param obj The object to save.
0191: * @throws StoreException If save is not successfull.
0192: */
0193: public void save(Object obj) {
0194: // {{{ Save
0195: // Transaction
0196: Transaction transaction = context.getTransactionTracker()
0197: .getTransaction(TransactionTracker.TX_REQUIRED);
0198: transaction.begin();
0199: transaction.setLastOperation("saving object: " + obj);
0200: try {
0201: Long currentSerial = context.getNodeManager()
0202: .getNextSerial();
0203: Long currentTxSerial = transaction.getSerial(currentSerial);
0204: logger.debug("save called, using serial: " + currentSerial
0205: + ", tx serial: " + currentTxSerial);
0206: // Save object or insert, possibly recursively.
0207: // The waitingObjects list is an ordered list which
0208: // contains all objects currently waiting for saving.
0209: // First it contains the object to be saved, then if
0210: // it referenced more objects, those will be appended to the
0211: // list. An object can only be saved, when all referencing objects
0212: // are all at least created.
0213: LinkedList events = new LinkedList();
0214: LinkedList savedHandledObjects = new LinkedList();
0215: HashSet waitingObjects = new HashSet();
0216: waitingObjects.add(context.getObjectTracker().getWrapper(
0217: obj));
0218: while (waitingObjects.size() > 0) {
0219: if (logger.isDebugEnabled())
0220: logger.debug("saving object, waiting list: "
0221: + waitingObjects.size() + ", memory: "
0222: + Runtime.getRuntime().freeMemory());
0223: // {{{ Selecting objects which wait for saving
0224: // Get the last object (the bottom of dependency tree)
0225: ObjectTracker.ObjectWrapper currentWrapper = (ObjectTracker.ObjectWrapper) waitingObjects
0226: .iterator().next();
0227: Object current = currentWrapper.getObject();
0228: // If object does not exists, register it with object
0229: // tracker. Note, that object tracker only leases an id
0230: // and exists() will return false until object commited.
0231: // Also, if object already has an id, this will do nothing.
0232: context.getObjectTracker().registerObject(current, 0);
0233: long currentId = context.getObjectTracker()
0234: .getIdentifier(current);
0235: logger.debug("saving object with id: " + currentId);
0236: // Lock object
0237: context.getLockTracker().lock(current);
0238: transaction.addSavedObject(currentWrapper);
0239: // Now get object's class info. This method does not
0240: // return null. If the class info is not available, it
0241: // creates it, if it is available it checks whether to
0242: // update the info in database (new attibutes, etc)
0243: ClassInfo classInfo = context.getClassTracker()
0244: .getClassInfo(current.getClass(), current);
0245: logger.debug("object class info is: " + classInfo);
0246: // Assemble changed attributes of this object into a Map.
0247: // If an attribute is not a primitive type it is checked,
0248: // if it exists. If it does not, it is appended to waitingObjects
0249: // and we start from beginning with this object. If it exists,
0250: // then the reference id will be inserted into the change Map.
0251: Map changedAttributes = new HashMap();
0252: Map updatedAttributes = new HashMap();
0253: // }}}
0254: // {{{ Assemble previous state of object
0255: Map nonchangedAttributes = new HashMap();
0256: if (context.getObjectTracker().exists(current)) {
0257: // First, get all original attributes from the database,
0258: // because we'll have to create a full row, even when a
0259: // single attribute changed.
0260: ClassEntry selectClassEntry = classInfo
0261: .getSourceEntry();
0262: TableTerm selectTerm = new TableTerm(classInfo
0263: .getTableName(selectClassEntry), null);
0264: List relatedClassEntries = context
0265: .getClassTracker().getRelatedClassEntries(
0266: selectClassEntry);
0267: TimeControl timeControl = new TimeControl(
0268: currentSerial, currentTxSerial, true);
0269: for (int u = 0; u < relatedClassEntries.size(); u++) {
0270: ClassEntry relatedClassEntry = (ClassEntry) relatedClassEntries
0271: .get(u);
0272: ClassInfo relatedClassInfo = context
0273: .getClassTracker().getClassInfo(
0274: relatedClassEntry);
0275: if (relatedClassInfo == null)
0276: throw new ParserException(
0277: ParserException.ABORT,
0278: "object class not found for loading: '"
0279: + relatedClassEntry + "'");
0280: // Create the term and add to mainterm
0281: TableTerm leftTerm = new TableTerm(
0282: relatedClassInfo
0283: .getTableName(relatedClassEntry),
0284: null);
0285: selectTerm.getLeftTableTerms().add(leftTerm);
0286: }
0287: Expression expr = new Expression();
0288: expr.add(new ReferenceTerm(selectTerm,
0289: "persistence_id"));
0290: expr.add("=");
0291: expr.add(new ConstantTerm(new Long(currentId)));
0292: expr.add("and");
0293: timeControl.apply(expr, selectTerm);
0294: for (int u = 0; u < selectTerm.getLeftTableTerms()
0295: .size(); u++) {
0296: expr.add("and");
0297: timeControl.applyToLeftTable(expr,
0298: (TableTerm) selectTerm
0299: .getLeftTableTerms().get(u));
0300: }
0301: QueryStatement referredStatement = new QueryStatement(
0302: selectTerm, expr, null);
0303: referredStatement.setStaticRepresentation("FIND "
0304: + selectClassEntry
0305: + " where persistence_id = " + currentId);
0306: referredStatement.setAllLeftTableTerms(selectTerm
0307: .getLeftTableTerms());
0308: referredStatement.setTimeControl(timeControl);
0309: SearchResult referredResult = find(transaction,
0310: referredStatement, null);
0311: if (referredResult.getResultSize() > 1) {
0312: throw new StoreException(
0313: "object's last state was ambigous, results for object id '"
0314: + currentId + "' was: "
0315: + referredResult.getResult());
0316: } else if (referredResult.getResultSize() == 1) {
0317: // Set nonchanged attributes from this last state of object
0318: nonchangedAttributes = (Map) referredResult
0319: .getResult().get(0);
0320: if (logger.isDebugEnabled())
0321: logger
0322: .debug("object nonchanged attributes: "
0323: + nonchangedAttributes);
0324: } else {
0325: // Object was not found, it most likely was deleted
0326: // so mark object as non-existent. This is a trick to
0327: // again save all attributes to database. For example,
0328: // when selecting an old instance, it is possible, the
0329: // instance is deleted in present time, so we must save
0330: // all attributes.
0331: context.getObjectTracker().makeUnexist(current);
0332: }
0333: } else {
0334: // Object does not exists, there are no nonchanged attributes
0335: logger
0336: .debug("object did not exist, no nonchanged attributes.");
0337: }
0338: // }}}
0339: // {{{ Assembling changed attributes
0340: List attributeNames = classInfo.getAttributeNames();
0341: logger
0342: .debug("class tracker reported object to save has following attributes: "
0343: + attributeNames
0344: + ", it has id: "
0345: + currentId
0346: + ", object tracker says it exists: "
0347: + context.getObjectTracker().exists(
0348: current));
0349: for (int i = 0; i < attributeNames.size(); i++) {
0350: String attributeName = (String) attributeNames
0351: .get(i);
0352: String attributeNameLowerCase = attributeName
0353: .toLowerCase();
0354: // Do not handle special attributes
0355: if (("persistence_id"
0356: .equals(attributeNameLowerCase))
0357: || ("persistenceid"
0358: .equals(attributeNameLowerCase))) {
0359: // We found a persistence id, fill it with id
0360: classInfo.setAttributeValue(current,
0361: attributeName, new Long(currentId));
0362: continue;
0363: }
0364: // Skip reserved prefix'd attributes
0365: if (attributeNameLowerCase
0366: .startsWith("persistence"))
0367: continue;
0368: // Handle non-special attributes
0369: Object attributeValue = classInfo
0370: .getAttributeValue(current, attributeName);
0371: logger.debug("saving attribute: " + attributeName
0372: + ", if changed.");
0373: if (context.getObjectTracker().hasChanged(
0374: classInfo, current, attributeName,
0375: nonchangedAttributes)) {
0376: // Current object's current attribute has changed,
0377: // so arrange it's save.
0378: // This is a switch for the type, this should be
0379: // replaced with something more adequate. For
0380: // example a type manager who will handle types
0381: // and is extensible.
0382: Class attributeType = classInfo
0383: .getAttributeType(attributeName);
0384: switch (context.getClassTracker().getType(
0385: attributeType)) {
0386: case ClassTracker.TYPE_PRIMITIVE:
0387: // Primitive type, just add to attributes
0388: changedAttributes.put(attributeName,
0389: attributeValue);
0390: // Set object tracker attribute
0391: updatedAttributes.put(attributeName,
0392: attributeValue);
0393: break;
0394: case ClassTracker.TYPE_HANDLED:
0395: logger.debug("changing handled attribute: "
0396: + attributeName + ", type: "
0397: + attributeType);
0398: Object oldValue = context
0399: .getObjectTracker()
0400: .getAttributeValue(current,
0401: attributeName);
0402: TypeHandler handler = context
0403: .getTypeHandlerTracker()
0404: .getHandler(attributeType);
0405: Object newValue = handler.save(classInfo,
0406: current, attributeName,
0407: transaction, currentSerial,
0408: oldValue, attributeValue,
0409: waitingObjects, events,
0410: changedAttributes,
0411: nonchangedAttributes);
0412: updatedAttributes.put(attributeName,
0413: newValue);
0414: if (newValue != null) {
0415: savedHandledObjects.add(handler);
0416: savedHandledObjects.add(newValue);
0417: }
0418: break;
0419: case ClassTracker.TYPE_OBJECT:
0420: logger
0421: .debug("attribute decided as custom object '"
0422: + attributeName
0423: + "', class was: "
0424: + classInfo
0425: .getAttributeType(attributeName));
0426: // Object type
0427: if (attributeValue == null) {
0428: // Object type, but null
0429: changedAttributes.put(attributeName,
0430: null);
0431: updatedAttributes.put(attributeName,
0432: null);
0433: } else {
0434: if (!context.getObjectTracker().exists(
0435: attributeValue)) {
0436: // Object referred does not exists, so it will
0437: // have to be created after we're done
0438: waitingObjects
0439: .add(context
0440: .getObjectTracker()
0441: .getWrapper(
0442: attributeValue));
0443: }
0444: context.getObjectTracker()
0445: .registerObject(attributeValue,
0446: 0);
0447: long objectId = context
0448: .getObjectTracker()
0449: .getIdentifier(attributeValue);
0450: // Got id, so give it to the man
0451: changedAttributes.put(attributeName,
0452: new Long(objectId));
0453: // Set object tracker attribute
0454: updatedAttributes.put(attributeName,
0455: new Long(objectId));
0456: }
0457: break;
0458: default:
0459: throw new StoreException(
0460: "unknown type in attribute: "
0461: + attributeName
0462: + ", value was: "
0463: + attributeValue);
0464: }
0465: }
0466: } // End of iterating over attributes
0467: // }}}
0468: // {{{ Saving the object's changed attributes
0469: // Now do the database thing on the assembled changed
0470: // attributes, since the waitingObjects list did not
0471: // change, meaning no new dependencies were discovered.
0472: // All changes are saved in multiple save/inserts according
0473: // to class structure.
0474: Iterator strictClassEntriesIterator = classInfo
0475: .getStrictClassEntries().iterator();
0476: while (strictClassEntriesIterator.hasNext()) {
0477: // Assemble changes for this strict class
0478: ClassEntry entry = (ClassEntry) strictClassEntriesIterator
0479: .next();
0480: HashMap strictChanges = new HashMap();
0481: HashMap strictNonChanges = new HashMap();
0482: List strictAttributeNames = classInfo
0483: .getStrictAttributeNames(entry);
0484: if (logger.isDebugEnabled())
0485: logger.debug("assembling changed for class: "
0486: + entry + ", strict attributes: "
0487: + strictAttributeNames);
0488: for (int i = 0; i < strictAttributeNames.size(); i++) {
0489: String attributeName = (String) strictAttributeNames
0490: .get(i);
0491: Class attributeType = classInfo
0492: .getAttributeType(attributeName);
0493: TypeHandler handler = context
0494: .getTypeHandlerTracker().getHandler(
0495: attributeType);
0496: if (handler == null) {
0497: // No handler, this is a simple attribute
0498: if (changedAttributes
0499: .containsKey(attributeName))
0500: strictChanges.put(attributeName,
0501: changedAttributes
0502: .remove(attributeName));
0503: if (nonchangedAttributes
0504: .containsKey(attributeName))
0505: strictNonChanges.put(attributeName,
0506: nonchangedAttributes
0507: .remove(attributeName));
0508: } else {
0509: // Got handler, then iterate on it's attributes
0510: Map embeddedAttributes = handler
0511: .getAttributeTypes(attributeName);
0512: if (logger.isDebugEnabled())
0513: logger
0514: .debug("attribute: "
0515: + attributeName
0516: + ", was an embedded attribute, adding: "
0517: + embeddedAttributes);
0518: Iterator embeddedAttributeNamesIterator = embeddedAttributes
0519: .keySet().iterator();
0520: while (embeddedAttributeNamesIterator
0521: .hasNext()) {
0522: attributeName = (String) embeddedAttributeNamesIterator
0523: .next();
0524: if (changedAttributes
0525: .containsKey(attributeName))
0526: strictChanges
0527: .put(
0528: attributeName,
0529: changedAttributes
0530: .remove(attributeName));
0531: if (nonchangedAttributes
0532: .containsKey(attributeName))
0533: strictNonChanges
0534: .put(
0535: attributeName,
0536: nonchangedAttributes
0537: .remove(attributeName));
0538: }
0539: }
0540: }
0541: // Make changes
0542: if (context.getObjectTracker().exists(current)) {
0543: // Save
0544: logger
0545: .debug("changing object with following attributes: "
0546: + strictChanges
0547: + ", not changed: "
0548: + strictNonChanges);
0549: if (strictChanges.size() > 0) {
0550: // First set enddate on used entry
0551: HashMap removeChanges = new HashMap();
0552: HashMap keys = new HashMap();
0553: keys.put("persistence_id", new Long(
0554: currentId));
0555: keys.put("persistence_end", new Long(
0556: DateSerialUtil.getMaxSerial()));
0557: keys.put("persistence_txend", new Long(
0558: DateSerialUtil.getMaxSerial()));
0559: removeChanges.put("persistence_txend",
0560: currentSerial);
0561: removeChanges.put("persistence_txendid",
0562: currentTxSerial);
0563: context.getDatabase().save(transaction,
0564: classInfo.getTableName(entry),
0565: keys, removeChanges);
0566: transaction.addRemoveTable(classInfo
0567: .getTableName(entry));
0568: // Create new entry
0569: strictNonChanges.putAll(strictChanges);
0570: strictNonChanges.put("persistence_id",
0571: new Long(currentId));
0572: strictNonChanges.put("persistence_start",
0573: new Long(DateSerialUtil
0574: .getMaxSerial()));
0575: strictNonChanges.put("persistence_end",
0576: new Long(DateSerialUtil
0577: .getMaxSerial()));
0578: strictNonChanges.put("persistence_txendid",
0579: new Long(0));
0580: strictNonChanges.put("persistence_txstart",
0581: currentSerial);
0582: strictNonChanges.put(
0583: "persistence_txstartid",
0584: currentTxSerial);
0585: strictNonChanges.put("persistence_txend",
0586: new Long(DateSerialUtil
0587: .getMaxSerial()));
0588: context.getDatabase().insert(transaction,
0589: classInfo.getTableName(entry),
0590: strictNonChanges);
0591: // Add to modified tables list
0592: transaction.addSaveTable(classInfo
0593: .getTableName(entry));
0594: }
0595: } else {
0596: // Insert
0597: logger
0598: .debug("inserting object with following attributes: "
0599: + strictChanges);
0600: strictChanges.put("persistence_id", new Long(
0601: currentId));
0602: strictChanges
0603: .put("persistence_start", new Long(
0604: DateSerialUtil.getMaxSerial()));
0605: strictChanges.put("persistence_end", new Long(
0606: DateSerialUtil.getMaxSerial()));
0607: strictChanges.put("persistence_txendid",
0608: new Long(0));
0609: strictChanges.put("persistence_txstart",
0610: currentSerial);
0611: strictChanges.put("persistence_txstartid",
0612: currentTxSerial);
0613: strictChanges
0614: .put("persistence_txend", new Long(
0615: DateSerialUtil.getMaxSerial()));
0616: context.getDatabase().insert(transaction,
0617: classInfo.getTableName(entry),
0618: strictChanges);
0619: // Add to modified tables list
0620: transaction.addSaveTable(classInfo
0621: .getTableName(entry));
0622: }
0623: }
0624: if (changedAttributes.size() != 0)
0625: logger
0626: .warn("there are attributes that do not belong in any superclass of object to be saved, classinfo: "
0627: + classInfo
0628: + ", attributes: "
0629: + changedAttributes);
0630: logger.debug("saving object with id: " + currentId
0631: + " updating meta-data.");
0632: // Remove object from waiting list
0633: waitingObjects.remove(currentWrapper);
0634: // Notify event listeners
0635: if (context.getObjectTracker().exists(current)) {
0636: // Modify
0637: events.add(new ModifyObjectEvent(current));
0638: } else {
0639: // Create
0640: events.add(new CreateObjectEvent(current));
0641: }
0642: // Update object tracker
0643: context.getObjectTracker().updateObject(current,
0644: updatedAttributes);
0645: context.getObjectTracker().makeExist(current); // Exists for this transaction
0646: // }}}
0647: logger.debug("saving object with id: " + currentId
0648: + " finished.");
0649: }
0650: // {{{ Go through handled objects, and notify that save ended
0651: for (int i = 0; i < savedHandledObjects.size();) {
0652: TypeHandler handler = (TypeHandler) savedHandledObjects
0653: .get(i++);
0654: Object value = (Object) savedHandledObjects.get(i++);
0655: handler.postSave(value);
0656: }
0657: // }}}
0658: // {{{ Notify dispatcher of events occured during save
0659: for (int i = 0; i < events.size(); i++)
0660: context.getEventDispatcher().notify(
0661: (PersistenceEvent) events.get(i));
0662: // }}}
0663: // "Happy, happy, joy, joy". Object saved.
0664: } catch (StoreException e) {
0665: transaction.rollback();
0666: logger.error("throwing store exception", e);
0667: throw e;
0668: } catch (Throwable e) {
0669: transaction.rollback();
0670: logger.error("throwing unexpected exception", e);
0671: throw new StoreException("unexpected exception", e);
0672: }
0673: transaction.commit();
0674: /// }}}
0675: }
0676:
0677: /**
0678: * Remove the object given. If the object is not stored yet, no
0679: * operation will take place.
0680: * @param obj The object to remove.
0681: * @throws StoreException If remove is not successfull.
0682: */
0683: public void remove(Object obj) {
0684: // {{{ Remove object
0685: // Transaction
0686: Transaction transaction = context.getTransactionTracker()
0687: .getTransaction(TransactionTracker.TX_REQUIRED);
0688: transaction.begin();
0689: transaction.setLastOperation("removing object: " + obj);
0690: try {
0691: // Check id. If id is not given, this object does not exists,
0692: // do nothing.
0693: long id = context.getObjectTracker().getIdentifier(obj);
0694: if (id == 0)
0695: return;
0696: Long currentSerial = context.getNodeManager()
0697: .getNextSerial();
0698: Long currentTxSerial = transaction.getSerial(currentSerial);
0699: // Lock object
0700: context.getLockTracker().lock(obj);
0701: transaction.addRemovedObject(context.getObjectTracker()
0702: .getWrapper(obj));
0703: // Get class info and assemble attributes identifying
0704: // the object
0705: ClassInfo classInfo = context.getClassTracker()
0706: .getClassInfo(obj.getClass(), obj);
0707: Map removeChanges = new HashMap();
0708: logger.debug("remove called, using serial: "
0709: + currentSerial);
0710: removeChanges.put("persistence_txend", currentSerial);
0711: removeChanges.put("persistence_txendid", currentTxSerial);
0712: Map keys = new HashMap();
0713: keys.put("persistence_id", new Long(id));
0714: keys.put("persistence_end", new Long(DateSerialUtil
0715: .getMaxSerial()));
0716: keys.put("persistence_txend", new Long(DateSerialUtil
0717: .getMaxSerial()));
0718: // Execute remove on class and all superclasses, et voila'
0719: for (int i = 0; i < classInfo.getStrictClassEntries()
0720: .size(); i++) {
0721: ClassEntry entry = (ClassEntry) classInfo
0722: .getStrictClassEntries().get(i);
0723: context.getDatabase().save(transaction,
0724: classInfo.getTableName(entry), keys,
0725: removeChanges);
0726: // Add changed table
0727: transaction.addRemoveTable(classInfo
0728: .getTableName(entry));
0729: }
0730: // Notify event listeners
0731: context.getEventDispatcher().notify(
0732: new DeleteObjectEvent(obj));
0733: } catch (StoreException e) {
0734: transaction.rollback();
0735: logger.error("throwing store exception", e);
0736: throw e;
0737: } catch (Throwable e) {
0738: transaction.rollback();
0739: logger.error("throwing unexpected exception", e);
0740: throw new StoreException("unexpected exception", e);
0741: }
0742: transaction.commit();
0743: /// }}}
0744: }
0745:
0746: /**
0747: * Query an object from the datastore. The List returned is
0748: * a lazy list, the implementation tries to limit the communication
0749: * with the database layer, as much as possible and pratical. Only
0750: * parts of the list will be loaded when an item is referenced, not
0751: * the whole list. Some features of the query language:<br>
0752: * <pre>find book where book.name='Snow Crash'</pre>
0753: * The statement always starts with the keyword 'find', and all
0754: * keywords and parts of the statement not between apostrophs are
0755: * case in-sensitive.<br>
0756: * The second word of the statement determines
0757: * the class you are trying to find. You can abbreviate the classname
0758: * (strip the package) if it is unique, but you can use the full
0759: * name (com.acme.book) if you wish, but then you MUST provide an
0760: * alias name (see below)<br>
0761: * The following parts are all optional. First, there can be
0762: * a select statement, to specify which objects to select which are
0763: * instances of given class. If you want to have a where part, the
0764: * third word should be 'where'. After it there should be an
0765: * expression almost as in SQL. Parts of the expression can be:<br>
0766: * <ul>
0767: * <li><strong>Member attributes of classes.</strong> The class given previously
0768: * (the target of statement) is always available as declared. If
0769: * you wish to use other classes, they can be referenced by
0770: * class (abbreviated or fully declared). For example
0771: * <pre>find book where book.author=author and author.birthdate=1959</pre>
0772: * Of course Book, and Author classes must exits in the store with
0773: * given attributes.
0774: * Note also, that the above statement can be simply written:
0775: * <pre>find book where book.author.birthdate=1959</pre>
0776: * You can name the classes you are referencing for later
0777: * use (handy if you must "join" the class with itself):
0778: * <pre>find book where book.author=otherbook(book).author and otherbook.name='Snow Crash'</pre>
0779: * Here, the "otherbook" does not refer to a class, it is only another
0780: * name for the class book, and means, that is should be not the same
0781: * instance as the one referenced with "book".
0782: * </li>
0783: * <li><strong>"Now" constant</strong>: For easy use the current date/time
0784: * is represented with the special word 'now'. So you can write:
0785: * <pre>find movie where movie.startdate > now</pre>
0786: * This also means, that attributes named 'now' must be escaped (prefixed
0787: * with it's table name).
0788: * <li><strong>Constants</strong>: Numbers and strings, see examples above.
0789: * Dates and objects can be given by using the question mark (?), and adding the object
0790: * as parameters (same as in jdbc).</li>
0791: * <li><strong>The operators</strong>: <, >, =, !=, like, is null, not null, is not null.
0792: * Note however, that not all database backends are required to support
0793: * all of these. If they are not supported, an exception will be thrown.</li>
0794: * <li><strong>Logical operators</strong>: or, and, not. See examples above.</li>
0795: * <li><strong>Grouping with parenthesis.</strong> As usual in expressions,
0796: * you can use grouping:
0797: * <pre>find book where ((book.author.firstname='Neal') and (book.author.lastname='Stephenson'))</pre>
0798: * </li>
0799: * <li><strong>Special container operator</strong>: contains.
0800: * These are used in conjunction with container types such as Map and List:
0801: * <pre>find book where book.genres contains genre and genre.name='postcyberpunk'</pre>
0802: * <pre>find author where author.books contains book and book.name='Snow Crash'</pre>
0803: * A container operator can not be negated, an exception will be thrown,
0804: * if the expression would try to do that.
0805: * </li>
0806: * <li><strong>Special map operator</strong>: [, ]. These are used when
0807: * referencing a Map. Note also, that [] can only contain strings.
0808: * <pre>find book where book.metadata['author']=author and author.name='Neal Stephenson</pre>
0809: * However, if after a map operator an attribute is referenced,
0810: * there is a mandatory class specifier:
0811: * <pre>find book where book.metadata['author'](author).name='Neal Stephenson</pre>
0812: * </li>
0813: * </ul><br>
0814: * You can also sort the result list with the 'order by' command:
0815: * <pre>find book order by book.name asc</pre>
0816: * The order by command takes attributes as aguments.
0817: * You can give more than one attribute separated by commas.
0818: * Also you can append 'asc' (ascending) or 'desc' (descending) to
0819: * mark the direction of sort.
0820: * <pre>find book order by book.author.name asc, book.name desc</pre>
0821: * Note, that method will silently return an empty list, if the
0822: * specified table or one specified in where clause does not exist.<br>
0823: * For more detailed information, check the documentation.
0824: * @param statement The query statement to select.
0825: */
0826: public List find(String statement) {
0827: return find(statement, null, null, null);
0828: }
0829:
0830: /**
0831: * Same as <code>find(statement)</code>. When a statement contains
0832: * the question mark (?), the object which should be in the place of
0833: * the mark should be given as parameters (parameters are usually of
0834: * Date, or custom classes).
0835: * @param statement The query statement to execute.
0836: * @param parameters The parameters.
0837: */
0838: public List find(String statement, Object[] parameters) {
0839: return find(statement, parameters, null, null);
0840: }
0841:
0842: /**
0843: * This method in addition to all usual parameters can define the
0844: * exact time of the query with the timeControl parameter. If the
0845: * query is a historical query, this control will be overridden.
0846: * @param statement The query statement to execute.
0847: * @param parameters The parameters.
0848: * @param timeControl The exact default time of the query.
0849: */
0850: List find(String statement, Object[] parameters,
0851: TimeControl timeControl, Map unmarshalledObjects) {
0852: // {{{ Parse statement, and create result list
0853: // Convert object parameters. If they contain
0854: // objects, substitute with object id
0855: Transaction transaction = context.getTransactionTracker()
0856: .getTransaction(TransactionTracker.TX_REQUIRED);
0857: transaction.begin();
0858: transaction.setLastOperation(statement);
0859: try {
0860: Object[] realParameters = null;
0861: if (parameters != null) {
0862: realParameters = new Object[parameters.length];
0863: for (int i = 0; i < parameters.length; i++) {
0864: if (parameters[i] == null) {
0865: // Handle null parameters
0866: realParameters[i] = null;
0867: } else {
0868: // If parameter is not null, translate object parameters
0869: // to persistent id, leave others
0870: int type = context.getClassTracker().getType(
0871: parameters[i].getClass());
0872: if (type == ClassTracker.TYPE_RESERVED)
0873: throw new StoreException(
0874: "parameter at position: "
0875: + i
0876: + ", value: "
0877: + parameters[i]
0878: + " is of unsupported type.");
0879: if ((type == ClassTracker.TYPE_OBJECT)
0880: && (!(parameters[i] instanceof Collection))) {
0881: // If object has no id, that's not a problem. It will
0882: // receive an id of 0, and no object should match
0883: // that id anyway.
0884: realParameters[i] = new Identifier(
0885: new Long(context.getObjectTracker()
0886: .getIdentifier(
0887: parameters[i])));
0888: } else if (parameters[i] instanceof Collection) {
0889: // Check, whether the collection's items are objects,
0890: // in which case translate them to their ids
0891: Vector result = new Vector();
0892: Iterator itemIterator = ((Collection) parameters[i])
0893: .iterator();
0894: while (itemIterator.hasNext()) {
0895: Object item = itemIterator.next();
0896: if (context.getClassTracker().getType(
0897: item.getClass()) == ClassTracker.TYPE_OBJECT)
0898: result.add(new Long(context
0899: .getObjectTracker()
0900: .getIdentifier(item)));
0901: else
0902: result.add(item);
0903: }
0904: realParameters[i] = result;
0905: } else {
0906: realParameters[i] = parameters[i];
0907: }
0908: }
0909: if (logger.isDebugEnabled())
0910: logger.debug("parameter: " + parameters[i]
0911: + " -> real parameter #" + i + ":"
0912: + realParameters[i]);
0913: }
0914: }
0915: // Process it, get expression
0916: QueryStatementList stmts = null;
0917: try {
0918: // Only apply in-transaction search conditions if there is a transaction
0919: // and something changed during transaction. Only calculate if
0920: // no 'default default' is given.
0921: HashSet modifiedTables = new HashSet();
0922: modifiedTables.addAll(transaction.getSaveTables());
0923: modifiedTables.addAll(transaction.getRemoveTables());
0924: if (timeControl == null) {
0925: Long serial = context.getNodeManager()
0926: .getNextSerial();
0927: Long txSerial = transaction.getSerial(serial);
0928: timeControl = new TimeControl(serial, txSerial,
0929: modifiedTables.size() > 0);
0930: }
0931: // Parse statement
0932: if (logger.isDebugEnabled())
0933: logger.debug("executing parser, serial: "
0934: + timeControl.getSerial() + ", tx serial: "
0935: + timeControl.getTxSerial());
0936: stmts = Parser.parse(statement, new WhereResolver(
0937: context), realParameters, timeControl,
0938: modifiedTables);
0939: // Wait now for all commits() before the given serial to finish.
0940: // If this would not be the case, the lazy list might not
0941: // contain the data from previously initiated commits. Which
0942: // would mean, once those finished, the lazy list would change.
0943: context.getNodeManager().waitForQuery(
0944: timeControl.getSerial());
0945: } catch (ParserException e) {
0946: if (e.getCode() == ParserException.ABORT) {
0947: logger
0948: .error("aborting query, because of parser exception.");
0949: throw new StoreException(e.getMessage(), e);
0950: } else {
0951: logger
0952: .info("returning empty result list because of non-fatal symbol error. Parser said: "
0953: + e.getMessage()
0954: + ", statement was: " + statement);
0955: return new LinkedList(); // Return empty list on non-fatal symbol errors
0956: }
0957: } catch (StoreException e) {
0958: throw e;
0959: } catch (Exception e) {
0960: throw new StoreException(
0961: "unknown exception while select", e);
0962: }
0963: // Return list
0964: return new LazyList(context, stmts, unmarshalledObjects);
0965: } catch (StoreException e) {
0966: transaction.markRollbackOnly();
0967: throw e;
0968: } catch (Throwable e) {
0969: transaction.markRollbackOnly();
0970: throw new StoreException("unexpected exception", e);
0971: } finally {
0972: transaction.commit();
0973: }
0974: // }}}
0975: }
0976:
0977: /**
0978: * Same as <code>find(statement,parameters)</code>, but the result should be
0979: * a single object.
0980: * @param statement The query statement to execute.
0981: * @param parameters The parameters to the statement.
0982: * @return The object selected, or null if no such object exists. If
0983: * the result contains more objects, an arbitrary one is selected.
0984: */
0985: public Object findSingle(String statement, Object[] parameters) {
0986: // {{{ Call normal find and evaluate result
0987: Iterator iterator = find(statement, parameters).iterator();
0988: if (iterator.hasNext())
0989: return iterator.next();
0990: return null;
0991: // }}}
0992: }
0993:
0994: /**
0995: * Same as <code>find(statement)</code>, but the result should be
0996: * a single object.
0997: * @param statement The query statement to execute.
0998: * @return The object selected, or null if no such object exists. If
0999: * the result contains more objects, an arbitrary one is selected.
1000: */
1001: public Object findSingle(String statement) {
1002: return findSingle(statement, null);
1003: }
1004:
1005: /**
1006: * Internal raw loading. All finder methods sooner or later call into
1007: * this method to get real results in form of attribute name-value maps.
1008: */
1009: SearchResult find(Transaction transaction, QueryStatement stmt,
1010: Limits limits) {
1011: // {{{ Do physical query
1012: // First, check if statement is visible from this transaction
1013: TimeControl timeControl = stmt.getTimeControl();
1014: if ((timeControl.isApplyTransaction())
1015: && (timeControl.getTxSerial() != null)
1016: && (!timeControl.getTxSerial().equals(
1017: transaction.getSerial()))
1018: && (context.getTransactionTracker()
1019: .hasOpenTransaction(timeControl.getTxSerial())))
1020: throw new StoreException(
1021: "tried to do a query which was outside it's transaction '"
1022: + timeControl.getTxSerial()
1023: + "', which was still open. Current tx was: "
1024: + transaction.getSerial());
1025: // Get resultset from cache or database
1026: SearchResult result = context.getCache().getEntry(stmt, limits);
1027: if (result == null) {
1028: result = context.getDatabase().search(transaction, stmt,
1029: limits);
1030: context.getCache().addEntry(stmt, limits, result);
1031: }
1032: return result;
1033: // }}}
1034: }
1035:
1036: /**
1037: * Unmarshall an object. This means create an object of given class
1038: * and set attributes from a given map of attributes. All referred
1039: * objects are assumed to be in the already allocated list, indexed
1040: * by object id.
1041: * @param classInfo The class info of the object that needs to be instantiated.
1042: * @param marshalledValues The attributes values.
1043: * @param unmarshalledObjects The already unmarshalled objects.
1044: */
1045: private Object unmarshallObject(ClassInfo classInfo,
1046: Map marshalledValues, Map unmarshalledObjects,
1047: Map missingAttributes, QueryStatement stmt)
1048: throws InstantiationException, IllegalAccessException {
1049: // {{{ Umarshall object
1050: Object obj = classInfo.newInstance(marshalledValues);
1051: if (obj == null)
1052: return null;
1053: // Important! Register object into tracker!
1054: // Note: all attributes are updated into the object tracker.
1055: // Previously this was not done, because object's had a shared
1056: // state (instances of the same database row), but now no shared
1057: // state exists (at least, not with attributes).
1058: context.getObjectTracker().registerObject(
1059: obj,
1060: ((Long) marshalledValues.get("persistence_id"))
1061: .longValue());
1062: context.getObjectTracker().updateObject(obj, marshalledValues);
1063: PersistenceMetaData metaData = context.getObjectTracker()
1064: .getMetaData(obj);
1065: metaData.setPersistenceStart(((Long) marshalledValues
1066: .get("persistence_start")));
1067: metaData.setPersistenceEnd(((Long) marshalledValues
1068: .get("persistence_end")));
1069: metaData.setQuerySerial(stmt.getTimeControl().getSerial());
1070: unmarshalledObjects.put(marshalledValues.get("persistence_id"),
1071: obj);
1072: // Set properties in obj, go through object attributes
1073: // (except is the object is of primitive type)
1074: List attributeNames = classInfo.getAttributeNames();
1075: for (int o = 0; (o < attributeNames.size())
1076: && (!classInfo.getSourceEntry().isPrimitive()); o++) {
1077: String attributeName = attributeNames.get(o).toString();
1078: // Handle peristence_id specially. If this is an attribute
1079: // named something like persistenceId, then fill in the id.
1080: if (("persistence_id".equalsIgnoreCase(attributeName))
1081: || ("persistenceid".equalsIgnoreCase(attributeName))) {
1082: // Fill attribute with persistence id
1083: classInfo.setAttributeValue(obj, attributeName,
1084: marshalledValues.get("persistence_id"));
1085: continue;
1086: }
1087: // Handle other (normal) attributes
1088: Object attributeValue = marshalledValues.get(attributeName
1089: .toLowerCase());
1090: if (logger.isDebugEnabled())
1091: logger.debug("setting object property: "
1092: + attributeName + ", value: " + attributeValue);
1093: Class attributeClass = classInfo
1094: .getAttributeType(attributeName);
1095: if (attributeClass == null) {
1096: logger.error("object property '" + attributeName
1097: + "' cannot set, object has no such property.");
1098: continue;
1099: }
1100: switch (context.getClassTracker().getType(attributeClass)) {
1101: case ClassTracker.TYPE_PRIMITIVE:
1102: classInfo.setAttributeValue(obj, attributeName,
1103: attributeValue);
1104: break;
1105: case ClassTracker.TYPE_HANDLED:
1106: TypeHandler handler = context.getTypeHandlerTracker()
1107: .getHandler(attributeClass);
1108: Object value = handler.unmarshallType(classInfo, obj,
1109: attributeName, marshalledValues, stmt
1110: .getTimeControl());
1111: classInfo.setAttributeValue(obj, attributeName, value);
1112: context.getObjectTracker().updateAttribute(obj,
1113: attributeName, value);
1114: break;
1115: case ClassTracker.TYPE_OBJECT:
1116: // Handle null
1117: if (attributeValue == null) {
1118: classInfo.setAttributeValue(obj, attributeName,
1119: null);
1120: break;
1121: }
1122: // Get object from list
1123: Object relatedObj = unmarshalledObjects
1124: .get(attributeValue);
1125: classInfo.setAttributeValue(obj, attributeName,
1126: relatedObj);
1127: if (relatedObj == null) {
1128: if (logger.isDebugEnabled())
1129: logger
1130: .debug("referred object of id: "
1131: + attributeValue
1132: + " not found, currently unmarshalled objects: "
1133: + unmarshalledObjects);
1134: // Remember with ids entry. An entry holds all necessary
1135: // information for a single attribute to reconstruct
1136: // it's objects for all referrers:
1137: // - classinfo: of attribute declared type (for select)
1138: // - ids: all ids of referred objects
1139: // - objects: indexed by referred object id, hold a set
1140: // of objects which need the specified referred object
1141: IdsEntry idsEntry = (IdsEntry) missingAttributes
1142: .get(attributeName);
1143: if (idsEntry == null) {
1144: idsEntry = new IdsEntry(new HashMap(),
1145: new HashSet(), classInfo);
1146: missingAttributes.put(attributeName, idsEntry);
1147: }
1148: idsEntry.ids.add(attributeValue);
1149: List objectsSet = (List) idsEntry.objects
1150: .get(attributeValue);
1151: if (objectsSet == null) {
1152: objectsSet = new Vector();
1153: idsEntry.objects
1154: .put(attributeValue, objectsSet);
1155: }
1156: objectsSet.add(obj);
1157: }
1158: break;
1159: default:
1160: throw new StoreException("attribute: " + attributeName
1161: + "'s type was not valid.");
1162: }
1163: } // Iteration over attributes
1164: return obj;
1165: // }}}
1166: }
1167:
1168: /**
1169: * Internal loading.
1170: */
1171: SearchResult find(QueryStatement rawStmt, Limits limits,
1172: Map unmarshalledObjects) {
1173: // {{{ Internal loading code
1174: if (unmarshalledObjects == null)
1175: unmarshalledObjects = new HashMap();
1176: Transaction transaction = context.getTransactionTracker()
1177: .getTransaction(TransactionTracker.TX_REQUIRED);
1178: transaction.begin();
1179: logger.debug("called store internal find.");
1180: try {
1181: // If the query is a view query, then return raw map format.
1182: // Else get the main term.
1183: if (rawStmt.getMode() == QueryStatement.MODE_VIEW)
1184: return find(transaction, rawStmt, limits);
1185: TableTerm mainTerm = (TableTerm) rawStmt.getSelectTerms()
1186: .get(0);
1187: // Modify the query statement, so the statement should return
1188: // the object's table in question (so the object instance can be
1189: // created)
1190: ClassInfo classInfo = context.getClassTracker()
1191: .getTableClassInfo(mainTerm.getTableName());
1192: if (classInfo == null)
1193: throw new StoreException(
1194: "no class found for table name: "
1195: + mainTerm.getTableName());
1196: QueryStatement stmt = new QueryStatement(rawStmt);
1197: if (logger.isDebugEnabled())
1198: logger.debug("main term is: " + mainTerm
1199: + ", left terms: "
1200: + mainTerm.getLeftTableTerms());
1201: if (mainTerm.getLeftTableTerms().size() > 0) {
1202: // Modify main term to include object table information for
1203: // un-marshalling
1204: Vector mainTerms = new Vector();
1205: TableTerm mainTermCopy = new TableTerm(mainTerm
1206: .getTableName(), mainTerm.getAlias(),
1207: new Vector(mainTerm.getLeftTableTerms()));
1208: mainTermCopy.getLeftTableTerms().add(
1209: new TableTerm("persistence_object_ids", null));
1210: mainTerms.add(mainTermCopy);
1211: mainTerms.addAll(stmt.getSelectTerms().subList(1,
1212: stmt.getSelectTerms().size()));
1213: stmt.setSelectTerms(mainTerms);
1214: stmt.setStaticRepresentation(stmt
1215: .getStaticRepresentation()
1216: + "-ids");
1217: }
1218: // Run the real database search
1219: logger.debug("find running real select statement.");
1220: SearchResult rawResult = find(transaction, stmt, limits);
1221: // Take the raw data and umarshall them into objects
1222: logger.debug("find unmarshalling objects.");
1223: HashMap missingAttributes = new HashMap();
1224: SearchResult cookedResult = new SearchResult();
1225: cookedResult.setResultSize(rawResult.getResultSize());
1226: Vector cookedResultList = new Vector();
1227: cookedResult.setResult(cookedResultList);
1228: for (int i = 0; i < rawResult.getResult().size(); i++) {
1229: // Get values
1230: Map marshalledValues = (Map) rawResult.getResult().get(
1231: i);
1232: // If object already unmarshalled, then get from list,
1233: // else instantiate
1234: Object obj = unmarshalledObjects.get(marshalledValues
1235: .get("persistence_id"));
1236: if (obj == null) {
1237: if (logger.isDebugEnabled())
1238: logger.debug("got marshalled values: "
1239: + marshalledValues);
1240: // Instantiate and unmarshall object
1241: ClassInfo localClassInfo = classInfo;
1242: if (mainTerm.getLeftTableTerms().size() > 0) {
1243: // If there were left join tables, try to get the
1244: // correct class. This means, that the object
1245: // can be the subclass of queried class, and not
1246: // exactly that. We can determine the exact class
1247: // from the table name.
1248: String objectTable = (String) marshalledValues
1249: .get("object_table");
1250: localClassInfo = context.getClassTracker()
1251: .getTableClassInfo(objectTable);
1252: }
1253: // Unmarshall, with the exact class info given
1254: obj = unmarshallObject(localClassInfo,
1255: marshalledValues, unmarshalledObjects,
1256: missingAttributes, rawStmt);
1257: }
1258: // Add object to result list. The 'obj' is an umarshalled full
1259: // object, which is gethered from the main table of the query.
1260: // How ever, if there are other referenced attributes the
1261: // query should return, then we do the whole thing into a Map.
1262: // The object itself will have the key 'object' in this case.
1263: if (rawStmt.getSelectTerms().size() > 1) {
1264: // There are other attributes the caller wants, so
1265: // do the whole thing into a Map. Insert all wanted attributes,
1266: // and the unmarshalled main object too.
1267: HashMap resultObj = new HashMap();
1268: for (int o = 1; o < rawStmt.getSelectTerms().size(); o++) {
1269: ReferenceTerm refTerm = (ReferenceTerm) rawStmt
1270: .getSelectTerms().get(o);
1271: resultObj.put(refTerm.getColumnFinalName(),
1272: marshalledValues.get(refTerm
1273: .getColumnFinalName()
1274: .toLowerCase()));
1275: }
1276: resultObj.put("object", obj);
1277: cookedResultList.add(resultObj);
1278: } else {
1279: // There was just the object, so insert it into the result
1280: // list, just in itself.
1281: cookedResultList.add(obj);
1282: }
1283: }
1284: // Load all referred objects for all classes which were unmarshalled,
1285: // the missing list was assembled in the unmarshall code.
1286: Iterator missingAttributesIterator = missingAttributes
1287: .entrySet().iterator();
1288: while (missingAttributesIterator.hasNext()) {
1289: // Get all necessary meta-data
1290: Map.Entry entry = (Map.Entry) missingAttributesIterator
1291: .next();
1292: String attributeName = entry.getKey().toString();
1293: IdsEntry idsEntry = (IdsEntry) entry.getValue();
1294: Class selectClass = idsEntry.classInfo
1295: .getAttributeType(attributeName);
1296: // We got the class of the object, so we select all objects to
1297: // this attribute into a map keyed with the persistence id.
1298: HashMap referredObjects = new HashMap();
1299: if (logger.isDebugEnabled())
1300: logger.debug("getting member attribute: "
1301: + selectClass + ", for ids: "
1302: + idsEntry.ids);
1303: List referredObjectList = find(
1304: "find member(" + selectClass.getName()
1305: + ") where member in ?",
1306: new Object[] { idsEntry.ids }, null,
1307: unmarshalledObjects);
1308: for (int i = 0; i < referredObjectList.size(); i++) {
1309: Object referredObject = referredObjectList.get(i);
1310: referredObjects.put(new Long(context
1311: .getObjectTracker().getIdentifier(
1312: referredObject)), referredObject);
1313: }
1314: // Now fill in this attribute with the ready referred objects
1315: Iterator objectEntryIterator = idsEntry.objects
1316: .entrySet().iterator();
1317: while (objectEntryIterator.hasNext()) {
1318: Map.Entry objectEntry = (Map.Entry) objectEntryIterator
1319: .next();
1320: Object referredObject = referredObjects
1321: .get(objectEntry.getKey());
1322: // Now set to all referring objects
1323: Iterator objectIterator = ((List) objectEntry
1324: .getValue()).iterator();
1325: while (objectIterator.hasNext()) {
1326: Object referrerObject = objectIterator.next();
1327: ClassInfo referrerClassInfo = context
1328: .getClassTracker().getClassInfo(
1329: referrerObject.getClass(),
1330: referrerObject);
1331: referrerClassInfo.setAttributeValue(
1332: referrerObject, attributeName,
1333: referredObject);
1334: }
1335: }
1336: }
1337: // Return result
1338: logger.debug("find returning result list");
1339: return cookedResult;
1340: } catch (StoreException e) {
1341: transaction.markRollbackOnly();
1342: logger.error("throwing store exception", e);
1343: throw e;
1344: } catch (Exception e) {
1345: transaction.markRollbackOnly();
1346: logger.error("throwing unexpected exception", e);
1347: throw new StoreException("unexpected exception", e);
1348: } finally {
1349: transaction.commit();
1350: }
1351: // }}}
1352: }
1353:
1354: /**
1355: * Unlock all objects in transaction.
1356: */
1357: private void unlockObjects(Transaction transaction) {
1358: // {{{ Unlock all objects in transaction
1359: List removedObjects = transaction.getRemovedObjects();
1360: for (int i = 0; i < removedObjects.size(); i++)
1361: context.getLockTracker().unlock(
1362: ((ObjectTracker.ObjectWrapper) removedObjects
1363: .get(i)).getObject());
1364: List addedObjects = transaction.getSavedObjects();
1365: for (int i = 0; i < addedObjects.size(); i++)
1366: context.getLockTracker().unlock(
1367: ((ObjectTracker.ObjectWrapper) addedObjects.get(i))
1368: .getObject());
1369: // }}}
1370: }
1371:
1372: /**
1373: * Notify server of all objects that changed. Server keeps track of the last modifications
1374: * to know which object versions are current. To do this, we notify the server, that
1375: * this transaction is about to be commited. The object modifications will be finalized,
1376: * if the server receives the endCommit() call. If that event fails to be delivered,
1377: * the server will mark the changes as unknown, and will check from the database to be
1378: * sure.
1379: */
1380: private void notifyServerOfChanges(Transaction transaction) {
1381: // {{{ Notify server of object changes inside transaction
1382: Vector objects = new Vector();
1383: objects.addAll(transaction.getRemovedObjects());
1384: objects.addAll(transaction.getSavedObjects());
1385: List metas = new Vector();
1386: for (int i = 0; i < objects.size(); i++)
1387: metas
1388: .add(getPersistenceMetaData(((ObjectTracker.ObjectWrapper) objects
1389: .get(i)).getObject()));
1390: context.getNodeManager().notifyChange(metas,
1391: transaction.getSerial(), transaction.getEndSerial());
1392: // }}}
1393: }
1394:
1395: /**
1396: * Modify object tracker's metadata for the transaction's changes.
1397: */
1398: private void modifyMetaData(Transaction transaction) {
1399: // {{{ Modify transaction objects' metadata
1400: for (int i = 0; i < transaction.getRemovedObjects().size(); i++) {
1401: Object obj = ((ObjectTracker.ObjectWrapper) transaction
1402: .getRemovedObjects().get(i)).getObject();
1403: PersistenceMetaData metaData = getPersistenceMetaData(obj);
1404: metaData.setPersistenceEnd(transaction.getEndSerial());
1405: }
1406: for (int i = 0; i < transaction.getSavedObjects().size(); i++) {
1407: Object obj = ((ObjectTracker.ObjectWrapper) transaction
1408: .getSavedObjects().get(i)).getObject();
1409: PersistenceMetaData metaData = getPersistenceMetaData(obj);
1410: metaData.setQuerySerial(transaction.getEndSerial());
1411: metaData.setPersistenceStart(transaction.getEndSerial());
1412: // If object was saved, it also was removed if it existed, so
1413: // if the object still exists, clear the end serial
1414: if (context.getObjectTracker().exists(obj))
1415: metaData.setPersistenceEnd(null);
1416: }
1417: // }}}
1418: }
1419:
1420: /**
1421: * Rollback a transaction. Rollback has to only unlock objects
1422: * the transaction carries, and call the database rollback.
1423: */
1424: void rollback(Transaction transaction) {
1425: // {{{ Rollback transaction
1426: try {
1427: logger.debug("store rollback runs on transaction: "
1428: + transaction);
1429: // Call transaction tracker rollback (which also calls notify threads)
1430: context.getTransactionTracker().internalRollback(
1431: transaction);
1432: } finally {
1433: // Unlock objects
1434: unlockObjects(transaction);
1435: }
1436: // }}}
1437: }
1438:
1439: /**
1440: * Commit a transaction. This method is necessary, because on the
1441: * end of a transaction, the store must set all persistence_start
1442: * and persistence_end date/serials to the actual close-serial
1443: * of transaction.
1444: */
1445: void commit(Transaction transaction) {
1446: // {{{ Commit transaction
1447: logger
1448: .debug("store commit runs on transaction: "
1449: + transaction);
1450: Long endSerial = null;
1451: boolean success = true;
1452: try {
1453: Throwable failureCause = null;
1454: if (transaction.isRollbackOnly())
1455: success = false;
1456: // We need a serial for the transaction to end. This serial
1457: // will denote the commit itself. The beginning of a commit
1458: // must be marked, because while the commit is running, no
1459: // query can execute which has a higher serial, because this
1460: // would mean the query will change once this commit finishes.
1461: endSerial = context.getNodeManager().startCommit(
1462: context.getNodeManager().getNodeIndex());
1463: transaction.setEndSerial(endSerial);
1464: // All operations done now must finish, if an exception occurs,
1465: // roll it back manually and throw exception.
1466: try {
1467: // Save: set startdates where startdate is maxdate
1468: List saveTables = transaction.getSaveTables();
1469: HashMap keys = new HashMap();
1470: keys.put("persistence_txstartid", transaction
1471: .getSerial());
1472: HashMap changes = new HashMap();
1473: changes.put("persistence_start", endSerial);
1474: for (int i = 0; i < saveTables.size(); i++) {
1475: String tableName = (String) saveTables.get(i);
1476: logger.debug("fixing save table: " + tableName);
1477: context.getDatabase().save(transaction, tableName,
1478: keys, changes);
1479: // Notify cache, that table changed for everyone
1480: context.getCache().updateEntries(tableName,
1481: endSerial);
1482: context.getNodeManager().updateEntries(tableName,
1483: endSerial);
1484: }
1485: // Remove: set enddates where txenddate is not maxdate
1486: List removeTables = transaction.getRemoveTables();
1487: keys = new HashMap();
1488: keys
1489: .put("persistence_txendid", transaction
1490: .getSerial());
1491: changes = new HashMap();
1492: changes.put("persistence_end", endSerial);
1493: for (int i = 0; i < removeTables.size(); i++) {
1494: String tableName = (String) removeTables.get(i);
1495: logger.debug("fixing remove table: " + tableName);
1496: context.getDatabase().save(transaction, tableName,
1497: keys, changes);
1498: // Notify cache, that table changed for everyone
1499: context.getCache().updateEntries(tableName,
1500: endSerial);
1501: context.getNodeManager().updateEntries(tableName,
1502: endSerial);
1503: }
1504: // Notify the server of all objects that changed. This operation
1505: // must be before the commit physically occurs, because this notification
1506: // will cause the server to know which objects are modified.
1507: notifyServerOfChanges(transaction);
1508: } catch (Exception e) {
1509: failureCause = e;
1510: success = false;
1511: }
1512: // Call transaction tracker commit (which also calls notify threads)
1513: if (success) {
1514: success = false;
1515: logger
1516: .debug("store calls internal commit on transaction: "
1517: + transaction);
1518: context.getTransactionTracker().internalCommit(
1519: transaction);
1520: success = true; // Commit really ran
1521: } else {
1522: // Rollback and throw exception
1523: logger
1524: .debug("store commit calls rollback on transaction: "
1525: + transaction);
1526: context.getTransactionTracker().internalRollback(
1527: transaction);
1528: throw new StoreException(
1529: "exception while commiting transaction.",
1530: failureCause);
1531: }
1532: } finally {
1533: // If the commit was successfull, we must update objects'
1534: // metadata to reflect changes.
1535: if (success)
1536: modifyMetaData(transaction);
1537: // Exit commit semaphore. If the end of the commit is reached,
1538: // queries can be initiated with this serial, because all information
1539: // is readyly commited.
1540: if (endSerial != null)
1541: context.getNodeManager().endCommit(
1542: context.getNodeManager().getNodeIndex(),
1543: endSerial);
1544: // Unlock objects. Unlock event must come after the commit has been
1545: // closed. If this event does not reach the server, the communication
1546: // error will cause the server to unlock all objects anyway.
1547: unlockObjects(transaction);
1548: // Log end
1549: logger.debug("store commit ended on transaction: "
1550: + transaction);
1551: }
1552: // }}}
1553: }
1554:
1555: private class IdsEntry {
1556: public Set ids;
1557: public ClassInfo classInfo;
1558: public Map objects;
1559:
1560: public IdsEntry(Map objects, Set ids, ClassInfo classInfo) {
1561: this .objects = objects;
1562: this .ids = ids;
1563: this .classInfo = classInfo;
1564: }
1565: }
1566:
1567: /**
1568: * This is a shutdown logic, which simply calls <code>close()</code>
1569: * when the JVM exists.
1570: */
1571: private class ShutdownProcess implements Runnable {
1572: public void run() {
1573: close(true);
1574: }
1575: }
1576:
1577: }
|