Index: cachestore/jdbm/src/test/java/org/infinispan/loaders/jdbm/JdbmCacheStoreTest.java
===================================================================
--- cachestore/jdbm/src/test/java/org/infinispan/loaders/jdbm/JdbmCacheStoreTest.java	(revision 0)
+++ cachestore/jdbm/src/test/java/org/infinispan/loaders/jdbm/JdbmCacheStoreTest.java	(revision 0)
@@ -0,0 +1,120 @@
+package org.infinispan.loaders.jdbm;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.infinispan.container.entries.InternalCacheEntry;
+import org.infinispan.container.entries.InternalEntryFactory;
+import org.infinispan.io.UnclosableObjectOutputStream;
+import org.infinispan.loaders.BaseCacheStoreTest;
+import org.infinispan.loaders.CacheLoaderException;
+import org.infinispan.loaders.CacheStore;
+import org.infinispan.test.TestingUtil;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "loaders.jdbm.JdbmCacheStoreTest")
+public class JdbmCacheStoreTest extends BaseCacheStoreTest {
+
+   private JdbmCacheStore fcs;
+   private String tmpDirectory;
+
+   @BeforeTest
+   @Parameters({"basedir"})
+   protected void setUpTempDir() {
+      tmpDirectory = "." + TestingUtil.TEST_PATH + File.separator + getClass().getSimpleName();
+   }
+
+   @AfterTest
+   protected void clearTempDir() {
+      TestingUtil.recursiveFileRemove(tmpDirectory);
+      new File(tmpDirectory).mkdirs();
+   }
+
+   @Override
+   protected CacheStore createCacheStore() throws CacheLoaderException {
+      clearTempDir();
+      fcs = new JdbmCacheStore();
+      JdbmCacheStoreConfig cfg = new JdbmCacheStoreConfig();
+      cfg.setLocation(tmpDirectory);
+      cfg.setPurgeSynchronously(true); // for more accurate unit testing
+      fcs.init(cfg, getCache(), getMarshaller());
+      fcs.start();
+      return fcs;
+   }
+
+   @Override
+   public void testPreload() throws CacheLoaderException {
+      super.testPreload();
+   }
+
+   @Override
+   public void testPurgeExpired() throws Exception {
+      long lifespan = 1000;
+      InternalCacheEntry k1 = InternalEntryFactory.create("k1", "v1", lifespan);
+      InternalCacheEntry k2 = InternalEntryFactory.create("k2", "v2", lifespan);
+      InternalCacheEntry k3 = InternalEntryFactory.create("k3", "v3", lifespan);
+      cs.store(k1);
+      cs.store(k2);
+      cs.store(k3);
+      assert cs.containsKey("k1");
+      assert cs.containsKey("k2");
+      assert cs.containsKey("k3");
+      Thread.sleep(lifespan + 100);
+      cs.purgeExpired();
+      JdbmCacheStore fcs = (JdbmCacheStore) cs;
+      assert fcs.load("k1") == null;
+      assert fcs.load("k2") == null;
+      assert fcs.load("k3") == null;
+   }
+
+   public void testIterator() throws Exception {
+      InternalCacheEntry k1 = InternalEntryFactory.create("k1", "v1");
+      InternalCacheEntry k2 = InternalEntryFactory.create("k2", "v2");
+      cs.store(k1);
+      cs.store(k2);
+      
+      Set<InternalCacheEntry> set = cs.loadAll();
+      Iterator<InternalCacheEntry> i = set.iterator();
+      assert i.hasNext() == true;
+      assert i.hasNext() == true;
+      assert i.next().getKey().equals("k1");
+      assert i.next().getKey().equals("k2");
+      assert i.hasNext() == false;
+      assert i.hasNext() == false;
+      try {
+         i.next();
+         assert false;
+      } catch (NoSuchElementException e) {}
+   }
+
+   public void testToStream() throws Exception {
+      cs.store(InternalEntryFactory.create("k1", "v1", -1, -1));
+
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      ObjectOutputStream oos = new ObjectOutputStream(out);
+      cs.toStream(new UnclosableObjectOutputStream(oos));
+      oos.flush();
+      oos.close();
+      out.close();
+
+      ObjectInputStream ois = null;
+      try {
+         ois = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
+         assert ois.readLong() == 1 : "we have 3 different buckets";
+         Object readObject = ois.readObject();
+         assert readObject instanceof InternalCacheEntry;
+         // assert ois.readInt() > 0; //size on disk
+      } finally {
+         if (ois != null) ois.close();
+      }
+   }
+}
Index: cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmCacheStoreConfig.java
===================================================================
--- cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmCacheStoreConfig.java	(revision 0)
+++ cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmCacheStoreConfig.java	(revision 0)
@@ -0,0 +1,68 @@
+package org.infinispan.loaders.jdbm;
+
+import java.util.Comparator;
+
+import org.infinispan.CacheException;
+import org.infinispan.loaders.LockSupportCacheStoreConfig;
+import org.infinispan.marshall.Marshaller;
+
+/**
+ * Configures {@link JdbmCacheStore}.
+ * <p/>
+ * <ul>
+ * <li><tt>location</tt> - a location on disk where the store can write internal
+ * files.</li>
+ * <li><tt>comparatorClassName</tt> - comparator class used to sort the keys
+ * by the cache loader. This should only need to be set when using keys that
+ * do not have a natural ordering.
+ * </ul>
+ * 
+ * @author Elias Ross
+ * @since 4.0
+ */
+public class JdbmCacheStoreConfig extends LockSupportCacheStoreConfig {
+
+    private static final long serialVersionUID = 1L;
+    
+    String location = "jdbm";
+    String comparatorClassName = NaturalComparator.class.getName();
+
+    public JdbmCacheStoreConfig() {
+        setCacheLoaderClassName(JdbmCacheStore.class.getName());
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        testImmutability("location");
+        this.location = location;
+    }
+
+    /**
+     * Returns comparatorClassName.
+     */
+    public String getComparatorClassName() {
+        return comparatorClassName;
+    }
+
+    /**
+     * Sets comparatorClassName.
+     */
+    public void setComparatorClassName(String comparatorClassName) {
+        this.comparatorClassName = comparatorClassName;
+    }
+
+    /**
+     * Returns a new comparator instance based on {@link #setComparatorClassName(String)}.
+     */
+    public Comparator createComparator() {
+        try {
+            return (Comparator) Class.forName(comparatorClassName).newInstance();
+        } catch (Exception e) {
+            throw new CacheException(e);
+        }
+    }
+
+}
Index: cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/package-info.java
===================================================================
--- cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/package-info.java	(revision 0)
+++ cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/package-info.java	(revision 0)
@@ -0,0 +1,5 @@
+/**
+ * This package contains a {@link org.infinispan.loaders.CacheStore} implementation based on
+ * persisting to JDBM.
+ */
+package org.infinispan.loaders.jdbm;
\ No newline at end of file
Index: cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmSerializer.java
===================================================================
--- cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmSerializer.java	(revision 0)
+++ cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmSerializer.java	(revision 0)
@@ -0,0 +1,41 @@
+package org.infinispan.loaders.jdbm;
+
+import java.io.IOException;
+
+import org.infinispan.marshall.Marshaller;
+
+import jdbm.helper.Serializer;
+
+/**
+ * Uses the configured (runtime) {@link Marshaller} of the cache.
+ * This Serializer is thus not really serializiable.
+ * 
+ * @author Elias Ross
+ */
+@SuppressWarnings("serial")
+public class JdbmSerializer implements Serializer {
+    
+    private transient Marshaller marshaller;
+
+    /**
+     * Constructs a new JdbmSerializer.
+     */
+    public JdbmSerializer(Marshaller marshaller) {
+        if (marshaller == null)
+            throw new NullPointerException("marshaller");
+        this.marshaller = marshaller;
+    }
+
+    public Object deserialize(byte[] buf) throws IOException {
+        try {
+            return marshaller.objectFromByteBuffer(buf);
+        } catch (ClassNotFoundException e) {
+            throw (IOException)new IOException().initCause(e);
+        }
+    }
+
+    public byte[] serialize(Object obj) throws IOException {
+        return marshaller.objectToByteBuffer(obj);
+    }
+
+}
Index: cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmCacheStore.java
===================================================================
--- cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmCacheStore.java	(revision 0)
+++ cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/JdbmCacheStore.java	(revision 0)
@@ -0,0 +1,459 @@
+package org.infinispan.loaders.jdbm;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.text.SimpleDateFormat;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+import java.util.Set;
+
+import jdbm.RecordManager;
+import jdbm.RecordManagerFactory;
+import jdbm.btree.BTree;
+import jdbm.helper.Serializer;
+import jdbm.helper.Tuple;
+import jdbm.helper.TupleBrowser;
+import net.jcip.annotations.ThreadSafe;
+
+import org.infinispan.Cache;
+import org.infinispan.CacheException;
+import org.infinispan.config.ConfigurationException;
+import org.infinispan.container.entries.InternalCacheEntry;
+import org.infinispan.loaders.AbstractCacheStore;
+import org.infinispan.loaders.CacheLoaderConfig;
+import org.infinispan.loaders.CacheLoaderException;
+import org.infinispan.loaders.modifications.Modification;
+import org.infinispan.loaders.modifications.Remove;
+import org.infinispan.loaders.modifications.Store;
+import org.infinispan.marshall.Marshaller;
+import org.infinispan.util.logging.Log;
+import org.infinispan.util.logging.LogFactory;
+
+/**
+ * A persistent <code>CacheLoader</code> based on the JDBM project. See
+ * http://jdbm.sourceforge.net/ . Does not support transaction isolation.
+ * <p/>
+ * Supports removal of expired entries.
+ * <p/>
+ * It would probably be better if meta-data (expiry time) was stored independent
+ * of the value of the entry. That is, if (key,"m") == meta and (key,"v") ==
+ * value.
+ * 
+ * @author Elias Ross
+ * @version $Id: JdbmCacheLoader.java 7261 2008-12-07 18:53:38Z genman $
+ */
+@ThreadSafe
+public class JdbmCacheStore extends AbstractCacheStore {
+
+   private static final Log log = LogFactory.getLog(JdbmCacheStore.class);
+   private static final boolean trace = log.isTraceEnabled();
+
+   private static final String NAME = "CacheLoader";
+   private static final String EXPIRY = "Expiry";
+   private final String DATE = "HH:mm:ss.SSS";
+
+   private final Object expiryLock = new Object();
+
+   private JdbmCacheStoreConfig config;
+   private RecordManager recman;
+   private BTree tree;
+   private BTree expiryTree;
+   private Cache cache;
+
+   public Class<? extends CacheLoaderConfig> getConfigurationClass() {
+      return JdbmCacheStoreConfig.class;
+   }
+
+   @Override
+   public void init(CacheLoaderConfig clc, Cache cache, Marshaller m) {
+      super.init(clc, cache, m);
+      this.config = (JdbmCacheStoreConfig) clc;
+      this.cache = cache;
+   }
+
+   @Override
+   public void start() throws CacheLoaderException {
+      String locationStr = config.getLocation();
+      if (locationStr == null) {
+         locationStr = System.getProperty("java.io.tmpdir");
+         config.setLocation(locationStr);
+      }
+
+      // JBCACHE-1448 db name parsing fix courtesy of Ciro Cavani
+      /* Parse config string. */
+      int offset = locationStr.indexOf('#');
+      String cacheDbName;
+      if (offset >= 0 && offset < locationStr.length() - 1) {
+         cacheDbName = locationStr.substring(offset + 1);
+         locationStr = locationStr.substring(0, offset);
+      } else {
+         cacheDbName = cache.getName(); // TODO
+         if (cacheDbName == null)
+            cacheDbName = "jdbm";
+      }
+
+      // test location
+      File location = new File(locationStr);
+      if (!location.exists()) {
+         boolean created = location.mkdirs();
+         if (!created)
+            throw new ConfigurationException("Unable to create cache loader location " + location);
+      }
+      if (!location.isDirectory()) {
+         throw new ConfigurationException("Cache loader location [" + location + "] is not a directory!");
+      }
+
+      try {
+         openDatabase(new File(location, cacheDbName));
+      } catch (Exception e) {
+         throw new ConfigurationException(e);
+      }
+
+      log.debug("cleaning up expired entries...");
+      purgeInternal();
+
+      log.debug("started");
+      super.start();
+   }
+
+   public InternalCacheEntry load(Object key) throws CacheLoaderException {
+      try {
+         return (InternalCacheEntry) tree.find(key);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   public Set<InternalCacheEntry> loadAll() throws CacheLoaderException {
+      return new BTreeSet();
+   }
+
+   /**
+    * Opens all databases and initializes database related information.
+    */
+   private void openDatabase(File f) throws Exception {
+      Properties props = new Properties();
+      // Incorporate properties from setConfig() ?
+      // props.put(RecordManagerOptions.SERIALIZER,
+      // RecordManagerOptions.SERIALIZER_EXTENSIBLE);
+      // props.put(RecordManagerOptions.PROFILE_SERIALIZATION, "false");
+      recman = RecordManagerFactory.createRecordManager(f.toString(), props);
+      long recid = recman.getNamedObject(NAME);
+      log.debug(NAME + " located as " + recid);
+      if (recid == 0) {
+         createTree();
+      } else {
+         tree = BTree.load(recman, recid);
+         recid = recman.getNamedObject(EXPIRY);
+         expiryTree = BTree.load(recman, recid);
+         setSerializer();
+      }
+
+      log.info("JDBM database " + f + " opened with " + tree.size() + " entries");
+   }
+
+   /**
+    * Resets the value serializer to point to our marshaller.
+    */
+   private void setSerializer() {
+      tree.setValueSerializer(new JdbmSerializer(getMarshaller()));
+      expiryTree.setValueSerializer(new JdbmSerializer(getMarshaller()));
+   }
+
+   private void createTree() throws IOException {
+      tree = BTree.createInstance(recman, config.createComparator(), (Serializer) null, (Serializer) null);
+      expiryTree = BTree.createInstance(recman, new NaturalComparator(), (Serializer) null, (Serializer) null);
+      recman.setNamedObject(NAME, tree.getRecid());
+      recman.setNamedObject(EXPIRY, expiryTree.getRecid());
+      setSerializer();
+   }
+
+   /**
+    * Closes all databases, ignoring exceptions, and nulls references to all
+    * database related information.
+    */
+   @Override
+   public void stop() {
+      if (recman != null) {
+         try {
+            recman.close();
+         } catch (IOException e) {
+            throw new CacheException(e);
+         }
+      }
+      recman = null;
+      tree = null;
+      expiryTree = null;
+   }
+
+   public void clear() throws CacheLoaderException {
+      if (trace)
+         log.trace("clear()");
+      try {
+         recman.delete(tree.getRecid());
+         recman.delete(expiryTree.getRecid());
+         createTree();
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   public boolean remove(Object key) throws CacheLoaderException {
+      try {
+         return remove0(key);
+      } finally {
+         commit();
+      }
+   }
+
+   private void commit() throws CacheLoaderException {
+      try {
+         recman.commit();
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   public boolean remove0(Object key) throws CacheLoaderException {
+      if (trace)
+         log.trace("remove() " + key);
+      try {
+         return tree.remove(key) != null;
+      } catch (IllegalArgumentException e) {
+         // can happen during normal operation
+         return false;
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   public void store(InternalCacheEntry entry) throws CacheLoaderException {
+      store0(entry);
+      commit();
+   }
+
+   private void store0(InternalCacheEntry entry) throws CacheLoaderException {
+      Object key = entry.getKey();
+      if (trace)
+         log.trace("store() " + key);
+      try {
+         tree.insert(key, entry, true); // TODO a waste to ignore the return
+                                        // value
+         if (entry.canExpire())
+            addNewExpiry(entry);
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   private void addNewExpiry(InternalCacheEntry entry) throws IOException {
+      long expiry = entry.getExpiryTime();
+      if (entry.getMaxIdle() > 0) {
+         // TODO do we need both?
+         expiry = entry.getMaxIdle() + System.currentTimeMillis();
+      }
+      Long at = new Long(expiry);
+      Object key = entry.getKey();
+      if (trace)
+         log.trace("at " + new SimpleDateFormat(DATE).format(new Date(at)) + " expire " + key);
+      // TODO could store expiry entries in a separate thread; would remove this
+      // lock as well
+      synchronized (expiryLock) {
+         Object existing = expiryTree.insert(at, key, false);
+         if (existing != null) {
+            // in the case of collision make the key a List ...
+            if (existing instanceof List) {
+               ((List) existing).add(key);
+               expiryTree.insert(at, existing, true);
+            } else {
+               List<Object> al = new ArrayList<Object>(2);
+               al.add(existing);
+               al.add(key);
+               expiryTree.insert(at, al, true);
+            }
+         }
+      }
+   }
+
+   /**
+    * Writes to a stream the number of entries (long) then the entries
+    * themselves.
+    */
+   public void toStream(ObjectOutput outputStream) throws CacheLoaderException {
+      try {
+         Set<InternalCacheEntry> loadAll = loadAll();
+         outputStream.writeLong(loadAll.size());
+         log.debug("toStream() " + loadAll.size() + " entries");
+         for (InternalCacheEntry entry : loadAll)
+            outputStream.writeObject(entry);
+         log.debug("done!");
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   /**
+    * Reads from a stream the number of entries (long) then the entries
+    * themselves.
+    */
+   public void fromStream(ObjectInput inputStream) throws CacheLoaderException {
+      try {
+         long count = inputStream.readLong();
+         log.debug("fromStream() " + count + " entries");
+         for (int i = 0; i < count; i++) {
+            InternalCacheEntry entry = (InternalCacheEntry) inputStream.readObject();
+            store(entry);
+         }
+         log.debug("done!");
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      } catch (ClassNotFoundException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   /**
+    * Purge expired entries.
+    */
+   @Override
+   protected void purgeInternal() throws CacheLoaderException {
+      log.trace("purgeInternal");
+      try {
+         purgeInternal0();
+      } catch (IOException e) {
+         throw new CacheLoaderException(e);
+      }
+   }
+
+   /**
+    * Find all times less than current time. Build a list of keys for those
+    * times. Then purge those keys, assuming those keys' expiry has not changed.
+    */
+   private void purgeInternal0() throws IOException {
+      TupleBrowser browse = expiryTree.browse();
+      Tuple tuple = new Tuple();
+      List<Long> times = new ArrayList<Long>();
+      List<Object> keys = new ArrayList<Object>();
+      synchronized (expiryLock) {
+         while (browse.getNext(tuple)) {
+            Long time = (Long) tuple.getKey();
+            if (time > System.currentTimeMillis())
+               break;
+            times.add(time);
+            Object key = tuple.getValue();
+            if (key instanceof List)
+               keys.addAll((List) key);
+            else
+               keys.add(key);
+         }
+         for (Long time : times) {
+            expiryTree.remove(time);
+         }
+      }
+
+      if (!keys.isEmpty())
+         log.debug("purge (up to) " + keys.size() + " entries");
+      int count = 0;
+      for (Object key : keys) {
+         InternalCacheEntry ice = (InternalCacheEntry) tree.find(key);
+         if (ice == null)
+            continue;
+         if (ice.isExpired()) {
+            // somewhat inefficient to FIND then REMOVE...
+            tree.remove(key);
+            count++;
+         }
+      }
+      if (count != 0)
+         log.debug("purged " + count + " entries");
+      recman.commit();
+   }
+
+   @Override
+   protected void applyModifications(List<? extends Modification> mods) throws CacheLoaderException {
+      for (Modification m : mods) {
+         switch (m.getType()) {
+         case STORE:
+            store0(((Store) m).getStoredEntry());
+            break;
+         case CLEAR:
+            clear();
+            break;
+         case REMOVE:
+            remove0(((Remove) m).getKey());
+            break;
+         default:
+            throw new AssertionError();
+         }
+      }
+      commit();
+   }
+
+   @Override
+   public String toString() {
+      BTree bt = tree;
+      BTree et = expiryTree;
+      int size = (bt == null) ? -1 : bt.size();
+      int expiry = (et == null) ? -1 : et.size();
+      return "JdbmCacheLoader locationStr=" + config.getLocation() + " size=" + size + " expirySize=" + expiry;
+   }
+
+   private final class BTreeSet extends AbstractSet<InternalCacheEntry> {
+
+      @Override
+      public Iterator<InternalCacheEntry> iterator() {
+         final TupleBrowser browse;
+         try {
+            browse = tree.browse();
+         } catch (IOException e) {
+            throw new CacheException(e);
+         }
+
+         return new Iterator<InternalCacheEntry>() {
+
+            final Tuple tuple = new Tuple();
+            InternalCacheEntry current = null;
+            boolean next = true;
+
+            public boolean hasNext() {
+               if (current == null) {
+                  try {
+                     next = browse.getNext(tuple);
+                     current = (InternalCacheEntry) tuple.getValue();
+                  } catch (IOException e) {
+                     throw new CacheException(e);
+                  }
+               }
+               return next;
+            }
+
+            public InternalCacheEntry next() {
+               if (!hasNext())
+                  throw new NoSuchElementException();
+               try {
+                  return current;
+               } finally {
+                  current = null;
+               }
+            }
+
+            public void remove() {
+               throw new UnsupportedOperationException();
+            }
+
+         };
+      }
+
+      @Override
+      public int size() {
+         return tree.size();
+      }
+   }
+}
\ No newline at end of file
Index: cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/NaturalComparator.java
===================================================================
--- cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/NaturalComparator.java	(revision 0)
+++ cachestore/jdbm/src/main/java/org/infinispan/loaders/jdbm/NaturalComparator.java	(revision 0)
@@ -0,0 +1,20 @@
+package org.infinispan.loaders.jdbm;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Compares keys using their <i>natural ordering</i>.
+ * <p/>
+ * 
+ * @author Elias Ross
+ */
+public class NaturalComparator implements Comparator, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    public int compare(Object o1, Object o2) {
+        return ((Comparable)o1).compareTo(o2);
+    }
+
+}
Index: cachestore/jdbm/pom.xml
===================================================================
--- cachestore/jdbm/pom.xml	(revision 0)
+++ cachestore/jdbm/pom.xml	(revision 0)
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.infinispan</groupId>
+        <artifactId>infinispan-parent</artifactId>
+        <version>4.0.0-SNAPSHOT</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+    <groupId>org.infinispan</groupId>
+    <artifactId>infinispan-cachestore-jdbm</artifactId>
+    <name>Infinispan JDBM CacheStore</name>
+    <description>Infinispan JDBM CacheStore module</description> 
+    <properties>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project-package}</groupId>
+            <artifactId>infinispan-core</artifactId>
+            <version>${project-version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>${project-package}</groupId>
+            <artifactId>infinispan-core</artifactId>
+            <version>${project-version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+			<!-- Contains fixes not officially released by JDBM group -->
+			<groupId>org.apache.directory.server</groupId>
+			<artifactId>apacheds-jdbm</artifactId>
+			<version>1.5.4</version>
+        </dependency>
+        
+		<!--
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.14</version>
+        </dependency>
+		-->
+
+    </dependencies>
+    <build>
+    </build>
+
+</project>

Property changes on: cachestore/jdbm/pom.xml
___________________________________________________________________
Added: svn:executable
   + *
Added: svn:keywords
   + Id Revision
Added: svn:eol-style
   + LF

Index: core/src/test/java/org/infinispan/loaders/BaseCacheStoreTest.java
===================================================================
--- core/src/test/java/org/infinispan/loaders/BaseCacheStoreTest.java	(revision 212)
+++ core/src/test/java/org/infinispan/loaders/BaseCacheStoreTest.java	(working copy)
@@ -113,6 +113,7 @@
       se = InternalEntryFactory.create("k", "v", lifespan);
       cs.store(se);
       Thread.sleep(100);
+      cs.purgeExpired();
       assert se.isExpired();
       assert cs.load("k") == null;
       assert !cs.containsKey("k");
@@ -135,6 +136,7 @@
       se = InternalEntryFactory.create("k", "v", -1, idle);
       cs.store(se);
       Thread.sleep(100);
+      cs.purgeExpired();
       assert se.isExpired();
       assert cs.load("k") == null;
       assert !cs.containsKey("k");
@@ -158,6 +160,7 @@
       se = InternalEntryFactory.create("k", "v", lifespan, idle);
       cs.store(se);
       Thread.sleep(100);
+      cs.purgeExpired();
       assert se.isExpired();
       assert cs.load("k") == null;
       assert !cs.containsKey("k");
@@ -557,4 +560,5 @@
 
       if (!exceptions.isEmpty()) throw exceptions.get(0);
    }
-}
+   
+}
\ No newline at end of file
Index: pom.xml
===================================================================
--- pom.xml	(revision 212)
+++ pom.xml	(working copy)
@@ -26,6 +26,7 @@
       <module>cachestore/bdbje</module>
       <module>cachestore/s3</module>
       <module>cachestore/jdbc</module>
+      <module>cachestore/jdbm</module>
       <module>gui-demo</module>
    </modules>
 
@@ -81,6 +82,7 @@
                            <fileset dir="cachestore/bdbje/target/classes"/>
                            <fileset dir="cachestore/jdbc/target/classes"/>
                            <fileset dir="cachestore/s3/target/classes"/>
+                           <fileset dir="cachestore/jdbm/target/classes"/>
                            <fileset dir="core/target/classes"/>
                            <fileset dir="tree/target/classes"/>
                         </zip>
