[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