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