[jboss-cvs] JBossAS SVN: r61216 - in branches/EJB3_RC9_Patch_1_CP/ejb3: src/main/org/jboss/ejb3/entity and 6 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Wed Mar 7 20:27:53 EST 2007


Author: bdecoste
Date: 2007-03-07 20:27:52 -0500 (Wed, 07 Mar 2007)
New Revision: 61216

Added:
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticJBCCache.java
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticTreeCacheProviderHook.java
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/resources/test/entityoptimisticlocking/
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/resources/test/entityoptimisticlocking/META-INF/
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/resources/test/entityoptimisticlocking/META-INF/persistence.xml
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/test/org/jboss/ejb3/test/entityoptimisticlocking/
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/test/org/jboss/ejb3/test/entityoptimisticlocking/unit/
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/test/org/jboss/ejb3/test/entityoptimisticlocking/unit/EntityUnitTestCase.java
Modified:
   branches/EJB3_RC9_Patch_1_CP/ejb3/build-test.xml
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/JBCCache.java
   branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/TreeCacheProviderHook.java
Log:
[EJBTHREE-844] optimistic locking and patch for NPE

Modified: branches/EJB3_RC9_Patch_1_CP/ejb3/build-test.xml
===================================================================
--- branches/EJB3_RC9_Patch_1_CP/ejb3/build-test.xml	2007-03-08 01:11:00 UTC (rev 61215)
+++ branches/EJB3_RC9_Patch_1_CP/ejb3/build-test.xml	2007-03-08 01:27:52 UTC (rev 61216)
@@ -1916,6 +1916,22 @@
          </fileset>
       </jar>
    </target>
+
+   <target name="entityoptimisticlocking"
+      description="Builds all jar files."
+      depends="compile-classes">
+
+      <mkdir dir="${build.lib}"/>
+
+      <jar jarfile="${build.lib}/entityoptimisticlocking-test.jar">
+         <fileset dir="${build.classes}">
+            <include name="org/jboss/ejb3/test/entity/*.class"/>
+         </fileset>
+         <fileset dir="${resources}/test/entityoptimisticlocking">
+            <include name="META-INF/persistence.xml"/>
+         </fileset>
+      </jar>
+   </target>
    
    <target name="mdbtransactions"
       description="Builds all jar files."
@@ -2638,7 +2654,7 @@
       </jar>
    </target>
    
-   <target name="jars" depends="arjuna, mdbtransactions, unauthenticatedprincipal, clusteredservice, invoker, classloader, 
+   <target name="jars" depends="entityoptimisticlocking, arjuna, mdbtransactions, unauthenticatedprincipal, clusteredservice, invoker, classloader, 
       circulardependency, jsp, timerdependency, servicedependency, servlet, stateless14, webservices, ejbthree440, 
       ejbthree454, ejbthree653, ejbthree670, aspectdomain, ejbcontext, schema, mail, scopedclassloader, dependency, 
       securitydomain, enventry, 
@@ -3157,6 +3173,9 @@
          <param name="test" value="entity"/>
       </antcall>
       <antcall target="test" inheritRefs="true">
+         <param name="test" value="entityoptimisticlocking"/>
+      </antcall>
+      <antcall target="test" inheritRefs="true">
          <param name="test" value="manytomany"/>
       </antcall>
       <antcall target="test" inheritRefs="true">

Modified: branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/JBCCache.java
===================================================================
--- branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/JBCCache.java	2007-03-08 01:11:00 UTC (rev 61215)
+++ branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/JBCCache.java	2007-03-08 01:27:52 UTC (rev 61216)
@@ -1,4 +1,24 @@
-//$Id: TreeCache.java 9965 2006-05-30 18:00:28Z steve.ebersole at jboss.com $
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
 package org.jboss.ejb3.entity;
 
 import java.util.HashMap;
@@ -67,6 +87,10 @@
                  cache.setFetchInMemoryState(fetchState);              
            }
         }
