KickJava   Java API By Example, From Geeks To Geeks.

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


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.ByteArrayInputStream JavaDoc;
25 import java.io.DataInputStream JavaDoc;
26 import java.io.File JavaDoc;
27 import java.io.FileInputStream JavaDoc;
28 import java.io.FileNotFoundException JavaDoc;
29 import java.io.IOException JavaDoc;
30 import java.io.ObjectInputStream JavaDoc;
31 import java.io.RandomAccessFile JavaDoc;
32 import java.nio.ByteBuffer JavaDoc;
33 import java.nio.channels.FileChannel JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.List JavaDoc;
37 import java.util.Map JavaDoc;
38
39 import org.jboss.logging.Logger;
40 import org.jboss.tm.XidFactoryBase;
41
42 /**
43  * Simple implementation of <code>RecoveryLogReader</code> used at recovery
44  * time. The <code>BatchRecoveryLogger</code>'s implementation of method
45  * <code>getRecoveryLogs()</code> instantiates
46  * <code>BatchRecoveryLogReader</code>s for the existing recovery log files.
47  * It returns an array containing those readers, which the recovery manager
48  * uses to get the information in the log files.
49  *
50  * @author <a HREF="mailto:bill@jboss.org">Bill Burke</a>
51  * @author <a HREF="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
52  * @version $Revision: 37459 $
53  */

