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,v 1.1.1.1 2003/03/03 07:08:44 kvisco Exp $
*/


package org.exolab.castor.persist;

import java.util.Vector;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Enumeration;
import java.util.Iterator;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.Persistent;
import org.exolab.castor.jdo.TimeStampable;
import org.exolab.castor.jdo.ObjectNotFoundException;
import org.exolab.castor.jdo.LockNotGrantedException;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.ClassNotPersistenceCapableException;
import org.exolab.castor.jdo.DuplicateIdentityException;
import org.exolab.castor.jdo.ClassNotPersistenceCapableException;
import org.exolab.castor.jdo.LockNotGrantedException;
import org.exolab.castor.jdo.ObjectDeletedException;
import org.exolab.castor.jdo.ObjectModifiedException;
import org.exolab.castor.jdo.TransactionAbortedException;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.MappingResolver;
import org.exolab.castor.mapping.AccessMode;
import org.exolab.castor.mapping.loader.MappingLoader;
import org.exolab.castor.persist.spi.Persistence;
import org.exolab.castor.persist.spi.PersistenceFactory;
import org.exolab.castor.persist.spi.LogInterceptor;
import org.exolab.castor.util.Messages;


/**
* 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 LRU} 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.
*
*
* @author <a href="arkin@intalio.com">Assaf Arkin</a>
* @author <a href="yip@intalio.com">Thomas Yip</a>
* @version $Revision: 1.1.1.1 $ $Date: 2003/03/03 07:08:44 $
*/
public final class LockEngine {


    /**
     * IMPLEMENTATION NOTES:
     *
     * 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.
     *
     * Each class hierarchy gets its own cache, so caches can be
     * controlled on a class-by-class basis.
     *
     */


    /**
     * 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();

   
    /**
     * Used by the constructor when creating handlers to temporarily
     * hold the persistence factory for use by {@link #addClassMolder}.
     */
    private PersistenceFactory _factory;


    /**
     * The log interceptor used to trace persistence operations. May be null.
     */
    private LogInterceptor     _logInterceptor;


    /**
     * Construct a new cache engine with the specified mapping table,
     * persistence engine and the log interceptor.
     *
     * @param mapResolver Provides mapping information for objects
     *  supported by this cache
     * @param factory Factory for creating persistence engines for each
     *  object described in the map
     * @param logInterceptor Log interceptor to use for cache and all its
     *  persistence engines
     * @throws MappingException Indicate that one of the mappings is
     *  invalid
     */
    LockEngine( MappingResolver mapResolver, PersistenceFactory factory,
            LogInterceptor logInterceptor )
            throws MappingException {

        try {
            Vector v = ClassMolder.resolve( (MappingLoader) mapResolver, this, factory, logInterceptor );
   
            _typeInfo = new HashMap();
            Enumeration enum = v.elements();

            HashSet processedClasses = new HashSet();
            HashSet freshClasses = new HashSet();
            // copy things into an arraylist
            while ( enum.hasMoreElements() )
                freshClasses.add( enum.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 LRU for the base type
                        LRU lru = LRU.create( molder.getCacheType(), molder.getCacheParam() );
                        TypeInfo info = new TypeInfo( molder, new HashMap(), lru );
                        _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();
                    _logInterceptor.message("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!");
            }
            /*
            while ( enum.hasMoreElements() ) {
                molder = (ClassMolder) enum.nextElement();
                if ( molder.getExtends() != null ) {
                    ClassMolder extend = molder.getExtends();
                    while ( extend.getExtends() != null ) {
                        extend = extend.getExtends();
                    }
                    // ssa, FIXME : Is that part still necessary ?
//                    if ( _typeInfo.containsKey( extend.getName() ) ) {
                    if ( false ) {
                        baseInfo = (TypeInfo)_typeInfo.get( extend.getName() );
                        _typeInfo.put( molder.getName(), baseInfo );
                    } else {
                        waitingForBase.add( molder );
                    }
                } else {
                    LRU lru = LRU.create( molder.getCacheType(), molder.getCacheParam() );

                    info = new TypeInfo( molder, new HashMap(), lru );

                    _typeInfo.put( molder.getName(), info );
                }
            }
            // we then iterate through all extended classes in which the
            // using the base typeInfo.
            enum = waitingForBase.elements();
            while ( enum.hasMoreElements() ) {
                molder = (ClassMolder) enum.nextElement();
                ClassMolder extend = molder.getExtends();
                while ( extend.getExtends() != null ) {
                    extend = extend.getExtends();
                }
                baseInfo = (TypeInfo) _typeInfo.get( extend.getName() );
                if ( baseInfo != null ) {
                    info = new TypeInfo( molder, baseInfo );
                    _typeInfo.put( molder.getName(), info );
                } else {
                    throw new MappingException("Base class "+extend.getName()+" of "+molder.getName()+" not found!");
                }
            } */

            _logInterceptor = logInterceptor;
   
            _factory = factory;
        } catch ( ClassNotFoundException e ) {
            throw new MappingException("Declared Class not found!" );
        }
    }

