Package org.exolab.castor.persist

Source Code of org.exolab.castor.persist.LockEngine

/**
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
*    statements and notices.  Redistributions must also contain a
*    copy of this document.
*
* 2. Redistributions in binary form must reproduce the
*    above copyright notice, this list of conditions and the
*    following disclaimer in the documentation and/or other
*    materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
*    products derived from this Software without prior written
*    permission of Intalio, Inc.  For written permission,
*    please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
*    nor may "Exolab" appear in their names without prior written
*    permission of Intalio, Inc. Exolab is a registered
*    trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
*    (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 1999 (C) Intalio, Inc. All Rights Reserved.
*
* $Id: LockEngine.java 8034 2009-01-26 00:59:04Z rjoachim $
*/

package org.exolab.castor.persist;

import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.cache.Cache;
import org.castor.cache.CacheAcquireException;
import org.castor.cache.CacheFactory;
import org.castor.cache.CacheFactoryRegistry;
import org.castor.core.util.AbstractProperties;
import org.castor.core.util.Messages;
import org.castor.cpa.CPAProperties;
import org.castor.jdo.engine.ConnectionFactory;
import org.castor.persist.ProposedEntity;
import org.castor.persist.TransactionContext;
import org.castor.persist.cache.CacheEntry;
import org.exolab.castor.jdo.ClassNotPersistenceCapableException;
import org.exolab.castor.jdo.DuplicateIdentityException;
import org.exolab.castor.jdo.LockNotGrantedException;
import org.exolab.castor.jdo.ObjectDeletedException;
import org.exolab.castor.jdo.ObjectModifiedException;
import org.exolab.castor.jdo.ObjectNotFoundException;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.mapping.AccessMode;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.persist.spi.Identity;
import org.exolab.castor.persist.spi.Persistence;
import org.exolab.castor.persist.spi.PersistenceFactory;
import org.exolab.castor.xml.ClassDescriptorResolver;

/**
* LockEngine is a gateway for all the <tt>ClassMolder</tt>s of a persistence
* storage. It mantains dirty checking cache state and lock, and provides a
* thread safe enviroment for <tt>ClassMolder</tt>. LockEngine garantees that
* no two conflicting operations will be let running concurrently for the same object.
* <p>
* For example, it ensures that exactly one transaction may read (load) exclusively
* on one object; transaction can not deleted an object while the other is
* reading it, etc...
* <p>
* It also provides caching for a persistence storage. Different {@link Cache} mechanisms
* can be specified.
* <p>
* User should not create more than one instance of LockEngine for each persistent
* storage. So that object can be properly locked and ObjectModifiedException can
* be avoided.
* <p>
* However, if more than one instance of LockEngine or some other external
* application run concurrently, if the {@link Persistence} supports dirty checking,
* like a fully complaint JDBC Relational Database, proper
* ObjectModifiedException will be thrown to ensure data consistency.
* <p>
* IMPLEMENTATION NOTES:
* <p>
* An object may be persistent in multiple caches at any given
* time. There is no way to load an object from multiple caches,
* but an object can be loaded in one engine and then made
* persistent in another. The engines are totally independent and
* no conflicts should occur.
* <p>
* Each class hierarchy gets its own cache, so caches can be
* controlled on a class-by-class basis.
*
* @author <a href="arkin@intalio.com">Assaf Arkin</a>
* @author <a href="yip@intalio.com">Thomas Yip</a>
* @author <a href="mailto:ferret AT frii dot com">Bruce Snyder</a>
* @version $Revision: 8034 $ $Date: 2006-04-22 11:05:30 -0600 (Sat, 22 Apr 2006) $
*/
public final class LockEngine {
    /**
     * The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
     * Commons Logging</a> instance used for all logging.
     */
    private static Log _log = LogFactory.getFactory().getInstance(LockEngine.class);
   
    private static CacheFactoryRegistry _cacheFactoryRegistry;

    /**
     * Mapping of type information to object types. The object's class is used
     * as the key and {@link TypeInfo} is the value. {@link TypeInfo} provides
     * sufficient information to persist the object, manipulated it in memory
     * and invoke the object's interceptor.
     */
    private HashMap _typeInfo = new HashMap();

    /**
     * All the XA transactions running against this cache engine.
     */
    private HashMap _xaTx = new HashMap();
   
    /**
     * The ConnectionFactory.
     */
    private ConnectionFactory _connectionFactory;
   
    /**
     * Used by the constructor when creating handlers to temporarily
     * hold the persistence factory for use by {@link #getClassMolder}.
     */
    private PersistenceFactory _persistenceFactory;

