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 }