All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bitronix.tm.journal.TransactionLogCursor Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * 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.internal.LogDebugCheck;
import bitronix.tm.utils.Uid;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;

/**
 * Used to read {@link TransactionLogRecord} objects from a log file.
 *
 * @author Ludovic Orban
 */
public class TransactionLogCursor
		implements AutoCloseable
{

	private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(TransactionLogCursor.class.toString());
	private static final String CORRUPTED_LOGS = "corrupted log found at position ";

	private final FileInputStream fis;
	private final FileChannel fileChannel;
	private final long endPosition;
	private final ByteBuffer page;
	private long currentPosition;

	/**
	 * 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.
	 */
	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 + 8L;
	}

	/**
	 * 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.
	 */
	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.
	 */
	TransactionLogRecord readLog(boolean skipCrcCheck) throws IOException
	{
		if (currentPosition >= endPosition)
		{
			if (LogDebugCheck.isDebugEnabled())
			{
				log.finer("end of transaction log file reached at " + currentPosition);
			}
			return null;
		}

		int status = page.getInt();

		int recordLength = page.getInt();

		currentPosition += 8;

		if (page.position() + recordLength + 8 > page.limit())
		{
			page.compact();
			fileChannel.read(page);
			page.rewind();
		}

		int endOfRecordPosition = page.position() + recordLength;
		if (currentPosition + recordLength > endPosition)
		{
			page.position(page.position() + recordLength);
			currentPosition += recordLength;
			throw new CorruptedTransactionLogException(CORRUPTED_LOGS + currentPosition
			                                           + " (record terminator outside of file bounds: " + currentPosition + recordLength + " of "
			                                           + endPosition + ", recordLength: " + recordLength + ")");
		}

		int headerLength = page.getInt();

		long time = page.getLong();

		int sequenceNumber = page.getInt();

		int crc32 = page.getInt();

		byte gtridSize = page.get();

		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_LOGS + 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_LOGS + currentPosition
			                                           + " (GTRID size too long)");
		}

		byte[] gtridArray = new byte[gtridSize];
		page.get(gtridArray);
		currentPosition += gtridSize;
		Uid gtrid = new Uid(gtridArray);
		int uniqueNamesCount = page.getInt();
		currentPosition += 4;
		Set uniqueNames = new HashSet<>();
		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_LOGS + 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, StandardCharsets.US_ASCII));
		}
		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_LOGS + 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.
	 */
	@Override
	public void close() throws IOException
	{
		fis.close();
		fileChannel.close();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy