KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > cache > loader > jdbm > JdbmCacheLoader


1 package org.jboss.cache.loader.jdbm;
2
3 import jdbm.RecordManager;
4 import jdbm.RecordManagerFactory;
5 import jdbm.btree.BTree;
6 import jdbm.helper.Tuple;
7 import jdbm.helper.TupleBrowser;
8 import org.apache.commons.logging.Log;
9 import org.apache.commons.logging.LogFactory;
10 import org.jboss.cache.CacheSPI;
11 import org.jboss.cache.Fqn;
12 import org.jboss.cache.FqnComparator;
13 import org.jboss.cache.Modification;
14 import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
15 import org.jboss.cache.loader.AbstractCacheLoader;
16
17 import java.io.File JavaDoc;
18 import java.io.IOException JavaDoc;
19 import java.io.Serializable JavaDoc;
20 import java.util.Collections JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.HashSet JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Properties JavaDoc;
27 import java.util.Set JavaDoc;
28 import java.util.concurrent.ConcurrentHashMap JavaDoc;
29
30 /**
31  * A persistent <code>CacheLoader</code> based on the JDBM project.
32  * See http://jdbm.sourceforge.net/ .
33  * Does not support transaction isolation.
34  * <p/>
35  * <p>The configuration string format is:</p>
36  * <pre>environmentDirectoryName[#databaseName]</pre>
37  * <p>where databaseName, if omitted, defaults to the ClusterName property
38  * of the CacheImpl.</p>
39  * <p/>
40  * Data is sorted out like:
41  * <pre>
42  * / = N
43  * /node1 = N
44  * /node1/K/k1 = v1
45  * /node1/K/k2 = v2
46  * /node2 = N
47  * /node2/node3 = N
48  * /node2/node3/K/k1 = v1
49  * /node2/node3/K/k2 = v2
50  * /node2/node4 = N
51  * </pre>
52  * N represents a node, K represents a key block. k and v represent key/value
53  * pairs.
54  * <p/>
55  * TODO the browse operations lock the entire tree; eventually the JDBM team
56  * plans to fix this.
57  *
58  * @author Elias Ross
59  * @version $Id: JdbmCacheLoader.java,v 1.23 2007/01/01 22:12:19 msurtani Exp $
60  */

