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: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $ 047 */ 048 049 package jdbm.recman; 050 051 import java.io.IOException; 052 053 import java.util.HashMap; 054 import java.util.Map; 055 056 import org.apache.directory.server.i18n.I18n; 057 058 import jdbm.RecordManager; 059 import jdbm.helper.Serializer; 060 import jdbm.helper.DefaultSerializer; 061 062 /** 063 * This class manages records, which are uninterpreted blobs of data. The 064 * set of operations is simple and straightforward: you communicate with 065 * the class using long "rowids" and byte[] data blocks. Rowids are returned 066 * on inserts and you can stash them away someplace safe to be able to get 067 * back to them. Data blocks can be as long as you wish, and may have 068 * lengths different from the original when updating. 069 * <p> 070 * Operations are synchronized, so that only one of them will happen 071 * concurrently even if you hammer away from multiple threads. Operations 072 * are made atomic by keeping a transaction log which is recovered after 073 * a crash, so the operations specified by this interface all have ACID 074 * properties. 075 * <p> 076 * You identify a file by just the name. The package attaches <tt>.db</tt> 077 * for the database file, and <tt>.lg</tt> for the transaction log. The 078 * transaction log is synchronized regularly and then restarted, so don't 079 * worry if you see the size going up and down. 080 * 081 * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a> 082 * @author <a href="cg@cdegroot.com">Cees de Groot</a> 083 * @version $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $ 084 */ 085 public final class BaseRecordManager 086 implements RecordManager 087 { 088 089 /** Underlying record file. */ 090 private RecordFile recordFile; 091 092 /** Physical row identifier manager. */ 093 private PhysicalRowIdManager physMgr; 094 095 /** Logical to Physical row identifier manager. */ 096 private LogicalRowIdManager logMgr; 097 098 /** Page manager. */ 099 private PageManager pageMgr; 100 101 /** Reserved slot for name directory. */ 102 public static final int NAME_DIRECTORY_ROOT = 0; 103 104 /** Static debugging flag */ 105 public static final boolean DEBUG = false; 106 107 /** 108 * Directory of named JDBMHashtables. This directory is a persistent 109 * directory, stored as a Hashtable. It can be retrieved by using 110 * the NAME_DIRECTORY_ROOT. 111 */ 112 private Map<String,Long> nameDirectory; 113 114 115 /** 116 * Creates a record manager for the indicated file 117 * 118 * @throws IOException when the file cannot be opened or is not 119 * a valid file content-wise. 120 */ 121 public BaseRecordManager( String filename ) throws IOException 122 { 123 recordFile = new RecordFile( filename ); 124 pageMgr = new PageManager( recordFile ); 125 physMgr = new PhysicalRowIdManager( recordFile, pageMgr ); 126 logMgr = new LogicalRowIdManager( recordFile, pageMgr ); 127 } 128 129 130 /** 131 * Get the underlying Transaction Manager 132 */ 133 public synchronized TransactionManager getTransactionManager() 134 { 135 checkIfClosed(); 136 return recordFile.txnMgr; 137 } 138 139 140 /** 141 * Switches off transactions for the record manager. This means 142 * that a) a transaction log is not kept, and b) writes aren't 143 * synch'ed after every update. This is useful when batch inserting 144 * into a new database. 145 * <p> 146 * Only call this method directly after opening the file, otherwise 147 * the results will be undefined. 148 */ 149 public synchronized void disableTransactions() 150 { 151 checkIfClosed(); 152 recordFile.disableTransactions(); 153 } 154 155 156 /** 157 * Closes the record manager. 158 * 159 * @throws IOException when one of the underlying I/O operations fails. 160 */ 161 public synchronized void close() throws IOException 162 { 163 checkIfClosed(); 164 165 pageMgr.close(); 166 pageMgr = null; 167 168 recordFile.close(); 169 recordFile = null; 170 } 171 172 173 /** 174 * Inserts a new record using standard java object serialization. 175 * 176 * @param obj the object for the new record. 177 * @return the rowid for the new record. 178 * @throws IOException when one of the underlying I/O operations fails. 179 */ 180 public long insert( Object obj ) throws IOException 181 { 182 return insert( obj, DefaultSerializer.INSTANCE ); 183 } 184 185 186 /** 187 * Inserts a new record using a custom serializer. 188 * 189 * @param obj the object for the new record. 190 * @param serializer a custom serializer 191 * @return the rowid for the new record. 192 * @throws IOException when one of the underlying I/O operations fails. 193 */ 194 public synchronized long insert( Object obj, Serializer serializer ) throws IOException 195 { 196 byte[] data; 197 long recid; 198 Location physRowId; 199 200 checkIfClosed(); 201 202 data = serializer.serialize( obj ); 203 physRowId = physMgr.insert( data, 0, data.length ); 204 recid = logMgr.insert( physRowId ).toLong(); 205 206 if ( DEBUG ) 207 { 208 System.out.println( "BaseRecordManager.insert() recid " + recid + " length " + data.length ) ; 209 } 210 211 return recid; 212 } 213 214 215 /** 216 * Deletes a record. 217 * 218 * @param recid the rowid for the record that should be deleted. 219 * @throws IOException when one of the underlying I/O operations fails. 220 */ 221 public synchronized void delete( long recid ) throws IOException 222 { 223 checkIfClosed(); 224 225 if ( recid <= 0 ) 226 { 227 throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) ); 228 } 229 230 if ( DEBUG ) 231 { 232 System.out.println( "BaseRecordManager.delete() recid " + recid ) ; 233 } 234 235 Location logRowId = new Location( recid ); 236 Location physRowId = logMgr.fetch( logRowId ); 237 physMgr.delete( physRowId ); 238 logMgr.delete( logRowId ); 239 } 240 241 242 /** 243 * Updates a record using standard java object serialization. 244 * 245 * @param recid the recid for the record that is to be updated. 246 * @param obj the new object for the record. 247 * @throws IOException when one of the underlying I/O operations fails. 248 */ 249 public void update( long recid, Object obj ) throws IOException 250 { 251 update( recid, obj, DefaultSerializer.INSTANCE ); 252 } 253 254 255 /** 256 * Updates a record using a custom serializer. 257 * 258 * @param recid the recid for the record that is to be updated. 259 * @param obj the new object for the record. 260 * @param serializer a custom serializer 261 * @throws IOException when one of the underlying I/O operations fails. 262 */ 263 public synchronized void update( long recid, Object obj, Serializer serializer ) throws IOException 264 { 265 checkIfClosed(); 266 267 if ( recid <= 0 ) 268 { 269 throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) ); 270 } 271 272 Location logRecid = new Location( recid ); 273 Location physRecid = logMgr.fetch( logRecid ); 274 275 byte[] data = serializer.serialize( obj ); 276 277 if ( DEBUG ) 278 { 279 System.out.println( "BaseRecordManager.update() recid " + recid + " length " + data.length ) ; 280 } 281 282 Location newRecid = physMgr.update( physRecid, data, 0, data.length ); 283 284 if ( ! newRecid.equals( physRecid ) ) 285 { 286 logMgr.update( logRecid, newRecid ); 287 } 288 } 289 290 291 /** 292 * Fetches a record using standard java object serialization. 293 * 294 * @param recid the recid for the record that must be fetched. 295 * @return the object contained in the record. 296 * @throws IOException when one of the underlying I/O operations fails. 297 */ 298 public Object fetch( long recid ) throws IOException 299 { 300 return fetch( recid, DefaultSerializer.INSTANCE ); 301 } 302 303 304 /** 305 * Fetches a record using a custom serializer. 306 * 307 * @param recid the recid for the record that must be fetched. 308 * @param serializer a custom serializer 309 * @return the object contained in the record. 310 * @throws IOException when one of the underlying I/O operations fails. 311 */ 312 public synchronized Object fetch( long recid, Serializer serializer ) 313 throws IOException 314 { 315 byte[] data; 316 317 checkIfClosed(); 318 319 if ( recid <= 0 ) 320 { 321 throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) ); 322 } 323 324 data = physMgr.fetch( logMgr.fetch( new Location( recid ) ) ); 325 326 if ( DEBUG ) 327 { 328 System.out.println( "BaseRecordManager.fetch() recid " + recid + " length " + data.length ) ; 329 } 330 return serializer.deserialize( data ); 331 } 332 333 334 /** 335 * Returns the number of slots available for "root" rowids. These slots 336 * can be used to store special rowids, like rowids that point to 337 * other rowids. Root rowids are useful for bootstrapping access to 338 * a set of data. 339 */ 340 public int getRootCount() 341 { 342 return FileHeader.NROOTS; 343 } 344 345 346 /** 347 * Returns the indicated root rowid. 348 * 349 * @see #getRootCount 350 */ 351 public synchronized long getRoot( int id ) throws IOException 352 { 353 checkIfClosed(); 354 355 return pageMgr.getFileHeader().getRoot( id ); 356 } 357 358 359 /** 360 * Sets the indicated root rowid. 361 * 362 * @see #getRootCount 363 */ 364 public synchronized void setRoot( int id, long rowid ) throws IOException 365 { 366 checkIfClosed(); 367 368 pageMgr.getFileHeader().setRoot( id, rowid ); 369 } 370 371 372 /** 373 * Obtain the record id of a named object. Returns 0 if named object 374 * doesn't exist. 375 */ 376 public long getNamedObject( String name ) throws IOException 377 { 378 checkIfClosed(); 379 380 Map<String,Long> nameDirectory = getNameDirectory(); 381 Long recid = nameDirectory.get( name ); 382 383 if ( recid == null ) 384 { 385 return 0; 386 } 387 388 return recid; 389 } 390 391 392 /** 393 * Set the record id of a named object. 394 */ 395 public void setNamedObject( String name, long recid ) throws IOException 396 { 397 checkIfClosed(); 398 399 Map<String,Long> nameDirectory = getNameDirectory(); 400 if ( recid == 0 ) 401 { 402 // remove from hashtable 403 nameDirectory.remove( name ); 404 } 405 else 406 { 407 nameDirectory.put( name, recid ); 408 } 409 saveNameDirectory( nameDirectory ); 410 } 411 412 413 /** 414 * Commit (make persistent) all changes since beginning of transaction. 415 */ 416 public synchronized void commit() 417 throws IOException 418 { 419 checkIfClosed(); 420 421 pageMgr.commit(); 422 } 423 424 425 /** 426 * Rollback (cancel) all changes since beginning of transaction. 427 */ 428 public synchronized void rollback() throws IOException 429 { 430 checkIfClosed(); 431 432 pageMgr.rollback(); 433 } 434 435 436 /** 437 * Load name directory 438 */ 439 @SuppressWarnings("unchecked") 440 private Map<String,Long> getNameDirectory() throws IOException 441 { 442 // retrieve directory of named hashtable 443 long nameDirectory_recid = getRoot( NAME_DIRECTORY_ROOT ); 444 445 if ( nameDirectory_recid == 0 ) 446 { 447 nameDirectory = new HashMap<String, Long>(); 448 nameDirectory_recid = insert( nameDirectory ); 449 setRoot( NAME_DIRECTORY_ROOT, nameDirectory_recid ); 450 } 451 else 452 { 453 nameDirectory = ( Map<String, Long> ) fetch( nameDirectory_recid ); 454 } 455 456 return nameDirectory; 457 } 458 459 460 private void saveNameDirectory( Map<String,Long> directory ) throws IOException 461 { 462 long recid = getRoot( NAME_DIRECTORY_ROOT ); 463 464 if ( recid == 0 ) 465 { 466 throw new IOException( I18n.err( I18n.ERR_537 ) ); 467 } 468 469 update( recid, nameDirectory ); 470 } 471 472 473 /** 474 * Check if RecordManager has been closed. If so, throw an IllegalStateException. 475 */ 476 private void checkIfClosed() throws IllegalStateException 477 { 478 if ( recordFile == null ) 479 { 480 throw new IllegalStateException( I18n.err( I18n.ERR_538 ) ); 481 } 482 } 483 }