+        else
+        {
+           log.debug("TreeCache is not configured for region based marshalling");
+        }
 	}
 
 	public Object get(Object key) throws CacheException {
@@ -186,12 +210,11 @@
 
 	public void destroy() throws CacheException {
 		try {
-			// NOTE : evict() operates locally only (i.e., does not propogate
-			// to any other nodes in the potential cluster).  This is
-			// exactly what is needed when we destroy() here; destroy() is used
-			// as part of the process of shutting down a SessionFactory; thus
-			// these removals should not be propogated
-			cache.evict( regionFqn );
+			// NOTE : Hibernate's class uses evict() but that isn't recursive!
+			//cache.evict( regionFqn );
+            Option opt = new Option();
+            opt.setCacheModeLocal(true);
+            cache.remove(regionFqn, opt);
             
             if (cache.getUseRegionBasedMarshalling() && !isSharedClassLoaderRegion(regionName))
             {
@@ -297,6 +320,10 @@
              throw new CacheException("Problem activating region " + regionName, e);
           }
        }
+       else
+       {
+          log.debug("activateCacheRegion(): Region " + fqnString + " is already active");
+       }
     }
     
     private void inactivateCacheRegion() throws CacheException
@@ -314,6 +341,10 @@
           {
              throw new CacheException("Problem activating region " + fqnString, e);
           }
-       }        
+       }     
+       else
+       {
+          log.debug("inactivateCacheRegion(): Region " + fqnString + " is already inactive");
+       }
     }	
 }