    /**
     * Construct a new cache engine with the specified mapping table,
     * persistence engine and the log interceptor.
     *
     * @param  persistenceFactory      Factory for creating persistence engines for each
     *                      object described in the map
     * @throws MappingException Indicate that one of the mappings is invalid
     */
    public LockEngine(final ConnectionFactory connectionFactory,
                      final ClassDescriptorResolver cdResolver,
                      final PersistenceFactory persistenceFactory)
    throws MappingException {
        if (_cacheFactoryRegistry == null) {
            AbstractProperties properties = CPAProperties.getInstance();
            _cacheFactoryRegistry = new CacheFactoryRegistry(properties);
        }
       
        _connectionFactory = connectionFactory;
        _persistenceFactory = persistenceFactory;
       
        try {
            Vector v = ClassMolderHelper.resolve(cdResolver, this, _persistenceFactory);
   
            _typeInfo = new HashMap();
            Enumeration enumeration = v.elements();

            HashSet processedClasses = new HashSet();
            HashSet freshClasses = new HashSet();
            // copy things into an arraylist
            while (enumeration.hasMoreElements()) {
                freshClasses.add(enumeration.nextElement());
            }

            // iterates through all the ClassMolders in the LockEngine.
            // We first create a TypeInfo for all the base class (ie not extends
            // other class) in the first iteration.
            int counter = 0;
            do {
                counter = freshClasses.size();
                Iterator itor = freshClasses.iterator();
                while (itor.hasNext()) {
                    ClassMolder molder = (ClassMolder) itor.next();
                    ClassMolder extend = molder.getExtends();

                    if (extend == null) {
                        // create new Cache instance for the base type
                        Cache cache = null;
                        try {
                            cache = _cacheFactoryRegistry.getCache(
                                    molder.getCacheParams(),
                                    cdResolver.getMappingLoader().getClassLoader());
                        } catch (CacheAcquireException e) {
                            String msg = Messages.message("persist.cacheCreationFailed");
                            _log.error(msg, e);
                            throw new MappingException(msg, e);
                        }
                        TypeInfo info = new TypeInfo(molder, new HashMap(), cache);
                        _typeInfo.put(molder.getName(), info);
                        itor.remove();
                        processedClasses.add(molder);

                    } else if (processedClasses.contains(molder.getExtends())) {
                        // use the base type to construct the new type
                        TypeInfo baseInfo = (TypeInfo) _typeInfo.get(extend.getName());
                        _typeInfo.put(molder.getName(), new TypeInfo(molder, baseInfo));
                        itor.remove();
                        processedClasses.add(molder);

                    } else {
                        // do nothing and wait for the next turn
                    }

                }
            } while ((freshClasses.size() > 0) && (counter != freshClasses.size()));

            // error if there is molder left.
            if (freshClasses.size() > 0) {
                Iterator itor = freshClasses.iterator();
                while (itor.hasNext()) {
                    ClassMolder molder = (ClassMolder) itor.next();
                    _log.error("The base class, " + (molder.getExtends().getName())
                        + ", of the extends class ," + molder.getName()
                        + " can not be resolved! ");
                }
                throw new MappingException("Some base class can not be resolved!");
            }
        } catch (ClassNotFoundException e) {
            throw new MappingException("Declared Class not found!");
        }
    }
   
    public ConnectionFactory getConnectionFactory() { return _connectionFactory; }

    /**
     * Get classMolder which represents the given java data object class.
     * Dependent class will not be returned to avoid persistenting
     * a dependent class without.
     *
     * @param cls Class instance for whic a class molder should be returned.
     * @return The class molder for the specified class.
     */
    public ClassMolder getClassMolder(final Class cls) {
        TypeInfo info = (TypeInfo) _typeInfo.get(cls.getName());
        if (info != null) {
            if (!info._molder.isDependent()) {
                return info._molder;
            }
        }
        return null;
    }
   
    public ClassMolder getClassMolderWithDependent(final Class cls) {
        TypeInfo info = (TypeInfo) _typeInfo.get(cls.getName());
        return (info != null) ? info._molder : null;
    }
   
    /**
     * Returns the ClassMolder instance that has a named query associated with the name given.
     * @param name Name of a named query.
     * @return ClassMolder instance associated with the named query
     */
    public ClassMolder getClassMolderByQuery(final String name) {       
        Iterator typeIterator = _typeInfo.values().iterator();
        while (typeIterator.hasNext()) {
            TypeInfo info = (TypeInfo) typeIterator.next();
            if (info._molder.getNamedQuery(name) != null) {
                return info._molder;
            }
        }
        return null;
    }

    public Persistence getPersistence(final Class cls) {
        ClassMolder molder = getClassMolder(cls);
        if (molder != null) {
            return molder.getPersistence();
        }
        return null;
    }

    /**
     * Loads an object of the specified type and identity from
     * persistent storage. In exclusive mode the object is always
     * loaded and a write lock is obtained on the object, preventing
     * concurrent updates. In non-exclusive mode the object is either
     * loaded or obtained from the cache with a read lock. The object's
     * OID is always returned.
     *
     * @param tx The transaction context
     * @param oid The identity of the object to load
     * @param proposedObject The type of the object to load
     * @param suggestedAccessMode The desired access mode
     * @param timeout The timeout waiting to acquire a lock on the
     *  object (specified in seconds)
     * @return The object's OID
     * @throws ObjectNotFoundException The object was not found in
     *  persistent storage
     * @throws LockNotGrantedException Timeout or deadlock occured
     *  attempting to acquire lock on object
     * @throws PersistenceException An error reported by the
     *  persistence engine
     * @throws ClassNotPersistenceCapableException The class is not
     *  persistent capable
     * @throws ObjectDeletedWaitingForLockException The object has been deleted, but is waiting
     *         for a lock.
     */
    public OID load(final TransactionContext tx, final OID oid, final ProposedEntity proposedObject,
            final AccessMode suggestedAccessMode, final int timeout, final QueryResults results)
    throws PersistenceException {
        TypeInfo typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        if (typeInfo == null) {
            throw new ClassNotPersistenceCapableException(Messages.format(
                    "persist.classNotPersistenceCapable", oid.getName()));
        }

        ClassMolder molder = oid.getMolder();
        AccessMode accessMode = molder.getAccessMode(suggestedAccessMode);

        boolean succeed = false;
        ObjectLock lock = null;
        try {
            short action;
            if ((accessMode == AccessMode.Exclusive) || (accessMode == AccessMode.DbLocked)) {
                action = ObjectLock.ACTION_WRITE;
            } else {
                action = ObjectLock.ACTION_READ;
            }

            lock = typeInfo.acquire(oid, tx, action, timeout);

            typeInfo._molder.load(tx, lock, proposedObject, suggestedAccessMode, results);

            // if object has been expanded, return early           
            if (proposedObject.isExpanded()) {
                // Current transaction holds lock for old OID
                typeInfo.release(oid, tx);
            } else {
                if (_log.isDebugEnabled()) {
                    _log.debug(Messages.format("jdo.loading.with.id",
                            typeInfo._molder.getName(), oid.getIdentity()));
                }

                succeed = true;
            }
           
            return lock.getOID();
        } catch (ObjectDeletedWaitingForLockException except) {
            // This is equivalent to object does not exist
            throw new ObjectNotFoundException(Messages.format(
                    "persist.objectNotFound", oid.getName(), oid.getIdentity()), except);
        } catch (LockNotGrantedException e) {
            if (lock != null) { lock.release(tx); }
            throw e;
        } finally {
            if (lock != null) { lock.confirm(tx, succeed); }
        }
    }

