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 * Contributions are Copyright (C) 2000 by their associated contributors.
044 *
045 * $Id: RecordFile.java,v 1.6 2005/06/25 23:12:32 doomdark Exp $
046 */
047 package jdbm.recman;
048
049
050 import java.io.*;
051 import java.util.*;
052
053 import org.apache.directory.server.i18n.I18n;
054
055
056 /**
057 * This class represents a random access file as a set of fixed size
058 * records. Each record has a physical record number, and records are
059 * cached in order to improve access.
060 *<p>
061 * The set of dirty records on the in-use list constitutes a transaction.
062 * Later on, we will send these records to some recovery thingy.
063 */
064 public final class RecordFile
065 {
066 final TransactionManager txnMgr;
067
068 // state transitions: free -> inUse -> dirty -> inTxn -> free
069 // free is a cache, thus a FIFO. The rest are hashes.
070 private final LinkedList<BlockIo> free = new LinkedList<BlockIo>();
071 private final HashMap<Long,BlockIo> inUse = new HashMap<Long,BlockIo>();
072 private final HashMap<Long,BlockIo> dirty = new HashMap<Long,BlockIo>();
073 private final HashMap<Long,BlockIo> inTxn = new HashMap<Long,BlockIo>();
074
075 // transactions disabled?
076 private boolean transactionsDisabled = false;
077
078 /** The length of a single block. */
079 public final static int BLOCK_SIZE = 8192;//4096;
080
081 /** The extension of a record file */
082 final static String extension = ".db";
083
084 /** A block of clean data to wipe clean pages. */
085 final static byte[] cleanData = new byte[BLOCK_SIZE];
086
087 private RandomAccessFile file;
088 private final String fileName;
089
090
091 /**
092 * Creates a new object on the indicated filename. The file is
093 * opened in read/write mode.
094 *
095 * @param fileName the name of the file to open or create, without
096 * an extension.
097 * @throws IOException whenever the creation of the underlying
098 * RandomAccessFile throws it.
099 */
100 RecordFile ( String fileName ) throws IOException
101 {
102 this.fileName = fileName;
103 file = new RandomAccessFile(fileName + extension, "rw");
104 txnMgr = new TransactionManager( this );
105 }
106
107
108 /**
109 * Returns the file name.
110 */
111 String getFileName()
112 {
113 return fileName;
114 }
115
116
117 /**
118 * Disables transactions: doesn't sync and doesn't use the
119 * transaction manager.
120 */
121 void disableTransactions()
122 {
123 transactionsDisabled = true;
124 }
125
126
127 /**
128 * Gets a block from the file. The returned byte array is the in-memory
129 * copy of the record, and thus can be written (and subsequently released
130 * with a dirty flag in order to write the block back).
131 *
132 * @param blockid The record number to retrieve.
133 */
134 BlockIo get( long blockid ) throws IOException
135 {
136 // try in transaction list, dirty list, free list
137
138 BlockIo node = inTxn.get( blockid );
139 if ( node != null )
140 {
141 inTxn.remove( blockid );
142 inUse.put( blockid, node );
143 return node;
144 }
145
146 node = dirty.get( blockid );
147 if ( node != null )
148 {
149 dirty.remove( blockid );
150 inUse.put( blockid, node );
151 return node;
152 }
153
154 for ( Iterator<BlockIo> i = free.iterator(); i.hasNext(); )
155 {
156 BlockIo cur = i.next();
157 if ( cur.getBlockId() == blockid )
158 {
159 node = cur;
160 i.remove();
161 inUse.put( blockid, node );
162 return node;
163 }
164 }
165
166 // sanity check: can't be on in use list
167 if ( inUse.get( blockid ) != null )
168 {
169 throw new Error( I18n.err( I18n.ERR_554, blockid ) );
170 }
171
172 // get a new node and read it from the file
173 node = getNewNode( blockid );
174 long offset = blockid * BLOCK_SIZE;
175 if ( file.length() > 0 && offset <= file.length() )
176 {
177 read( file, offset, node.getData(), BLOCK_SIZE );
178 }
179 else
180 {
181 System.arraycopy( cleanData, 0, node.getData(), 0, BLOCK_SIZE );
182 }
183
184 inUse.put( blockid, node );
185 node.setClean();
186 return node;
187 }
188
189
190 /**
191 * Releases a block.
192 *
193 * @param blockid The record number to release.
194 * @param isDirty If true, the block was modified since the get().
195 */
196 void release( long blockid, boolean isDirty ) throws IOException
197 {
198 BlockIo node = inUse.get( blockid );
199
200 if ( node == null )
201 {
202 throw new IOException( I18n.err( I18n.ERR_555, blockid ) );
203 }
204
205 if ( ! node.isDirty() && isDirty )
206 {
207 node.setDirty();
208 }
209
210 release( node );
211 }
212
213
214 /**
215 * Releases a block.
216 *
217 * @param block The block to release.
218 */
219 void release( BlockIo block )
220 {
221 inUse.remove( block.getBlockId() );
222
223 if ( block.isDirty() )
224 {
225 // System.out.println( "Dirty: " + key + block );
226 dirty.put( block.getBlockId(), block );
227 }
228 else
229 {
230 if ( ! transactionsDisabled && block.isInTransaction() )
231 {
232 inTxn.put( block.getBlockId(), block );
233 }
234 else
235 {
236 free.add( block );
237 }
238 }
239 }
240
241
242 /**
243 * Discards a block (will not write the block even if it's dirty)
244 *
245 * @param block The block to discard.
246 */
247 void discard( BlockIo block )
248 {
249 inUse.remove( block.getBlockId() );
250
251 // note: block not added to free list on purpose, because
252 // it's considered invalid
253 }
254
255
256 /**
257 * Commits the current transaction by flushing all dirty buffers to disk.
258 */
259 void commit() throws IOException
260 {
261 // debugging...
262 if ( ! inUse.isEmpty() && inUse.size() > 1 )
263 {
264 showList( inUse.values().iterator() );
265 throw new Error( I18n.err( I18n.ERR_556, inUse.size() ) );
266 }
267
268 // System.out.println("committing...");
269
270 if ( dirty.size() == 0 )
271 {
272 // if no dirty blocks, skip commit process
273 return;
274 }
275
276
277 if ( ! transactionsDisabled )
278 {
279 txnMgr.start();
280 }
281
282
283 for ( Iterator<BlockIo> i = dirty.values().iterator(); i.hasNext(); )
284 {
285 BlockIo node = ( BlockIo ) i.next();
286 i.remove();
287
288 // System.out.println("node " + node + " map size now " + dirty.size());
289 if ( transactionsDisabled )
290 {
291 long offset = node.getBlockId() * BLOCK_SIZE;
292 file.seek( offset );
293 file.write( node.getData() );
294 node.setClean();
295 free.add( node );
296 }
297 else
298 {
299 txnMgr.add( node );
300 inTxn.put( node.getBlockId(), node );
301 }
302 }
303
304 if ( ! transactionsDisabled )
305 {
306 txnMgr.commit();
307 }
308 }
309
310
311 /**
312 * Rollback the current transaction by discarding all dirty buffers
313 */
314 void rollback() throws IOException
315 {
316 // debugging...
317 if ( ! inUse.isEmpty() )
318 {
319 showList( inUse.values().iterator() );
320 throw new Error( I18n.err( I18n.ERR_557, inUse.size() ) );
321 }
322
323 // System.out.println("rollback...");
324 dirty.clear();
325
326 txnMgr.synchronizeLogFromDisk();
327
328 if ( ! inTxn.isEmpty() )
329 {
330 showList( inTxn.values().iterator() );
331 throw new Error( I18n.err( I18n.ERR_558, inTxn.size() ) );
332 };
333 }
334
335
336 /**
337 * Commits and closes file.
338 */
339 void close() throws IOException
340 {
341 if ( ! dirty.isEmpty() )
342 {
343 commit();
344 }
345
346 txnMgr.shutdown();
347
348 if ( ! inTxn.isEmpty() )
349 {
350 showList( inTxn.values().iterator() );
351 throw new Error( I18n.err( I18n.ERR_559 ) );
352 }
353
354 // these actually ain't that bad in a production release
355 if ( ! dirty.isEmpty() )
356 {
357 System.out.println( "ERROR: dirty blocks at close time" );
358 showList( dirty.values().iterator() );
359 throw new Error( I18n.err( I18n.ERR_560 ) );
360 }
361
362 if ( ! inUse.isEmpty() )
363 {
364 System.out.println( "ERROR: inUse blocks at close time" );
365 showList( inUse.values().iterator() );
366 throw new Error( I18n.err( I18n.ERR_561 ) );
367 }
368
369 // debugging stuff to keep an eye on the free list
370 // System.out.println("Free list size:" + free.size());
371 file.close();
372 file = null;
373 }
374
375
376 /**
377 * Force closing the file and underlying transaction manager.
378 * Used for testing purposed only.
379 */
380 void forceClose() throws IOException
381 {
382 txnMgr.forceClose();
383 file.close();
384 }
385
386
387 /**
388 * Prints contents of a list
389 */
390 private void showList( Iterator<BlockIo> i )
391 {
392 int cnt = 0;
393 while ( i.hasNext() )
394 {
395 System.out.println( "elem " + cnt + ": " + i.next() );
396 cnt++;
397 }
398 }
399
400
401 /**
402 * Returns a new node. The node is retrieved (and removed) from the
403 * released list or created new.
404 */
405 private BlockIo getNewNode( long blockid ) throws IOException
406 {
407 BlockIo retval = null;
408
409 if ( ! free.isEmpty() )
410 {
411 retval = ( BlockIo ) free.removeFirst();
412 }
413
414 if ( retval == null )
415 {
416 retval = new BlockIo( 0, new byte[BLOCK_SIZE] );
417 }
418
419 retval.setBlockId(blockid);
420 retval.setView( null );
421 return retval;
422 }
423
424
425 /**
426 * Synchronizes a node to disk. This is called by the transaction manager's
427 * synchronization code.
428 */
429 void synch( BlockIo node ) throws IOException
430 {
431 byte[] data = node.getData();
432 if ( data != null )
433 {
434 long offset = node.getBlockId() * BLOCK_SIZE;
435 file.seek( offset );
436 file.write( data );
437 }
438 }
439
440
441 /**
442 * Releases a node from the transaction list, if it was sitting there.
443 *
444 * @param recycle true if block data can be reused
445 */
446 void releaseFromTransaction( BlockIo node, boolean recycle ) throws IOException
447 {
448 if ( ( inTxn.remove( node.getBlockId() ) != null ) && recycle )
449 {
450 free.add( node );
451 }
452 }
453
454
455 /**
456 * Synchronizes the file.
457 */
458 void sync() throws IOException
459 {
460 file.getFD().sync();
461 }
462
463
464 /**
465 * Utility method: Read a block from a RandomAccessFile
466 */
467 private static void read( RandomAccessFile file, long offset, byte[] buffer, int nBytes ) throws IOException
468 {
469 file.seek( offset );
470 int remaining = nBytes;
471 int pos = 0;
472 while ( remaining > 0 )
473 {
474 int read = file.read( buffer, pos, remaining );
475 if ( read == -1 )
476 {
477 System.arraycopy( cleanData, 0, buffer, pos, remaining );
478 break;
479 }
480 remaining -= read;
481 pos += read;
482 }
483 }
484 }