/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.activemq.artemis.core.io.mapped;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import io.netty.buffer.ByteBuf;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.api.core.ActiveMQIOErrorException;
import org.apache.activemq.artemis.core.io.IOCallback;
import org.apache.activemq.artemis.core.io.IOCriticalErrorListener;
import org.apache.activemq.artemis.core.io.SequentialFile;
import org.apache.activemq.artemis.core.io.buffer.TimedBuffer;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
import org.apache.activemq.artemis.journal.ActiveMQJournalBundle;
import org.apache.activemq.artemis.journal.ActiveMQJournalLogger;
final class MappedSequentialFile implements SequentialFile {
private final File directory;
private final long chunkBytes;
private final long overlapBytes;
private final IOCriticalErrorListener criticalErrorListener;
private final MappedSequentialFileFactory factory;
private File file;
private File absoluteFile;
private String fileName;
private MappedFile mappedFile;
MappedSequentialFile(MappedSequentialFileFactory factory,
final File directory,
final File file,
final long chunkBytes,
final long overlapBytes,
final IOCriticalErrorListener criticalErrorListener) {
this.factory = factory;
this.directory = directory;
this.file = file;
this.absoluteFile = null;
this.fileName = null;
this.chunkBytes = chunkBytes;
this.overlapBytes = overlapBytes;
this.mappedFile = null;
this.criticalErrorListener = criticalErrorListener;
}
private void checkIsOpen() {
if (!isOpen()) {
throw new IllegalStateException("File not opened!");
}
}
private void checkIsOpen(IOCallback callback) {
if (!isOpen()) {
callback.onError(ActiveMQExceptionType.IO_ERROR.getCode(), "File not opened");
}
}
private void checkIsNotOpen() {
if (isOpen()) {
throw new IllegalStateException("File opened!");
}
}
@Override
public boolean isOpen() {
return this.mappedFile != null;
}
@Override
public boolean exists() {
return this.file.exists();
}
@Override
public void open() throws IOException {
if (this.mappedFile == null) {
this.mappedFile = MappedFile.of(file, chunkBytes, overlapBytes);
}
}
@Override
public void open(int maxIO, boolean useExecutor) throws IOException {
//ignore maxIO e useExecutor
this.open();
}
@Override
public boolean fits(int size) {
checkIsOpen();
final long newPosition = this.mappedFile.position() + size;
final boolean hasRemaining = newPosition <= this.mappedFile.length();
return hasRemaining;
}
@Override
public int calculateBlockStart(int position) {
return position;
}
@Override
public String getFileName() {
if (this.fileName == null) {
this.fileName = this.file.getName();
}
return this.fileName;
}
@Override
public void fill(int size) throws IOException {
checkIsOpen();
this.mappedFile.zeros(this.mappedFile.position(), size);
}
@Override
public void delete() {
close();
if (file.exists() && !file.delete()) {
ActiveMQJournalLogger.LOGGER.errorDeletingFile(this);
}
}
@Override
public void write(ActiveMQBuffer bytes, boolean sync, IOCallback callback) throws IOException {
if (callback == null) {
throw new NullPointerException("callback parameter need to be set");
}
checkIsOpen(callback);
try {
final ByteBuf byteBuf = bytes.byteBuf();
final int writerIndex = byteBuf.writerIndex();
final int readerIndex = byteBuf.readerIndex();
final int readableBytes = writerIndex - readerIndex;
if (readableBytes > 0) {
this.mappedFile.write(byteBuf, readerIndex, readableBytes);
if (factory.isDatasync() && sync) {
this.mappedFile.force();
}
}
callback.done();
} catch (IOException e) {
if (this.criticalErrorListener != null) {
this.criticalErrorListener.onIOException(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
}
callback.onError(ActiveMQExceptionType.IO_ERROR.getCode(), e.getMessage());
throw e;
}
}
@Override
public void write(ActiveMQBuffer bytes, boolean sync) throws IOException {
checkIsOpen();
final ByteBuf byteBuf = bytes.byteBuf();
final int writerIndex = byteBuf.writerIndex();
final int readerIndex = byteBuf.readerIndex();
final int readableBytes = writerIndex - readerIndex;
if (readableBytes > 0) {
this.mappedFile.write(byteBuf, readerIndex, readableBytes);
if (factory.isDatasync() && sync) {
this.mappedFile.force();
}
}
}
@Override
public void write(EncodingSupport bytes, boolean sync, IOCallback callback) throws IOException {
if (callback == null) {
throw new NullPointerException("callback parameter need to be set");
}
checkIsOpen(callback);
try {
this.mappedFile.write(bytes);
if (factory.isDatasync() && sync) {
this.mappedFile.force();
}
callback.done();
} catch (IOException e) {
if (this.criticalErrorListener != null) {
this.criticalErrorListener.onIOException(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
}
callback.onError(ActiveMQExceptionType.IO_ERROR.getCode(), e.getMessage());
throw e;
}
}
@Override
public void write(EncodingSupport bytes, boolean sync) throws IOException {
checkIsOpen();
this.mappedFile.write(bytes);
if (factory.isDatasync() && sync) {
this.mappedFile.force();
}
}
@Override
public void writeDirect(ByteBuffer bytes, boolean sync, IOCallback callback) {
if (callback == null) {
throw new NullPointerException("callback parameter need to be set");
}
checkIsOpen(callback);
try {
final int position = bytes.position();
final int limit = bytes.limit();
final int remaining = limit - position;
if (remaining > 0) {
this.mappedFile.write(bytes, position, remaining);
final int newPosition = position + remaining;
bytes.position(newPosition);
if (factory.isDatasync() && sync) {
this.mappedFile.force();
}
}
callback.done();
} catch (IOException e) {
if (this.criticalErrorListener != null) {
this.criticalErrorListener.onIOException(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
}
callback.onError(ActiveMQExceptionType.IO_ERROR.getCode(), e.getMessage());
throw new RuntimeException(e);
}
}
@Override
public void writeDirect(ByteBuffer bytes, boolean sync) throws IOException {
checkIsOpen();
final int position = bytes.position();
final int limit = bytes.limit();
final int remaining = limit - position;
if (remaining > 0) {
this.mappedFile.write(bytes, position, remaining);
final int newPosition = position + remaining;
bytes.position(newPosition);
if (factory.isDatasync() && sync) {
this.mappedFile.force();
}
}
}
@Override
public int read(ByteBuffer bytes, IOCallback callback) throws IOException {
if (callback == null) {
throw new NullPointerException("callback parameter need to be set");
}
checkIsOpen(callback);
try {
final int position = bytes.position();
final int limit = bytes.limit();
final int remaining = limit - position;
if (remaining > 0) {
final int bytesRead = this.mappedFile.read(bytes, position, remaining);
final int newPosition = position + bytesRead;
bytes.position(newPosition);
bytes.flip();
callback.done();
return bytesRead;
} else {
callback.done();
return 0;
}
} catch (IOException e) {
if (this.criticalErrorListener != null) {
this.criticalErrorListener.onIOException(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
}
callback.onError(ActiveMQExceptionType.IO_ERROR.getCode(), e.getMessage());
throw e;
}
}
@Override
public int read(ByteBuffer bytes) throws IOException {
checkIsOpen();
final int position = bytes.position();
final int limit = bytes.limit();
final int remaining = limit - position;
if (remaining > 0) {
final int bytesRead = this.mappedFile.read(bytes, position, remaining);
final int newPosition = position + bytesRead;
bytes.position(newPosition);
bytes.flip();
return bytesRead;
}
return 0;
}
@Override
public void position(long pos) {
checkIsOpen();
this.mappedFile.position(pos);
}
@Override
public long position() {
checkIsOpen();
return this.mappedFile.position();
}
@Override
public void close() {
if (this.mappedFile != null) {
this.mappedFile.closeAndResize(this.mappedFile.length());
this.mappedFile = null;
}
}
@Override
public void sync() throws IOException {
checkIsOpen();
this.mappedFile.force();
}
@Override
public long size() {
if (this.mappedFile != null) {
return this.mappedFile.length();
} else {
return this.file.length();
}
}
@Override
public void renameTo(String newFileName) throws Exception {
try {
close();
} catch (Exception e) {
if (e instanceof IOException) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
}
throw e;
}
if (this.fileName == null) {
this.fileName = this.file.getName();
}
if (!this.fileName.contentEquals(newFileName)) {
final File newFile = new File(this.directory, newFileName);
if (!file.renameTo(newFile)) {
throw ActiveMQJournalBundle.BUNDLE.ioRenameFileError(file.getName(), newFileName);
} else {
this.file = newFile;
this.fileName = newFileName;
this.absoluteFile = null;
}
}
}
@Override
public SequentialFile cloneFile() {
checkIsNotOpen();
return new MappedSequentialFile(factory, this.directory, this.file, this.chunkBytes, this.overlapBytes, this.criticalErrorListener);
}
@Override
public void copyTo(SequentialFile dstFile) throws IOException {
checkIsNotOpen();
if (dstFile.isOpen()) {
throw new IllegalArgumentException("dstFile must be closed too");
}
try (RandomAccessFile src = new RandomAccessFile(file, "rw"); FileChannel srcChannel = src.getChannel(); FileLock srcLock = srcChannel.lock()) {
final long readableBytes = srcChannel.size();
if (readableBytes > 0) {
try (RandomAccessFile dst = new RandomAccessFile(dstFile.getJavaFile(), "rw"); FileChannel dstChannel = dst.getChannel(); FileLock dstLock = dstChannel.lock()) {
final long oldLength = dst.length();
final long newLength = oldLength + readableBytes;
dst.setLength(newLength);
final long transferred = dstChannel.transferFrom(srcChannel, oldLength, readableBytes);
if (transferred != readableBytes) {
dstChannel.truncate(oldLength);
throw new IOException("copied less then expected");
}
}
}
}
}
@Override
@Deprecated
public void setTimedBuffer(TimedBuffer buffer) {
throw new UnsupportedOperationException("the timed buffer is not currently supported");
}
@Override
public File getJavaFile() {
if (this.absoluteFile == null) {
this.absoluteFile = this.file.getAbsoluteFile();
}
return this.absoluteFile;
}
}