    /**
     * Mark an object and its related or dependent object to be created.
     *
     * @param tx The transaction context.
     * @param oid The identity of the object, or null.
     * @param object The newly created object.
     * @throws PersistenceException An error reported by the persistence engine. Timeout or
     *         deadlock occured attempting to acquire lock on object.
     */
    public void markCreate(final TransactionContext tx, final OID oid, final Object object)
    throws PersistenceException {

        TypeInfo   typeInfo;
        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());

        if (typeInfo == null) {
            throw new ClassNotPersistenceCapableException(Messages.format(
                    "persist.classNotPersistenceCapable", oid.getName()));
        }
        typeInfo._molder.markCreate(tx, oid, null, object);
    }

    /**
     * Creates a new object in the persistence storage. The object must not
     * be persistent and must have a unique identity within this engine.
     * If the identity is specified the object is created in
     * persistent storage immediately with the identity. If the
     * identity is not specified, the object is created only when the
     * transaction commits. The object's OID is returned. The OID is
     * guaranteed to be unique for this engine even if no identity was
     * specified.
     *
     * @param tx The transaction context
     * @param oid The identity of the object, or null
     * @param object The newly created object
     * @return The object's OID
     * @throws DuplicateIdentityException An object with this identity
     *  already exists in persistent storage
     * @throws PersistenceException An error reported by the
     *  persistence engine
     * @throws ClassNotPersistenceCapableException The class is not
     *  persistent capable
     */
    public OID create(final TransactionContext tx, final OID oid, final Object object)
    throws PersistenceException {
        OID internaloid = oid;
        TypeInfo typeInfo;
        ObjectLock lock;
        OID newoid;
        boolean succeed;

        typeInfo = (TypeInfo) _typeInfo.get(object.getClass().getName());
        if (typeInfo == null) {
            throw new ClassNotPersistenceCapableException(Messages.format(
                    "persist.classNotPersistenceCapable", object.getClass().getName()));
        }
           
        lock = null;

        if (internaloid.getIdentity() != null) {

            lock = null;

            succeed = false;

            try {

                lock = typeInfo.acquire(internaloid, tx, ObjectLock.ACTION_CREATE, 0);

                if (_log.isDebugEnabled()) {
                    _log.debug(Messages.format("jdo.creating.with.id", typeInfo._molder.getName(),
                            internaloid.getIdentity()));
                }

                internaloid = lock.getOID();

                typeInfo._molder.create(tx, internaloid, lock, object);

                succeed = true;

                internaloid.setDbLock(true);

                return internaloid;
                // should catch some other exception if destory is not succeed
            } catch (LockNotGrantedException except) {
                // Someone else is using the object, definite duplicate key
                throw new DuplicateIdentityException(Messages.format(
                    "persist.duplicateIdentity", object.getClass().getName(),
                    internaloid.getIdentity()), except);
            } catch (DuplicateIdentityException except) {
                // we got a write lock and the persistence storage already
                // recorded. Should destory the lock
                //typeInfo.delete( oid, tx );
                throw except;
            } finally {
                if (lock != null) {
                    lock.confirm(tx, succeed);
                }
            }
        }
        // identity is null

        succeed = false;

        try {
            if (_log.isDebugEnabled()) {
                _log.debug(Messages.format("jdo.creating.with.id", typeInfo._molder.getName(),
                        internaloid.getIdentity()));
            }

            lock = typeInfo.acquire(internaloid, tx, ObjectLock.ACTION_CREATE, 0);

            internaloid = lock.getOID();

            Identity newids = typeInfo._molder.create(tx, internaloid, lock, object);
            succeed = true;

            internaloid.setDbLock(true);

            newoid = new OID(internaloid.getMolder(), internaloid.getDepends(), newids);

            typeInfo.rename(internaloid, newoid, tx);

            return newoid;
        } catch (LockNotGrantedException e) {
//            e.printStackTrace();
            throw new PersistenceException(Messages.format(
                    "persist.nested", "Key Generator Failure. Duplicated Identity is generated!"));
        } finally {
            if (lock != null) {
                lock.confirm(tx, succeed);
            }
        }
    }

    /**
     * Called at transaction commit time to delete the object. Object
     * deletion occurs in three distinct steps:
     * <ul>
     * <li>A write lock is obtained on the object to assure it can be
     *     deleted and the object is marked for deletion in the
     *     transaction context
     * <li>As part of transaction preparation the object is deleted
     *     from persistent storage using this method
     * <li>The object is removed from the cache when the transaction
     *     completes with a call to {@link #forgetObject}
     * </ul>
     *
     * @param tx The transaction context
     * @param oid The object's identity
     * @throws PersistenceException An error reported by the
     *  persistence engine
     */
    public void delete(final TransactionContext tx, final OID oid)
            throws PersistenceException {
        TypeInfo   typeInfo = (TypeInfo) _typeInfo.get(oid.getName());

        try {
            typeInfo.assure(oid, tx, true);

            if (_log.isDebugEnabled()) {
                _log.debug(Messages.format("jdo.removing", typeInfo._molder.getName(),
                        oid.getIdentity()));
            }

            typeInfo._molder.delete(tx, oid);

        } catch (LockNotGrantedException except) {
            throw new IllegalStateException(Messages.format(
                    "persist.internal", "Attempt to delete object for which no lock was acquired"));
        }
    }

    public void markDelete(final TransactionContext tx, final OID oid, final Object object,
            final int timeout) throws PersistenceException {

        TypeInfo typeInfo = (TypeInfo) _typeInfo.get(oid.getName());

        ObjectLock lock = typeInfo.upgrade(oid, tx, timeout);

        typeInfo._molder.markDelete(tx, oid, lock, object);

        lock.expire();
    }


    /**
     * Updates an existing object to this engine. The object must not be
     * persistent and must not have the identity of a persistent object.
     * The object's OID is returned. The OID is guaranteed to be unique
     * for this engine even if no identity was specified.
     * If the object implements TimeStampable interface, verify
     * the object's timestamp.
     *
     * @param tx The transaction context
     * @param oid The object's identity
     * @param object The object
     * @param suggestedAccessMode The desired access mode
     * @param timeout The timeout waiting to acquire a lock on the
     *  object (specified in seconds)
     * @return The object's OID
     * @throws ObjectNotFoundException The object was not found in
     *  persistent storage
     * @throws LockNotGrantedException Timeout or deadlock occured
     *  attempting to acquire lock on object
     * @throws PersistenceException An error reported by the
     *  persistence engine
     * @throws ClassNotPersistenceCapableException The class is not
     *  persistent capable
     * @throws ObjectModifiedException Dirty checking mechanism may immediately
     *  report that the object was modified in the database during the long
     *  transaction.
     * @throws ObjectDeletedWaitingForLockException
     */
    public boolean update(final TransactionContext tx, final OID oid, final Object object,
            final AccessMode suggestedAccessMode, final int timeout) throws PersistenceException {
        // If the object is new, don't try to load it from the cache
        TypeInfo typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        if (typeInfo == null) {
            throw new ClassNotPersistenceCapableException(Messages.format(
                    "persist.classNotPersistenceCapable", oid.getName()));
        }

        boolean succeed = false;
        ObjectLock lock = null;
        OID internaloid = oid;
        try {
            // exclude objects that are locked, cached, dependend or to be created
            if (!typeInfo.isLocked(internaloid)
                    && !typeInfo.isCached(internaloid)
                    && !typeInfo._molder.isDependent()
                    && (internaloid.getIdentity() != null)) {
                lock = typeInfo.acquire(internaloid, tx, ObjectLock.ACTION_UPDATE, timeout);
                internaloid = lock.getOID();
               
                // set timestamp of lock to the one of persistent object
                try {
                    typeInfo._molder.loadTimeStamp(tx, lock, suggestedAccessMode);
                } catch (PersistenceException ex) {
                    // ignore
                }
            } else {
                lock = typeInfo.acquire(internaloid, tx, ObjectLock.ACTION_UPDATE, timeout);
                internaloid = lock.getOID();
            }
           
            succeed = !typeInfo._molder.update(tx, internaloid, lock, object, suggestedAccessMode);

            return !succeed;
        } catch (ObjectModifiedException e) {
            throw e;
        } catch (ObjectDeletedWaitingForLockException except) {
            // This is equivalent to object not existing
            throw new ObjectNotFoundException(Messages.format(
                    "persist.objectNotFound", internaloid.getName(),
                    internaloid.getIdentity()), except);
        } finally {
            if (lock != null) {
                lock.confirm(tx, succeed);
            }
        }
    }

    /**
     * Called at transaction commit to store an object that has been
     * loaded during the transaction. If the object has been created
     * in this transaction but without an identity, the object will
     * be created in persistent storage. Otherwise the object will be
     * stored and dirty checking might occur in order to determine
     * whether the object is valid. The object's OID might change
     * during this process, and the new OID will be returned. If the
     * object was not stored (not modified), null is returned.
     *
     * @param tx The transaction context
     * @param oid The object's identity
     * @param object The object to store
     * @param timeout The timeout waiting to acquire a lock on the
     *  object (specified in seconds)
     * @return The object's OID if stored, null if ignored
     * @throws LockNotGrantedException Timeout or deadlock occured
     *  attempting to acquire lock on object
     * @throws ObjectDeletedException The object has been deleted from
     *  persistent storage
     * @throws ObjectModifiedException The object has been modified in
     *  persistent storage since it was loaded, the memory image is
     *  no longer valid
     * @throws DuplicateIdentityException An object with this identity
     *  already exists in persistent storage
     * @throws PersistenceException An error reported by the
     *  persistence engine
     */
    public OID preStore(final TransactionContext tx, final OID oid, final Object object,
            final int timeout) throws PersistenceException {
        OID internaloid = oid;
        TypeInfo   typeInfo;
        ObjectLock lock = null;
        boolean    modified;

        typeInfo = (TypeInfo) _typeInfo.get(object.getClass().getName());

        // Acquire a read lock first. Only if the object has been modified
        // do we need a write lock.

        internaloid = new OID(typeInfo._molder, internaloid.getIdentity());

        // acquire read lock
        // getLockedField();
        // isPersistFieldChange()?
        // if no, return null
        // if yes, get flattened fields,
        // acquire write lock
        // setLockedField( );
        try {
            lock = typeInfo.assure(internaloid, tx, false);

            internaloid = lock.getOID();

            modified = typeInfo._molder.preStore(tx, internaloid, lock, object, timeout);
        } catch (LockNotGrantedException e) {
            throw e;
        } catch (ObjectModifiedException e) {
            lock.invalidate(tx);
            throw e;
        } catch (ObjectDeletedException e) {
            lock.delete(tx);
            throw e;
        }

        if (modified) {
            return internaloid;
        }

        return null;
    }

    public void store(final TransactionContext tx, final OID oid, final Object object)
    throws PersistenceException {

        ObjectLock lock = null;
        TypeInfo   typeInfo;

        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        // Attempt to obtain a lock on the database. If this attempt
        // fails, release the lock and report the exception.

        try {
            lock = typeInfo.assure(oid, tx, false);

            if (_log.isDebugEnabled ()) {
                _log.debug(Messages.format("jdo.storing.with.id",
                        typeInfo._molder.getName(), oid.getIdentity()));
            }

            typeInfo._molder.store(tx, oid, lock, object);
        } catch (ObjectModifiedException e) {
            lock.invalidate(tx);
            throw e;
        } catch (DuplicateIdentityException e) {
            throw e;
        } catch (LockNotGrantedException e) {
            throw e;
        } catch (PersistenceException e) {
            lock.invalidate(tx);
            throw e;
        }
    }



    /**
     * Acquire a write lock on the object. A write lock assures that
     * the object exists and can be stored/deleted when the
     * transaction commits. It prevents any concurrent updates to the
     * object from this point on. However, it does not guarantee that
     * the object has not been modified in the course of the
     * transaction. For that the object must be loaded with exclusive
     * access.
     *
     * @param tx The transaction context
     * @param oid The object's OID
     * @param timeout The timeout waiting to acquire a lock on the
     *  object (specified in seconds)
     * @throws LockNotGrantedException Timeout or deadlock occured
     *  attempting to acquire lock on object
     * @throws ObjectDeletedException The object has been deleted from
     *  persistent storage
     * @throws PersistenceException An error reported by the
     *  persistence engine
     */
    public void writeLock(final TransactionContext tx, final OID oid, final int timeout)
    throws PersistenceException {

        TypeInfo   typeInfo;

        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        // Attempt to obtain a lock on the database. If this attempt
        // fails, release the lock and report the exception.

        try {
            typeInfo.upgrade(oid, tx, timeout);

            // typeInfo.engine.writeLock( tx, lock...);
        } catch (IllegalStateException e) {
            throw e;
        } catch (ObjectDeletedWaitingForLockException e) {
            throw new IllegalStateException("Object deleted waiting for lock?????????");
        } catch (LockNotGrantedException e) {
            throw e;
        }
    }


    /**
     * Acquire a write lock on the object in memory. A soft lock prevents
     * other threads from changing the object, but does not acquire a lock
     * on the database.
     *
     * @param tx The transaction context
     * @param oid The object's OID
     * @param timeout The timeout waiting to acquire a lock on the
     *  object (specified in seconds)
     * @throws LockNotGrantedException Timeout or deadlock occured
     *  attempting to acquire lock on object
     *  persistent storage
     */
    public void softLock(final TransactionContext tx, final OID oid, final int timeout)
    throws LockNotGrantedException {
        TypeInfo   typeInfo;

        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        typeInfo.upgrade(oid, tx, timeout);
    }

    /**
     * Reverts an object to the cached copy given the object's OID.
     * The cached object is copied into the supplied object without
     * affecting the locks, loading relations or emitting errors.
     * This method is used during the rollback phase.
     *
     * @param tx The transaction context
     * @param oid The object's oid
     * @param object The object into which to copy
     * @throws PersistenceException An error reported by the
     *  persistence engine obtaining a dependent object
     */
    public void revertObject(final TransactionContext tx, final OID oid, final Object object)
    throws PersistenceException {
        TypeInfo   typeInfo;
        ObjectLock lock;

        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        try {
            lock = typeInfo.assure(oid, tx, false);
            typeInfo._molder.revertObject(tx, oid, lock, object);
        } catch (LockNotGrantedException e) {
            throw new IllegalStateException("Write Lock expected!");
        } catch (PersistenceException except) {
            //typeInfo.destory( oid, tx );
            throw except;
        }
    }

    /**
     * Update the cached object with changes done to its copy. The
     * supplied object is copied into the cached object using a write
     * lock. This method is generally called after a successful return
     * from {@link #store} and is assumed to have obtained a write
     * lock.
     *
     * @param tx The transaction context
     * @param oid The object's oid
     * @param object The object to copy from
     */
    public void updateCache(final TransactionContext tx, final OID oid, final Object object) {
        TypeInfo   typeInfo;
        ObjectLock lock;

        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        lock = typeInfo.assure(oid, tx, true);
        typeInfo._molder.updateCache(tx, oid, lock, object);
    }

    /**
     * Called at transaction commit or rollback to release all locks
     * held on the object. Must be called for all objects that were
     * queried but not created within the transaction.
     *
     * @param tx The transaction context
     * @param oid The object OID
     */
    public void releaseLock(final TransactionContext tx, final OID oid) {
        ObjectLock lock;
        TypeInfo   typeInfo;
        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        lock = typeInfo.release(oid, tx);
        lock.getOID().setDbLock(false);
    }


    /**
     * Called at transaction commit or rollback to forget an object
     * and release its locks. Must be called for all objects that were
     * created when the transaction aborts, and for all objects that
     * were deleted when the transaction completes. The transaction is
     * known to have a write lock on the object.
     *
     * @param tx The transaction context
     * @param oid The object OID
     */
    public void forgetObject(final TransactionContext tx, final OID oid) {
        TypeInfo   typeInfo;

        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        //lock = typeInfo.locks.aquire( oid, tx );
        typeInfo.assure(oid, tx, true);
        typeInfo.delete(oid, tx);
        typeInfo.release(oid, tx);
    }

    /**
     * Expire object from the cache.  If the object to be expired is currently
     * cached, then a write lock is first acquired for this object. In addition,
     * a write lock is acquired on all objects related to, or contained within,
     * this object.  The version of the objects represented by their locks is
     * then marked as "expired".  Upon the release of each write lock
     * (@see TransactionContext#expireCache), the cached version of the objects
     * will not be placed back in the cache (@see TypeInfo#release).
     * A subsequent read/query transaction will therefore load the values of
     * the object from persistent storage.
     *
     * @param tx The transaction context
     * @param oid The object OID
     * @param timeout The max time to wait while acquiring a lock on the
     *  object (specified in seconds)
     * @return True if the object was expired successfully from the cache.
     * @throws LockNotGrantedException Timeout or deadlock occured attempting to acquire lock
     *         on object
     * @throws PersistenceException An error reported by the persistence engine
     * @throws ClassNotPersistenceCapableException The class is not persistent capable
     * @throws ObjectModifiedException Dirty checking mechanism may immediately
     *  report that the object was modified in the database during the long
     *  transaction.
     * @throws ObjectDeletedException Object has been deleted from the persistence store.
     */
    public boolean expireCache(final TransactionContext tx, final OID oid, final int timeout)
    throws PersistenceException {
       
        TypeInfo   typeInfo;
        boolean    succeed;
        ObjectLock lock;
        typeInfo = (TypeInfo) _typeInfo.get(oid.getName());
        if (typeInfo == null) {
            throw new ClassNotPersistenceCapableException(Messages.format(
                    "persist.classNotPersistenceCapable", oid.getName()));
        }
  
        succeed = false;
        lock = null;
        try {
            if (typeInfo.isCached(oid)) {
                lock = typeInfo.acquire(oid, tx, ObjectLock.ACTION_WRITE, timeout);
                typeInfo._molder.expireCache(tx, lock);
                lock.expire();
                succeed = true;
            }
        } catch (LockNotGrantedException e) {
            throw e;
        } catch (ObjectDeletedException e) {
            throw e;
        } catch (PersistenceException e) {
            throw e;
        } finally {
            if (lock != null) {
                lock.confirm(tx, succeed);
            }
        }
        return succeed;
    }
    /**
     * Forces the cache to be expired for the object represented by
     * ClassMolder and identity.  If identity is null then expire
     * all objects of the type represented by ClassMolder.
     * @param cls Class type instance.
     */
    public void expireCache(final Class cls) {
        TypeInfo typeInfo = (TypeInfo) _typeInfo.get(cls.getName());
        if (typeInfo != null) {
            typeInfo.expireCache();
        }
    }
    /**
     * Expires all objects of all types from cache.
     */
    public void expireCache() {
        for (Iterator iter = _typeInfo.values().iterator(); iter.hasNext(); ) {
            ((TypeInfo) iter.next()).expireCache();
        }
    }
    /**
     * Dump cached objects of all types to output.
     */
    public void dumpCache() {
        for (Iterator iter = _typeInfo.values().iterator(); iter.hasNext(); ) {
            ((TypeInfo) iter.next()).dumpCache();
        }
    }

    /**
     * Close all caches (to allow for resource clean-up).
     */
    public void closeCaches() {
        for (Iterator iter = _typeInfo.values().iterator(); iter.hasNext(); ) {
            ((TypeInfo) iter.next()).closeCache();
        }
       
        Collection cacheFactories = _cacheFactoryRegistry.getCacheFactories();
        for (Iterator iter = cacheFactories.iterator(); iter.hasNext(); ) {
            ((CacheFactory) iter.next()).shutdown();
        }
    }

    /**
     * Dump cached objects of specific type to output.
     * @param cls A class type.
     */
    public void dumpCache(final Class cls) {
        TypeInfo typeInfo = (TypeInfo) _typeInfo.get(cls.getName());
        if (typeInfo != null) {
            typeInfo.dumpCache();
        }
    }

    /**
     * Returns an association between Xid and transactions contexts.
     * The association is shared between all transactions open against
     * this cache engine through the XAResource interface.
     * @return Association between XId and transaction contexts.
     */
    public HashMap getXATransactions() {
        return _xaTx;
    }

    /**
     * Provides information about an object of a specific type (class's full name).
     * This information includes the object's descriptor and lifecycle interceptor
     * requesting notification about activities that affect an object.
     */
    private final class TypeInfo {
        /** The molder for this class. */
        private final ClassMolder _molder;

        /** The full qualified name of the Java class represented by this type info. */
        private final String _name;

        /** The Map contains all the in-used ObjectLock of the class type, which
         *  keyed by the OID representing the object. All extends classes share the
         *  same map as the base class. */
        private final HashMap _locks;
       
        /** The Map contains all the freed ObjectLock of the class type, which keyed
         *  by the OID representing the object. ObjectLock put into cache maybe
         *  disposed by LRU mechanisum. All extends classes share the same map as the
         *  base class. */
        private final Cache _cache;

        /**
         * Constructor for creating base class info.
         *
         * @param  molder   The classMolder of this type.
         * @param  locks    The new HashMap which will be used
         *         for holding all the in-used ObjectLock.
         * @param  cache    The new LRU which will be used to
         *         store and dispose freed ObjectLock.
         */
        private TypeInfo(final ClassMolder molder, final HashMap locks, final Cache cache) {
            this._name = molder.getName();
            this._molder = molder;
            this._locks = locks;
            this._cache = cache;
        }

        /**
         * Constructor for creating extended class info.
         *
         * @param  molder   The classMolder of this type.
         * @param  base     The TypeInfo of the base class of
         *         the molder's class.
         */
        private TypeInfo(final ClassMolder molder, final TypeInfo base) {
            this(molder, base._locks, base._cache);
        }

        /**
         * Life-cycle method to allow shutdown of cache instances.
         */
        public void closeCache() {
            _cache.close();
        }
       
        /**
         * Dump all objects in cache or lock to output.
         */
        public void dumpCache() {
            _log.info(_name + ".dumpCache()...");
            synchronized (_locks) {
                for (Iterator iter = _locks.values().iterator(); iter.hasNext(); ) {
                    ObjectLock entry = (ObjectLock) iter.next();
                    _log.info("In locks: " + entry);
                }

                for (Iterator iter = _cache.values().iterator(); iter.hasNext(); ) {
                    ObjectLock entry = (ObjectLock) iter.next();
                    _log.info("In cache: " + entry.getOID());
                }
            }
        }
       
        /**
         * Expire all objects of this class from the cache.
         */
        public void expireCache() {
            synchronized (_locks) {
                // Mark all objects currently participating in a
                // transaction as expired.  They will be not be added back to
                // the LRU when the transaction's complete (@see release)
                // XXX [SMH]: Reconsider removing from locks (unknown side-effects?).
                for (Iterator iter = _locks.values().iterator(); iter.hasNext(); ) {
                    ObjectLock objectLock = (ObjectLock) iter.next();
                    objectLock.expire();
                    iter.remove();
                }
               
                // Remove all objects not participating in a transaction from the cache.
                _cache.clear();
            }
        }

        /**
         * Acquire the object lock for transaction. After this method is called,
         * user must call {@link ObjectLock#confirm(TransactionContext, boolean)}
         * exactly once.
         *
         * @param oid The OID of the lock.
         * @param tx The context of the transaction to acquire lock.
         * @param lockAction The inital action to be performed on the lock.
         * @param timeout    The time limit to acquire the lock.
         * @return The object lock for the OID within this transaction context.
         * @throws ObjectDeletedWaitingForLockException
         * @throws LockNotGrantedException Timeout or deadlock occured attempting
         *         to acquire lock on object
         */
        private ObjectLock acquire(final OID oid, final TransactionContext tx,
                final short lockAction, final int timeout)
        throws LockNotGrantedException {
            OID internaloid = oid;
            ObjectLock entry = null;
            // sync on "locks" is, unfortunately, necessary if we employ
            // some LRU mechanism, especially if we allow NoCache, to avoid
            // duplicated LockEntry exist at the same time.
            synchronized (_locks) {
                // consult with the 'first level' cache, aka current transaction
                entry = (ObjectLock) _locks.get(internaloid);
                if (entry == null) {
                    // consult with the 'second level' cache, aka physical cache
                    CacheEntry cachedEntry = (CacheEntry) _cache.remove(internaloid);
                    if (cachedEntry != null) {
                        OID cacheOid = cachedEntry.getOID();
                        if (internaloid.getName().equals(cacheOid.getName())) {
                            entry = new ObjectLock(cachedEntry.getOID(),
                                    cachedEntry.getEntry(), cachedEntry.getTimeStamp());
                           
                            entry.setOID(internaloid);
                        } else {
                            entry = new ObjectLock(internaloid);
                        }
                    } else {
                        entry = new ObjectLock(internaloid);
                    }
                    _locks.put(internaloid, entry);
                }
                entry.enter();
            }
           
            // ObjectLock.acquire() may call Object.wait(), so a thread can not
            // been synchronized with ANY shared object before acquire().
            // So, it must be called outside synchronized( locks ) block.
            boolean failed = true;
            try {
                switch (lockAction) {
                case ObjectLock.ACTION_READ:
                    entry.acquireLoadLock(tx, false, timeout);
                    break;

                case ObjectLock.ACTION_WRITE:
                    entry.acquireLoadLock(tx, true, timeout);
                    break;

                case ObjectLock.ACTION_CREATE:
                    entry.acquireCreateLock(tx);
                    break;

                case ObjectLock.ACTION_UPDATE:
                    entry.acquireUpdateLock(tx, timeout);
                    break;

                default:
                    throw new IllegalArgumentException(
                            "lockType " + lockAction + " is undefined!");
                }
               
                failed = false;
                return entry;
            } finally {
                synchronized (_locks) {
                    entry.leave();
                    if (failed) {
                        // The need of this block may not be too obvious.
                        // At the very moment, if it happens, current thread
                        // failed to acquire a lock. Then, another thread just
                        // release the lock right after. The released entry
                        // then will not be moved to cache because inLocksGap
                        // isn't zero. So, there maybe a chance of memory
                        // leak, as the entry was in "locks", but not in
                        // "cache" as supposed. To avoid it from happening,
                        // we ensure here that the entry which should be move
                        // to "cache" from "locks" is actually moved.
                        if (entry.isDisposable()) {
                            _locks.remove(internaloid);
                            if (entry.isExpired()) {
                                _cache.expire(internaloid);
                                entry.expired();
                            } else {
                                _cache.put(internaloid, new CacheEntry(
                                        entry.getOID(), entry.getObject(), entry.getTimeStamp()));
                            }
                        }
                    }
                }
            }
        }

        /**
         * Upgrade the lock to write lock.
         *
         * @param  oid The OID of the lock.
         * @param  tx The transaction in action.
         * @param  timeout  Time limit.
         * @return The upgraded ObjectLock instance.
         * @throws ObjectDeletedWaitingForLockException
         * @throws LockNotGrantedException Timeout or deadlock occured attempting
         *         to acquire lock on object.
         */
        private ObjectLock upgrade(final OID oid, final TransactionContext tx, final int timeout)
        throws LockNotGrantedException {
            OID internaloid = oid;
            ObjectLock entry = null;
            synchronized (_locks) {
                entry = (ObjectLock) _locks.get(internaloid);
                if (entry == null) {
                    throw new ObjectDeletedWaitingForLockException(
                            "Lock entry not found. Deleted?");
                }
                if (!entry.hasLock(tx, false)) {
                    throw new IllegalStateException(
                            "Transaction does not hold the any lock on " + internaloid + "!");   
                }
                internaloid = entry.getOID();
                entry.enter();
            }
           
            try {
                entry.upgrade(tx, timeout);
                return entry;
            } finally {
                synchronized (_locks) {
                    entry.leave();
                }
            }
        }

        /**
         * Reassure the lock which have been successfully acquired by the transaction.
         *
         * @param  oid      The OID of the lock.
         * @param  tx       The transaction in action.
         * @param  write    <code>true</code> if we want to upgrade or reassure a
         *                  write lock, <code>false</code> for read lock.
         * @return The reassured ObjectLock instance.
         */
        private ObjectLock assure(final OID oid, final TransactionContext tx, final boolean write) {
            synchronized (_locks) {
                ObjectLock entry = (ObjectLock) _locks.get(oid);
                if (entry == null) {
                    throw new IllegalStateException(
                            "Lock, " + oid + ", doesn't exist or no lock!");
                }
                if (!entry.hasLock(tx, write)) {
                    throw new IllegalStateException(
                            "Transaction " + tx + " does not hold the "
                            + (write ? "write" : "read") + " lock: " + entry + "!");
                }
                return entry;
            }
        }

        /**
         * Move the locked object from one OID to another OID for transaction.
         * It is to be called by after create.
         *
         * @param orgoid  Orginal OID before the object is created.
         * @param newoid  New OID after the object is created.
         * @param tx      The TransactionContext of the transaction in action.
         * @return An ObjectLock instance whose OID has been assigned to a new value.
         * @throws LockNotGrantedException Timeout or deadlock occured attempting to
         *         acquire lock on object
         */
        private ObjectLock rename(final OID orgoid, final OID newoid, final TransactionContext tx)
        throws LockNotGrantedException {
            synchronized (_locks) {
                ObjectLock entry;
                ObjectLock newentry;
                entry = (ObjectLock) _locks.get(orgoid);
                newentry = (ObjectLock) _locks.get(newoid);

                // validate locks
                if (orgoid == newoid) {
                    throw new LockNotGrantedException("Locks are the same");
                }
                if (entry == null) {
                    throw new LockNotGrantedException("Lock doesn't exist!");
                }
                if (!entry.isExclusivelyOwned(tx)) {
                    throw new LockNotGrantedException(
                            "Lock to be renamed is not own exclusively by transaction!");
                }
                if (entry.isEntered()) {
                    throw new LockNotGrantedException(
                            "Lock to be renamed is acquired by another transaction!");
                }
                if (newentry != null) {
                    throw new LockNotGrantedException(
                            "Lock is already existed for the new oid.");
                }

                entry = (ObjectLock) _locks.remove(orgoid);
                entry.setOID(newoid);
                _locks.put(newoid, entry);

                // copy oid status
                newoid.setDbLock(orgoid.isDbLock());

                return newentry;
            }
        }

        /**
         * Delete the object lock. It's called after the object is deleted from
         * the persistence and the transaction committed.
         *
         * @param oid   The OID of the ObjectLock.
         * @param tx    The transactionContext of transaction in action.
         * @return The just-deleted ObjectLock instance.
         */
        private ObjectLock delete(final OID oid, final TransactionContext tx) {
            ObjectLock entry;
            synchronized (_locks) {
                entry = (ObjectLock) _locks.get(oid);
                if (entry == null) {
                    throw new IllegalStateException("No lock to destroy!");
                }
                entry.enter();
            }

            try {
                entry.delete(tx);
                return entry;
            } finally {
                synchronized (_locks) {
                    entry.leave();
                    if (entry.isDisposable()) {
                        _cache.put(oid, new CacheEntry(
                                entry.getOID(), entry.getObject(), entry.getTimeStamp()));
                        _locks.remove(oid);
                    }
                }
            }
        }

        /**
         * Release the object lock. It's called after the object the transaction
         * has been committed.
         *
         * @param oid   The OID of the ObjectLock.
         * @param tx    The transactionContext of transaction in action.
         * @return The just-released ObjectLock instance.
         */
        private ObjectLock release(final OID oid, final TransactionContext tx) {
            ObjectLock entry = null;
            synchronized (_locks) {
                entry = (ObjectLock) _locks.get(oid);
                if (entry == null) {
                    throw new IllegalStateException(
                            "No lock to release! " + oid + " for transaction " + tx);
                }
                entry.enter();
            }

            try {
                entry.release(tx);
                return entry;
            } finally {
                synchronized (_locks) {
                    entry.leave();
                    if (entry.isDisposable()) {
                        _cache.put(oid, new CacheEntry(
                                entry.getOID(), entry.getObject(), entry.getTimeStamp()));
                        if (entry.isExpired()) {
                            _cache.expire(oid);
                            entry.expired();
                        }
                        _locks.remove(oid);
                    }
                }
            }
        }

        /**
         * Indicates whether an object with the specified identifier is curretly cached.
         * 
         * @param oid     The Object identifier.
         * @return True if the object is cached.
         */
        public boolean isCached(final OID oid) {
            return _cache.containsKey(oid);
        }
       
        /**
         * Indicates whether an object with the specified OID is currently locked.
         *
         * @param oid Object identifier.
         * @return True if the object is locked.
         */
        public boolean isLocked(final OID oid) {
            return _locks.containsKey(oid);
        }
    }

    /**
     * Provides information about whether an object of Class cls with identity iod is
     * currently cached.
     *
     * @param cls Class type.
     * @param oid Object identity
     * @return True if the specified object is in the cache.
     */
    public boolean isCached(final Class cls, final OID oid) {
        TypeInfo typeInfo = (TypeInfo) _typeInfo.get(cls.getName());
        return typeInfo.isCached (oid);
    }

    public boolean isLocked(final Class cls, final OID oid) {
        TypeInfo typeInfo = (TypeInfo) _typeInfo.get(cls.getName());
        return typeInfo.isLocked (oid);
    }
}
TOP

Related Classes of org.exolab.castor.persist.LockEngine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.