View Javadoc

1   /**
2    * JDBM LICENSE v1.00
3    *
4    * Redistribution and use of this software and associated documentation
5    * ("Software"), with or without modification, are permitted provided
6    * that the following conditions are met:
7    *
8    * 1. Redistributions of source code must retain copyright
9    *    statements and notices.  Redistributions must also contain a
10   *    copy of this document.
11   *
12   * 2. Redistributions in binary form must reproduce the
13   *    above copyright notice, this list of conditions and the
14   *    following disclaimer in the documentation and/or other
15   *    materials provided with the distribution.
16   *
17   * 3. The name "JDBM" must not be used to endorse or promote
18   *    products derived from this Software without prior written
19   *    permission of Cees de Groot.  For written permission,
20   *    please contact cg@cdegroot.com.
21   *
22   * 4. Products derived from this Software may not be called "JDBM"
23   *    nor may "JDBM" appear in their names without prior written
24   *    permission of Cees de Groot.
25   *
26   * 5. Due credit should be given to the JDBM Project
27   *    (http://jdbm.sourceforge.net/).
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
30   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
31   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
32   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
33   * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
34   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
35   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
38   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
40   * OF THE POSSIBILITY OF SUCH DAMAGE.
41   *
42   * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
43   * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
44   * Contributions are Copyright (C) 2000 by their associated contributors.
45   *
46   * $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
47   */
48  
49  package jdbm.recman;
50  
51  import java.io.IOException;
52  
53  import java.util.HashMap;
54  import java.util.Map;
55  
56  import jdbm.RecordManager;
57  import jdbm.helper.Serializer;
58  import jdbm.helper.DefaultSerializer;
59  
60  /**
61   *  This class manages records, which are uninterpreted blobs of data. The
62   *  set of operations is simple and straightforward: you communicate with
63   *  the class using long "rowids" and byte[] data blocks. Rowids are returned
64   *  on inserts and you can stash them away someplace safe to be able to get
65   *  back to them. Data blocks can be as long as you wish, and may have
66   *  lengths different from the original when updating.
67   *  <p>
68   *  Operations are synchronized, so that only one of them will happen
69   *  concurrently even if you hammer away from multiple threads. Operations
70   *  are made atomic by keeping a transaction log which is recovered after
71   *  a crash, so the operations specified by this interface all have ACID
72   *  properties.
73   *  <p>
74   *  You identify a file by just the name. The package attaches <tt>.db</tt>
75   *  for the database file, and <tt>.lg</tt> for the transaction log. The
76   *  transaction log is synchronized regularly and then restarted, so don't
77   *  worry if you see the size going up and down.
78   *
79   * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
80   * @author <a href="cg@cdegroot.com">Cees de Groot</a>
81   * @version $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
82   */
83  public final class BaseRecordManager
84      implements RecordManager
85  {
86  
87      /**
88       * Underlying record file.
89       */
90      private RecordFile _file;
91  
92  
93      /**
94       * Physical row identifier manager.
95       */
96      private PhysicalRowIdManager _physMgr;
97  
98  
99      /**
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 }