/*
* 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;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQIOErrorException;
import org.apache.activemq.artemis.core.io.buffer.TimedBuffer;
import org.apache.activemq.artemis.core.io.buffer.TimedBufferObserver;
import org.apache.activemq.artemis.core.io.util.FileIOUtil;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
import org.apache.activemq.artemis.core.journal.impl.SimpleWaitIOCallback;
import org.apache.activemq.artemis.journal.ActiveMQJournalBundle;
import org.apache.activemq.artemis.journal.ActiveMQJournalLogger;
public abstract class AbstractSequentialFile implements SequentialFile {
private File file;
protected final File directory;
protected final SequentialFileFactory factory;
protected long fileSize = 0;
protected final AtomicLong position = new AtomicLong(0);
protected TimedBuffer timedBuffer;
/**
* Instead of having AIOSequentialFile implementing the Observer, I have done it on an inner class.
* This is the class returned to the factory when the file is being activated.
*/
protected final TimedBufferObserver timedBufferObserver = new LocalBufferObserver();
/**
* @param file
* @param directory
*/
public AbstractSequentialFile(final File directory,
final String file,
final SequentialFileFactory factory,
final Executor writerExecutor) {
super();
this.file = new File(directory, file);
this.directory = directory;
this.factory = factory;
}
// Public --------------------------------------------------------
@Override
public final boolean exists() {
return file.exists();
}
@Override
public final String getFileName() {
return file.getName();
}
@Override
public final void delete() throws IOException, InterruptedException, ActiveMQException {
if (isOpen()) {
close();
}
if (file.exists() && !file.delete()) {
ActiveMQJournalLogger.LOGGER.errorDeletingFile(this);
}
}
@Override
public void copyTo(SequentialFile newFileName) throws Exception {
try {
ActiveMQJournalLogger.LOGGER.debug("Copying " + this + " as " + newFileName);
if (!newFileName.isOpen()) {
newFileName.open();
}
if (!isOpen()) {
this.open();
}
ByteBuffer buffer = ByteBuffer.allocate(10 * 1024);
FileIOUtil.copyData(this, newFileName, buffer);
newFileName.close();
this.close();
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
}
/**
* @throws IOException only declare exception due to signature. Sub-class needs it.
*/
@Override
public void position(final long pos) throws IOException {
position.set(pos);
}
@Override
public long position() {
return position.get();
}
@Override
public final void renameTo(final String newFileName) throws IOException, InterruptedException, ActiveMQException {
try {
close();
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
File newFile = new File(directory + "/" + newFileName);
if (!file.equals(newFile)) {
if (!file.renameTo(newFile)) {
throw ActiveMQJournalBundle.BUNDLE.ioRenameFileError(file.getName(), newFileName);
}
file = newFile;
}
}
/**
* @throws IOException we declare throwing IOException because sub-classes need to do it
* @throws ActiveMQException
*/
@Override
public synchronized void close() throws IOException, InterruptedException, ActiveMQException {
}
@Override
public final boolean fits(final int size) {
if (timedBuffer == null) {
return position.get() + size <= fileSize;
} else {
return timedBuffer.checkSize(size);
}
}
@Override
public void setTimedBuffer(final TimedBuffer buffer) {
if (timedBuffer != null) {
timedBuffer.setObserver(null);
}
timedBuffer = buffer;
if (buffer != null) {
buffer.setObserver(timedBufferObserver);
}
}
@Override
public void write(final ActiveMQBuffer bytes, final boolean sync, final IOCallback callback) throws IOException {
if (timedBuffer != null) {
bytes.setIndex(0, bytes.capacity());
timedBuffer.addBytes(bytes, sync, callback);
} else {
final int readableBytes = bytes.readableBytes();
final ByteBuffer buffer = factory.newBuffer(readableBytes);
//factory::newBuffer doesn't necessary return a buffer with limit == readableBytes!!
buffer.limit(readableBytes);
bytes.getBytes(bytes.readerIndex(), buffer);
buffer.flip();
writeDirect(buffer, sync, callback);
}
}
@Override
public void write(final ActiveMQBuffer bytes,
final boolean sync) throws IOException, InterruptedException, ActiveMQException {
if (sync) {
SimpleWaitIOCallback completion = new SimpleWaitIOCallback();
write(bytes, true, completion);
completion.waitCompletion();
} else {
write(bytes, false, DummyCallback.getInstance());
}
}
@Override
public void write(final EncodingSupport bytes, final boolean sync, final IOCallback callback) {
if (timedBuffer != null) {
timedBuffer.addBytes(bytes, sync, callback);
} else {
final int encodedSize = bytes.getEncodeSize();
ByteBuffer buffer = factory.newBuffer(encodedSize);
ActiveMQBuffer outBuffer = ActiveMQBuffers.wrappedBuffer(buffer);
bytes.encode(outBuffer);
buffer.clear();
buffer.limit(encodedSize);
writeDirect(buffer, sync, callback);
}
}
@Override
public void write(final EncodingSupport bytes, final boolean sync) throws InterruptedException, ActiveMQException {
if (sync) {
SimpleWaitIOCallback completion = new SimpleWaitIOCallback();
write(bytes, true, completion);
completion.waitCompletion();
} else {
write(bytes, false, DummyCallback.getInstance());
}
}
protected File getFile() {
return file;
}
private static final class DelegateCallback implements IOCallback {
final List<IOCallback> delegates;
private DelegateCallback(final List<IOCallback> delegates) {
this.delegates = delegates;
}
@Override
public void done() {
final int size = delegates.size();
for (int i = 0; i < size; i++) {
try {
delegates.get(i).done();
} catch (Throwable e) {
ActiveMQJournalLogger.LOGGER.errorCompletingCallback(e);
}
}
}
@Override
public void onError(final int errorCode, final String errorMessage) {
final int size = delegates.size();
for (int i = 0; i < size; i++) {
try {
delegates.get(i).onError(errorCode, errorMessage);
} catch (Throwable e) {
ActiveMQJournalLogger.LOGGER.errorCallingErrorCallback(e);
}
}
}
}
protected ByteBuffer newBuffer(int size, int limit) {
size = factory.calculateBlockSize(size);
limit = factory.calculateBlockSize(limit);
ByteBuffer buffer = factory.newBuffer(size);
buffer.limit(limit);
return buffer;
}
protected class LocalBufferObserver implements TimedBufferObserver {
@Override
public void flushBuffer(final ByteBuffer buffer, final boolean requestedSync, final List<IOCallback> callbacks) {
buffer.flip();
if (buffer.limit() == 0) {
factory.releaseBuffer(buffer);
} else {
writeDirect(buffer, requestedSync, new DelegateCallback(callbacks));
}
}
@Override
public ByteBuffer newBuffer(final int size, final int limit) {
return AbstractSequentialFile.this.newBuffer(size, limit);
}
@Override
public int getRemainingBytes() {
if (fileSize - position.get() > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) (fileSize - position.get());
}
}
@Override
public String toString() {
return "TimedBufferObserver on file (" + getFile().getName() + ")";
}
}
@Override
public File getJavaFile() {
return getFile().getAbsoluteFile();
}
}