54 class BatchRecoveryLogReader
55    implements RecoveryLogReader
56
57 {
58    /** Class <code>Logger</code>, for trace messages. */
59    private static Logger errorLog =
60       Logger.getLogger(BatchRecoveryLogReader.class);
61
62    /** The log file read by this reader. */
63    private File JavaDoc logFile;
64    
65    /** Xid factory for converting local transaction ids into global ids. */
66    private XidFactoryBase xidFactory;
67    
68    /**
69     * Constructs a <code>BatchRecoveryLogReader</code>.
70     *
71     * @param logFile the log file that will be read by the reader
72     * @param xidFactory Xid factory that the reader will use to convert local
73     * transaction ids into global ids.
74     */

75    BatchRecoveryLogReader(File JavaDoc logFile, XidFactoryBase xidFactory)
76    {
77       this.logFile = logFile;
78       this.xidFactory = xidFactory;
79    }
80
81    /**
82     * Reads the header object written out to the beginning of the log file via
83     * Java serialization.
84     *
85     * @param dis a <code>DataInputStream</code> view of the log file
86     * @return the header object read from the log file.
87     * @throws IOException if there is an error reading the header object
88     * @throws ClassNotFoundException if the class of the header object is not
89     * available.
90     */

91    private Object JavaDoc readHeaderObject(DataInputStream JavaDoc dis)
92          throws IOException JavaDoc,
93                 ClassNotFoundException JavaDoc
94    {
95       int num = dis.readInt();
96       byte[] bytes = new byte[num];
97       dis.read(bytes);
98       ByteArrayInputStream JavaDoc bais = new ByteArrayInputStream JavaDoc(bytes);
99       ObjectInputStream JavaDoc ois = new ObjectInputStream JavaDoc(bais);
100       return ois.readObject();
101    }
102
103    // RecoveryLogReader implementation ------------------------------
104

105    /**
106     * Gets the name of the underlying log file.
107     *
108     * @return the name of the log file.
109     */

110    public String JavaDoc getLogFileName()
111    {
112       return logFile.toString();
113    }
114
115    /**
116     * Gets the branch qualifier string stored in the log file header.
117     *
118     * @return the branch qualifier read from the log file header.
119     */

120    public String JavaDoc getBranchQualifier()
121    {
122       FileInputStream JavaDoc fis;
123       DataInputStream JavaDoc dis;
124
125       try
126       {
127          fis = new FileInputStream JavaDoc(logFile);
128          dis = new DataInputStream JavaDoc(fis);
129          try
130          {
131             return (String JavaDoc) readHeaderObject(dis);
132          }
133          finally
134          {
135             fis.close();
136          }
137       }
138       catch (Exception JavaDoc e)
139       {
140          throw new RuntimeException JavaDoc(e);
141       }
142    }
143
144    /**
145     * Recovers transaction information from the log file.
146     *
147     * @param committedSingleTmTransactions a <code>List</code> of
148     * <code>LogRecord.Data</code> instances with one element per
149     * committed single-TM transaction logged to the log file
150     * @param committedMultiTmTransactions a <code>List</code> of
151     * <code>LogRecord.Data</code> instances with one element per
152     * committed multi-TM transaction that has not yet completed the
153     * second phase of the 2PC protocol when the server crashed
154     * @param inDoubtTransactions a <code>List</code> of
155     * <code>LogRecord.Data</code> instances with one element per
156     * foreign transaction that arrived at the server via DTM/OTS
157     * context propagation and was in the in-doubt state (i.e.,
158     * replied to prepare with a commit vote but has not yet received
159     * information on the transaction outcome) when the server crashed
160     * @param inDoubtJcaTransactions a <code>List</code> of
161     * <code>LogRecord.Data</code> instances with one element per
162     * foreign transaction that arrived at the server via JCA
163     * transaction inflow and was in the in-doubt state (i.e., replied
164     * to prepare with a commit vote and was waiting for information
165     * on the transaction outcome) when the server crashed.
166     */

167    public void recover(List JavaDoc committedSingleTmTransactions,
168                        List JavaDoc committedMultiTmTransactions,
169                        List JavaDoc inDoubtTransactions,
170                        List JavaDoc inDoubtJcaTransactions)
171    {
172       Map JavaDoc activeMultiTmTransactions = new HashMap JavaDoc();
173       FileInputStream JavaDoc fis;
174       DataInputStream JavaDoc dis;
175       CorruptedLogRecordException corruptedLogRecordException = null;
176
177       try
178       {
179          fis = new FileInputStream JavaDoc(logFile);
180          dis = new DataInputStream JavaDoc(fis);
181
182       }
183       catch (IOException JavaDoc e)
184       {
185          throw new RuntimeException JavaDoc(e);
186       }
187
188       try
189       {
190          readHeaderObject(dis); // branch qualifier
191

192          if (fis.available() < LogRecord.FULL_HEADER_LEN)
193             return;
194          
195          FileChannel JavaDoc channel = fis.getChannel();
196          ByteBuffer JavaDoc buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
197          channel.read(buf);
198          
199          LogRecord.Data data;
200          int len = LogRecord.getNextRecordLength(buf, 0);
201
202          while (len > 0)
203          {
204             buf = ByteBuffer.allocate(len + LogRecord.FULL_HEADER_LEN);
205             if (channel.read(buf) < len)
206             {
207                errorLog.info("Unexpected end of file in transaction log file " +
208                              logFile.getName());
209                break;
210             }
211             buf.flip();
212             data = new LogRecord.Data();
213             try
214             {
215                LogRecord.getData(buf, len, data);
216             }
217             catch (CorruptedLogRecordException e)
218             {
219                // Save the exception only if no previous exception is saved.
220
if (corruptedLogRecordException == null)
221                   corruptedLogRecordException = e;
222                long corruptedRecordPos =
223                   channel.position() - buf.limit() - LogRecord.FULL_HEADER_LEN;
224                long nextPos = scanForward(corruptedRecordPos + 1);
225                if (nextPos == 0)
226                {
227                   errorLog.info("LOG CORRUPTION AT THE END OF LOG FILE " +
228                                 logFile.getName());
229                   break;
230                }
231                else
232                {
233                   errorLog.info("LOG CORRUPTION IN THE MIDDLE OF LOG FILE " +
234                                 logFile.getName() + ". Skipping " +
235                                 (nextPos - corruptedRecordPos) + " bytes" +
236                                 ". Disabling presumed rollback.");
237                   channel.position(nextPos);
238                   buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
239                   channel.read(buf);
240                   len = LogRecord.getNextRecordLength(buf, 0);
241                   corruptedLogRecordException.disablePresumedRollback = true;
242                   continue;
243                }
244             }
245             switch (data.recordType)
246             {
247             case LogRecord.TX_COMMITTED:
248                data.globalTransactionId =
249                   xidFactory.localIdToGlobalId(data.localTransactionId);
250                committedSingleTmTransactions.add(data);
251                break;
252             case LogRecord.MULTI_TM_TX_COMMITTED:
253                data.globalTransactionId =
254                   xidFactory.localIdToGlobalId(data.localTransactionId);
255                // fall through
256
case LogRecord.TX_PREPARED:
257             case LogRecord.JCA_TX_PREPARED:
258                activeMultiTmTransactions.put(new Long JavaDoc(data.localTransactionId),
259                                              data);
260                break;
261             case LogRecord.TX_END:
262                activeMultiTmTransactions.remove(
263                                              new Long JavaDoc(data.localTransactionId));
264                break;
265             default:
266                errorLog.warn("INVALID TYPE IN LOG RECORD.");
267                break;
268             }
269             try
270             {
271                len = LogRecord.getNextRecordLength(buf, len);
272             }
273             catch (CorruptedLogRecordException e)
274             {
275                // Save the exception only if no previous exception is saved.
276
if (corruptedLogRecordException == null)
277                   corruptedLogRecordException = e;
278                long corruptedRecordPos =
279                   channel.position() - buf.limit() - LogRecord.FULL_HEADER_LEN;
280                long nextPos = scanForward(corruptedRecordPos + 1);
281                if (nextPos == 0)
282                {
283                   errorLog.info("LOG CORRUPTION AT THE END OF LOG FILE " +
284                                 logFile.getName());
285                   len = 0;
286                }
287                else
288                {
289                   errorLog.info("LOG CORRUPTION IN THE MIDDLE OF LOG FILE " +
290                                 logFile.getName() + ". Skipping " +
291                                 (nextPos - corruptedRecordPos) + " bytes" +
292                                 ". Disabling presumed rollback.");
293                   channel.position(nextPos);
294                   buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
295                   channel.read(buf);
296                   len = LogRecord.getNextRecordLength(buf, 0);
297                   corruptedLogRecordException.disablePresumedRollback = true;
298                }
299             }
300          }
301          
302          Iterator JavaDoc iter = activeMultiTmTransactions.values().iterator();
303          while (iter.hasNext())
304          {
305             data = (LogRecord.Data) iter.next();
306             
307             switch (data.recordType)
308             {
309             case LogRecord.MULTI_TM_TX_COMMITTED:
310                committedMultiTmTransactions.add(data);
311                break;
312             case LogRecord.TX_PREPARED:
313                inDoubtTransactions.add(data);
314                break;
315             case LogRecord.JCA_TX_PREPARED:
316                inDoubtJcaTransactions.add(data);
317                break;
318             default:
319                errorLog.warn("INCONSISTENT STATE.");
320                break;
321             }
322          }
323          
324          if (corruptedLogRecordException != null)
325             throw corruptedLogRecordException;
326       }
327       catch (IOException JavaDoc e)
328       {
329          errorLog.warn("Unexpected exception in recover:", e);
330       }
331       catch (ClassNotFoundException JavaDoc e)
332       {
333          errorLog.warn("Unexpected exception in recover:", e);
334       }
335       try
336       {
337          fis.close();
338       }
339       catch (IOException JavaDoc e)
340       {
341          throw new RuntimeException JavaDoc(e);
342       }
343    }
344    
345    /**
346     * Removes the log file.
347     */

348    public void finishRecovery()
349    {
350       logFile.delete();
351    }
352
353    /**
354     * Scans the log file for a valid log record. This is a helper method for
355     * log file corruption handling.
356     *
357     * @param pos the file position where the forward scan should start.
358     * @return the file position in which a valid log record was found, or
359     * 0 if end of file was reached and no valid log record was found.
360     */

361    private long scanForward(long pos)
362    {
363       errorLog.trace("entering scanForward");
364       RandomAccessFile JavaDoc file = null;
365       
366       try
367       {
368          file = new RandomAccessFile JavaDoc(logFile, "r");
369          
370          while (pos + LogRecord.FULL_HEADER_LEN < logFile.length())
371          {
372             if (match(file, pos, LogRecord.HEADER))
373             {
374                errorLog.trace("scanForward: match at pos=" + pos);
375                file.seek(pos + LogRecord.HEADER_LEN);
376                short recLen = file.readShort();
377                errorLog.trace("scanForward: recLen=" + recLen);
378                if (pos + LogRecord.FULL_HEADER_LEN + recLen < logFile.length())
379                {
380                   byte[] buf = new byte[recLen];
381                   file.seek(pos + LogRecord.FULL_HEADER_LEN);
382                   file.read(buf, 0, recLen);
383                   if (LogRecord.hasValidChecksum(buf))
384                   {
385                      errorLog.trace("scanForward: returning " + pos);
386                      return pos;
387                   }
388                   else
389                   {
390                      errorLog.trace("scanForward: " +
391                                     "bad checksum in record at pos=" + pos);
392                      pos += LogRecord.HEADER_LEN;
393                   }
394                }
395                else
396                   pos =+ LogRecord.HEADER_LEN;
397             }
398             else
399                pos++;
400          }
401          errorLog.trace("scanForward: returning 0");
402          return 0;
403       }
404       catch (FileNotFoundException JavaDoc e)
405       {
406          errorLog.warn("Unexpected exception in scanForward:", e);
407          return 0;
408       }
409       catch (IOException JavaDoc e)
410       {
411          errorLog.warn("Unexpected exception in scanForward:", e);
412          return 0;
413       }
414       finally
415       {
416          if (file != null)
417          {
418             try
419             {
420                file.close();
421             }
422             catch (IOException JavaDoc e)
423             {
424                errorLog.warn("Unexpected exception in scanForward:", e);
425             }
426          }
427       }
428    }
429    
430    /**
431     * Returns true if the byte sequence that starts at given position of a
432     * file matches a given byte array.
433     *
434     * @param file a random access file open for reading
435     * @param pos the file position of the byte sequence to be compared against
436     * the <code>pattern</code> byte array
437     * @param pattern the byte pattern to match.
438     * @return true if the pattern appears at the specified position of the
439     * file, and false otherwise/
440     * @throws IOException if there is a problem reading the file.
441     */

442    private static boolean match(RandomAccessFile JavaDoc file, long pos, byte[] pattern)
443       throws IOException JavaDoc
444    {
445       for (int i = 0; i < pattern.length; i++)
446       {
447          file.seek(pos + i);
448          if (file.readByte() != pattern[i])
449             return false;
450       }
451       return true;
452    }
453    
454 }
455
Popular Tags