BlockLogReader.java revision 8d98d66a588015d4f8c4fc9c77a2a1ba9544689c
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2014-2015 ForgeRock AS.
*/
/**
* A log reader with binary search support.
* <p>
* The log file contains record offsets at fixed block size : given a block size N,
* an offset is written at every N bytes. The offset contains the number of bytes to
* reach the beginning of previous record (or next record if offset equals 0).
* <p>
* The reader provides both sequential access, using the {@code readRecord()} method,
* and reasonably fast random access, using the {@code seekToRecord(K, boolean)} method.
*
* @param <K>
* Type of the key of a record, which must be comparable.
* @param <V>
* Type of the value of a record.
*/
{
static final int SIZE_OF_BLOCK_OFFSET = 4;
static final int SIZE_OF_RECORD_SIZE = 4;
/**
* Size of a block, after which an offset to the nearest record is written.
* <p>
* This value has been fixed based on performance tests. See
* OPENDJ-1472</a> for details.
*/
static final int BLOCK_SIZE = 256;
private final int blockSize;
private final RecordParser<K, V> parser;
private final RandomAccessFile reader;
/**
* Creates a reader for the provided file, file reader and parser.
*
* @param <K>
* Type of the key of a record, which must be comparable.
* @param <V>
* Type of the value of a record.
* @param file
* The log file to read.
* @param reader
* The random access reader on the log file.
* @param parser
* The parser to decode the records read.
* @return a new log reader
*/
{
}
/**
* Creates a reader for the provided file, file reader, parser and block size.
* <p>
* This method is intended for tests only, to allow tuning of the block size.
*
* @param <K>
* Type of the key of a record, which must be comparable.
* @param <V>
* Type of the value of a record.
* @param file
* The log file to read.
* @param reader
* The random access reader on the log file.
* @param parser
* The parser to decode the records read.
* @param blockSize
* The size of each block, or frequency at which the record offset is
* present in the log file.
* @return a new log reader
*/
{
}
private BlockLogReader(
final File file, final RandomAccessFile reader, final RecordParser<K, V> parser, final int blockSize)
{
}
/**
* Position the reader to the record corresponding to the provided key and
* matching and positioning strategies. Returns the last record read.
*
* @param key
* Key to use as a start position. Key must not be {@code null}.
* @param matchStrategy
* The key matching strategy.
* @param positionStrategy
* The positioning strategy.
* @return The pair (key_found, last_record_read). key_found is a boolean
* indicating if reader is successfully positioned. last_record_read
* is the last record that was read. When key_found is equals to
* {@code false}, then last_record_read is always {@code null}. When
* key_found is equals to {@code true}, last_record_read can be valued
* or be {@code null}
* @throws ChangelogException
* If an error occurs when seeking the key.
*/
final K key,
final KeyMatchingStrategy matchStrategy,
final PositionStrategy positionStrategy)
throws ChangelogException
{
if (markerPosition >= 0)
{
}
}
/**
* Position the reader to the provided file position.
*
* @param filePosition
* offset from the beginning of the file, in bytes.
* @throws ChangelogException
* If an error occurs.
*/
{
try
{
}
catch (IOException e)
{
}
}
/**
* Read a record from current position.
*
* @return the record read
* @throws ChangelogException
* If an error occurs during read.
*/
{
return readRecord(-1);
}
/**
* Returns the file position for this reader.
*
* @return the position of reader on the log file
* @throws ChangelogException
* If an error occurs.
*/
public long getFilePosition() throws ChangelogException
{
try
{
return reader.getFilePointer();
}
catch (IOException e)
{
throw new ChangelogException(
}
}
/** {@inheritDoc} */
public void close() throws IOException
{
}
/**
* Read a record, either from the provided start of block position or from
* the current position.
*
* @param blockStartPosition
* The position of start of block, where a record offset is written.
* If provided value is -1, then record is read from current position instead.
* @return the record read
* @throws ChangelogException
* If an error occurs during read.
*/
{
try
{
if (blockStartPosition != -1)
{
}
}
{
}
}
/**
* Position to the record given by the offset read from provided block
* start.
*
* @param blockStartPosition
* Position of read pointer in the file, expected to be the start of
* a block where a record offset is written.
* @throws IOException
* If an error occurs during read.
*/
{
if (blockStartPosition > 0)
{
final byte[] offsetData = new byte[SIZE_OF_BLOCK_OFFSET];
if (offsetToRecord > 0)
{
} // if offset is zero, reader is already well positioned
}
}
/**
* Reads the next record.
*
* @return the bytes of the next record, or {@code null} if no record is available
* @throws IOException
* If an error occurs while reading.
*/
{
try
{
// read length of record
// read the record
final ByteStringBuilder recordBytes =
int remainingBytesToRead = recordLength;
while (distanceToBlockStart < remainingBytesToRead)
{
if (distanceToBlockStart != 0)
{
}
// skip the offset
// next step
}
if (remainingBytesToRead > 0)
{
// last bytes of the record
}
return recordBytes.toByteString();
}
catch (EOFException e)
{
// end of stream, no record or uncomplete record
return null;
}
}
/**
* Returns the total length in bytes taken by a record when stored in log file,
* including size taken by block offsets.
*
* @param recordLength
* The length of record to write.
* @param distanceToBlockStart
* Distance before the next block start.
* @return the length in bytes necessary to store the record in the log
*/
{
int totalLength = recordLength;
if (recordLength > distanceToBlockStart)
{
final int remainingBlocks = (recordLength - distanceToBlockStart -1) / (blockSize - SIZE_OF_BLOCK_OFFSET);
}
return totalLength;
}
/** Read the length of a record. */
{
{
// skip the offset
}
else
{
if (distanceToBlockStart == 0)
{
// skip the offset
}
}
}
/**
* Search the closest block start to the provided key, using binary search.
* <p>
* Note that position of reader is modified by this method.
*
* @param key
* The key to search
* @return the file position of block start that must be used to find the given key,
* or a negative number if no position could be found.
* @throws ChangelogException
* if a problem occurs
*/
{
long lowPos = 0L;
{
if (middleRecord == null)
{
return -1;
}
if (keyComparison < 0)
{
if (middleBlockStartPos <= lowPos)
{
return lowPos;
}
}
else if (keyComparison > 0)
{
if (middleBlockStartPos >= highPos)
{
return highPos;
}
}
else
{
return middleBlockStartPos;
}
}
// Unable to find a position where key can be found
return -1;
}
private long getFileLength() throws ChangelogException
{
try
{
}
catch (IOException e)
{
}
}
/**
* Position before, at or after provided key, starting from provided block
* start position and reading sequentially until key is found according to
* matching and positioning strategies.
*
* @param blockStartPosition
* Position of read pointer in the file, expected to be the start of
* a block where a record offset is written
* @param key
* The key to find
* @param matchStrategy
* The key matching strategy
* @param positionStrategy
* The positioning strategy
* @return The pair ({@code true}, selected record) if reader is successfully
* positioned (selected record may be null if end of file is reached),
* ({@code false}, null) otherwise.
* @throws ChangelogException
* If an error occurs.
*/
final KeyMatchingStrategy matchStrategy, final PositionStrategy positionStrategy) throws ChangelogException
{
long previousPosition = blockStartPosition;
boolean matchingKeyIsLowerThanAnyRecord = true;
{
if (keysComparison <= 0)
{
matchingKeyIsLowerThanAnyRecord = false;
}
{
return getMatchingRecord(matchStrategy, positionStrategy, keysComparison, matchingKeyIsLowerThanAnyRecord,
}
record = readRecord();
}
{
}
}
throws ChangelogException
{
if (positionStrategy == AFTER_MATCHING_KEY)
{
{
}
if (keysComparison == 0)
{
// skip matching key
record = readRecord();
}
}
else if (positionStrategy == ON_MATCHING_KEY && matchStrategy == LESS_THAN_OR_EQUAL_TO_KEY && keysComparison > 0)
{
}
}
final PositionStrategy positionStrategy, final Record<K, V> previousRecord, final long previousPosition)
throws ChangelogException
{
if (positionStrategy == ON_MATCHING_KEY)
{
}
else
{
}
}
/**
* Returns the closest start of block which has a position lower than or equal
* to the provided file position.
*
* @param filePosition
* The position of reader on file.
* @return the file position of the block start.
*/
long getClosestBlockStartBeforeOrAtPosition(final long filePosition)
{
}
/**
* Returns the closest start of block which has a position strictly
* higher than the provided file position.
*
* @param filePosition
* The position of reader on file.
* @return the file position of the block start.
*/
long getClosestBlockStartStrictlyAfterPosition(final long filePosition)
{
}
/**
* Returns the distance to next block for the provided file position.
*
* @param filePosition
* offset from the beginning of the file, in bytes.
* @param blockSize
* Size of each block in bytes.
* @return the distance to next block in bytes
*/
{
if (filePosition == 0)
{
return blockSize;
}
}
/**
* Check if the log file is valid, by reading the latest records at the end of
* the log file.
* <p>
* The intent of this method is to allow self-recovery in case a partial
* record as been written at the end of file (eg, after a server crash).
* <p>
* Any unexpected exception is considered as a severe error where
* self-recovery is not appropriate and thus will lead to a
* ChangelogException.
*
* @return -1 if log is valid, or a positive number if log is invalid, where
* the number represents the last valid position in the log file.
* @throws ChangelogException
* if an error occurs while checking the log
*/
long checkLogIsValid() throws ChangelogException
{
try
{
final long fileSize = getFileLength();
long lastValidPosition = lastBlockStart;
}
}
catch (Exception e)
{
}
}
}