Added: branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticJBCCache.java
===================================================================
--- branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticJBCCache.java	                        (rev 0)
+++ branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticJBCCache.java	2007-03-08 01:27:52 UTC (rev 61216)
@@ -0,0 +1,458 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.entity;
+
+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.hibernate.cache.CacheException;
+import org.hibernate.cache.OptimisticCache;
+import org.hibernate.cache.OptimisticCacheSource;
+import org.hibernate.cache.StandardQueryCache;
+import org.hibernate.cache.UpdateTimestampsCache;
+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 org.hibernate.cache.OptimisticTreeCacheProvider for more details
+ *
+ * @author Steve Ebersole
+ * @author Brian Stansberry
+ */
+public class OptimisticJBCCache implements OptimisticCache {
+
+	// todo : eventually merge this with TreeCache and just add optional opt-lock support there.
+
+	private static final Log log = LogFactory.getLog( OptimisticJBCCache.class);
+
+	private static final String ITEM = "item";
+
+	private org.jboss.cache.TreeCache cache;
+	private final String regionName;
+	private final Fqn regionFqn;
+	private OptimisticCacheSource source;
+    private boolean localWritesOnly;
+
+	public OptimisticJBCCache(org.jboss.cache.TreeCache cache, String regionName)
+	throws CacheException {
+		this.cache = cache;
+		this.regionName = regionName;
+		this.regionFqn = Fqn.fromString( regionName.replace( '.', '/' ) );
+        if (cache.getUseRegionBasedMarshalling())
+        {           
+           localWritesOnly = StandardQueryCache.class.getName().equals(regionName);
+           
+           boolean fetchState = cache.getFetchInMemoryState();
+           try
+           {
+              // We don't want a state transfer for the StandardQueryCache,
+              // as it can include classes from multiple scoped classloaders
+              if (localWritesOnly)
+                 cache.setFetchInMemoryState(false);
+              
+              // We always activate
+              activateCacheRegion(regionFqn.toString());
+           }
+           finally
+           {
+              // Restore the normal state transfer setting
+              if (localWritesOnly)
+                 cache.setFetchInMemoryState(fetchState);              
+           }
+        }
+        else
+        {
+           log.debug("TreeCache is not configured for region based marshalling");
+        }
+	}
+
+
+	// OptimisticCache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	public void setSource(OptimisticCacheSource source) {
+		this.source = source;
+	}
+
+	public void writeInsert(Object key, Object value, Object currentVersion) {
+		writeUpdate( key, 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 DataVersionAdapter( currentVersion, previousVersion, source.getVersionComparator(), source.toString() )
+			                 : NonLockingDataVersion.INSTANCE;
+			option.setDataVersion( dv );
+            option.setCacheModeLocal(localWritesOnly);
+			cache.put( new Fqn( regionFqn, key ), ITEM, value, option );
+		}
+		catch ( Exception e ) {
+			throw new CacheException( e );
+		}
+	}
+
+	public void writeLoad(Object key, Object value, Object currentVersion) {
+		try {
+			Option option = new Option();
+			option.setFailSilently( true );
+			option.setDataVersion( NonLockingDataVersion.INSTANCE );
+            option.setCacheModeLocal(localWritesOnly);
+			cache.remove( new Fqn( regionFqn, key ), "ITEM", option );
+
+			option = new Option();
+			option.setFailSilently( true );
+			DataVersion dv = ( source != null && source.isVersioned() )
+			                 ? new DataVersionAdapter( currentVersion, currentVersion, source.getVersionComparator(), source.toString() )
+			                 : NonLockingDataVersion.INSTANCE;
+			option.setDataVersion( dv );
+            option.setCacheModeLocal(localWritesOnly);
+			cache.put( new Fqn( regionFqn, key ), ITEM, value, option );
+		}
+		catch (Exception e) {
+			throw new CacheException(e);
+		}
+	}
+
+
+	// Cache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+	public Object get(Object key) throws CacheException {
+		try {
+			Option option = new Option();
+			option.setFailSilently( true );
+//			option.setDataVersion( NonLockingDataVersion.INSTANCE );
+			return cache.get( new Fqn( regionFqn, key ), ITEM, option );
+		}
+		catch (Exception e) {
+			throw new CacheException(e);
+		}
+	}
+
+	public Object read(Object key) throws CacheException {
+		try {
+			return cache.get( new Fqn( regionFqn, key ), ITEM );
+		}
+		catch (Exception e) {
+			throw new CacheException(e);
+		}
+	}
+
+	public void update(Object key, Object value) throws CacheException {
+		try {
+			Option option = new Option();
+			option.setDataVersion( NonLockingDataVersion.INSTANCE );
+            option.setCacheModeLocal(localWritesOnly);
+			cache.put( new Fqn( regionFqn, key ), ITEM, value, option );
+		}
+		catch (Exception e) {
+			throw new CacheException(e);
+		}
+	}
+
+	public void put(Object key, Object value) throws CacheException {
+		try {
+			log.trace( "performing put() into region [" + regionName + "]" );
+			// do the put outside the scope of the JTA txn
+			Option option = new Option();
+			option.setFailSilently( true );
+			option.setDataVersion( NonLockingDataVersion.INSTANCE );
+            option.setCacheModeLocal(localWritesOnly);
+			cache.put( new Fqn( regionFqn, 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 key) throws CacheException {
+		try {
+			// tree cache in optimistic mode seems to have as very difficult
+			// time with remove calls on non-existent nodes (NPEs)...
+			if ( cache.get( new Fqn( regionFqn, key ), ITEM ) != null ) {
+				Option option = new Option();
+				option.setDataVersion( NonLockingDataVersion.INSTANCE );
+                option.setCacheModeLocal(localWritesOnly);
+				cache.remove( new Fqn( regionFqn, 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.setDataVersion( NonLockingDataVersion.INSTANCE );
+            option.setCacheModeLocal(localWritesOnly);
+			cache.remove( regionFqn, option );
+		}
+		catch (Exception e) {
+			throw new CacheException(e);
+		}
+	}
+
+	public void destroy() throws CacheException {
+		try {
+			Option option = new Option();
+			option.setCacheModeLocal( true );
+			option.setFailSilently( true );
+			option.setDataVersion( NonLockingDataVersion.INSTANCE );
+			cache.remove( regionFqn, option );
+            
+            if (cache.getUseRegionBasedMarshalling() && !isSharedClassLoaderRegion(regionName))
+            {
+               inactivateCacheRegion();
+            }
+		}
+		catch( Exception e ) {
+			throw new CacheException( e );
+		}
+	}
+
+	public void lock(Object key) throws CacheException {
+		throw new UnsupportedOperationException( "TreeCache is a fully transactional cache" + regionName );
+	}
+
+	public void unlock(Object key) throws 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.getChildrenNames( regionFqn );
+			return children == null ? 0 : 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.getChildrenNames( regionFqn );
+			if (childrenNames != null) {
+				Iterator iter = childrenNames.iterator();
+				while ( iter.hasNext() ) {
+					Object key = iter.next();
+					result.put(
+							key,
+					        cache.get( new Fqn( regionFqn, key ), ITEM )
+						);
+				}
+			}
+			return result;
+		}
+		catch (Exception e) {
+			throw new CacheException(e);
+		}
+	}
+
+	public String toString() {
+		return "OptimisticTreeCache(" + regionName + ')';
+	}
+       
+    private boolean isSharedClassLoaderRegion(String regionName)
+    {
+       return (StandardQueryCache.class.getName().equals(regionName) 
+                || UpdateTimestampsCache.class.getName().equals(regionName));
+    }
+    
+    private void activateCacheRegion(String regionName) throws CacheException
+    {
+       String fqnString = regionFqn.toString();
+       // FIXME -- find a way that doesn't involve this API
+       if (cache.getMarshaller().isInactive(fqnString))
+       {
+          try
+          {
+             // Only register the classloader if it's not a shared region.  
+             // If it's shared, no single classloader is valid
+             if (!isSharedClassLoaderRegion(regionName))
+             {
+                cache.registerClassLoader(fqnString, Thread.currentThread().getContextClassLoader());
+             }
+             cache.activateRegion(fqnString);
+          }
+          catch (Exception e)
+          {
+             throw new CacheException("Problem activating region " + regionName, e);
+          }
+       }
+       else
+       {
+          log.debug("activateCacheRegion(): Region " + fqnString + " is already active");
+       }
+    }
+    
+    private void inactivateCacheRegion() throws CacheException
+    {
+       String fqnString = regionFqn.toString();
+       // FIXME -- find a way that doesn't involve this API
+       if (!cache.getMarshaller().isInactive(fqnString))
+       {
+          try
+          {
+             cache.inactivateRegion(fqnString);
+             cache.unregisterClassLoader(fqnString);
+          }
+          catch (Exception e)
+          {
+             throw new CacheException("Problem activating region " + fqnString, e);
+          }
+       }     
+       else
+       {
+          log.debug("inactivateCacheRegion(): Region " + fqnString + " is already inactive");
+       }
+    }   
+
+	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;
+				}
+			}
+            
+            if (currentVersion == null)
+            {
+               // If the workspace node has null as well, OK; if not we've
+               // been modified in a non-comparable manner, which we have to
+               // treat as us being newer 
+               return (other.previousVersion != null);
+            }
+            
+			return versionComparator.compare( currentVersion, 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" );
+		}
+	}
+}