    /**
     * Get classMolder which represents the given java data object class
     * Dependent class will not be returned to avoid persistenting
     * a dependent class without
     */
    public ClassMolder getClassMolder( Class cls ) {
        TypeInfo info = (TypeInfo)_typeInfo.get( cls.getName() );
        if ( info != null ) {
            if ( !info.molder.isDependent() )
                return info.molder;
        }
        return null;
    }

    public Persistence getPersistence( 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 object The type of the object to load
     * @param accessMode 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
     */
    public OID load( TransactionContext tx, OID oid, Object object, AccessMode suggestedAccessMode, int timeout )
            throws ObjectNotFoundException, LockNotGrantedException, PersistenceException,
            ClassNotPersistenceCapableException, ObjectDeletedWaitingForLockException {
        return load( tx, oid, object, suggestedAccessMode, timeout, null );
    }

    public OID load( TransactionContext tx, OID oid, Object object, AccessMode suggestedAccessMode, int timeout, QueryResults results )
            throws ObjectNotFoundException, LockNotGrantedException, PersistenceException,
            ClassNotPersistenceCapableException, ObjectDeletedWaitingForLockException {

        OID        lockedOid;
        ObjectLock lock;
        TypeInfo   typeInfo;
        Object[]   fields;
        boolean    write;
        boolean    succeed;
        short      action;

        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 );

        succeed = false;

        lock = null;

        try {

            if ( accessMode == AccessMode.Exclusive || accessMode == AccessMode.DbLocked )
                action = ObjectLock.ACTION_WRITE;
            else
                action = ObjectLock.ACTION_READ;

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

            lockedOid = lock.getOID();

            Object stamp = typeInfo.molder.load( tx, lockedOid, lock, object, suggestedAccessMode, results );
            // proposal change: lockedOid parameter is not really neccesary.
            // we can added getOID() method in DepositBox. It make code a little
            // bit clear?
            // And should ClassMolder the one who set stamp?

            succeed = true;

            lockedOid.setStamp( stamp );

            if ( lockedOid != null )
                oid = lockedOid;

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

    /**
     * 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
     * @return The object's OID
     */
    public void markCreate( TransactionContext tx, OID oid, Object object )
            throws PersistenceException, LockNotGrantedException {

        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( TransactionContext tx, OID oid, Object object )
            throws DuplicateIdentityException, PersistenceException,
            ClassNotPersistenceCapableException {

        TypeInfo typeInfo;
        ObjectLock lock;
        Object[] fields;
        OID lockedOid;
        OID newoid;
        boolean succeed;

        typeInfo = (TypeInfo) _typeInfo.get( object.getClass().getName() );
        if ( typeInfo == null )
            throw new ClassNotPersistenceCapableException( Messages.format( "persist.classNotPersistenceCapable", object.getClass().getName()) );
           
        boolean write = true;   // just for readability

        lock = null;

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

            lock = null;

            succeed = false;

            try {

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

                if ( _logInterceptor != null )
                    _logInterceptor.creating( typeInfo.molder.getName(), oid.getIdentity() );

                oid = lock.getOID();

                typeInfo.molder.create( tx, oid, lock, object );

                succeed = true;

                oid.setDbLock( true );

                return oid;
                // 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(),
                    oid.getIdentity() ) );
            } 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 );
            }
        } else {    // identity is null

            succeed = false;

            try {
                if ( _logInterceptor != null )
                    _logInterceptor.creating( typeInfo.molder.getName(), oid.getIdentity() );

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

                oid = lock.getOID();

                Object newids = typeInfo.molder.create( tx, oid, lock, object );
                succeed = true;

                oid.setDbLock( true );

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

                typeInfo.rename( oid, 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
     * @param object The object type
     * @throws PersistenceException An error reported by the
     *  persistence engine
     */
    public void delete( TransactionContext tx, OID oid, Object object )
            throws PersistenceException {
        ObjectLock lock;
        TypeInfo   typeInfo;
        Object[]   fields;

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

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

            if ( _logInterceptor != null )
                _logInterceptor.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( TransactionContext tx, OID oid, Object object, int timeout )
            throws PersistenceException, LockNotGrantedException {

        ObjectLock lock;
        TypeInfo   typeInfo;
        Object[]   fields;

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

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

        typeInfo.molder.markDelete( tx, oid, lock, object );
    }


    /**
     * 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 accessMode 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.
     */
    public boolean update( TransactionContext tx, OID oid, Object object, AccessMode suggestedAccessMode, int timeout )
            throws ObjectNotFoundException, LockNotGrantedException, ObjectModifiedException,
                   PersistenceException, ClassNotPersistenceCapableException,
                   ObjectDeletedWaitingForLockException {

        TypeInfo   typeInfo;
        Object   identity;
        ObjectLock lock;
        boolean    succeed;
        // [oleg] these variables are not used
        //boolean    write;
        //AccessMode accessMode;

        // If the object is new, don't try to load it from the cache

        typeInfo = (TypeInfo) _typeInfo.get( oid.getName() );
        if ( typeInfo == null )
            throw new ClassNotPersistenceCapableException( Messages.format("persist.classNotPersistenceCapable", oid.getName() ) );

        //accessMode = typeInfo.molder.getAccessMode( suggestedAccessMode );
        //write = ( accessMode == AccessMode.Exclusive || accessMode == AccessMode.DbLocked );
        succeed = false;
        lock = null;
        try {
            // Create an OID to represent the object and see if we
            // have a lock (i.e. object is cached).

            // Object has been loaded before, must acquire lock
            // on it (write in exclusive mode)

            // [Yip] I rather limited update to always acquire read lock
            // (preferrably dblock), to avoid further concurrency problem.
            lock = typeInfo.acquire( oid, tx, ObjectLock.ACTION_UPDATE, timeout );

            /*
            if ( write && ! oid.isDbLock() ) {
                // Db-lock mode we always synchronize the object with
                // the database and obtain a lock on the object.
                if ( _logInterceptor != null )
                    _logInterceptor.loading( typeInfo.javaClass, OID.flatten( oid.getIdentities() ) );
            }*/
            oid = lock.getOID();

            boolean creating = typeInfo.molder.update( tx, oid, lock, object, suggestedAccessMode );

            if ( creating )
                succeed = false;
            else
                succeed = true;

            return creating;

            /*
            if ( accessMode == AccessMode.DbLocked )
                oid.setDbLock( true );
             */
            /*
            if ( accessMode == AccessMode.ReadOnly )
                typeInfo.release( oid, tx );
            */
        } catch ( ObjectModifiedException e ) {
            throw e;
        } catch ( ObjectDeletedWaitingForLockException except ) {
            // This is equivalent to object not existing
            throw new ObjectNotFoundException( Messages.format("persist.objectNotFound", oid.getName(), oid.getIdentity()) );
        } 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( TransactionContext tx, OID oid, Object object, int timeout )
            throws LockNotGrantedException, PersistenceException {


        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.

        oid = new OID( this, typeInfo.molder, oid.getIdentity() );

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

            oid = lock.getOID();

            modified = typeInfo.molder.preStore( tx, oid, 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 oid;
        else
            return null;
    }

    public void store( TransactionContext tx, OID oid, Object object )
            throws LockNotGrantedException, ObjectDeletedException,
            ObjectModifiedException, DuplicateIdentityException,
            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 ( _logInterceptor != null )
                _logInterceptor.storing( 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( TransactionContext tx, OID oid, int timeout )
            throws ObjectDeletedException, LockNotGrantedException, PersistenceException {

        ObjectLock lock;
        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( TransactionContext tx, OID oid, int timeout )
            throws LockNotGrantedException {
        ObjectLock lock;
        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( TransactionContext tx, OID oid, 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( TransactionContext tx, OID oid, Object object ) {
        TypeInfo   typeInfo;
        Object[]   fields;
        ObjectLock lock;

        typeInfo = (TypeInfo) _typeInfo.get( oid.getName() );
        try {
            lock = typeInfo.assure( oid, tx, true );
            typeInfo.molder.updateCache( tx, oid, lock, object );
        } catch ( LockNotGrantedException e ) {
            throw new IllegalStateException("Write Lock expected!");
        } catch ( PersistenceException except ) {
            typeInfo.delete( oid, tx );
        }
    }

    /**
     * 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( TransactionContext tx, 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( TransactionContext tx, OID oid ) {
        ObjectLock lock;
        Object[]   fields;
        TypeInfo   typeInfo;

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

            typeInfo.release( oid, tx );
        } catch ( LockNotGrantedException except ) {
            // If this transaction has no write lock on the object,
            // something went foul.
            if ( _logInterceptor != null )
                _logInterceptor.message( Messages.format( "persist.internal", "forgetObject: " + except.toString() ) );
            throw new IllegalStateException( except.toString() );
        }
    }

    /**
     * Returns an association between Xid and transactions contexts.
     * The association is shared between all transactions open against
     * this cache engine through the XAResource interface.
     */
    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 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 LRU cache;

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

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

        /**
         * Acquire the object lock for transaction. After this method is call,
         * user must call {@link ObjectLock.confirm} exactly once.
         *
         * @param oid  the OID of the lock
         * @param tx   the transactionContext 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
         */
        private ObjectLock acquire( OID oid, TransactionContext tx, short lockAction,
                int timeout ) throws ObjectDeletedWaitingForLockException,
                LockNotGrantedException, ObjectDeletedException {
            ObjectLock entry = null;
            boolean newentry = false;
            boolean failed = true;
            // 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 ) {
                entry = (ObjectLock) locks.get( oid );
                if ( entry == null ) {
                    entry = (ObjectLock) cache.remove( oid );
                    if ( entry != null ) {
                        OID cacheOid = entry.getOID();

                        if (oid.getName().equals(cacheOid.getName())) {
                          entry.setOID(oid);
                          locks.put( oid, entry );
                        } else {
                            entry = null;
                        }
                    }
                }
                if ( entry == null ) {
                    newentry = true;
                    entry = new ObjectLock( oid );
                    locks.put( oid, entry );
                } else {
                    oid = entry.getOID();
                }
                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.
            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( oid );
                            cache.put( oid, entry );
                        }
                    }
                }
            }
        }

        /**
         * Upgrade the lock to write lock.

         * @param  oid  the OID of the lock
         * @param  tx   the transaction in action
         * @param  timeout  time limit
         */
        private ObjectLock upgrade( OID oid, TransactionContext tx, int timeout )
                throws ObjectDeletedWaitingForLockException, LockNotGrantedException {
            ObjectLock entry = null;
            synchronized ( locks ) {
                entry = (ObjectLock) locks.get( oid );
                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 "+oid+"!");   
                oid = entry.getOID();
                entry.enter();
            }
            try {
                entry.upgrade( tx, timeout );
                return entry;
            } finally {
                synchronized ( locks ) {
                    entry.leave();
                }
            }
        }

        /**
         * Reaasure 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  true if we want to upgrade or reassure a write lock
         *                false for read lock
         * @param  timeout  time limit
         *
         */
        private ObjectLock assure( OID oid, TransactionContext tx, boolean write )
                throws ObjectDeletedWaitingForLockException, LockNotGrantedException {

            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
         *
         */
        private ObjectLock rename( OID orgoid, OID newoid, TransactionContext tx )
                throws LockNotGrantedException {

            synchronized( locks ) {
                ObjectLock entry, newentry;
                boolean write;

                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 exsit!");
                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 being 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() );
                newoid.setStamp( orgoid.getStamp() );

                return newentry;
            }

        }

        /**
         * Delete the object lock. It's called after the object is
         * deleted from the persistence and the transaction committed.
         *
         * @param oid is the OID of the ObjectLock
         * @param tx is the transactionContext of transaction in action
         *
         */
        private ObjectLock delete( OID oid, TransactionContext tx ) {

            ObjectLock entry;
            synchronized( locks ) {
                entry = (ObjectLock) locks.get( oid );

                if ( entry == null )
                    throw new IllegalStateException("No lock to destory!");
                entry.enter();
            }

            try {
                entry.delete(tx);
                return entry;
            } finally {
                synchronized( locks ) {
                    entry.leave();
                    if ( entry.isDisposable() ) {
                        locks.remove( oid );
                    }
                }
            }
        }

        /**
         * Release the object lock. It's called after the object is
         * the transaction committed.
         *
         * @param oid is the OID of the ObjectLock
         * @param tx is the transactionContext of transaction in action
         *
         */
        private ObjectLock release( OID oid, TransactionContext tx ) {
            boolean failed = true;
            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);
                failed = false;
                return entry;
            } finally {
                synchronized( locks ) {
                    entry.leave();
                    if ( entry.isDisposable() ) {
                        cache.put( oid, entry );
                        locks.remove( 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.