Open Source Repository

Home /hibernate/hibernate-3.2.4.ga | Repository Home


org/hibernate/cache/OptimisticTreeCache.java
//$Id: OptimisticTreeCache.java 10118 2006-07-13 21:38:41Z [email protected] $
package org.hibernate.cache;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Comparator;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.config.Option;
import org.jboss.cache.lock.TimeoutException;

/**
 * Represents a particular region within the given JBossCache TreeCache
 * utilizing TreeCache's optimistic locking capabilities.
 *
 @see OptimisticTreeCacheProvider for more details
 *
 @author Steve Ebersole
 */
public class OptimisticTreeCache implements OptimisticCache {

  // todo : eventually merge this with TreeCache and just add optional opt-lock support there.

  private static final Log log = LogFactory.getLogOptimisticTreeCache.class);

  private static final String ITEM = "item";

  private org.jboss.cache.TreeCache cache;
  private final String regionName;
  private final Fqn regionFqn;
  private OptimisticCacheSource source;

  public OptimisticTreeCache(org.jboss.cache.TreeCache cache, String regionName)
  throws CacheException {
    this.cache = cache;
    this.regionName = regionName;
    this.regionFqn = Fqn.fromStringregionName.replace'.''/' ) );
  }


  // OptimisticCache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  public void setSource(OptimisticCacheSource source) {
    this.source = source;
  }

  public void writeInsert(Object key, Object value, Object currentVersion) {
    writeUpdatekey, value, currentVersion, null );
  }

  public void writeUpdate(Object key, Object value, Object currentVersion, Object previousVersion) {
    try {
      Option option = new Option();
      DataVersion dv = source != null && source.isVersioned() )
                       new DataVersionAdaptercurrentVersion, previousVersion, source.getVersionComparator(), source.toString() )
                       : NonLockingDataVersion.INSTANCE;
      option.setDataVersiondv );
      cache.putnew FqnregionFqn, key ), ITEM, value, option );
    }
    catch Exception e ) {
      throw new CacheException);
    }
  }

  public void writeLoad(Object key, Object value, Object currentVersion) {
    try {
      Option option = new Option();
      option.setFailSilentlytrue );
      option.setDataVersionNonLockingDataVersion.INSTANCE );
      cache.removenew FqnregionFqn, key )"ITEM", option );

      option = new Option();
      option.setFailSilentlytrue );
      DataVersion dv = source != null && source.isVersioned() )
                       new DataVersionAdaptercurrentVersion, currentVersion, source.getVersionComparator(), source.toString() )
                       : NonLockingDataVersion.INSTANCE;
      option.setDataVersiondv );
      cache.putnew FqnregionFqn, key ), ITEM, value, option );
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }


  // Cache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  public Object get(Object keythrows CacheException {
    try {
      Option option = new Option();
      option.setFailSilentlytrue );
