net.kuujo.copycat.log.FileLogSegment Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 the original author or authors.
*
* 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 net.kuujo.copycat.log;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
/**
* File log segment.
*
* @author Jordan Halterman
*/
public class FileLogSegment extends AbstractLogSegment {
private final FileLogManager log;
private final File logFile;
private final File indexFile;
private final File metadataFile;
private long timestamp;
private FileChannel logFileChannel;
private FileChannel indexFileChannel;
private Long firstIndex;
private Long lastIndex;
private final ByteBuffer indexBuffer = ByteBuffer.allocate(8);
FileLogSegment(FileLogManager log, long id, long firstIndex) {
super(id, firstIndex);
this.log = log;
this.logFile = new File(log.base.getParentFile(), String.format("%s-%d.log", log.base.getName(), id));
this.indexFile = new File(log.base.getParentFile(), String.format("%s-%d.index", log.base.getName(), id));
this.metadataFile = new File(log.base.getParentFile(), String.format("%s-%d.metadata", log.base.getName(), id));
}
@Override
public LogManager log() {
return log;
}
@Override
public long timestamp() {
assertIsOpen();
return timestamp;
}
@Override
public void open() throws IOException {
assertIsNotOpen();
if (!logFile.getParentFile().exists()) {
logFile.getParentFile().mkdirs();
}
if (!metadataFile.exists()) {
timestamp = System.currentTimeMillis();
try (RandomAccessFile metaFile = new RandomAccessFile(metadataFile, "rw")) {
metaFile.writeLong(super.firstIndex); // First index of the segment.
metaFile.writeLong(timestamp); // Timestamp of the time at which the segment was created.
}
} else {
try (RandomAccessFile metaFile = new RandomAccessFile(metadataFile, "r")) {
if (metaFile.readLong() != super.firstIndex) {
throw new LogException("Segment metadata out of sync");
}
timestamp = metaFile.readLong();
}
}
logFileChannel = FileChannel.open(this.logFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
logFileChannel.position(logFileChannel.size());
indexFileChannel = FileChannel.open(this.indexFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
indexFileChannel.position(indexFileChannel.size());
if (indexFileChannel.size() > 0) {
firstIndex = super.firstIndex;
lastIndex = firstIndex + indexFileChannel.size() / 8 - 1;
}
}
@Override
public boolean isEmpty() {
assertIsOpen();
return firstIndex == null;
}
@Override
public boolean isOpen() {
return logFileChannel != null && indexFileChannel != null;
}
@Override
public long size() {
assertIsOpen();
try {
return logFileChannel.size();
} catch (IOException e) {
throw new LogException(e);
}
}
@Override
public long entryCount() {
assertIsOpen();
return firstIndex != null ? lastIndex - firstIndex + 1 : 0;
}
/**
* Returns the next log index.
*/
private long nextIndex() {
if (firstIndex == null) {
firstIndex = super.firstIndex;
lastIndex = firstIndex;
return firstIndex;
}
return ++lastIndex;
}
@Override
public long appendEntry(ByteBuffer entry) {
assertIsOpen();
long index = nextIndex();
try {
entry.rewind();
long position = logFileChannel.position();
logFileChannel.write(entry);
storePosition(index, position);
} catch (IOException e) {
throw new LogException(e);
}
return index;
}
/**
* Stores the position of an entry in the log.
*/
private void storePosition(long index, long position) {
try {
ByteBuffer buffer = ByteBuffer.allocate(8).putLong(position);
buffer.flip();
indexFileChannel.write(buffer, (index - firstIndex) * 8);
} catch (IOException e) {
throw new LogException(e);
}
}
/**
* Finds the position of the given index in the segment.
*/
private long findPosition(long index) {
try {
if (firstIndex == null || index <= firstIndex) {
return 0;
} else if (lastIndex == null || index > lastIndex) {
return logFileChannel.size();
}
indexFileChannel.read(indexBuffer, (index - firstIndex) * 8);
indexBuffer.flip();
long position = indexBuffer.getLong();
indexBuffer.clear();
return position;
} catch (IOException e) {
throw new LogException(e);
}
}
@Override
public Long firstIndex() {
assertIsOpen();
return firstIndex;
}
@Override
public Long lastIndex() {
assertIsOpen();
return lastIndex;
}
@Override
public boolean containsIndex(long index) {
assertIsOpen();
return firstIndex != null && lastIndex != null && firstIndex <= index && index <= lastIndex;
}
@Override
public ByteBuffer getEntry(long index) {
assertIsOpen();
assertContainsIndex(index);
try {
long startPosition = findPosition(index);
long endPosition = findPosition(index + 1);
ByteBuffer buffer = ByteBuffer.allocate((int) (endPosition - startPosition));
logFileChannel.read(buffer, startPosition);
buffer.flip();
return buffer;
} catch (IOException e) {
throw new LogException(e);
}
}
@Override
public void removeAfter(long index) {
assertIsOpen();
if (containsIndex(index + 1)) {
try {
logFileChannel.truncate(findPosition(index + 1));
indexFileChannel.truncate(((index + 1) - firstIndex) * 8);
if (index >= firstIndex) {
lastIndex = index;
} else {
lastIndex = null;
firstIndex = null;
}
} catch (IOException e) {
throw new LogException(e);
}
}
}
@Override
public void flush() {
try {
logFileChannel.force(false);
indexFileChannel.force(false);
} catch (IOException e) {
throw new LogException(e);
}
}
@Override
public void close() throws IOException {
assertIsOpen();
logFileChannel.close();
logFileChannel = null;
indexFileChannel.close();
indexFileChannel = null;
}
@Override
public boolean isClosed() {
return logFileChannel == null;
}
@Override
public void delete() {
logFile.delete();
indexFile.delete();
metadataFile.delete();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy