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 package jdbm.recman;
049
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 import org.apache.directory.server.i18n.I18n;
063
064
065 /**
066 * A RecordManager wrapping and caching another RecordManager.
067 *
068 * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
069 * @author <a href="cg@cdegroot.com">Cees de Groot</a>
070 * @version $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
071 */
072 public class CacheRecordManager implements RecordManager
073 {
074 /** Wrapped RecordManager */
075 protected RecordManager recMgr;
076
077 /** Cache for underlying RecordManager */
078 protected CachePolicy<Long,CacheEntry> cache;
079
080
081 /**
082 * Construct a CacheRecordManager wrapping another RecordManager and
083 * using a given cache policy.
084 *
085 * @param recMgr Wrapped RecordManager
086 * @param cache Cache policy
087 */
088 public CacheRecordManager( RecordManager recMgr, CachePolicy<Long,CacheEntry> cache )
089 {
090 if ( recMgr == null )
091 {
092 throw new IllegalArgumentException( I18n.err( I18n.ERR_517 ) );
093 }
094
095 if ( cache == null )
096 {
097 throw new IllegalArgumentException( I18n.err( I18n.ERR_542 ) );
098 }
099
100 this.recMgr = recMgr;
101 this.cache = cache;
102
103 this.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 recMgr;
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<Long,CacheEntry> 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 ) throws IOException
139 {
140 return insert( obj, DefaultSerializer.INSTANCE );
141 }
142
143
144 /**
145 * Inserts a new record using a custom serializer.
146 *
147 * @param obj the object for the new record.
148 * @param serializer a custom serializer
149 * @return the rowid for the new record.
150 * @throws IOException when one of the underlying I/O operations fails.
151 */
152 public synchronized long insert( Object obj, Serializer serializer ) throws IOException
153 {
154 checkIfClosed();
155
156 long recid = recMgr.insert( obj, serializer );
157 try
158 {
159 cache.put( recid, new CacheEntry( recid, obj, serializer, false ) );
160 }
161 catch ( CacheEvictionException except )
162 {
163 throw new WrappedRuntimeException( except );
164 }
165 return recid;
166 }
167
168
169 /**
170 * Deletes a record.
171 *
172 * @param recid the rowid for the record that should be deleted.
173 * @throws IOException when one of the underlying I/O operations fails.
174 */
175 public synchronized void delete( long recid ) throws IOException
176 {
177 checkIfClosed();
178
179 recMgr.delete( recid );
180 cache.remove( 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 ) throws IOException
192 {
193 update( recid, obj, DefaultSerializer.INSTANCE );
194 }
195
196
197 /**
198 * Updates a record using a custom serializer.
199 *
200 * @param recid the recid for the record that is to be updated.
201 * @param obj the new object for the record.
202 * @param serializer a custom serializer
203 * @throws IOException when one of the underlying I/O operations fails.
204 */
205 public synchronized void update( long recid, Object obj, Serializer serializer ) throws IOException
206 {
207 CacheEntry entry;
208
209 checkIfClosed();
210
211 try {
212 entry = cache.get( recid );
213
214 if ( entry != null )
215 {
216 // reuse existing cache entry
217 entry.obj = obj;
218 entry.serializer = serializer;
219 entry.isDirty = true;
220 }
221 else
222 {
223 cache.put( recid, new CacheEntry( recid, obj, serializer, true ) );
224 }
225 }
226 catch ( CacheEvictionException except )
227 {
228 throw new IOException( except.getLocalizedMessage() );
229 }
230 }
231
232
233 /**
234 * Fetches a record using standard Java serialization.
235 *
236 * @param recid the recid for the record that must be fetched.
237 * @return the object contained in the record.
238 * @throws IOException when one of the underlying I/O operations fails.
239 */
240 public Object fetch( long recid ) 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 ) throws IOException
255 {
256 checkIfClosed();
257
258 CacheEntry entry = cache.get( recid );
259 if ( entry == null )
260 {
261 entry = new CacheEntry( recid, null, serializer, false );
262 entry.obj = recMgr.fetch( recid, serializer );
263 try
264 {
265 cache.put( recid, entry );
266 }
267 catch ( CacheEvictionException except )
268 {
269 throw new WrappedRuntimeException( except );
270 }
271 }
272
273 if ( entry.obj instanceof byte[] )
274 {
275 byte[] copy = new byte[ ( ( byte[] ) entry.obj ).length ];
276 System.arraycopy( entry.obj, 0, copy, 0, ( ( byte[] ) entry.obj ).length );
277 return copy;
278 }
279
280 return entry.obj;
281 }
282
283
284 /**
285 * Closes the record manager.
286 *
287 * @throws IOException when one of the underlying I/O operations fails.
288 */
289 public synchronized void close() throws IOException
290 {
291 checkIfClosed();
292
293 updateCacheEntries();
294 recMgr.close();
295 recMgr = null;
296 cache = null;
297 }
298
299
300 /**
301 * Returns the number of slots available for "root" rowids. These slots
302 * can be used to store special rowids, like rowids that point to
303 * other rowids. Root rowids are useful for bootstrapping access to
304 * a set of data.
305 */
306 public synchronized int getRootCount()
307 {
308 checkIfClosed();
309
310 return recMgr.getRootCount();
311 }
312
313
314 /**
315 * Returns the indicated root rowid.
316 *
317 * @see #getRootCount
318 */
319 public synchronized long getRoot( int id ) throws IOException
320 {
321 checkIfClosed();
322
323 return recMgr.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 ) throws IOException
333 {
334 checkIfClosed();
335
336 recMgr.setRoot( id, rowid );
337 }
338
339
340 /**
341 * Commit (make persistent) all changes since beginning of transaction.
342 */
343 public synchronized void commit() throws IOException
344 {
345 checkIfClosed();
346 updateCacheEntries();
347 recMgr.commit();
348 }
349
350
351 /**
352 * Rollback (cancel) all changes since beginning of transaction.
353 */
354 public synchronized void rollback() throws IOException
355 {
356 checkIfClosed();
357
358 recMgr.rollback();
359
360 // discard all cache entries since we don't know which entries
361 // where part of the transaction
362 cache.removeAll();
363 }
364
365
366 /**
367 * Obtain the record id of a named object. Returns 0 if named object
368 * doesn't exist.
369 */
370 public synchronized long getNamedObject( String name ) throws IOException
371 {
372 checkIfClosed();
373
374 return recMgr.getNamedObject( name );
375 }
376
377
378 /**
379 * Set the record id of a named object.
380 */
381 public synchronized void setNamedObject( String name, long recid ) throws IOException
382 {
383 checkIfClosed();
384
385 recMgr.setNamedObject( name, recid );
386 }
387
388
389 /**
390 * Check if RecordManager has been closed. If so, throw an IllegalStateException
391 */
392 private void checkIfClosed() throws IllegalStateException
393 {
394 if ( recMgr == null )
395 {
396 throw new IllegalStateException( I18n.err( I18n.ERR_538 ) );
397 }
398 }
399
400
401 /**
402 * Update all dirty cache objects to the underlying RecordManager.
403 */
404 protected void updateCacheEntries() throws IOException
405 {
406 Enumeration<CacheEntry> enume = cache.elements();
407 while ( enume.hasMoreElements() )
408 {
409 CacheEntry entry = enume.nextElement();
410 if ( entry.isDirty )
411 {
412 recMgr.update( entry.recid, entry.obj, entry.serializer );
413 entry.isDirty = false;
414 }
415 }
416 }
417
418
419 private class CacheEntry
420 {
421 long recid;
422 Object obj;
423 Serializer serializer;
424 boolean isDirty;
425
426 CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
427 {
428 this.recid = recid;
429 this.obj = obj;
430 this.serializer = serializer;
431 this.isDirty = isDirty;
432 }
433
434 } // class CacheEntry
435
436
437 private class CacheListener implements CachePolicyListener<CacheEntry>
438 {
439
440 /**
441 * Notification that cache is evicting an object
442 *
443 * @param obj object evicted from cache
444 */
445 public void cacheObjectEvicted( CacheEntry obj ) throws CacheEvictionException
446 {
447 CacheEntry entry = obj;
448 if ( entry.isDirty )
449 {
450 try
451 {
452 recMgr.update( entry.recid, entry.obj, entry.serializer );
453 }
454 catch ( IOException except )
455 {
456 throw new CacheEvictionException( except );
457 }
458 }
459 }
460 }
461 }