Added: branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticTreeCacheProviderHook.java
===================================================================
--- branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticTreeCacheProviderHook.java	                        (rev 0)
+++ branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/OptimisticTreeCacheProviderHook.java	2007-03-08 01:27:52 UTC (rev 61216)
@@ -0,0 +1,39 @@
+/*
+ * JBoss, the OpenSource J2EE webOS
+ * 
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jboss.ejb3.entity;
+
+import java.util.Properties;
+
+import org.hibernate.cache.CacheException;
+
+/**
+ * Trivial {@link TreeCacheProviderHook} subclass that logs a warning in
+ * {@link #start(Properties) start} if the underlying JBoss Cache 
+ * is not configured for optimistic locking.  Like the superclass,
+ * will provide working Cache implementations whether JBoss Cache is
+ * configured for optimistic locking or not; the only added behavior
+ * is the logging of the warning if the JBoss Cache configuration doesn't 
+ * match the intent implied by the use of this class.
+ * 
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * @author Brian Stansberry
+ */
+public class OptimisticTreeCacheProviderHook 
+   extends TreeCacheProviderHook 
+{
+   public void start(Properties properties) throws CacheException
+   {
+      super.start(properties);
+         
+      if (isOptimistic() == false)
+      {
+         log.warn("JBoss Cache is not configured for optimistic locking; " +
+         "provided Cache implementations therefore will not implement OptimisticCache");
+      }
+   }
+
+}