//      option.setDataVersion( NonLockingDataVersion.INSTANCE );
      return cache.getnew FqnregionFqn, key ), ITEM, option );
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public Object read(Object keythrows CacheException {
    try {
      return cache.getnew FqnregionFqn, key ), ITEM );
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void update(Object key, Object valuethrows CacheException {
    try {
      Option option = new Option();
      option.setDataVersionNonLockingDataVersion.INSTANCE );
      cache.putnew FqnregionFqn, key ), ITEM, value, option );
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void put(Object key, Object valuethrows CacheException {
    try {
      log.trace"performing put() into region [" + regionName + "]" );
      // do the put outside the scope of the JTA txn
      Option option = new Option();
      option.setFailSilentlytrue );
      option.setDataVersionNonLockingDataVersion.INSTANCE );
      cache.putnew FqnregionFqn, key ), ITEM, value, option );
    }
    catch (TimeoutException te) {
      //ignore!
      log.debug("ignoring write lock acquisition failure");
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void remove(Object keythrows CacheException {
    try {
      // tree cache in optimistic mode seems to have as very difficult
      // time with remove calls on non-existent nodes (NPEs)...
      if cache.getnew FqnregionFqn, key ), ITEM != null ) {
        Option option = new Option();
        option.setDataVersionNonLockingDataVersion.INSTANCE );
        cache.removenew FqnregionFqn, key ), option );
      }
      else {
        log.trace"skipping remove() call as the underlying node did not seem to exist" );
      }
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void clear() throws CacheException {
    try {
      Option option = new Option();
      option.setDataVersionNonLockingDataVersion.INSTANCE );
      cache.removeregionFqn, option );
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void destroy() throws CacheException {
    try {
      Option option = new Option();
      option.setCacheModeLocaltrue );
      option.setFailSilentlytrue );
      option.setDataVersionNonLockingDataVersion.INSTANCE );
      cache.removeregionFqn, option );
    }
    catchException e ) {
      throw new CacheException);
    }
  }

  public void lock(Object keythrows CacheException {
    throw new UnsupportedOperationException"TreeCache is a fully transactional cache" + regionName );
  }

  public void unlock(Object keythrows CacheException {
    throw new UnsupportedOperationException"TreeCache is a fully transactional cache: " + regionName );
  }

  public long nextTimestamp() {
    return System.currentTimeMillis() 100;
  }

  public int getTimeout() {
    return 600//60 seconds
  }

  public String getRegionName() {
    return regionName;
  }

  public long getSizeInMemory() {
    return -1;
  }

  public long getElementCountInMemory() {
    try {
      Set children = cache.getChildrenNamesregionFqn );
      return children == null : children.size();
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public long getElementCountOnDisk() {
    return 0;
  }

  public Map toMap() {
    try {
      Map result = new HashMap();
      Set childrenNames = cache.getChildrenNamesregionFqn );
      if (childrenNames != null) {
        Iterator iter = childrenNames.iterator();
        while iter.hasNext() ) {
          Object key = iter.next();
          result.put(
              key,
                  cache.getnew FqnregionFqn, key ), ITEM )
            );
        }
      }
      return result;
    }
    catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public String toString() {
    return "OptimisticTreeCache(" + regionName + ')';
  }

  public static class DataVersionAdapter implements DataVersion {
    private final Object currentVersion;
    private final Object previousVersion;
    private final Comparator versionComparator;
    private final String sourceIdentifer;

    public DataVersionAdapter(Object currentVersion, Object previousVersion, Comparator versionComparator, String sourceIdentifer) {
      this.currentVersion = currentVersion;
      this.previousVersion = previousVersion;
      this.versionComparator = versionComparator;
      this.sourceIdentifer = sourceIdentifer;
      log.trace"created " this );
    }

    /**
     * newerThan() call is dispatched against the DataVersion currently
     * associated with the node; the passed dataVersion param is the
     * DataVersion associated with the data we are trying to put into
     * the node.
     <p/>
     * we are expected to return true in the case where we (the current
     * node DataVersion) are newer that then incoming value.  Returning
     * true here essentially means that a optimistic lock failure has
     * occured (because conversely, the value we are trying to put into
     * the node is "older than" the value already there...)
     */
    public boolean newerThan(DataVersion dataVersion) {
      log.trace"checking [" this "] against [" + dataVersion + "]" );
      if dataVersion instanceof CircumventChecksDataVersion ) {
        log.trace"skipping lock checks..." );
        return false;
      }
      else if dataVersion instanceof NonLockingDataVersion ) {
        // can happen because of the multiple ways Cache.remove()
        // can be invoked :(
        log.trace"skipping lock checks..." );
        return false;
      }
      DataVersionAdapter other = DataVersionAdapter dataVersion;
      if other.previousVersion == null ) {
        log.warn"Unexpected optimistic lock check on inserting data" );
        // work around the "feature" where tree cache is validating the
        // inserted node during the next transaction.  no idea...
        if this == dataVersion ) {
          log.trace"skipping lock checks due to same DV instance" );
          return false;
        }
      }
      return versionComparator.comparecurrentVersion, other.previousVersion >= 1;
    }

    public String toString() {
      return super.toString() " [current=" + currentVersion + ", previous=" + previousVersion + ", src=" + sourceIdentifer + "]";
    }
  }

  /**
   * Used in regions where no locking should ever occur.  This includes query-caches,
   * update-timestamps caches, collection caches, and entity caches where the entity
   * is not versioned.
   */
  public static class NonLockingDataVersion implements DataVersion {
    public static final DataVersion INSTANCE = new NonLockingDataVersion();
    public boolean newerThan(DataVersion dataVersion) {
      log.trace"non locking lock check...");
      return false;
    }
  }

  /**
   * Used to signal to a DataVersionAdapter to simply not perform any checks.  This
   * is currently needed for proper handling of remove() calls for entity cache regions
   * (we do not know the version info...).
   */
  public static class CircumventChecksDataVersion implements DataVersion {
    public static final DataVersion INSTANCE = new CircumventChecksDataVersion();
    public boolean newerThan(DataVersion dataVersion) {
      throw new CacheException"optimistic locking checks should never happen on CircumventChecksDataVersion" );
    }
  }
}