KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > tm > recovery > BatchLog


1 /*
2   * JBoss, Home of Professional Open Source
3   * Copyright 2005, JBoss Inc., and individual contributors as indicated
4   * by the @authors tag. See the copyright.txt in the distribution for a
5   * full listing of individual contributors.
6   *
7   * This is free software; you can redistribute it and/or modify it
8   * under the terms of the GNU Lesser General Public License as
9   * published by the Free Software Foundation; either version 2.1 of
10   * the License, or (at your option) any later version.
11   *
12   * This software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this software; if not, write to the Free
19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21   */

22 package org.jboss.tm.recovery;
23
24 import java.io.ByteArrayOutputStream JavaDoc;
25 import java.io.File JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.io.ObjectOutputStream JavaDoc;
28 import java.io.RandomAccessFile JavaDoc;
29 import java.nio.ByteBuffer JavaDoc;
30 import java.nio.channels.FileChannel JavaDoc;
31
32 import org.jboss.logging.Logger;
33
34 /**
35  * Class that encapsulates a log file opened for writing. It provides
36  * <code>write</code> methods that append log records to the log file.
37  * A <code>BatchLog</code> instance belongs to some <code>BatchWriter</code>
38  * and its write methods are called only from the <code>BatchWriter</code>
39  * thread.
40  * <p>
41  * To avoid changes in its metadata, the log file is created with a fixed
42  * length, which should never change until the file is closed.
43  * <p>
44  * A newly created log file is clean: it contains a header object followed
45  * by a sequence of null bytes that takes the entire lenght of the file. Log
46  * files are reusable, but they must be cleaned up for reuse. Restarting a
47  * <code>BatchLog</code> means overwriting with null bytes all the data that
48  * follows the header and returning the <code>BatchLog</code> to the pool of
49  * clean <code>BatchLog</code> instances maintained by a
50  * <code>BatchWriter</code>.
51  *
52  * @author <a HREF="mailto:bill@jboss.org">Bill Burke</a>
53  * @author <a HREF="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
54  * @version $Revision: 37459 $
55  */

