001 /**
002 * JDBM LICENSE v1.00
003 *
004 * Redistribution and use of this software and associated documentation
005 * ("Software"), with or without modification, are permitted provided
006 * that the following conditions are met:
007 *
008 * 1. Redistributions of source code must retain copyright
009 * statements and notices. Redistributions must also contain a
010 * copy of this document.
011 *
012 * 2. Redistributions in binary form must reproduce the
013 * above copyright notice, this list of conditions and the
014 * following disclaimer in the documentation and/or other
015 * materials provided with the distribution.
016 *
017 * 3. The name "JDBM" must not be used to endorse or promote
018 * products derived from this Software without prior written
019 * permission of Cees de Groot. For written permission,
020 * please contact cg@cdegroot.com.
021 *
022 * 4. Products derived from this Software may not be called "JDBM"
023 * nor may "JDBM" appear in their names without prior written
024 * permission of Cees de Groot.
025 *
026 * 5. Due credit should be given to the JDBM Project
027 * (http://jdbm.sourceforge.net/).
028 *
029 * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
033 * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040 * OF THE POSSIBILITY OF SUCH DAMAGE.
041 *
042 * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043 * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
044 * Contributions are Copyright (C) 2000 by their associated contributors.
045 *
046 * $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
047 */
048
049 package jdbm.recman;
050
051 import jdbm.RecordManager;
052 import jdbm.helper.CacheEvictionException;
053 import jdbm.helper.CachePolicy;
054 import jdbm.helper.CachePolicyListener;
055 import jdbm.helper.DefaultSerializer;
056 import jdbm.helper.Serializer;
057 import jdbm.helper.WrappedRuntimeException;
058
059 import java.io.IOException;
060 import java.util.Enumeration;
061
062 /**
063 * A RecordManager wrapping and caching another RecordManager.
064 *
065 * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
066 * @author <a href="cg@cdegroot.com">Cees de Groot</a>
067 * @version $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
068 */
069 public class CacheRecordManager
070 implements RecordManager
071 {
072
073 /**
074 * Wrapped RecordManager
075 */
076 protected RecordManager _recman;
077
078
079 /**
080 * Cache for underlying RecordManager
081 */
082 protected CachePolicy _cache;
083
084
085 /**
086 * Construct a CacheRecordManager wrapping another RecordManager and
087 * using a given cache policy.
088 *
089 * @param recman Wrapped RecordManager
090 * @param cache Cache policy
091 */
092 public CacheRecordManager( RecordManager recman, CachePolicy cache )
093 {
094 if ( recman == null ) {
095 throw new IllegalArgumentException( "Argument 'recman' is null" );
096 }
097 if ( cache == null ) {
098 throw new IllegalArgumentException( "Argument 'cache' is null" );
099 }
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
271 if ( entry._obj instanceof byte[] )
272 {
273 byte[] copy = new byte[((byte[])entry._obj).length];
274 System.arraycopy( entry._obj, 0, copy, 0, ((byte[])entry._obj).length );
275 return copy;
276 }
277
278 return entry._obj;
279 }
280
281
282 /**
283 * Closes the record manager.
284 *
285 * @throws IOException when one of the underlying I/O operations fails.
286 */
287 public synchronized void close()
288 throws IOException
289 {
290 checkIfClosed();
291
292 updateCacheEntries();
293 _recman.close();
294 _recman = null;
295 _cache = null;
296 }
297
298
299 /**
300 * Returns the number of slots available for "root" rowids. These slots
301 * can be used to store special rowids, like rowids that point to
302 * other rowids. Root rowids are useful for bootstrapping access to
303 * a set of data.
304 */
305 public synchronized int getRootCount()
306 {
307 checkIfClosed();
308
309 return _recman.getRootCount();
310 }
311
312
313 /**
314 * Returns the indicated root rowid.
315 *
316 * @see #getRootCount
317 */
318 public synchronized long getRoot( int id )
319 throws IOException
320 {
321 checkIfClosed();
322
323 return _recman.getRoot( id );
324 }
325
326
327 /**
328 * Sets the indicated root rowid.
329 *
330 * @see #getRootCount
331 */
332 public synchronized void setRoot( int id, long rowid )
333 throws IOException
334 {
335 checkIfClosed();
336
337 _recman.setRoot( id, rowid );
338 }
339
340
341 /**
342 * Commit (make persistent) all changes since beginning of transaction.
343 */
344 public synchronized void commit()
345 throws IOException
346 {
347 checkIfClosed();
348 updateCacheEntries();
349 _recman.commit();
350 }
351
352
353 /**
354 * Rollback (cancel) all changes since beginning of transaction.
355 */
356 public synchronized void rollback()
357 throws IOException
358 {
359 checkIfClosed();
360
361 _recman.rollback();
362
363 // discard all cache entries since we don't know which entries
364 // where part of the transaction
365 _cache.removeAll();
366 }
367
368
369 /**
370 * Obtain the record id of a named object. Returns 0 if named object
371 * doesn't exist.
372 */
373 public synchronized long getNamedObject( String name )
374 throws IOException
375 {
376 checkIfClosed();
377
378 return _recman.getNamedObject( name );
379 }
380
381
382 /**
383 * Set the record id of a named object.
384 */
385 public synchronized void setNamedObject( String name, long recid )
386 throws IOException
387 {
388 checkIfClosed();
389
390 _recman.setNamedObject( name, recid );
391 }
392
393
394 /**
395 * Check if RecordManager has been closed. If so, throw an
396 * IllegalStateException
397 */
398 private void checkIfClosed()
399 throws IllegalStateException
400 {
401 if ( _recman == null ) {
402 throw new IllegalStateException( "RecordManager has been closed" );
403 }
404 }
405
406
407 /**
408 * Update all dirty cache objects to the underlying RecordManager.
409 */
410 protected void updateCacheEntries()
411 throws IOException
412 {
413 Enumeration enume = _cache.elements();
414 while ( enume.hasMoreElements() ) {
415 CacheEntry entry = (CacheEntry) enume.nextElement();
416 if ( entry._isDirty ) {
417 _recman.update( entry._recid, entry._obj, entry._serializer );
418 entry._isDirty = false;
419 }
420 }
421 }
422
423
424 private class CacheEntry
425 {
426
427 long _recid;
428 Object _obj;
429 Serializer _serializer;
430 boolean _isDirty;
431
432 CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
433 {
434 _recid = recid;
435 _obj = obj;
436 _serializer = serializer;
437 _isDirty = isDirty;
438 }
439
440 } // class CacheEntry
441
442 private class CacheListener
443 implements CachePolicyListener
444 {
445
446 /** Notification that cache is evicting an object
447 *
448 * @arg obj object evited from cache
449 *
450 */
451 public void cacheObjectEvicted( Object obj )
452 throws CacheEvictionException
453 {
454 CacheEntry entry = (CacheEntry) obj;
455 if ( entry._isDirty ) {
456 try {
457 _recman.update( entry._recid, entry._obj, entry._serializer );
458 } catch ( IOException except ) {
459 throw new CacheEvictionException( except );
460 }
461 }
462 }
463
464 }
465 }