/*
* Copyright (C) 2006-2013 Bitronix Software (http://www.bitronix.be)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bitronix.tm.journal;
import bitronix.tm.utils.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashSet;
import java.util.Set;
/**
* Used to read {@link TransactionLogRecord} objects from a log file.
*
* @author Ludovic Orban
*/
public class TransactionLogCursor {
private final static Logger log = LoggerFactory.getLogger(TransactionLogCursor.class);
// private final RandomAccessFile randomAccessFile;
private final FileInputStream fis;
private final FileChannel fileChannel;
private long currentPosition;
private final long endPosition;
private final ByteBuffer page;
/**
* Create a TransactionLogCursor that will read from the specified file.
* This opens a new read-only file descriptor.
* @param file the file to read logs from
* @throws IOException if an I/O error occurs.
*/
public TransactionLogCursor(File file) throws IOException {
this.fis = new FileInputStream(file);
this.fileChannel = fis.getChannel();
this.page = ByteBuffer.allocate(8192);
fileChannel.position(TransactionLogHeader.CURRENT_POSITION_HEADER);
fileChannel.read(page);
page.rewind();
endPosition = page.getLong();
currentPosition = TransactionLogHeader.CURRENT_POSITION_HEADER + 8;
}
/**
* Fetch the next TransactionLogRecord from log, recalculating the CRC and checking it against the stored one.
* InvalidChecksumException is thrown if the check fails.
* @return the TransactionLogRecord or null if the end of the log file has been reached
* @throws IOException if an I/O error occurs.
*/
public TransactionLogRecord readLog() throws IOException {
return readLog(false);
}
/**
* Fetch the next TransactionLogRecord from log.
* @param skipCrcCheck if set to false, the method will thow an InvalidChecksumException if the CRC on disk does
* not match the recalculated one. Otherwise, the CRC is not recalculated nor checked agains the stored one.
* @return the TransactionLogRecord or null if the end of the log file has been reached
* @throws IOException if an I/O error occurs.
*/
public TransactionLogRecord readLog(boolean skipCrcCheck) throws IOException {
if (currentPosition >= endPosition) {
if (log.isDebugEnabled())
log.debug("end of transaction log file reached at " + currentPosition);
return null;
}
final int status = page.getInt();
// currentPosition += 4;
final int recordLength = page.getInt();
// currentPosition += 4;
currentPosition += 8;
if (page.position() + recordLength + 8 > page.limit()) {
page.compact();
fileChannel.read(page);
page.rewind();
}
final int endOfRecordPosition = page.position() + recordLength;
if (currentPosition + recordLength > endPosition) {
page.position(page.position() + recordLength);
currentPosition += recordLength;
throw new CorruptedTransactionLogException("corrupted log found at position " + currentPosition
+ " (record terminator outside of file bounds: " + currentPosition + recordLength + " of "
+ endPosition + ", recordLength: " + recordLength + ")");
}
final int headerLength = page.getInt();
// currentPosition += 4;
final long time = page.getLong();
// currentPosition += 8;
final int sequenceNumber = page.getInt();
// currentPosition += 4;
final int crc32 = page.getInt();
// currentPosition += 4;
final byte gtridSize = page.get();
// currentPosition += 1;
currentPosition += 21;
// check for log terminator
page.mark();
page.position(endOfRecordPosition - 4);
int endCode = page.getInt();
page.reset();
if (endCode != TransactionLogAppender.END_RECORD)
throw new CorruptedTransactionLogException("corrupted log found at position " + currentPosition + " (no record terminator found)");
// check that GTRID is not too long
if (4 + 8 + 4 + 4 + 1 + gtridSize > recordLength) {
page.position(endOfRecordPosition);
throw new CorruptedTransactionLogException("corrupted log found at position " + currentPosition
+ " (GTRID size too long)");
}
final byte[] gtridArray = new byte[gtridSize];
page.get(gtridArray);
currentPosition += gtridSize;
Uid gtrid = new Uid(gtridArray);
final int uniqueNamesCount = page.getInt();
currentPosition += 4;
Set<String> uniqueNames = new HashSet<String>();
int currentReadCount = 4 + 8 + 4 + 4 + 1 + gtridSize + 4;
for (int i = 0; i < uniqueNamesCount; i++) {
int length = page.getShort();
currentPosition += 2;
// check that names aren't too long
currentReadCount += 2 + length;
if (currentReadCount > recordLength) {
page.position(endOfRecordPosition);
throw new CorruptedTransactionLogException("corrupted log found at position " + currentPosition
+ " (unique names too long, " + (i + 1) + " out of " + uniqueNamesCount + ", length: " + length
+ ", currentReadCount: " + currentReadCount + ", recordLength: " + recordLength + ")");
}
byte[] nameBytes = new byte[length];
page.get(nameBytes);
currentPosition += length;
uniqueNames.add(new String(nameBytes, "US-ASCII"));
}
final int cEndRecord = page.getInt();
currentPosition += 4;
TransactionLogRecord tlog = new TransactionLogRecord(status, recordLength, headerLength, time, sequenceNumber,
crc32, gtrid, uniqueNames, cEndRecord);
// check that CRC is okay
if (!skipCrcCheck && !tlog.isCrc32Correct()) {
page.position(endOfRecordPosition);
throw new CorruptedTransactionLogException("corrupted log found at position " + currentPosition
+ "(invalid CRC, recorded: " + tlog.getCrc32() + ", calculated: " + tlog.calculateCrc32() + ")");
}
return tlog;
}
/**
* Close the cursor and the underlying file
* @throws IOException if an I/O error occurs.
*/
public void close() throws IOException {
fis.close();
fileChannel.close();
}
}