Modified: branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/TreeCacheProviderHook.java
===================================================================
--- branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/TreeCacheProviderHook.java	2007-03-08 01:11:00 UTC (rev 61215)
+++ branches/EJB3_RC9_Patch_1_CP/ejb3/src/main/org/jboss/ejb3/entity/TreeCacheProviderHook.java	2007-03-08 01:27:52 UTC (rev 61216)
@@ -30,37 +30,69 @@
 import org.hibernate.cache.CacheProvider;
 import org.jboss.cache.TreeCacheMBean;
 import org.jboss.ejb3.tx.TxUtil;
+import org.jboss.logging.Logger;
 import org.jboss.mx.util.MBeanProxyExt;
 import org.jboss.mx.util.MBeanServerLocator;
 
 /**
- * Support for a standalone JBossCache (TreeCache) instance.  The JBossCache is configured
- * via a local config resource.
- *
+ * Support for integration as a 2nd level cache with an already existing 
+ * JBoss Cache (TreeCache) instance.  The ObjectName of the cache is 
+ * provided via the <code>hibernate.treecache.mbean.object_name</code>
+ * configuration property.
+ * <p/>
+ * This class supports both optimistic and pessimistic locking, providing
+ * instances of <code>org.hibernate.cache.OptimisticCache</code> if the 
+ * underlying JBoss Cache is configured for optimistic locking.
+ * 
  * @author Gavin King
+ * @author Brian Stansberry
  */
 public class TreeCacheProviderHook implements CacheProvider
 {
-
+   /**
+    * Name of the Hibernate configuration property used to provide
+    * the ObjectName of the JBoss Cache instance.
+    */
+   public static final String HIBERNATE_CACHE_OBJECT_NAME_PROPERTY = 
+      "hibernate.treecache.mbean.object_name";
+   
+   /**
+    * Default ObjectName for the JBoss Cache instance that will be used
+    * if {@link HIBERNATE_CACHE_OBJECT_NAME_PROPERTY} is not provided.
+    */
+   public static final String DEFAULT_MBEAN_OBJECT_NAME = "jboss.cache:service=EJB3EntityTreeCache";
+   
+   protected Logger log = Logger.getLogger(getClass());
+   
    private org.jboss.cache.TreeCache cache;
-
+   private boolean optimistic;
+   
    /**
     * Construct and configure the Cache representation of a named cache region.
     *
     * @param regionName the name of the cache region
     * @param properties configuration settings
-    * @return The Cache representation of the named cache region.
+    * @return The Cache representation of the named cache region.  If the
+    *         underlying JBoss Cache is configured for optimistic locking,
+    *         the returned object will also implement org.hibernate.cache.OptimisticCache.
     * @throws org.hibernate.cache.CacheException
     *          Indicates an error building the cache region.
     */
    public Cache buildCache(String regionName, Properties properties) throws CacheException
    {
-      return new JBCCache(cache, regionName, TxUtil.getTransactionManager());
+      if (optimistic)
+      {
+         return new OptimisticJBCCache(cache, regionName);
+      }
+      else
+      {
+         return new JBCCache(cache, regionName, TxUtil.getTransactionManager());
+      }
    }
 
    public boolean isMinimalPutsEnabledByDefault()
    {
-      return false;
+      return true;
    }
 
    public long nextTimestamp()
@@ -69,9 +101,12 @@
    }
 
    /**
-    * Prepare the underlying JBossCache TreeCache instance.
+    * Find the underlying JBoss Cache TreeCache instance.
     *
-    * @param properties All current config settings.
+    * @param properties  All current config settings. 
+    *                    If {@link #HIBERNATE_CACHE_OBJECT_NAME_PROPERTY} is provided,
+    *                    the value will be the expected name of the cache; otherwise
+    *                    {@link #DEFAULT_MBEAN_OBJECT_NAME} will be used.
     * @throws org.hibernate.cache.CacheException
     *          Indicates a problem preparing cache for use.
     */
