[jbosscache-commits] JBoss Cache SVN: r7884 - in core/branches/flat: src/main/java/org/horizon/loader and 3 other directories.

jbosscache-commits at lists.jboss.org jbosscache-commits at lists.jboss.org
Sun Mar 8 17:03:03 EDT 2009


Author: adriancole
Date: 2009-03-08 17:03:03 -0400 (Sun, 08 Mar 2009)
New Revision: 7884

Added:
   core/branches/flat/src/main/java/org/horizon/loader/jdbm/
   core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStore.java
   core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStoreConfig.java
   core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmConnection.java
   core/branches/flat/src/test/java/org/horizon/loader/jdbm/
   core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmCacheStoreTest.java
   core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmLearningTest.java
Modified:
   core/branches/flat/pom.xml
Log:
added jdbm cachestore

Modified: core/branches/flat/pom.xml
===================================================================
--- core/branches/flat/pom.xml	2009-03-08 21:02:07 UTC (rev 7883)
+++ core/branches/flat/pom.xml	2009-03-08 21:03:03 UTC (rev 7884)
@@ -52,6 +52,13 @@
          <version>1.0</version>
          <optional>true</optional>
       </dependency>
+      <!-- needed for jdbm -->
+      <dependency>
+         <groupId>commons-collections</groupId>
+         <artifactId>commons-collections</artifactId>
+         <version>3.2.1</version>
+         <optional>true</optional>
+      </dependency> 
 
       <dependency>
          <groupId>c3p0</groupId>

