| | |
| | | import java.io.FileWriter; |
| | | import java.io.IOException; |
| | | import java.io.RandomAccessFile; |
| | | import java.util.concurrent.locks.Lock; |
| | | import java.util.concurrent.locks.ReadWriteLock; |
| | | import java.util.concurrent.locks.ReentrantReadWriteLock; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | |
| | | * </ul> |
| | | * <p> |
| | | * A log file is NOT intended to be used directly, but only has part of a |
| | | * {@link Log}. In particular, there is no concurrency management and no checks |
| | | * to ensure that log is not closed when performing any operation on it. Those |
| | | * are managed at the {@code Log} level. |
| | | * {@link Log}. |
| | | * |
| | | * @param <K> |
| | | * Type of the key of a record, which must be comparable. |
| | |
| | | /** Indicates if log is enabled for write. */ |
| | | private final boolean isWriteEnabled; |
| | | |
| | | /** Lock used to ensure write atomicity. */ |
| | | private final Lock exclusiveLock; |
| | | |
| | | /** Lock used to ensure that log file is in a consistent state when reading it. */ |
| | | private final Lock sharedLock; |
| | | |
| | | private Record<K, V> newestRecord; |
| | | |
| | | /** |
| | |
| | | writer = null; |
| | | } |
| | | readerPool = new LogReaderPool<>(logfile, parser); |
| | | |
| | | final ReadWriteLock rwLock = new ReentrantReadWriteLock(); |
| | | exclusiveLock = rwLock.writeLock(); |
| | | sharedLock = rwLock.readLock(); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | private void ensureLogFileIsValid(final RecordParser<K, V> parser) throws ChangelogException |
| | | { |
| | | try(final RandomAccessFile readerWriter = new RandomAccessFile(logfile, "rws"); |
| | | final BlockLogReader<K, V> reader = BlockLogReader.newReader(logfile, readerWriter, parser)) |
| | | try (final RandomAccessFile readerWriter = new RandomAccessFile(logfile, "rws"); |
| | | final BlockLogReader<K, V> reader = BlockLogReader.newReader(logfile, readerWriter, parser)) |
| | | { |
| | | final long lastValidPosition = reader.checkLogIsValid(); |
| | | if (lastValidPosition != -1) |
| | |
| | | void append(final Record<K, V> record) throws ChangelogException |
| | | { |
| | | checkLogIsEnabledForWrite(); |
| | | writer.write(record); |
| | | newestRecord = record; |
| | | exclusiveLock.lock(); |
| | | try |
| | | { |
| | | writer.write(record); |
| | | newestRecord = record; |
| | | } |
| | | finally |
| | | { |
| | | exclusiveLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | void dumpAsTextFile(File dumpFile) throws ChangelogException |
| | | { |
| | | try(final BufferedWriter textWriter = new BufferedWriter(new FileWriter(dumpFile)); |
| | | final DBCursor<Record<K, V>> cursor = getCursor()) |
| | | try (final BufferedWriter textWriter = new BufferedWriter(new FileWriter(dumpFile)); |
| | | final DBCursor<Record<K, V>> cursor = getCursor()) |
| | | { |
| | | while (cursor.getRecord() != null) |
| | | { |
| | |
| | | void syncToFileSystem() throws ChangelogException |
| | | { |
| | | checkLogIsEnabledForWrite(); |
| | | sharedLock.lock(); |
| | | try |
| | | { |
| | | writer.sync(); |
| | |
| | | { |
| | | throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_SYNC.get(getPath()), e); |
| | | } |
| | | finally |
| | | { |
| | | sharedLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | try (BlockLogReader<K, V> reader = getReader()) |
| | | { |
| | | newestRecord = reader.getNewestRecord(); |
| | | sharedLock.lock(); |
| | | try |
| | | { |
| | | newestRecord = reader.getNewestRecord(); |
| | | } |
| | | finally |
| | | { |
| | | sharedLock.unlock(); |
| | | } |
| | | } |
| | | catch (IOException ioe) |
| | | { |
| | |
| | | long getNumberOfRecords() throws ChangelogException |
| | | { |
| | | // TODO : need a more efficient way to retrieve it |
| | | try(final DBCursor<Record<K, V>> cursor = getCursor()) |
| | | try (final DBCursor<Record<K, V>> cursor = getCursor()) |
| | | { |
| | | long counter = 0L; |
| | | while (cursor.next()) |
| | |
| | | */ |
| | | void delete() throws ChangelogException |
| | | { |
| | | if (!logfile.delete()) |
| | | exclusiveLock.lock(); |
| | | try |
| | | { |
| | | throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_DELETE_LOG_FILE.get(getPath())); |
| | | final boolean isDeleted = logfile.delete(); |
| | | if (!isDeleted) |
| | | { |
| | | throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_DELETE_LOG_FILE.get(getPath())); |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | exclusiveLock.unlock(); |
| | | } |
| | | } |
| | | |
| | |
| | | * 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 responsability of the caller of this method. |
| | | * 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.logFile = logFile; |
| | | this.reader = logFile.getReader(); |
| | | this.currentRecord = record; |
| | | reader.seekToPosition(filePosition); |
| | | logFile.sharedLock.lock(); |
| | | try |
| | | { |
| | | reader.seekToPosition(filePosition); |
| | | } |
| | | finally |
| | | { |
| | | logFile.sharedLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | |
| | | initialRecord = null; |
| | | return true; |
| | | } |
| | | currentRecord = reader.readRecord(); |
| | | logFile.sharedLock.lock(); |
| | | try |
| | | { |
| | | currentRecord = reader.readRecord(); |
| | | } |
| | | finally |
| | | { |
| | | logFile.sharedLock.unlock(); |
| | | } |
| | | return currentRecord != null; |
| | | } |
| | | |
| | |
| | | @Override |
| | | public boolean positionTo(final K key, final KeyMatchingStrategy match, final PositionStrategy pos) |
| | | throws ChangelogException { |
| | | final Pair<Boolean, Record<K, V>> result = reader.seekToRecord(key, match, pos); |
| | | final Pair<Boolean, Record<K, V>> result; |
| | | logFile.sharedLock.lock(); |
| | | try |
| | | { |
| | | result = reader.seekToRecord(key, match, pos); |
| | | } |
| | | finally |
| | | { |
| | | logFile.sharedLock.unlock(); |
| | | } |
| | | final boolean found = result.getFirst(); |
| | | initialRecord = found ? result.getSecond() : null; |
| | | return found; |
| | |
| | | */ |
| | | long getFilePosition() throws ChangelogException |
| | | { |
| | | return reader.getFilePosition(); |
| | | logFile.sharedLock.lock(); |
| | | try |
| | | { |
| | | return reader.getFilePosition(); |
| | | } |
| | | finally |
| | | { |
| | | logFile.sharedLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |