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 }