/*
* Bitronix Transaction Manager
*
* Copyright (c) 2011, Juergen Kellerer.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package bitronix.tm.journal.nio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.UUID;
import static bitronix.tm.journal.nio.NioJournalFileRecord.FindResult;
/**
* Low level file iterator.
* <p/>
* Iterates the given journal, returning records based on the given delimiter.
*
* @author juergen kellerer, 2011-04-30
*/
class NioJournalFileIterable implements Iterable<NioJournalFileRecord> {
static final int INITIAL_READ_BUFFER_SIZE = 64 * 1024;
private static final Logger log = LoggerFactory.getLogger(NioJournalFileIterable.class);
private static final boolean trace = log.isTraceEnabled();
private UUID delimiter;
private long journalSize;
private boolean readInvalid;
private FileChannel fileChannel;
NioJournalFileIterable(UUID delimiter, FileChannel fileChannel, boolean readInvalid) throws IOException {
this.delimiter = delimiter;
this.fileChannel = fileChannel;
this.readInvalid = readInvalid;
this.journalSize = fileChannel.size();
}
/**
* Iterates all elements and returns the absolute position after the last record.
*
* @return the absolute position inside the file after the last record.
*/
public long findPositionAfterLastRecord() {
RecordIterator recordIterator = new RecordIterator();
while (recordIterator.hasNext())
recordIterator.next();
return recordIterator.getPositionAfterLastRecord();
}
public Iterator<NioJournalFileRecord> iterator() {
return new RecordIterator();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "JournalIterable{" +
"delimiter=" + delimiter +
", journalSize=" + journalSize +
", fileChannel=" + fileChannel +
'}';
}
private class RecordIterator implements Iterator<NioJournalFileRecord> {
private long position, bufferOffsetInFile, positionAfterLastRecord, readEntries, brokenEntries;
private ByteBuffer buffer = ByteBuffer.allocate(INITIAL_READ_BUFFER_SIZE);
private NioJournalFileRecord nextEntry, disposableEntry;
/**
* Returns the exact byte position of the last returned record.
*
* @return the exact byte position of the last returned record.
*/
public long getPositionAfterLastRecord() {
return positionAfterLastRecord;
}
public boolean hasNext() {
if (nextEntry == null && disposableEntry != null) {
if (trace) { log.trace("Disposing previously returned journal file record " + disposableEntry + " to protect access in invalid state."); }
disposableEntry.dispose(false);
disposableEntry = null;
}
while (nextEntry == null && (nextEntry = readNextEntry()) != null) {
readEntries++;
if (!nextEntry.isValid()) {
if (readInvalid) {
log.warn("CRC32 differs in payload of record for " + nextEntry + ", returning this invalid entry.");
} else {
log.warn("CRC32 differs in payload of record " + nextEntry + ", skipping the entry.");
nextEntry = null;
}
brokenEntries++;
} else {
positionAfterLastRecord = bufferOffsetInFile + buffer.position();
}
}
return nextEntry != null;
}
public NioJournalFileRecord next() {
if (!hasNext())
throw new NoSuchElementException("There are no more entries inside the journal.");
try {
return nextEntry;
} finally {
disposableEntry = nextEntry;
nextEntry = null;
}
}
public void remove() {
throw new UnsupportedOperationException();
}
private NioJournalFileRecord readNextEntry() {
while (true) {
final FindResult findResult = NioJournalFileRecord.findNextRecord(delimiter, buffer);
switch (findResult.getStatus()) {
case FoundPartialRecord:
if (buffer.position() == 0) {
int newSize = buffer.capacity() + 4 * 1024;
if (log.isDebugEnabled()) {
log.debug("Detected a buffer underflow on the attempt to read the journal file. " +
"Creating a new buffer of size " + newSize + " to fit in the current journal record.");
}
buffer = ByteBuffer.allocate(newSize).put(buffer);
} else if (buffer.hasRemaining()) {
if (trace) {
log.trace("Found partial record at the end of the buffer, moving partial content " +
"(" + buffer.remaining() + " bytes) to the beginning of the buffer.");
}
buffer.compact();
}
break;
case NoHeaderInBuffer:
buffer.clear();
break;
}
if (findResult.getStatus() != NioJournalFileRecord.ReadStatus.ReadOk) {
try {
// Capture the position of the buffer within the file.
bufferOffsetInFile = position - buffer.position();
int readBytes = fileChannel.read(buffer, position);
if (readBytes == -1)
break;
position += readBytes;
buffer.flip();
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
return findResult.getRecord();
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "JournalIterabl$Iterator{" +
"position=" + position +
", readEntries=" + readEntries +
", brokenEntries=" + brokenEntries +
", nextEntry=" + nextEntry +
", buffer=" + NioJournalFileRecord.bufferToString(buffer) +
'}';
}
}
}