56 class BatchLog
57    implements TxCompletionHandler
58 {
59    /**
60     * Class <code>Logger</code>, for trace messages.
61     */

62    private static Logger errorLog = Logger.getLogger(BatchLog.class);
63
64    /**
65     * Length of the null-filled buffer used to clean log file.
66     */

67    private static final int CLEAN_LENGTH = 16 * 1024;
68    
69    /**
70     * Buffer used to fill the log file with null bytes.
71     */

72    private static byte[] nulls = new byte[CLEAN_LENGTH];
73    
74    /**
75     * The log file.
76     */

77    private File JavaDoc logFile;
78    
79    /**
80     * A <code>RandomAccessFile</code> view of the log file.
81     */

82    private RandomAccessFile JavaDoc os;
83    
84    /**
85     * The <code>RandomAccessFile</code>'s underlying <code>FileChannel</code>.
86     */

87    private FileChannel JavaDoc channel;
88    
89    /**
90     * Number of transactions logged. Counts commit, multi-TM commit, prepare,
91     * and JCA prepare records written out to the log. Only the
92     * <code>BatchWriter</code> thread updates this field, which is declared
93     * as volatile so that its updates are seen by any threads that call
94     * <code>handleTxCompletion</code>.
95     */

96    private volatile int numLoggedTransactions;
97    
98    /**
99     * Number of end records written out to the log. Only the
100     * <code>BatchWriter</code> thread updates this field, which is declared
101     * as volatile so that its updates are seen by any threads that call
102     * <code>handleTxCompletion</code>.
103     */

104    private volatile int numEndRecords;
105    
106    /**
107     * Number of completed transactions that need no end records. Access to this
108     * field is synchronized, as it is concurrently updated via calls to
109     * <code>handleTxCompletion</code>.
110     */

111    private int numLocalTransactionsCompleted;
112    
113    /**
114     * Indicates that this <code>BatchLog</code> will not receive additional
115     * records for new transactions. This <code>BatchLog</code> should be
116     * cleaned up and returned to the <code>BatchWriter</code>'s pool of clean
117     * <code>BatchLog</code> instances as soon as every outstanding transaction
118     * signals its completion either by writing out an end record or by invoking
119     * <code>handleTxCompletion</code>.
120     */

121    private boolean markedForRestart;
122    
123    /**
124     * Header object written to the beginning of the log file via Java
125     * serialization. It is preceded by four bytes (an int value) with the
126     * length of the serialized header.
127     */

128    private Object JavaDoc header;
129    
130    /**
131     * Log file position that follows the four bytes with the header lenght and
132     * the header object. (Its value is headerLenght + 4.)
133     */

134    private long topFp;
135    
136    /**
137     * The <code>BatchWriter</code> that owns this <code>BatchLog</code>.
138     */

139    private BatchWriter writer;
140    
141    /**
142     * Auxiliary buffer used to fill up with null bytes the part of the file
143     * that follows the header.
144     */

145    private ByteBuffer JavaDoc cleanBuffer = ByteBuffer.wrap(nulls);
146
147    /**
148     * Constructs a new <code>BatchLog</code>.
149     *
150     * @param writer the <code>BatchWriter</code> that will own
151     * the new <code>BatchLog</code>
152     * @param header an object to be written at the beginning of the log file
153     * @param dir the directory in which the log file will be created
154     * @param fileSize the fixed size of the log file
155     * @throws IOException
156     */

157    BatchLog(BatchWriter writer, Object JavaDoc header, File JavaDoc dir, int fileSize)
158       throws IOException JavaDoc
159    {
160       this.writer = writer;
161       this.header = header;
162       logFile = File.createTempFile("TX_RECOVERY_LOG", ".log", dir);
163       os = new RandomAccessFile JavaDoc(logFile, "rw");
164
165       ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
166       ObjectOutputStream JavaDoc oos = new ObjectOutputStream JavaDoc(baos);
167       oos.writeObject(header);
168       byte[] bytes = baos.toByteArray();
169
170       os.setLength(fileSize);
171       os.writeInt(bytes.length);
172       os.write(bytes);
173
174       channel = os.getChannel();
175       // Force also metadata. We do this just once, as the file size is fixed.
176
channel.force(true);
177       topFp = channel.position();
178       
179       cleanUpLogFile();
180    }
181
182    /**
183     * Gets the <code>BatchWriter</code> that owns this <code>BatchLog</code>.
184     *
185     * @return the <code>BatchWriter</code> that issues write calls on this
186     * this <code>BatchLog</code>.
187     */

188    BatchWriter getBatchWriter()
189    {
190       return writer;
191    }
192    
193    /**
194     * Gets this <code>BatchLog</code>'s current position, which is also the
195     * number of currently used bytes of its log file.
196     *
197     * @return the offset from the beginning of the log file, in bytes,
198     * at which the next write will happen.
199     * @throws IOException
200     */

201    int getPosition()
202          throws IOException JavaDoc
203    {
204       return (int) channel.position();
205    }
206
207    /**
208     * Gets the name of the underlying log file.
209     *
210     * @return the name of this <code>BatchLog</code>'s log file.
211     */

212    String JavaDoc getFilename()
213    {
214       return logFile.getName();
215    }
216
217    /**
218     * Writes one record at the current position of this <code>BatchLog</code>.
219     *
220     * @param record a buffer with the record to be written
221     * @param isEndRecord true if the record is an end record and false otherwise
222     * @throws IOException
223     */

224    void write(ByteBuffer JavaDoc record, boolean isEndRecord)
225        throws IOException JavaDoc
226    {
227       channel.write(record);
228       if (isEndRecord)
229       {
230          numEndRecords++;
231          
232          synchronized (this)
233          {
234             if (markedForRestart == true &&
235                   numLoggedTransactions ==
236                      numLocalTransactionsCompleted + numEndRecords)
237             {
238                writer.restartBatchLog(this);
239             }
240          }
241       }
242       else
243       {
244          channel.force(false);
245          numLoggedTransactions++;
246       }
247    }
248
249    /**
250     * Writes a sequence of records to this <code>BatchLog</code>.
251     * The sequence of records is taken from the given array of buffers,
252     * starting at the specified offset, and it is written at the current
253     * position of this <code>BatchLog</code>.
254     *
255     * @param records an array of buffers containing records that should be
256     * written to the log
257     * @param offset the index of the first record of the <code>records</code>
258     * array that should be written to the log
259     * @param length the number of records that should be written to the log
260     * @param numTransactionRecords specifies how many of the <code>lenght</code>
261     * records are records for new transactions (commit, multi-TM
262     * commit, prepare and JCA prepare records). The remaining
263     * <code>length - numTransactionRecords</code> records are
264     * end-of-transaction records.
265     * @throws IOException
266     */

267    void write(ByteBuffer JavaDoc[] records,
268               int offset,
269               int length,
270               int numTransactionRecords)
271       throws IOException JavaDoc
272    {
273       channel.write(records, offset, length);
274
275       if (numTransactionRecords > 0)
276       {
277          channel.force(false);
278          numLoggedTransactions += numTransactionRecords;
279       }
280
281       if (numTransactionRecords < length)
282       {
283          numEndRecords += (length - numTransactionRecords);
284       
285          synchronized (this)
286          {
287             if (markedForRestart == true &&
288                   numLoggedTransactions ==
289                      numLocalTransactionsCompleted + numEndRecords)
290             {
291                writer.restartBatchLog(this);
292             }
293          }
294       }
295       
296    }
297    
298    /**
299     * Signals the end of the two-phase commit protocol for a committed
300     * transaction that does not need an end record to be logged. This method
301     * should be invoked when the second phase of the two-phase commit protocol
302     * completes successfully.
303     *
304     * @param localTransactionId the local id of the completed transaction.
305     */

306    public void handleTxCompletion(long localTransactionId)
307    {
308       synchronized (this)
309       {
310          numLocalTransactionsCompleted++;
311          if (markedForRestart == true &&
312                numLoggedTransactions ==
313                   numLocalTransactionsCompleted + numEndRecords)
314          {
315             writer.restartBatchLog(this);
316          }
317       }
318    }
319
320    /**
321     * Marks this <code>BatchLog</code> for restart. The restart will occur
322     * only when there are no more outstanding transactions, i.e, when
323     * <code>numLocalTransactionsCompleted + numEndRecords</code> reaches
324     * <code>numLoggedTransactions</code>.
325     */

326    void markForRestart()
327    {
328       synchronized (this)
329       {
330          markedForRestart = true;
331          if (numLoggedTransactions ==
332             numLocalTransactionsCompleted + numEndRecords)
333          {
334             writer.restartBatchLog(this);
335          }
336       }
337    }
338
339    /**
340     * Restarts this <code>BatchLog</code>. Overwrites with null bytes all
341     * log records in the log file, then returns the <code>BatchLog</code>
342     * to its <code>BatchWriter</code>s pool of clean <code>BatchLog</code>
343     * instances.
344     * <p>
345     * Only the <code>LogRestarter</code> calls this method.
346     *
347     * @throws IOException
348     */

349    void restart()
350       throws IOException JavaDoc
351    {
352       // TODO may have to rewrite header when we start recording XAResource
353
// handles in it as a new one may be deployed/undeployed
354
channel.position(topFp);
355       cleanUpLogFile();
356       writer.getNextLogs().add(this);
357    }
358    
359    /**
360     * Overwrites with null bytes all log records in the log file, without
361     * changing the size of the file.
362     */

363    void cleanUpLogFile()
364       throws IOException JavaDoc
365    {
366       numLoggedTransactions = 0;
367       numLocalTransactionsCompleted = 0;
368       numEndRecords = 0;
369       markedForRestart = false;
370
371       // Overwites the remainder of the log file with null bytes.
372
cleanBuffer.limit(cleanBuffer.capacity());
373       while (channel.position() <= channel.size() - cleanBuffer.limit())
374       {
375          cleanBuffer.rewind();
376          channel.write(cleanBuffer);
377       }
378       cleanBuffer.limit((int) (channel.size() - channel.position()));
379       cleanBuffer.rewind();
380       channel.write(cleanBuffer);
381       channel.force(false);
382       
383       channel.position(topFp);
384    }
385
386    /**
387     * Closes this <code>BatchLog</code>. If there are no outstanding
388     * transactions then all log records in the log file are erased.
389     */

390    void close()
391    {
392       errorLog.info("Closing transaction log " + getFilename() +
393                     ", numLoggedTransactions=" + numLoggedTransactions +
394                     ", numLocalTransactionsCompleted=" +
395                     numLocalTransactionsCompleted +
396                     ", numEndRecords=" + numEndRecords);
397       try
398       {
399          if (numLoggedTransactions ==
400             numLocalTransactionsCompleted + numEndRecords)
401          {
402             channel.position(topFp);
403             channel.truncate(topFp);
404          }
405          os.close();
406       }
407       catch (IOException JavaDoc e)
408       {
409          errorLog.error("Error closing transaction log " + getFilename(), e);
410       }
411    }
412    
413 }
414
Popular Tags