View Javadoc

1   /**
2    * JDBM LICENSE v1.00
3    *
4    * Redistribution and use of this software and associated documentation
5    * ("Software"), with or without modification, are permitted provided
6    * that the following conditions are met:
7    *
8    * 1. Redistributions of source code must retain copyright
9    *    statements and notices.  Redistributions must also contain a
10   *    copy of this document.
11   *
12   * 2. Redistributions in binary form must reproduce the
13   *    above copyright notice, this list of conditions and the
14   *    following disclaimer in the documentation and/or other
15   *    materials provided with the distribution.
16   *
17   * 3. The name "JDBM" must not be used to endorse or promote
18   *    products derived from this Software without prior written
19   *    permission of Cees de Groot.  For written permission,
20   *    please contact cg@cdegroot.com.
21   *
22   * 4. Products derived from this Software may not be called "JDBM"
23   *    nor may "JDBM" appear in their names without prior written
24   *    permission of Cees de Groot.
25   *
26   * 5. Due credit should be given to the JDBM Project
27   *    (http://jdbm.sourceforge.net/).
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
30   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
31   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
33   * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
34   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
35   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
38   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
40   * OF THE POSSIBILITY OF SUCH DAMAGE.
41   *
42   * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
43   * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
44   * Contributions are Copyright (C) 2000 by their associated contributors.
45   *
46   * $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
47   */
48  
49  package jdbm.recman;
50  
51  import jdbm.RecordManager;
52  import jdbm.helper.CacheEvictionException;
53  import jdbm.helper.CachePolicy;
54  import jdbm.helper.CachePolicyListener;
55  import jdbm.helper.DefaultSerializer;
56  import jdbm.helper.Serializer;
57  import jdbm.helper.WrappedRuntimeException;
58  
59  import java.io.IOException;
60  import java.util.Enumeration;
61  
62  /**
63   *  A RecordManager wrapping and caching another RecordManager.
64   *
65   * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
66   * @author <a href="cg@cdegroot.com">Cees de Groot</a>
67   * @version $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
68   */
69  public class CacheRecordManager
70      implements RecordManager
71  {
72  
73      /**
74       * Wrapped RecordManager
75       */
76      protected RecordManager _recman;
77  
78  
79      /**
80       * Cache for underlying RecordManager
81       */
82      protected CachePolicy _cache;
83  
84  
85      /**
86       * Construct a CacheRecordManager wrapping another RecordManager and
87       * using a given cache policy.
88       *
89       * @param recman Wrapped RecordManager
90       * @param cache Cache policy
91       */
92      public CacheRecordManager( RecordManager recman, CachePolicy cache )
93      {
94          if ( recman == null ) {
95              throw new IllegalArgumentException( "Argument 'recman' is null" );
96          }
97          if ( cache == null ) {
98              throw new IllegalArgumentException( "Argument 'cache' is null" );
99          }
100         _recman = recman;
101         _cache = cache;
102         
103         _cache.addListener( new CacheListener() );
104     }
105 
106     
107     /**
108      * Get the underlying Record Manager.
109      *
110      * @return underlying RecordManager or null if CacheRecordManager has
111      *         been closed. 
112      */
113     public RecordManager getRecordManager()
114     {
115         return _recman;
116     }
117 
118     
119     /**
120      * Get the underlying cache policy
121      *
122      * @return underlying CachePolicy or null if CacheRecordManager has
123      *         been closed. 
124      */
125     public CachePolicy getCachePolicy()
126     {
127         return _cache;
128     }
129 
130     
131     /**
132      *  Inserts a new record using a custom serializer.
133      *
134      *  @param obj the object for the new record.
135      *  @return the rowid for the new record.
136      *  @throws IOException when one of the underlying I/O operations fails.
137      */
138     public long insert( Object obj )
139         throws IOException
140     {
141         return insert( obj, DefaultSerializer.INSTANCE );
142     }
143         
144         
145     /**
146      *  Inserts a new record using a custom serializer.
147      *
148      *  @param obj the object for the new record.
149      *  @param serializer a custom serializer
150      *  @return the rowid for the new record.
151      *  @throws IOException when one of the underlying I/O operations fails.
152      */
153     public synchronized long insert( Object obj, Serializer serializer )
154         throws IOException
155     {
156         checkIfClosed();
157 
158         long recid = _recman.insert( obj, serializer );
159         try {
160             _cache.put( new Long( recid ), new CacheEntry( recid, obj, serializer, false ) );
161         } catch ( CacheEvictionException except ) {
162             throw new WrappedRuntimeException( except );
163         }
164         return recid;
165     }
166 
167 
168     /**
169      *  Deletes a record.
170      *
171      *  @param recid the rowid for the record that should be deleted.
172      *  @throws IOException when one of the underlying I/O operations fails.
173      */
174     public synchronized void delete( long recid )
175         throws IOException
176     {
177         checkIfClosed();
178 
179         _recman.delete( recid );
180         _cache.remove( new Long( recid ) );
181     }
182 
183 
184     /**
185      *  Updates a record using standard Java serialization.
186      *
187      *  @param recid the recid for the record that is to be updated.
188      *  @param obj the new object for the record.
189      *  @throws IOException when one of the underlying I/O operations fails.
190      */
191     public void update( long recid, Object obj )
192         throws IOException
193     {
194         update( recid, obj, DefaultSerializer.INSTANCE );
195     }
196     
197 
198     /**
199      *  Updates a record using a custom serializer.
200      *
201      *  @param recid the recid for the record that is to be updated.
202      *  @param obj the new object for the record.
203      *  @param serializer a custom serializer
204      *  @throws IOException when one of the underlying I/O operations fails.
205      */
206     public synchronized void update( long recid, Object obj, 
207                                      Serializer serializer )
208         throws IOException
209     {
210         CacheEntry  entry;
211         Long        id;
212         
213         checkIfClosed();
214 
215         id = new Long( recid );
216         try {
217             entry = (CacheEntry) _cache.get( id );
218             if ( entry != null ) {
219                 // reuse existing cache entry
220                 entry._obj = obj;
221                 entry._serializer = serializer;
222                 entry._isDirty = true;
223             } else {
224                 _cache.put( id, new CacheEntry( recid, obj, serializer, true ) );
225             }
226         } catch ( CacheEvictionException except ) {
227             throw new IOException( except.getMessage() );
228         }
229     }
230 
231 
232     /**
233      *  Fetches a record using standard Java serialization.
234      *
235      *  @param recid the recid for the record that must be fetched.
236      *  @return the object contained in the record.
237      *  @throws IOException when one of the underlying I/O operations fails.
238      */
239     public Object fetch( long recid )
240         throws IOException
241     {
242         return fetch( recid, DefaultSerializer.INSTANCE );
243     }
244 
245         
246     /**
247      *  Fetches a record using a custom serializer.
248      *
249      *  @param recid the recid for the record that must be fetched.
250      *  @param serializer a custom serializer
251      *  @return the object contained in the record.
252      *  @throws IOException when one of the underlying I/O operations fails.
253      */
254     public synchronized Object fetch( long recid, Serializer serializer )
255         throws IOException
256     {
257         checkIfClosed();
258 
259         Long id = new Long( recid );
260         CacheEntry entry = (CacheEntry) _cache.get( id );
261         if ( entry == null ) {
262             entry = new CacheEntry( recid, null, serializer, false );
263             entry._obj = _recman.fetch( recid, serializer );
264             try {
265                 _cache.put( id, entry );
266             } catch ( CacheEvictionException except ) {
267                 throw new WrappedRuntimeException( except );
268             }
269         }
270         return entry._obj;
271     }
272 
273 
274     /**
275      *  Closes the record manager.
276      *
277      *  @throws IOException when one of the underlying I/O operations fails.
278      */
279     public synchronized void close()
280         throws IOException
281     {
282         checkIfClosed();
283 
284         updateCacheEntries();
285         _recman.close();
286         _recman = null;
287         _cache = null;
288     }
289 
290 
291     /**
292      *  Returns the number of slots available for "root" rowids. These slots
293      *  can be used to store special rowids, like rowids that point to
294      *  other rowids. Root rowids are useful for bootstrapping access to
295      *  a set of data.
296      */
297     public synchronized int getRootCount()
298     {
299         checkIfClosed();
300 
301         return _recman.getRootCount();
302     }
303 
304 
305     /**
306      *  Returns the indicated root rowid.
307      *
308      *  @see #getRootCount
309      */
310     public synchronized long getRoot( int id )
311         throws IOException
312     {
313         checkIfClosed();
314 
315         return _recman.getRoot( id );
316     }
317 
318 
319     /**
320      *  Sets the indicated root rowid.
321      *
322      *  @see #getRootCount
323      */
324     public synchronized void setRoot( int id, long rowid )
325         throws IOException
326     {
327         checkIfClosed();
328 
329         _recman.setRoot( id, rowid );
330     }
331 
332 
333     /**
334      * Commit (make persistent) all changes since beginning of transaction.
335      */
336     public synchronized void commit()
337         throws IOException
338     {
339         checkIfClosed();
340         updateCacheEntries();
341         _recman.commit();
342     }
343 
344 
345     /**
346      * Rollback (cancel) all changes since beginning of transaction.
347      */
348     public synchronized void rollback()
349         throws IOException
350     {
351         checkIfClosed();
352 
353         _recman.rollback();
354 
355         // discard all cache entries since we don't know which entries
356         // where part of the transaction
357         _cache.removeAll();
358     }
359 
360 
361     /**
362      * Obtain the record id of a named object. Returns 0 if named object
363      * doesn't exist.
364      */
365     public synchronized long getNamedObject( String name )
366         throws IOException
367     {
368         checkIfClosed();
369 
370         return _recman.getNamedObject( name );
371     }
372 
373 
374     /**
375      * Set the record id of a named object.
376      */
377     public synchronized void setNamedObject( String name, long recid )
378         throws IOException
379     {
380         checkIfClosed();
381 
382         _recman.setNamedObject( name, recid );
383     }
384 
385 
386     /**
387      * Check if RecordManager has been closed.  If so, throw an
388      * IllegalStateException
389      */
390     private void checkIfClosed()
391         throws IllegalStateException
392     {
393         if ( _recman == null ) {
394             throw new IllegalStateException( "RecordManager has been closed" );
395         }
396     }
397 
398     
399     /**
400      * Update all dirty cache objects to the underlying RecordManager.
401      */
402     protected void updateCacheEntries()
403         throws IOException
404     {
405         Enumeration enume = _cache.elements();
406         while ( enume.hasMoreElements() ) {
407             CacheEntry entry = (CacheEntry) enume.nextElement();
408             if ( entry._isDirty ) {
409                 _recman.update( entry._recid, entry._obj, entry._serializer );
410                 entry._isDirty = false;
411             }
412         }
413     }
414 
415     
416     private class CacheEntry
417     {
418 
419         long _recid;
420         Object _obj;
421         Serializer _serializer;
422         boolean _isDirty;
423         
424         CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
425         {
426             _recid = recid;
427             _obj = obj;
428             _serializer = serializer;
429             _isDirty = isDirty;
430         }
431         
432     } // class CacheEntry
433 
434     private class CacheListener
435         implements CachePolicyListener
436     {
437         
438         /** Notification that cache is evicting an object
439          *
440          * @arg obj object evited from cache
441          *
442          */
443         public void cacheObjectEvicted( Object obj ) 
444             throws CacheEvictionException
445         {
446             CacheEntry entry = (CacheEntry) obj;
447             if ( entry._isDirty ) {
448                 try {
449                     _recman.update( entry._recid, entry._obj, entry._serializer );
450                 } catch ( IOException except ) {
451                     throw new CacheEvictionException( except );
452                 }
453             }
454         }
455         
456     }
457 }