Copied: core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStore.java (from rev 7868, core/branches/flat/src/main/java/org/horizon/loader/file/FileCacheStore.java)
===================================================================
--- core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStore.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStore.java	2009-03-08 21:03:03 UTC (rev 7884)
@@ -0,0 +1,166 @@
+package org.horizon.loader.jdbm;
+
+import jdbm.btree.BTree;
+import jdbm.helper.Tuple;
+import jdbm.helper.TupleBrowser;
+import org.horizon.Cache;
+import org.horizon.loader.CacheLoaderConfig;
+import org.horizon.loader.CacheLoaderException;
+import org.horizon.loader.StoredEntry;
+import org.horizon.loader.bucket.Bucket;
+import org.horizon.loader.file.FileCacheStore;
+import org.horizon.marshall.Marshaller;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A JDBM (http://jdbm.sourceforge.net) extension of {@link org.horizon.loader.bucket.BucketBasedCacheStore}.  This file
+ * store stores stuff in a {@link BTree} at the following location: <tt>/{location}/cache name/db.db</tt>. The {@link
+ * BTree} is named the same as {@link org.horizon.Cache#getName()}.  {@link Bucket}s are stored inside the {@link BTree}
+ * keyed on {@link org.horizon.loader.bucket.Bucket#getBucketName()}.
+ * <p/>
+ * <p/>
+ * JdbmCacheStore uses {@link Bucket}s as entries in {@Btree} are limited by having <tt>int</tt> primary keys.
+ * <p/>
+ * JdbmCacheStore does not use JDBM transactions and instead relies on the locking mechanisms provided by {@link
+ * org.horizon.loader.LockSupportCacheStore}.
+ *
+ * @author Adrian Cole
+ * @since 1.0
+ */
+public class JdbmCacheStore extends FileCacheStore {
+
+   private BTree btree;
+   private String treeName;
+   private JdbmConnection connection;
+
+   @Override
+   public void init(CacheLoaderConfig config, Cache cache, Marshaller m) {
+      init((JdbmCacheStoreConfig) config, cache, m, new JdbmConnection());
+   }
+
+   public void init(JdbmCacheStoreConfig config, Cache cache, Marshaller m, JdbmConnection connection) {
+      super.init(config, cache, m);
+      this.treeName = cache.getName();
+      this.connection = connection;
+   }
+
+   public Class<? extends CacheLoaderConfig> getConfigurationClass() {
+      return JdbmCacheStoreConfig.class;
+   }
+
+   @Override
+   public void start() throws CacheLoaderException {
+      super.start();
+      open();
+   }
+
+   private void open() throws CacheLoaderException {
+      connection.openEnvironment(getRoot());
+      try {
+         btree = connection.loadOrCreateBTree(treeName);
+      } catch (IOException caught) {
+         throw new CacheLoaderException("Problem loading btree", caught);
+      }
+   }
+
+   @Override
+   public void stop() throws CacheLoaderException {
+      super.stop();
+      close();
+   }
+
+   private void close() throws CacheLoaderException {
+      btree = null;
+      connection.closeEnvironment();
+   }
+
+   @Override
+   protected Set<StoredEntry> loadAllLockSafe() throws CacheLoaderException {
+      Set<StoredEntry> result = new HashSet<StoredEntry>();
+      try {
+         TupleBrowser browser = btree.browse();
+         Tuple tuple = new Tuple();
+         while (browser.getNext(tuple)) {
+            if (tuple.getValue() != null) {
+               Bucket bucket = (Bucket) tuple.getValue();
+               if (bucket != null) {
+                  if (bucket.removeExpiredEntries()) {
+                     saveBucket(bucket);
+                  }
+                  result.addAll(bucket.getStoredEntries());
+               }
+            }
+         }
+      } catch (IOException e) {
+         throw new CacheLoaderException("Problem loading entries", e);
+      }
+      return result;
+   }
+
+   @Override
+   protected void fromStreamLockSafe(ObjectInput objectInput) throws CacheLoaderException {
+      try {
+         close();
+         String name = (String) objectInput.readObject();
+         super.fromStreamLockSafe(objectInput);
+         String oldName = treeName;
+         treeName = name;
+         open();
+         connection.associateBTreeWithName(btree, oldName);
+         treeName = oldName;
+      } catch (Exception e) {
+         throw new CacheLoaderException("Problem loading from stream", e);
+      }
+   }
+
+   @Override
+   protected void toStreamLockSafe(ObjectOutput objectOutput) throws CacheLoaderException {
+      try {
+         close();
+         objectOutput.writeObject(treeName);
+         super.toStreamLockSafe(objectOutput);
+      } catch (IOException e) {
+         throw new CacheLoaderException("Problem generating stream", e);
+      } finally {
+         open();
+      }
+   }
+
+   @Override
+   protected void clearLockSafe() throws CacheLoaderException {
+      try {
+         connection.deleteBTree(btree);
+         btree = connection.createBTree(treeName);
+      } catch (IOException caught) {
+         throw convertToCacheLoaderException("error recreating tree" + treeName, caught);
+      }
+   }
+
+   @Override
+   protected Bucket loadBucket(String bucketName) throws CacheLoaderException {
+      try {
+         return (Bucket) btree.find(bucketName);
+      } catch (IOException caught) {
+         throw convertToCacheLoaderException("error finding bucket " + bucketName, caught);
+      }
+   }
+
+   @Override
+   public void saveBucket(Bucket b) throws CacheLoaderException {
+      try {
+         btree.insert(b.getBucketName(), b, true);
+      } catch (IOException caught) {
+         throw convertToCacheLoaderException("error saving bucket " + b.getBucketName(), caught);
+      }
+   }
+
+   CacheLoaderException convertToCacheLoaderException(String message, Exception caught) {
+      return (caught instanceof CacheLoaderException) ? (CacheLoaderException) caught :
+            new CacheLoaderException(message, caught);
+   }
+}
\ No newline at end of file

Copied: core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStoreConfig.java (from rev 7868, core/branches/flat/src/main/java/org/horizon/loader/file/FileCacheStoreConfig.java)
===================================================================
--- core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStoreConfig.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmCacheStoreConfig.java	2009-03-08 21:03:03 UTC (rev 7884)
@@ -0,0 +1,19 @@
+package org.horizon.loader.jdbm;
+
+import org.horizon.loader.file.FileCacheStoreConfig;
+
+/**
+ * Configures {@link org.horizon.loader.jdbm.JdbmCacheStore}.
+ *
+ * @author Adrian Cole
+ * @see FileCacheStoreConfig
+ * @since 1.0
+ */
+public class JdbmCacheStoreConfig extends FileCacheStoreConfig {
+
+   public JdbmCacheStoreConfig() {
+      super();
+      setLocation("Horizon-JdbmCacheStore");
+      setCacheLoaderClassName(JdbmCacheStore.class.getName());
+   }
+}
\ No newline at end of file

Added: core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmConnection.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmConnection.java	                        (rev 0)
+++ core/branches/flat/src/main/java/org/horizon/loader/jdbm/JdbmConnection.java	2009-03-08 21:03:03 UTC (rev 7884)
@@ -0,0 +1,81 @@
+package org.horizon.loader.jdbm;
+
+import jdbm.RecordManager;
+import jdbm.RecordManagerFactory;
+import jdbm.btree.BTree;
+import org.apache.commons.collections.ComparatorUtils;
+import org.horizon.loader.CacheLoaderException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Properties;
+
+/**
+ * This interface defines the interactons between the {@link org.horizon.loader.jdbm.JdbmCacheStore} and the JDBM {@link
+ * RecordManager}.
+ *
+ * @author Adrian Cole
+ * @since 1.0
+ */
+public class JdbmConnection {
+
+   private RecordManager recman;
+
+   public void openEnvironment(File root) throws CacheLoaderException {
+      System.out.println("Opening environment in: " + root);
+      try {
+         recman = createRecordManager(root);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   public void closeEnvironment() throws CacheLoaderException {
+      try {
+         recman.close();
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+      recman = null;
+   }
+
+
+   public BTree createBTree(String name) throws IOException {
+      Comparator comp = ComparatorUtils.naturalComparator();
+      /* note the code of btree does not expose its serializer and key/value serializers
+         that are exposed are never used. Accordingly, we don't set the serializer */
+      BTree tree = BTree.createInstance(recman, comp);
+      associateBTreeWithName(tree, name);
+      return tree;
+   }
+
+   public void associateBTreeWithName(BTree tree, String name) throws IOException {
+      recman.setNamedObject(name, tree.getRecid());
+   }
+
+   public RecordManager createRecordManager(File home) throws IOException {
+      return RecordManagerFactory.createRecordManager(new File(home, "/db").getAbsolutePath(), new Properties());
+   }
+
+   public BTree loadOrCreateBTree(String name) throws IOException {
+      BTree btree;
+      try {
+         btree = loadBTree(name);
+      } catch (IllegalArgumentException e) {
+         btree = createBTree(name);
+      }
+      return btree;
+   }
+
+   public void deleteBTree(BTree btree) throws IOException {
+      recman.delete(btree.getRecid());
+   }
+
+   public BTree loadBTree(String name) throws IOException {
+      long recid = recman.getNamedObject(name);
+      if (recid != 0)
+         return BTree.load(recman, recid);
+      else throw new IllegalArgumentException("name not found in record manager" + name);
+   }
+}
\ No newline at end of file

Copied: core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmCacheStoreTest.java (from rev 7868, core/branches/flat/src/test/java/org/horizon/loader/file/FileCacheStoreTest.java)
===================================================================
--- core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmCacheStoreTest.java	                        (rev 0)
+++ core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmCacheStoreTest.java	2009-03-08 21:03:03 UTC (rev 7884)
@@ -0,0 +1,36 @@
+package org.horizon.loader.jdbm;
+
+import org.horizon.loader.BaseCacheStoreTest;
+import org.horizon.loader.CacheLoaderException;
+import org.horizon.loader.CacheStore;
+import org.horizon.test.TestingUtil;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.File;
+
+ at Test(groups = "unit", testName = "loader.jdbm.JdbmCacheStoreTest")
+public class JdbmCacheStoreTest extends BaseCacheStoreTest {
+
+   private final String tmpDirectory = TestingUtil.TEST_FILES + File.separator + getClass().getSimpleName();
+   private JdbmCacheStore jcs;
+
+   protected CacheStore createCacheStore() throws CacheLoaderException {
+      jcs = new JdbmCacheStore();
+      JdbmCacheStoreConfig cfg = new JdbmCacheStoreConfig();
+      cfg.setLocation(tmpDirectory);
+      cfg.setPurgeSynchronously(true); // for more accurate unit testing
+      jcs.init(cfg, getCache(), getMarshaller());
+      jcs.start();
+      return jcs;
+   }
+
+   @AfterTest
+   @BeforeTest
+   public void removeTempDirectory() {
+      TestingUtil.recursiveFileRemove(tmpDirectory);
+   }
+
+
+}
\ No newline at end of file

Added: core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmLearningTest.java
===================================================================
--- core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmLearningTest.java	                        (rev 0)
+++ core/branches/flat/src/test/java/org/horizon/loader/jdbm/JdbmLearningTest.java	2009-03-08 21:03:03 UTC (rev 7884)
@@ -0,0 +1,764 @@
+package org.horizon.loader.jdbm;
+
+import jdbm.RecordManager;
+import jdbm.RecordManagerFactory;
+import jdbm.btree.BTree;
+import jdbm.helper.Tuple;
+import jdbm.helper.TupleBrowser;
+import org.apache.commons.collections.ComparatorUtils;
+import org.easymock.EasyMock;
+import org.horizon.io.UnclosableObjectInputStream;
+import org.horizon.io.UnclosableObjectOutputStream;
+import org.horizon.loader.CacheLoaderException;
+import org.horizon.loader.StoredEntry;
+import org.horizon.loader.modifications.Clear;
+import org.horizon.loader.modifications.Modification;
+import org.horizon.loader.modifications.Remove;
+import org.horizon.loader.modifications.Store;
+import org.horizon.logging.Log;
+import org.horizon.logging.LogFactory;
+import org.horizon.test.TestingUtil;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import javax.transaction.Transaction;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Learning tests for JDBM (http://jdbm.sourceforge.net).  Behaviour here is used in JdbmCacheLoader.  When there are
+ * upgrades to JDBM, this test may warrant updating.
+ *
+ * @author Adrian Cole
+ * @version $Id: $
+ * @since 1.0
+ */
+ at Test(groups = "unit", enabled = true, testName = "loader.bdbje.JdbmLearningTest")
+public class JdbmLearningTest {
+
+   private static final Log log = LogFactory.getLog(JdbmLearningTest.class);
+   private static final boolean trace = log.isTraceEnabled();
+
+   String dbHome = TestingUtil.TEST_FILES + "/Horizon-JdbmLearningTest";
+   private BTree btree;
+   private RecordManager recman;
+   private String treeName = this.getClass().getName();
+
+   @BeforeTest
+   @AfterMethod
+   public void tearDown() throws Exception {
+      TestingUtil.recursiveFileRemove(dbHome);
+   }
+
+
+   @BeforeMethod
+   public void setUp() throws CacheLoaderException {
+      new File(dbHome).mkdirs();
+      start();
+
+   }
+
+   private BTree createBTree(RecordManager recman, String name) throws IOException {
+
+      Comparator comp = ComparatorUtils.naturalComparator();
+      /* note the code of btree does not expose its serializer and key/value serializers
+         that are exposed are never used. Accordingly, we don't set the serializer */
+      BTree tree = BTree.createInstance(recman, comp);
+      associateBTreeWithName(recman, tree, name);
+      return tree;
+   }
+
+   private void associateBTreeWithName(RecordManager recman, BTree tree, String name) throws IOException {
+      recman.setNamedObject(name, tree.getRecid());
+   }
+
+   private RecordManager createRecordManager(String home) throws IOException {
+      return RecordManagerFactory.createRecordManager(home + "/db", new Properties());
+   }
+
+   private void start() throws CacheLoaderException {
+      System.out.println("Opening environment in: " + dbHome);
+      try {
+         recman = createRecordManager(dbHome);
+         btree = loadOrCreateBTree(recman, treeName);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   private BTree loadOrCreateBTree(RecordManager recman, String name) throws IOException {
+      BTree btree;
+      try {
+         btree = loadBTree(recman, name);
+      } catch (IllegalArgumentException e) {
+         btree = createBTree(recman, name);
+      }
+      return btree;
+   }
+
+   private void deleteBTree(RecordManager recman, BTree btree) throws IOException {
+      recman.delete(btree.getRecid());
+   }
+
+   private BTree loadBTree(RecordManager recman, String name) throws IOException {
+      long recid = recman.getNamedObject(name);
+      log.debug(name + " located as " + recid);
+      if (recid != 0)
+         return BTree.load(recman, recid);
+      else throw new IllegalArgumentException("name not found in record manager" + name);
+   }
+
+   private void stop() throws CacheLoaderException {
+      btree = null;
+      try {
+         recman.close();
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+      recman = null;
+   }
+
+
+   public boolean containsKey(Object key) throws CacheLoaderException {
+      return load(key) != null;
+   }
+
+   public StoredEntry load(Object key) throws CacheLoaderException {
+      try {
+         StoredEntry entry = (StoredEntry) btree.find(key);
+         return removeAndReturnNullIfExpired(entry);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   private StoredEntry removeAndReturnNullIfExpired(StoredEntry entry) throws IOException {
+      if (entry != null && entry.isExpired()) {
+         btree.remove(entry.getKey());
+         return null;
+      }
+      return entry;
+   }
+
+   public Set<StoredEntry> loadAll() throws CacheLoaderException {
+      try {
+         Set<StoredEntry> entries = new HashSet<StoredEntry>();
+         TupleBrowser browser = btree.browse();
+         Tuple tuple = new Tuple();
+         while (browser.getNext(tuple)) {
+            if (tuple.getValue() != null) {
+               StoredEntry entry = (StoredEntry) tuple.getValue();
+               entry = removeAndReturnNullIfExpired(entry);
+               if (entry != null) entries.add(entry);
+            }
+         }
+         return entries;
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   public void store(StoredEntry ed) throws CacheLoaderException {
+      try {
+         btree.insert(ed.getKey(), ed, true);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   private void purgeExpired() throws CacheLoaderException {
+      try {
+         TupleBrowser browser = btree.browse();
+         Tuple tuple = new Tuple();
+         while (browser.getNext(tuple)) {
+            if (tuple.getValue() != null) {
+               StoredEntry entry = (StoredEntry) tuple.getValue();
+               removeAndReturnNullIfExpired(entry);
+            }
+         }
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+
+   public void fromStream(ObjectInput inputStream) throws CacheLoaderException {
+      FileChannel channel = null;
+      try {
+         stop();
+         String name = (String) inputStream.readObject();
+
+         channel = new RandomAccessFile(dbHome + "/db.db", "rw").getChannel();
+         long size = inputStream.readLong();
+         ByteBuffer buff = channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
+         long i = 0;
+         while (i++ < size) {
+            buff.put(inputStream.readByte());
+         }
+         channel.close();
+
+         channel = new RandomAccessFile(dbHome + "/db.lg", "rw").getChannel();
+         size = inputStream.readLong();
+         buff = channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
+         i = 0;
+         while (i++ < size) {
+            buff.put(inputStream.readByte());
+         }
+         channel.close();
+
+         String oldName = treeName;
+         treeName = name;
+         start();
+         associateBTreeWithName(recman, btree, oldName);
+         treeName = oldName;
+
+      } catch (FileNotFoundException e) {
+         throw new CacheLoaderException(e);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      } catch (ClassNotFoundException e) {
+         throw new CacheLoaderException(e);
+      } finally {
+         if (channel != null)
+            try {
+               channel.close();
+            } catch (IOException e) {
+               throw new CacheLoaderException(e);
+            }
+      }
+   }
+
+   public void toStream(ObjectOutput outputStream) throws CacheLoaderException {
+      FileChannel channel = null;
+      try {
+         stop();
+         outputStream.writeObject(treeName);
+
+         channel = new RandomAccessFile(dbHome + "/db.db", "r").getChannel();
+         long size = channel.size();
+         outputStream.writeLong(size);
+         ByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+         long i = 0;
+         while (i++ < size) {
+            outputStream.write(buff.get());
+         }
+         channel.close();
+
+         channel = new RandomAccessFile(dbHome + "/db.lg", "r").getChannel();
+         size = channel.size();
+         outputStream.writeLong(size);
+         buff = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+         i = 0;
+         while (i++ < size) {
+            outputStream.write(buff.get());
+         }
+         channel.close();
+         channel = null;
+
+      } catch (FileNotFoundException e) {
+         throw new CacheLoaderException(e);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      } finally {
+         if (recman == null)
+            start();
+         if (channel != null)
+            try {
+               channel.close();
+            } catch (IOException e) {
+               throw new CacheLoaderException(e);
+            }
+      }
+   }
+
+   public void clear() throws CacheLoaderException {
+      try {
+         deleteBTree(recman, btree);
+         btree = createBTree(recman, treeName);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   public boolean remove(Object key) throws CacheLoaderException {
+      try {
+         return btree.remove(key) != null;
+      } catch (IllegalArgumentException e) {
+         return false;
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+
+   private final Map<Transaction, List<? extends Modification>> transactions = new ConcurrentHashMap<Transaction, List<? extends Modification>>();
+
+   protected void applyModifications(List<? extends Modification> mods) throws CacheLoaderException {
+      for (Modification m : mods) {
+         switch (m.getType()) {
+            case STORE:
+               Store s = (Store) m;
+               store(s.getStoredEntry());
+               break;
+            case CLEAR:
+               clear();
+               break;
+            case REMOVE:
+               Remove r = (Remove) m;
+               remove(r.getKey());
+               break;
+            default:
+               throw new IllegalArgumentException("Unknown modification type " + m.getType());
+         }
+      }
+   }
+
+   public void prepare(List<? extends Modification> mods, Transaction tx, boolean isOnePhase) throws CacheLoaderException {
+      if (isOnePhase) {
+         applyModifications(mods);
+      } else {
+         transactions.put(tx, mods);
+      }
+   }
+
+   public void rollback(Transaction tx) {
+      transactions.remove(tx);
+   }
+
+   public void commit(Transaction tx) throws CacheLoaderException {
+      List<? extends Modification> list = transactions.remove(tx);
+      if (list != null && !list.isEmpty()) applyModifications(list);
+   }
+
+   public void removeAll(Set<Object> keys) throws CacheLoaderException {
+      if (keys != null && !keys.isEmpty()) {
+         for (Object key : keys) remove(key);
+      }
+   }
+
+
+   public void testLoadAndStore() throws InterruptedException, CacheLoaderException {
+      assert !containsKey("k");
+      StoredEntry se = new StoredEntry("k", "v", -1, -1);
+      store(se);
+
+      assert load("k").getValue().equals("v");
+      assert load("k").getLifespan() == -1;
+      assert !load("k").isExpired();
+      assert containsKey("k");
+
+      long now = System.currentTimeMillis();
+      long lifespan = 120000;
+      se = new StoredEntry("k", "v", now, now + lifespan);
+      store(se);
+
+      assert load("k").getValue().equals("v");
+      assert load("k").getLifespan() == lifespan;
+      assert !load("k").isExpired();
+      assert containsKey("k");
+
+      now = System.currentTimeMillis();
+      lifespan = 1;
+      se = new StoredEntry("k", "v", now, now + lifespan);
+      store(se);
+      Thread.sleep(100);
+      assert se.isExpired();
+      assert load("k") == null;
+      assert !containsKey("k");
+   }
+
+
+   public void testStopStartDoesntNukeValues() throws InterruptedException, CacheLoaderException {
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+
+      long now = System.currentTimeMillis();
+      long lifespan = 1;
+      StoredEntry se1 = new StoredEntry("k1", "v1", now, now + lifespan);
+      StoredEntry se2 = new StoredEntry("k2", "v2");
+
+      store(se1);
+      store(se2);
+      Thread.sleep(100);
+      stop();
+      start();
+      assert se1.isExpired();
+      assert load("k1") == null;
+      assert !containsKey("k1");
+      assert load("k2") != null;
+      assert containsKey("k2");
+      assert load("k2").getValue().equals("v2");
+
+   }
+
+
+   public void testOnePhaseCommit() throws CacheLoaderException {
+      List<Modification> mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Remove("k1"));
+      Transaction tx = EasyMock.createNiceMock(Transaction.class);
+      prepare(mods, tx, true);
+
+      assert load("k2").getValue().equals("v2");
+      assert !containsKey("k1");
+
+      clear();
+
+      mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Clear());
+      mods.add(new Store(new StoredEntry("k3", "v3", -1, -1)));
+
+      prepare(mods, tx, true);
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert containsKey("k3");
+   }
+
+   public void testTwoPhaseCommit() throws CacheLoaderException {
+      List<Modification> mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Remove("k1"));
+      Transaction tx = EasyMock.createNiceMock(Transaction.class);
+      prepare(mods, tx, false);
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+
+      commit(tx);
+
+      assert load("k2").getValue().equals("v2");
+      assert !containsKey("k1");
+
+      clear();
+
+      mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Clear());
+      mods.add(new Store(new StoredEntry("k3", "v3", -1, -1)));
+
+      prepare(mods, tx, false);
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert !containsKey("k3");
+
+      commit(tx);
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert containsKey("k3");
+   }
+
+
+   public void testRollback() throws CacheLoaderException {
+
+      store(new StoredEntry("old", "old", -1, -1));
+
+      List<Modification> mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Remove("k1"));
+      mods.add(new Remove("old"));
+      Transaction tx = EasyMock.createNiceMock(Transaction.class);
+      prepare(mods, tx, false);
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert containsKey("old");
+
+      rollback(tx);
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert containsKey("old");
+
+      mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Clear());
+      mods.add(new Store(new StoredEntry("k3", "v3", -1, -1)));
+
+      prepare(mods, tx, false);
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert !containsKey("k3");
+
+      rollback(tx);
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert !containsKey("k3");
+      assert containsKey("old");
+   }
+
+   public void testRollbackFromADifferentThreadReusingTransactionKey() throws CacheLoaderException, InterruptedException {
+
+      store(new StoredEntry("old", "old", -1, -1));
+
+      List<Modification> mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Remove("k1"));
+      mods.add(new Remove("old"));
+      final Transaction tx = EasyMock.createNiceMock(Transaction.class);
+      prepare(mods, tx, false);
+
+      Thread t = new Thread(new Runnable() {
+         public void run() {
+            rollback(tx);
+         }
+      });
+
+      t.start();
+      t.join();
+
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert containsKey("old");
+
+      mods = new ArrayList<Modification>();
+      mods.add(new Store(new StoredEntry("k1", "v1", -1, -1)));
+      mods.add(new Store(new StoredEntry("k2", "v2", -1, -1)));
+      mods.add(new Clear());
+      mods.add(new Store(new StoredEntry("k3", "v3", -1, -1)));
+
+      prepare(mods, tx, false);
+
+      Thread t2 = new Thread(new Runnable() {
+         public void run() {
+            rollback(tx);
+         }
+      });
+
+      t2.start();
+      t2.join();
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert !containsKey("k3");
+      assert containsKey("old");
+   }
+
+   public void testCommitAndRollbackWithoutPrepare() throws CacheLoaderException {
+      store(new StoredEntry("old", "old", -1, -1));
+      Transaction tx = EasyMock.createNiceMock(Transaction.class);
+      commit(tx);
+      store(new StoredEntry("old", "old", -1, -1));
+      rollback(tx);
+
+      assert containsKey("old");
+   }
+
+   public void testPreload() throws CacheLoaderException {
+      store(new StoredEntry("k1", "v1", -1, -1));
+      store(new StoredEntry("k2", "v2", -1, -1));
+      store(new StoredEntry("k3", "v3", -1, -1));
+
+      Set<StoredEntry> set = loadAll();
+
+      assert set.size() == 3;
+      Set expected = new HashSet();
+      expected.add("k1");
+      expected.add("k2");
+      expected.add("k3");
+      for (StoredEntry se : set) assert expected.remove(se.getKey());
+      assert expected.isEmpty();
+   }
+
+   @Test
+   public void testStoreAndRemoveAll() throws CacheLoaderException {
+      store(new StoredEntry("k1", "v1", -1, -1));
+      store(new StoredEntry("k2", "v2", -1, -1));
+      store(new StoredEntry("k3", "v3", -1, -1));
+      store(new StoredEntry("k4", "v4", -1, -1));
+
+
+      Set<StoredEntry> set = loadAll();
+
+      assert set.size() == 4;
+      Set expected = new HashSet();
+      expected.add("k1");
+      expected.add("k2");
+      expected.add("k3");
+      expected.add("k4");
+      for (StoredEntry se : set) assert expected.remove(se.getKey());
+      assert expected.isEmpty();
+
+      Set toRemove = new HashSet();
+      toRemove.add("k1");
+      toRemove.add("k2");
+      toRemove.add("k3");
+      removeAll(toRemove);
+
+      set = loadAll();
+      assert set.size() == 1;
+      set.remove("k4");
+      assert expected.isEmpty();
+   }
+
+   public void testPurgeExpired() throws Exception {
+      long now = System.currentTimeMillis();
+      long lifespan = 1000;
+      store(new StoredEntry("k1", "v1", now, now + lifespan));
+      store(new StoredEntry("k2", "v2", now, now + lifespan));
+      store(new StoredEntry("k3", "v3", now, now + lifespan));
+      assert containsKey("k1");
+      assert containsKey("k2");
+      assert containsKey("k3");
+      Thread.sleep(lifespan + 100);
+      purgeExpired();
+      assert !containsKey("k1");
+      assert !containsKey("k2");
+      assert !containsKey("k3");
+   }
+
+
+   public void testStreamingAPI() throws IOException, ClassNotFoundException, CacheLoaderException {
+      store(new StoredEntry("k1", "v1", -1, -1));
+      store(new StoredEntry("k2", "v2", -1, -1));
+      store(new StoredEntry("k3", "v3", -1, -1));
+
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      ObjectOutputStream oos = new ObjectOutputStream(out);
+      toStream(new UnclosableObjectOutputStream(oos));
+      oos.flush();
+      oos.close();
+      out.close();
+      clear();
+      ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
+      fromStream(new UnclosableObjectInputStream(ois));
+
+      Set<StoredEntry> set = loadAll();
+
+      assert set.size() == 3;
+      Set expected = new HashSet();
+      expected.add("k1");
+      expected.add("k2");
+      expected.add("k3");
+      for (StoredEntry se : set) assert expected.remove(se.getKey());
+      assert expected.isEmpty();
+   }
+
+   public void testStreamingAPIReusingStreams() throws IOException, ClassNotFoundException, CacheLoaderException {
+      store(new StoredEntry("k1", "v1", -1, -1));
+      store(new StoredEntry("k2", "v2", -1, -1));
+      store(new StoredEntry("k3", "v3", -1, -1));
+
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      byte[] dummyStartBytes = {1, 2, 3, 4, 5, 6, 7, 8};
+      byte[] dummyEndBytes = {8, 7, 6, 5, 4, 3, 2, 1};
+      out.write(dummyStartBytes);
+      ObjectOutputStream oos = new ObjectOutputStream(out);
+      toStream(new UnclosableObjectOutputStream(oos));
+      oos.flush();
+      oos.close();
+      out.write(dummyEndBytes);
+      out.close();
+      clear();
+
+      // first pop the start bytes
+      byte[] dummy = new byte[8];
+      ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+      int bytesRead = in.read(dummy, 0, 8);
+      assert bytesRead == 8;
+      for (int i = 1; i < 9; i++) assert dummy[i - 1] == i : "Start byte stream corrupted!";
+      fromStream(new UnclosableObjectInputStream(new ObjectInputStream(in)));
+      bytesRead = in.read(dummy, 0, 8);
+      assert bytesRead == 8;
+      for (int i = 8; i > 0; i--) assert dummy[8 - i] == i : "Start byte stream corrupted!";
+
+      Set<StoredEntry> set = loadAll();
+
+      assert set.size() == 3;
+      Set expected = new HashSet();
+      expected.add("k1");
+      expected.add("k2");
+      expected.add("k3");
+      for (StoredEntry se : set) assert expected.remove(se.getKey());
+      assert expected.isEmpty();
+   }
+
+   public void testConcurrency() throws Exception {
+      int numThreads = 3;
+      final int loops = 500;
+      final String[] keys = new String[10];
+      final String[] values = new String[10];
+      for (int i = 0; i < 10; i++) keys[i] = "k" + i;
+      for (int i = 0; i < 10; i++) values[i] = "v" + i;
+
+
+      final Random r = new Random();
+      final List<Exception> exceptions = new LinkedList<Exception>();
+
+      final Runnable store = new Runnable() {
+         public void run() {
+            try {
+               int randomInt = r.nextInt(10);
+               store(new StoredEntry(keys[randomInt], values[randomInt]));
+            } catch (Exception e) {
+               exceptions.add(e);
+            }
+         }
+      };
+
+      final Runnable remove = new Runnable() {
+         public void run() {
+            try {
+               remove(keys[r.nextInt(10)]);
+            } catch (Exception e) {
+               exceptions.add(e);
+            }
+         }
+      };
+
+      final Runnable get = new Runnable() {
+         public void run() {
+            try {
+               int randomInt = r.nextInt(10);
+               StoredEntry se = load(keys[randomInt]);
+               assert se == null || se.getValue().equals(values[randomInt]);
+               loadAll();
+            } catch (Exception e) {
+               exceptions.add(e);
+            }
+         }
+      };
+
+      Thread[] threads = new Thread[numThreads];
+
+      for (int i = 0; i < numThreads; i++) {
+         threads[i] = new Thread(getClass().getSimpleName() + "-" + i) {
+            public void run() {
+               for (int i = 0; i < loops; i++) {
+                  store.run();
+                  remove.run();
+                  get.run();
+               }
+            }
+         };
+      }
+
+      for (Thread t : threads) t.start();
+      for (Thread t : threads) t.join();
+
+      if (!exceptions.isEmpty()) throw exceptions.get(0);
+   }
+}




More information about the jbosscache-commits mailing list