/*
* 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 file, containing part of a {@code Log}. The log file may be:
* <ul>
* <li>write-enabled : allowing to append key-value records and read records
* from cursors,</li>
* <li>read-only : allowing to read records from cursors.</li>
* </ul>
* <p>
* A log file is NOT intended to be used directly, but only has part of a
* {@link Log}.
*
* @param <K>
* Type of the key of a record, which must be comparable.
* @param <V>
* Type of the value of a record.
*/
{
/** The file containing the records. */
/** The pool to obtain a reader on the log. */
/**
* The writer on the log file, which may be {@code null} if log file is not
* write-enabled.
*/
/** Indicates if log is enabled for write. */
private final boolean isWriteEnabled;
/** Lock used to ensure write atomicity. */
/** Lock used to ensure that log file is in a consistent state when reading it. */
/**
* Creates a new log file.
*
* @param logFilePath
* Path of the log file.
* @param parser
* Parser of records.
* @param isWriteEnabled
* {@code true} if this changelog is write-enabled, {@code false}
* otherwise.
* @throws ChangelogException
* If a problem occurs during initialization.
*/
throws ChangelogException
{
this.logfile = logFilePath;
this.isWriteEnabled = isWriteEnabled;
if (isWriteEnabled)
{
}
else
{
}
}
/**
* Creates a read-only log file with the provided root path and record parser.
*
* @param <K>
* Type of the key of a record, which must be comparable.
* @param <V>
* Type of the value of a record.
* @param logFilePath
* Path of the log file.
* @param parser
* Parser of records.
* @return a read-only log file
* @throws ChangelogException
* If a problem occurs during initialization.
*/
{
}
/**
* Creates a write-enabled log file that appends records to the end of file,
* with the provided root path and record parser.
*
* @param <K>
* Type of the key of a record, which must be comparable.
* @param <V>
* Type of the value of a record.
* @param logFilePath
* Path of the log file.
* @param parser
* Parser of records.
* @return a write-enabled log file
* @throws ChangelogException
* If a problem occurs during initialization.
*/
{
}
/**
* Returns the file containing the records.
*
* @return the file
*/
{
return logfile;
}
{
if (!isWriteEnabled)
{
}
}
{
try
{
{
}
}
catch (IOException e)
{
}
}
/**
* Ensure that log file is not corrupted, by checking it is valid and cleaning
* the end of file if necessary, to remove a partially written record.
* <p>
* If log file is cleaned to remove a partially written record, then a message
* is logged for information.
*
* @throws ChangelogException
* If an error occurs or if log file is corrupted and can't be
* cleaned
*/
{
{
if (lastValidPosition != -1)
{
// truncate the file to point where last valid record has been read
}
}
catch (IOException e)
{
}
}
/**
* Add the provided record at the end of this log.
* <p>
* In order to ensure that record is written out of buffers and persisted
* to file system, it is necessary to explicitely call the
* {@code syncToFileSystem()} method.
*
* @param record
* The record to add.
* @throws ChangelogException
* If the record can't be added to the log.
*/
{
try
{
}
finally
{
}
}
/**
* Dump this log file as a text file, intended for debugging purpose only.
*
* @param dumpFile
* File that will contains log records using a human-readable format
* @throws ChangelogException
* If an error occurs during dump
*/
{
{
{
}
}
catch (IOException e)
{
// No I18N needed, used for debugging purpose only
throw new ChangelogException(
LocalizableMessage.raw("Error when dumping content of log '%s' in target file : '%s'", getPath(), dumpFile),
e);
}
}
/**
* Synchronize all records added with the file system, ensuring that records
* are effectively persisted.
* <p>
* After a successful call to this method, it is guaranteed that all records
* added to the log are persisted to the file system.
*
* @throws ChangelogException
* If the synchronization fails.
*/
{
sharedLock.lock();
try
{
}
catch (Exception e)
{
}
finally
{
sharedLock.unlock();
}
}
/**
* Returns a cursor that allows to retrieve the records from this log,
* starting at the first position.
*
* @return a cursor on the log records, which is never {@code null}
* @throws ChangelogException
* If the cursor can't be created.
*/
{
return new LogFileCursor<>(this);
}
/**
* Returns a cursor initialized to the provided record and position in file.
*
* @param record
* The initial record this cursor points on
* @param position
* The file position this cursor points on
* @return the cursor
* @throws ChangelogException
* If a problem occurs while creating the cursor.
*/
LogFileCursor<K, V> getCursorInitialisedTo(Record<K,V> record, long position) throws ChangelogException
{
}
/**
* Returns the oldest (first) record from this log.
*
* @return the oldest record, which may be {@code null} if there is no record
* in the log.
* @throws ChangelogException
* If an error occurs while retrieving the record.
*/
{
{
}
}
/**
* Returns the newest (last) record from this log.
*
* @return the newest record, which may be {@code null}
* @throws ChangelogException
* If an error occurs while retrieving the record.
*/
{
if (newestRecord == null)
{
{
sharedLock.lock();
try
{
}
finally
{
sharedLock.unlock();
}
}
catch (IOException ioe)
{
throw new ChangelogException(ERR_CHANGELOG_CANNOT_READ_NEWEST_RECORD.get(logfile.getAbsolutePath()), ioe);
}
}
return newestRecord;
}
/**
* Returns the number of records in the log.
*
* @return the number of records
* @throws ChangelogException
* If an error occurs.
*/
{
// TODO : need a more efficient way to retrieve it
{
long counter = 0L;
{
counter++;
}
return counter;
}
}
/** {@inheritDoc} */
public void close()
{
if (isWriteEnabled)
{
try
{
}
catch (ChangelogException e)
{
logger.traceException(e);
}
}
}
/**
* Delete this log file (file is physically removed). Should be called only
* when log file is closed.
*
* @throws ChangelogException
* If log file can't be deleted.
*/
{
try
{
if (!isDeleted)
{
}
}
finally
{
}
}
/**
* Return the size of this log file in bytes.
*
* @return the size of log file
*/
long getSizeInBytes()
{
return writer.getBytesWritten();
}
/** The path of this log file as a String. */
{
}
/**
* Returns a reader for this log.
* <p>
* Assumes that calling methods ensure that log is not closed.
*/
{
return readerPool.get();
}
/** Release the provided reader. */
}
/** {@inheritDoc} */
public int hashCode()
{
}
/** {@inheritDoc} */
{
if (this == that)
{
return true;
}
{
return false;
}
}
{
}
/** Implements a repositionable cursor on the log file. */
{
/** The underlying log on which entries are read. */
/** To read the records. */
/** The current available record, may be {@code null}. */
/**
* The initial record when starting from a given key. It must be
* stored because it is read in advance.
*/
/**
* Creates a cursor on the provided log.
*
* @param logFile
* The log on which the cursor read records.
* @throws ChangelogException
* If an error occurs when creating the cursor.
*/
{
}
/**
* Creates a cursor on the provided log, initialised to the provided record and
* pointing to the provided file position.
* <p>
* Note: there is no check to ensure that provided record and file position are
* consistent. It is the responsibility of the caller of this method.
*/
private LogFileCursor(LogFile<K, V> logFile, Record<K, V> record, long filePosition) throws ChangelogException
{
this.currentRecord = record;
try
{
}
finally
{
}
}
/** {@inheritDoc} */
{
if (initialRecord != null)
{
// initial record is used only once
return true;
}
try
{
}
finally
{
}
return currentRecord != null;
}
/** {@inheritDoc} */
{
return currentRecord;
}
/** {@inheritDoc} */
throws ChangelogException {
try
{
}
finally
{
}
return found;
}
/** {@inheritDoc} */
public void close()
{
}
/**
* Returns the file position this cursor is pointing at.
*
* @return the position of reader on the log file
* @throws ChangelogException
* If an error occurs.
*/
{
try
{
return reader.getFilePosition();
}
finally
{
}
}
/** {@inheritDoc} */
{
}
}
}