@@ -79,9 +114,20 @@
    {
       try
       {
-         ObjectName mbeanObjectName = new ObjectName((String) properties.get("hibernate.treecache.mbean.object_name"));
+         String cacheName = (String) properties.get(HIBERNATE_CACHE_OBJECT_NAME_PROPERTY);
+         if (cacheName == null)
+         {
+            cacheName = DEFAULT_MBEAN_OBJECT_NAME;
+         }
+         ObjectName mbeanObjectName = new ObjectName(cacheName);
          TreeCacheMBean mbean = (TreeCacheMBean) MBeanProxyExt.create(TreeCacheMBean.class, mbeanObjectName, MBeanServerLocator.locateJBoss());
          cache = mbean.getInstance();
+         if ("OPTIMISTIC".equals(cache.getNodeLockingScheme()))
+         {
+            optimistic = true;
+            log.debug("JBoss Cache is configured for optimistic locking; " +
+                    "provided Cache implementations will also implement OptimisticCache");
+         }
       }
       catch (Exception e)
       {
@@ -92,5 +138,10 @@
    public void stop()
    {
    }
+   
+   public boolean isOptimistic()
+   {
+      return optimistic;
+   }
 
 }

Added: branches/EJB3_RC9_Patch_1_CP/ejb3/src/resources/test/entityoptimisticlocking/META-INF/persistence.xml
===================================================================
--- branches/EJB3_RC9_Patch_1_CP/ejb3/src/resources/test/entityoptimisticlocking/META-INF/persistence.xml	                        (rev 0)
+++ branches/EJB3_RC9_Patch_1_CP/ejb3/src/resources/test/entityoptimisticlocking/META-INF/persistence.xml	2007-03-08 01:27:52 UTC (rev 61216)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence>
+   <persistence-unit name="tempdb">
+      <jta-data-source>java:/DefaultDS</jta-data-source>
+      <properties>
+          <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
+          <property name="hibernate.cache.provider_class" value="org.jboss.ejb3.entity.OptimisticTreeCacheProviderHook"/>
+          <property name="hibernate.treecache.mbean.object_name" value="jboss.cache:service=EJB3EntityTreeCache"/>
+      </properties>
+   </persistence-unit>
+</persistence>

Added: branches/EJB3_RC9_Patch_1_CP/ejb3/src/test/org/jboss/ejb3/test/entityoptimisticlocking/unit/EntityUnitTestCase.java
===================================================================
--- branches/EJB3_RC9_Patch_1_CP/ejb3/src/test/org/jboss/ejb3/test/entityoptimisticlocking/unit/EntityUnitTestCase.java	                        (rev 0)
+++ branches/EJB3_RC9_Patch_1_CP/ejb3/src/test/org/jboss/ejb3/test/entityoptimisticlocking/unit/EntityUnitTestCase.java	2007-03-08 01:27:52 UTC (rev 61216)
@@ -0,0 +1,210 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.ejb3.test.entityoptimisticlocking.unit;
+
+import java.util.Set;
+import org.jboss.ejb3.test.entity.Address;
+import org.jboss.ejb3.test.entity.Company;
+import org.jboss.ejb3.test.entity.Customer;
+import org.jboss.ejb3.test.entity.EntityTest;
+import org.jboss.ejb3.test.entity.FieldAddress;
+import org.jboss.ejb3.test.entity.FieldCompany;
+import org.jboss.ejb3.test.entity.FieldCustomer;
+import org.jboss.ejb3.test.entity.FieldFlight;
+import org.jboss.ejb3.test.entity.FieldTicket;
+import org.jboss.ejb3.test.entity.Flight;
+import org.jboss.ejb3.test.entity.Ticket;
+import org.jboss.test.JBossTestCase;
+import junit.framework.Test;
+
+/**
+ * Sample client for the jboss container.
+ *
+ * @author <a href="mailto:bill at burkecentral.com">Bill Burke</a>
+ * @version $Id: EntityUnitTestCase.java 57207 2006-09-26 12:06:13Z dimitris at jboss.org $
+ */
+
+public class EntityUnitTestCase
+extends JBossTestCase
+{
+   org.apache.log4j.Category log = getLog();
+
+   static boolean deployed = false;
+   static int test = 0;
+
+   public EntityUnitTestCase(String name)
+   {
+
+      super(name);
+
+   }
+
+   public void testOneToMany() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      Customer c = test.oneToManyCreate();
+      assertNotNull(c);
+      assertNotNull(c.getTickets());
+      Set<Ticket> tickets = c.getTickets();
+      assertTrue(tickets.size() > 0);
+
+      // test find
+      c = test.findCustomerById(c.getId());
+      assertNotNull(c);
+      assertNotNull(c.getTickets());
+      tickets = c.getTickets();
+      assertTrue(tickets.size() > 0);
+
+      // test 1-1
+      Address address = c.getAddress();
+      assertTrue(address != null);
+      assertTrue(address.getCity().equals("Boston"));
+   }
+
+   public void testManyToOne() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      Flight f = test.manyToOneCreate();
+      f = test.findFlightById(f.getId());
+      assertTrue(f.getName().equals("AF0101"));
+      assertTrue(f.getCompany().getName().equals("Air France"));
+
+      Company c = test.findCompanyById(f.getCompany().getId());
+      assertTrue(c != null);
+      assertTrue(c.getFlights().size() == 1);
+   }
+
+   public void testManyToMany() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      test.manyToManyCreate();
+
+      Flight one = test.findFlightById(new Long(1));
+      assertTrue(one.getCompany().getName().equals("Air France"));
+
+      Flight two = test.findFlightById(new Long(2));
+      assertTrue(two.getCompany().getName().equals("USAir"));
+
+      System.out.println("Air France customers");
+      for (Customer c : one.getCustomers())
+      {
+         System.out.println(c.getName());
+
+      }
+      System.out.println("USAir customers");
+
+      for (Customer c : two.getCustomers())
+      {
+         System.out.println(c.getName());
+      }
+
+   }
+
+   public void testFieldOneToMany() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      FieldCustomer c = test.fieldOneToManyCreate();
+      assertNotNull(c);
+      assertNotNull(c.getTickets());
+      Set<FieldTicket> tickets = c.getTickets();
+      assertTrue(tickets.size() > 0);
+
+      // test find
+      c = test.fieldFindCustomerById(c.getId());
+      assertNotNull(c);
+      assertNotNull(c.getTickets());
+      tickets = c.getTickets();
+      assertTrue(tickets.size() > 0);
+
+      // test 1-1
+      FieldAddress address = c.getAddress();
+      assertTrue(address != null);
+      assertTrue(address.getCity().equals("Boston"));
+   }
+
+   public void testFieldManyToOne() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      FieldFlight f = test.fieldManyToOneCreate();
+      f = test.fieldFindFlightById(f.getId());
+      assertTrue(f.getName().equals("AF0101"));
+      assertTrue(f.getCompany().getName().equals("Air France"));
+
+      FieldCompany c = test.fieldFindCompanyById(f.getCompany().getId());
+      assertTrue(c != null);
+      assertTrue(c.getFlights().size() == 1);
+   }
+
+   public void testFieldManyToMany() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      test.fieldManyToManyCreate();
+
+      FieldFlight one = test.fieldFindFlightById(new Long(1));
+      assertTrue(one.getCompany().getName().equals("Air France"));
+
+      FieldFlight two = test.fieldFindFlightById(new Long(2));
+      assertTrue(two.getCompany().getName().equals("USAir"));
+
+      System.out.println("Air France customers");
+      for (FieldCustomer c : one.getCustomers())
+      {
+         System.out.println(c.getName());
+
+      }
+      System.out.println("USAir customers");
+
+      for (FieldCustomer c : two.getCustomers())
+      {
+         System.out.println(c.getName());
+      }
+
+   }
+
+   public void testNamedQueries() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      test.testNamedQueries();      
+   }
+
+   public void testOutsideTx() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      test.testOutsideTransaction();      
+
+   }
+   
+   public void testFlush() throws Exception
+   {
+      EntityTest test = (EntityTest) this.getInitialContext().lookup("EntityTestBean/remote");
+      Customer c = test.createCustomer("Emmanuel");
+	  test.changeCustomer(c.getId(), "Bill");
+	  Customer c2 = test.loadCustomer(c.getId());
+	  assertEquals("Bill", c2.getName());
+   }
+
+   public static Test suite() throws Exception
+   {
+      return getDeploySetup(EntityUnitTestCase.class, "entityoptimisticlocking-test.jar");
+   }
+
+}




More information about the jboss-cvs-commits mailing list