61 public class JdbmCacheLoader extends AbstractCacheLoader
62 {
63    private static final Log log = LogFactory.getLog(JdbmCacheLoader.class);
64
65    private static final String JavaDoc KEYS = "K";
66    private static final String JavaDoc NODE = "N";
67    private static final String JavaDoc NAME = "JdbmCacheLoader";
68
69    private JdbmCacheLoaderConfig config;
70    private String JavaDoc cacheDbName;
71    private RecordManager recman;
72    private BTree tree;
73    private Map JavaDoc<Object JavaDoc, List JavaDoc<Modification>> transactions = new ConcurrentHashMap JavaDoc<Object JavaDoc, List JavaDoc<Modification>>();
74
75    /*
76     * Service implementation -- lifecycle methods.
77     * Note that setConfig() and setCache() are called before create().
78     */

79
80    public void create() throws Exception JavaDoc
81    {
82       checkNotOpen();
83    }
84
85    public void destroy()
86    {
87    }
88
89    /**
90     * Opens the environment and the database specified by the configuration
91     * string. The environment and databases are created if necessary.
92     */

93    public void start()
94            throws Exception JavaDoc
95    {
96
97       log.trace("Starting JdbmCacheLoader instance.");
98       checkNotOpen();
99       checkNonNull(cache, "CacheSPI object is required");
100
101       String JavaDoc locationStr = config.getLocation();
102       if (locationStr == null)
103       {
104          locationStr = System.getProperty("java.io.tmpdir");
105          config.setLocation(locationStr);
106       }
107
108       // test location
109
File JavaDoc location = new File JavaDoc(locationStr);
110       if (!location.exists())
111       {
112          boolean created = location.mkdirs();
113          if (!created) throw new IOException JavaDoc("Unable to create cache loader location " + location);
114
115       }
116       if (!location.isDirectory())
117       {
118          throw new IOException JavaDoc("Cache loader location [" + location + "] is not a directory!");
119       }
120
121       /* Parse config string. */
122       File JavaDoc homeDir;
123       int offset = locationStr.indexOf('#');
124       if (offset >= 0 && offset < locationStr.length() - 1)
125       {
126          homeDir = new File JavaDoc(locationStr.substring(0, offset));
127          cacheDbName = locationStr.substring(offset + 1);
128       }
129       else
130       {
131          homeDir = new File JavaDoc(locationStr);
132          cacheDbName = cache.getClusterName();
133       }
134
135       try
136       {
137          openDatabase(new File JavaDoc(homeDir, cacheDbName));
138       }
139       catch (Exception JavaDoc e)
140       {
141          destroy();
142          throw e;
143       }
144    }
145
146    /**
147     * Opens all databases and initializes database related information.
148     */

149    private void openDatabase(File JavaDoc f)
150            throws Exception JavaDoc
151    {
152       Properties JavaDoc props = new Properties JavaDoc();
153       // Incorporate properties from setConfig() ?
154
// props.put(RecordManagerOptions.SERIALIZER, RecordManagerOptions.SERIALIZER_EXTENSIBLE);
155
// props.put(RecordManagerOptions.PROFILE_SERIALIZATION, "false");
156
recman = RecordManagerFactory.createRecordManager(f.toString(), props);
157       long recid = recman.getNamedObject(NAME);
158       log.debug(NAME + " located as " + recid);
159       if (recid == 0)
160       {
161          tree = BTree.createInstance(recman, new JdbmFqnComparator());
162          recman.setNamedObject(NAME, tree.getRecid());
163       }
164       else
165       {
166          tree = BTree.load(recman, recid);
167       }
168
169       log.info("JDBM database " + f + " opened with " + tree.size() + " entries");
170    }
171
172    /**
173     * Closes all databases, ignoring exceptions, and nulls references to all
174     * database related information.
175     */

176    private void closeDatabases()
177    {
178       if (recman != null)
179       {
180          try
181          {
182             recman.close();
183          }
184          catch (Exception JavaDoc shouldNotOccur)
185          {
186             log.warn("Caught unexpected exception", shouldNotOccur);
187          }
188       }
189       recman = null;
190       tree = null;
191    }
192
193    /**
194     * Closes the databases and environment, and nulls references to them.
195     */

196    public void stop()
197    {
198       log.debug("stop");
199       closeDatabases();
200    }
201
202    /*
203     * CacheLoader implementation.
204     */

205
206    /**
207     * Sets the configuration string for this cache loader.
208     */

209    public void setConfig(IndividualCacheLoaderConfig base)
210    {
211       checkNotOpen();
212
213       if (base instanceof JdbmCacheLoaderConfig)
214       {
215          this.config = (JdbmCacheLoaderConfig) base;
216       }
217       else
218       {
219          config = new JdbmCacheLoaderConfig(base);
220       }
221
222       if (log.isTraceEnabled()) log.trace("Configuring cache loader with location = " + config.getLocation());
223    }
224
225    public IndividualCacheLoaderConfig getConfig()
226    {
227       return config;
228    }
229
230    /**
231     * Sets the CacheImpl owner of this cache loader.
232     */

233    public void setCache(CacheSPI c)
234    {
235       super.setCache(c);
236       checkNotOpen();
237    }
238
239    /**
240     * Returns a special FQN for keys of a node.
241     */

242    private Fqn keys(Fqn name)
243    {
244       return new Fqn(name, KEYS);
245    }
246
247    /**
248     * Returns a special FQN for key of a node.
249     */

250    private Fqn key(Fqn name, Object JavaDoc key)
251    {
252       return new Fqn(name, KEYS, nullMask(key));
253    }
254
255    /**
256     * Returns an unmodifiable set of relative children names, or
257     * returns null if the parent node is not found or if no children are found.
258     * This is a fairly expensive operation, and is assumed to be performed by
259     * browser applications. Calling this method as part of a run-time
260     * transaction is not recommended.
261     */

262    public Set JavaDoc<String JavaDoc> getChildrenNames(Fqn name)
263            throws Exception JavaDoc
264    {
265
266       if (log.isTraceEnabled())
267       {
268          log.trace("getChildrenNames " + name);
269       }
270
271       synchronized (tree)
272       {
273          return getChildrenNames0(name);
274       }
275    }
276
277    private Set JavaDoc<String JavaDoc> getChildrenNames0(Fqn name) throws IOException JavaDoc
278    {
279       TupleBrowser browser = tree.browse(name);
280       Tuple t = new Tuple();
281
282       if (browser.getNext(t))
283       {
284          if (!t.getValue().equals(NODE))
285          {
286             log.trace(" not a node");
287             return null;
288          }
289       }
290       else
291       {
292          log.trace(" no nodes");
293          return null;
294       }
295
296       Set JavaDoc<String JavaDoc> set = new HashSet JavaDoc<String JavaDoc>();
297
298       // Want only /a/b/c/X nodes
299
int depth = name.size() + 1;
300       while (browser.getNext(t))
301       {
302          Fqn fqn = (Fqn) t.getKey();
303          int size = fqn.size();
304          if (size < depth)
305          {
306             break;
307          }
308          if (size == depth && t.getValue().equals(NODE))
309          {
310             set.add((String JavaDoc) fqn.getLastElement());
311          }
312       }
313
314       if (set.isEmpty())
315       {
316          return null;
317       }
318
319       return Collections.unmodifiableSet(set);
320    }
321
322    /**
323     * Returns a map containing all key-value pairs for the given FQN, or null
324     * if the node is not present.
325     * This operation is always non-transactional, even in a transactional
326     * environment.
327     */

328    public Map JavaDoc get(Fqn name)
329            throws Exception JavaDoc
330    {
331
332       checkOpen();
333       checkNonNull(name, "name");
334
335       if (tree.find(name) == null)
336       {
337          if (log.isTraceEnabled())
338          {
339             log.trace("get, no node: " + name);
340          }
341          return null;
342       }
343
344       Fqn keys = keys(name);
345       Tuple t = new Tuple();
346       Map JavaDoc map = new HashMap JavaDoc();
347
348       synchronized (tree)
349       {
350          TupleBrowser browser = tree.browse(keys);
351          while (browser.getNext(t))
352          {
353             Fqn fqn = (Fqn) t.getKey();
354             if (!fqn.isChildOf(keys))
355             {
356                break;
357             }
358             Object JavaDoc k = fqn.getLastElement();
359             Object JavaDoc v = t.getValue();
360             map.put(nullUnmask(k), nullUnmask(v));
361          }
362       }
363
364       if (log.isTraceEnabled())
365       {
366          log.trace("get " + name + " map=" + map);
367       }
368
369       return map;
370    }
371
372    /**
373     * Returns whether the given node exists.
374     */

375    public boolean exists(Fqn name) throws IOException JavaDoc
376    {
377       return tree.find(name) != null;
378    }
379
380    private void commit() throws Exception JavaDoc
381    {
382       recman.commit();
383    }
384
385    /**
386     * Stores a single FQN-key-value record.
387     * Intended to be used in a non-transactional environment, but will use
388     * auto-commit in a transactional environment.
389     */

390    public Object JavaDoc put(Fqn name, Object JavaDoc key, Object JavaDoc value) throws Exception JavaDoc
391    {
392       try
393       {
394          return put0(name, key, value);
395       }
396       finally
397       {
398          commit();
399       }
400    }
401
402    private Object JavaDoc put0(Fqn name, Object JavaDoc key, Object JavaDoc value) throws Exception JavaDoc
403    {
404       checkNonNull(name, "name");
405       makeNode(name);
406       Fqn rec = key(name, key);
407       Object JavaDoc oldValue = insert(rec, value);
408       if (log.isTraceEnabled())
409       {
410          log.trace("put " + rec + " value=" + value + " old=" + oldValue);
411       }
412       return oldValue;
413    }
414
415    /**
416     * Stores a map of key-values for a given FQN, but does not delete existing
417     * key-value pairs (that is, it does not erase).
418     * Intended to be used in a non-transactional environment, but will use
419     * auto-commit in a transactional environment.
420     */

421    public void put(Fqn name, Map JavaDoc values) throws Exception JavaDoc
422    {
423       put0(name, values);
424       commit();
425    }
426
427    private void put0(Fqn name, Map JavaDoc values) throws Exception JavaDoc
428    {
429       if (log.isTraceEnabled())
430       {
431          log.trace("put " + name + " values=" + values);
432       }
433       makeNode(name);
434       if (values == null)
435       {
436          return;
437       }
438       Iterator JavaDoc i = values.entrySet().iterator();
439       while (i.hasNext())
440       {
441          Map.Entry JavaDoc me = (Map.Entry JavaDoc) i.next();
442          Fqn rec = key(name, me.getKey());
443          insert(rec, nullMask(me.getValue()));
444       }
445    }
446
447    /**
448     * Marks a FQN as a node.
449     */

450    private void makeNode(Fqn fqn) throws IOException JavaDoc
451    {
452       if (exists(fqn))
453       {
454          return;
455       }
456       int size = fqn.size();
457       // TODO should not modify so darn often
458
for (int i = size; i >= 0; i--)
459       {
460          Fqn child = fqn.getFqnChild(i);
461          Object JavaDoc existing = tree.insert(child, NODE, false);
462          if (existing != null)
463          {
464             break;
465          }
466       }
467    }
468
469    private Object JavaDoc insert(Fqn fqn, Object JavaDoc value) throws IOException JavaDoc
470    {
471       return nullUnmask(tree.insert(fqn, nullMask(value), true));
472    }
473
474    /**
475     * Erase a FQN and children.
476     * Does not commit.
477     */

478    private void erase0(Fqn name)
479            throws IOException JavaDoc
480    {
481       erase0(name, true);
482    }
483
484    private void erase0(Fqn name, boolean self)
485            throws IOException JavaDoc
486    {
487       if (log.isTraceEnabled())
488       {
489          log.trace("erase " + name + " self=" + self);
490       }
491       synchronized (tree)
492       {
493          TupleBrowser browser = tree.browse(name);
494          Tuple t = new Tuple();
495          if (browser.getNext(t))
496          {
497             if (self)
498             {
499                tree.remove(t.getKey());
500             }
501          }
502          while (browser.getNext(t))
503          {
504             Fqn fqn = (Fqn) t.getKey();
505             if (!fqn.isChildOf(name))
506             {
507                break;
508             }
509             tree.remove(fqn);
510          }
511       }
512    }
513
514    /**
515     * Erase a FQN's key.
516     * Does not commit.
517     */

518    private Object JavaDoc eraseKey0(Fqn name, Object JavaDoc key)
519            throws IOException JavaDoc
520    {
521       if (log.isTraceEnabled())
522       {
523          log.trace("eraseKey " + name + " key " + key);
524       }
525       Fqn fqnKey = key(name, key);
526       try
527       {
528          return tree.remove(fqnKey);
529       }
530       catch (IllegalArgumentException JavaDoc e)
531       {
532          // Seems to be harmless
533
// log.warn("IllegalArgumentException for " + fqnKey);
534
// dump();
535
return null;
536       }
537    }
538
539    /**
540     * Applies the given modifications.
541     * Intended to be used in a non-transactional environment, but will use
542     * auto-commit in a transactional environment.
543     */

544    public void put(List JavaDoc<Modification> modifications)
545            throws Exception JavaDoc
546    {
547
548       checkOpen();
549       checkNonNull(modifications, "modifications");
550
551       super.put(modifications);
552       commit();
553    }
554
555    /**
556     * Deletes the node for a given FQN and all its descendent nodes.
557     * Intended to be used in a non-transactional environment, but will use
558     * auto-commit in a transactional environment.
559     */

560    public void remove(Fqn name)
561            throws Exception JavaDoc
562    {
563       erase0(name);
564       commit();
565    }
566
567    /**
568     * Deletes a single FQN-key-value record.
569     * Intended to be used in a non-transactional environment, but will use
570     * auto-commit in a transactional environment.
571     */

572    public Object JavaDoc remove(Fqn name, Object JavaDoc key)
573            throws Exception JavaDoc
574    {
575
576       try
577       {
578          return eraseKey0(name, key);
579       }
580       finally
581       {
582          commit();
583       }
584    }
585
586    /**
587     * Clears the map for the given node, but does not remove the node.
588     */

589    public void removeData(Fqn name)
590            throws Exception JavaDoc
591    {
592       erase0(name, false);
593    }
594
595    /**
596     * Applies and commits the given modifications in one transaction.
597     */

598    public void prepare(Object JavaDoc tx, List JavaDoc<Modification> modifications, boolean onePhase)
599            throws Exception JavaDoc
600    {
601       if (onePhase)
602       {
603          put(modifications);
604       }
605       else
606       {
607          transactions.put(tx, modifications);
608       }
609    }
610
611    /**
612     * Commits a transaction.
613     */

614    public void commit(Object JavaDoc tx) throws Exception JavaDoc
615    {
616       List JavaDoc<Modification> modifications = transactions.remove(tx);
617       if (modifications == null)
618       {
619          throw new IllegalStateException JavaDoc("transaction " + tx + " not found in transaction table");
620       }
621       put(modifications);
622       commit();
623    }
624
625    /**
626     * Removes transaction in progress.
627     */

628    public void rollback(Object JavaDoc tx)
629    {
630       transactions.remove(tx);
631    }
632
633    /**
634     * Throws an exception if the environment is not open.
635     */

636    private void checkOpen()
637    {
638       if (tree == null)
639       {
640          throw new IllegalStateException JavaDoc(
641                  "Operation not allowed before calling create()");
642       }
643    }
644
645    /**
646     * Throws an exception if the environment is not open.
647     */

648    private void checkNotOpen()
649    {
650       if (tree != null)
651       {
652          throw new IllegalStateException JavaDoc(
653                  "Operation not allowed after calling create()");
654       }
655    }
656
657    /**
658     * Throws an exception if the parameter is null.
659     */

660    private void checkNonNull(Object JavaDoc param, String JavaDoc paramName)
661    {
662       if (param == null)
663       {
664          throw new NullPointerException JavaDoc(
665                  "Parameter must not be null: " + paramName);
666       }
667    }
668
669    private Object JavaDoc nullMask(Object JavaDoc o)
670    {
671       return (o == null) ? Null.NULL : o;
672    }
673
674    private Object JavaDoc nullUnmask(Object JavaDoc o)
675    {
676       return (o == Null.NULL) ? null : o;
677    }
678
679    /**
680     * Dumps the tree to debug.
681     */

682    public void dump() throws IOException JavaDoc
683    {
684       dump(Fqn.ROOT);
685    }
686
687    /**
688     * Dumps the tree past the key to debug.
689     */

690    public void dump(Object JavaDoc key) throws IOException JavaDoc
691    {
692       TupleBrowser browser = tree.browse(key);
693       Tuple t = new Tuple();
694       log.debug("contents: " + key);
695       while (browser.getNext(t))
696       {
697          log.debug(t.getKey() + "\t" + t.getValue());
698       }
699       log.debug("");
700    }
701
702    public String JavaDoc toString()
703    {
704       BTree bt = tree;
705       int size = (bt == null) ? -1 : bt.size();
706       return "JdbmCacheLoader locationStr=" + config.getLocation() +
707               " size=" + size;
708    }
709
710 }
711
712 class JdbmFqnComparator extends FqnComparator implements Serializable JavaDoc
713 {
714    private static final long serialVersionUID = 1000;
715 }
716
717
Popular Tags