/*
* 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.FilenameFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.netty.util.internal.PlatformDependent;
import org.apache.activemq.artemis.core.io.IOCriticalErrorListener;
import org.apache.activemq.artemis.core.io.SequentialFile;
import org.apache.activemq.artemis.core.io.SequentialFileFactory;
import org.apache.activemq.artemis.core.io.buffer.TimedBuffer;
public final class MappedSequentialFileFactory implements SequentialFileFactory {
private static long DEFAULT_BLOCK_SIZE = 64L << 20;
private final File directory;
private final IOCriticalErrorListener criticalErrorListener;
private final TimedBuffer timedBuffer;
private long chunkBytes;
private long overlapBytes;
private boolean useDataSync;
private boolean supportCallbacks;
protected volatile int alignment = -1;
public MappedSequentialFileFactory(File directory,
IOCriticalErrorListener criticalErrorListener,
boolean supportCallbacks) {
this.directory = directory;
this.criticalErrorListener = criticalErrorListener;
this.chunkBytes = DEFAULT_BLOCK_SIZE;
this.overlapBytes = DEFAULT_BLOCK_SIZE / 4;
this.useDataSync = true;
this.timedBuffer = null;
this.supportCallbacks = supportCallbacks;
}
public MappedSequentialFileFactory(File directory, IOCriticalErrorListener criticalErrorListener) {
this(directory, criticalErrorListener, false);
}
public MappedSequentialFileFactory(File directory) {
this(directory, null);
}
public long chunkBytes() {
return chunkBytes;
}
public MappedSequentialFileFactory chunkBytes(long chunkBytes) {
this.chunkBytes = chunkBytes;
return this;
}
public long overlapBytes() {
return overlapBytes;
}
public MappedSequentialFileFactory overlapBytes(long overlapBytes) {
this.overlapBytes = overlapBytes;
return this;
}
@Override
public SequentialFile createSequentialFile(String fileName) {
final MappedSequentialFile mappedSequentialFile = new MappedSequentialFile(this, directory, new File(directory, fileName), chunkBytes, overlapBytes, criticalErrorListener);
if (this.timedBuffer == null) {
return mappedSequentialFile;
} else {
return new TimedSequentialFile(this, mappedSequentialFile);
}
}
@Override
public SequentialFileFactory setDatasync(boolean enabled) {
this.useDataSync = enabled;
return this;
}
@Override
public boolean isDatasync() {
return useDataSync;
}
@Override
public int getMaxIO() {
return 1;
}
@Override
public List<String> listFiles(final String extension) throws Exception {
final FilenameFilter extensionFilter = (file, name) -> name.endsWith("." + extension);
final String[] fileNames = directory.list(extensionFilter);
if (fileNames == null) {
return Collections.EMPTY_LIST;
}
return Arrays.asList(fileNames);
}
@Override
public boolean isSupportsCallbacks() {
return this.supportCallbacks;
}
@Override
public void onIOError(Exception exception, String message, SequentialFile file) {
if (criticalErrorListener != null) {
criticalErrorListener.onIOException(exception, message, file);
}
}
@Override
public ByteBuffer allocateDirectBuffer(final int size) {
return ByteBuffer.allocateDirect(size);
}
@Override
public void releaseDirectBuffer(final ByteBuffer buffer) {
PlatformDependent.freeDirectBuffer(buffer);
}
@Override
public ByteBuffer newBuffer(final int size) {
return ByteBuffer.allocate(size);
}
@Override
public void releaseBuffer(ByteBuffer buffer) {
if (buffer.isDirect()) {
PlatformDependent.freeDirectBuffer(buffer);
}
}
@Override
public void activateBuffer(SequentialFile file) {
if (timedBuffer != null) {
file.setTimedBuffer(timedBuffer);
}
}
@Override
public void deactivateBuffer() {
if (timedBuffer != null) {
// When moving to a new file, we need to make sure any pending buffer will be transferred to the buffer
timedBuffer.flush();
timedBuffer.setObserver(null);
}
}
@Override
public ByteBuffer wrapBuffer(final byte[] bytes) {
return ByteBuffer.wrap(bytes);
}
@Override
public int getAlignment() {
return 1;
}
@Override
public MappedSequentialFileFactory setAlignment(int alignment) {
this.alignment = alignment;
return this;
}
@Override
public int calculateBlockSize(int bytes) {
return bytes;
}
@Override
public File getDirectory() {
return this.directory;
}
@Override
public void clearBuffer(final ByteBuffer buffer) {
if (buffer.isDirect()) {
BytesUtils.zerosDirect(buffer);
} else if (buffer.hasArray()) {
final byte[] array = buffer.array();
//SIMD OPTIMIZATION
Arrays.fill(array, (byte) 0);
} else {
//TODO VERIFY IF IT COULD HAPPENS
final int capacity = buffer.capacity();
for (int i = 0; i < capacity; i++) {
buffer.put(i, (byte) 0);
}
}
buffer.rewind();
}
@Override
public void start() {
if (timedBuffer != null) {
timedBuffer.start();
}
}
@Override
public void stop() {
if (timedBuffer != null) {
timedBuffer.stop();
}
}
@Override
public void createDirs() throws Exception {
boolean ok = directory.mkdirs();
if (!ok) {
throw new IOException("Failed to create directory " + directory);
}
}
@Override
public void flush() {
if (timedBuffer != null) {
timedBuffer.flush();
}
}
}