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 }