package rocks.inspectit.shared.cs.storage.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.CompletionHandler; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileAttribute; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang.builder.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class that defines special need operation with {@link AsynchronousFileChannel}. * * @author Ivan Senic * */ public class CustomAsyncChannel { /** * The log of this class. Can not be assigned via spring because this is not a component. */ private Logger log = LoggerFactory.getLogger(CustomAsyncChannel.class);; /** * Path where the channel's file is. */ private Path path; /** * {@link AsynchronousFileChannel}. */ private AsynchronousFileChannel fileChannel; /** * Next writing position. */ private AtomicLong nextWritingPosition = new AtomicLong(); /** * Read write lock. */ private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * Lock for opening and closing. Has to be the write lock, because the state of channel is * changing. */ private Lock openCloseLock = readWriteLock.writeLock(); /** * Lock for writing. This lock only has to be closed, when the {@link #openCloseLock} is locked. * Thus, we can use read lock. */ private Lock writeReadChannelLock = readWriteLock.readLock(); /** * Default constructor. * * @param path * Path to the channel's file. */ public CustomAsyncChannel(Path path) { this.path = path; } /** * @return the path */ public Path getPath() { return path; } /** * @return the fileChannel */ public AsynchronousFileChannel getFileChannel() { return fileChannel; } /** * Opens the channel creating the file in the given path to the directory. The * {@link AsynchronousFileChannel} will work with default {@link ExecutorService}. * * @throws IOException * When {@link IOException} occurs during opening. */ public void openChannel() throws IOException { this.openChannel(null); } /** * Opens the channel creating the file in the given path to the directory. The * {@link AsynchronousFileChannel} will work with provided {@link ExecutorService}. * * @param executorService * Executor service that has threads that will work on * {@link AsynchronousFileChannel}. * @return True if the channel is opened, false if the channel is already opened and thus * operation is skipped. * @throws IOException * When {@link IOException} occurs during opening. */ public boolean openChannel(ExecutorService executorService) throws IOException { openCloseLock.lock(); try { if (!this.isOpened()) { Set<OpenOption> optionsSet = new HashSet<>(); optionsSet.add(StandardOpenOption.CREATE); optionsSet.add(StandardOpenOption.WRITE); optionsSet.add(StandardOpenOption.READ); fileChannel = AsynchronousFileChannel.open(path, optionsSet, executorService, new FileAttribute<?>[0]); if (log.isDebugEnabled()) { log.info("Channel opened for path " + path + ". Next write position is " + nextWritingPosition.get() + "."); } return true; } else { if (log.isDebugEnabled()) { log.info("Tried to open already opened channel for path " + path + "."); } return false; } } finally { openCloseLock.unlock(); } } /** * Closes the channel. Note that no write will be possible after calling this method. * * @return True if channel was closed, false if the channel was already closed. * * @throws IOException * If {@link IOException} happens during closing. */ public boolean closeChannel() throws IOException { openCloseLock.lock(); try { if (this.isOpened()) { fileChannel.force(true); fileChannel.close(); if (log.isDebugEnabled()) { log.info("Channel closed for path " + path + ". Next write position is " + nextWritingPosition.get() + "."); } return true; } else { if (log.isDebugEnabled()) { log.info("Tried to close already closed channel for path " + path + "."); } return false; } } finally { openCloseLock.unlock(); } } /** * Returns if channel is open. * * @return Returns if channel is open. */ public boolean isOpened() { return (fileChannel != null) && fileChannel.isOpen(); } /** * Writes to the file channel if the channel is open. If the channel is closed, the write will * not be done, and false will be returned. * * @param <A> * Type of attachment. * @param src * Buffer. * @param position * Position. * @param attachment * Attachment. * @param handler * Completion handler. * @return True if write succeeds. Fails if the channel is closed. */ public <A> boolean write(ByteBuffer src, long position, A attachment, CompletionHandler<Integer, ? super A> handler) { writeReadChannelLock.lock(); try { if (this.isOpened()) { fileChannel.write(src, position, attachment, handler); return true; } else { return false; } } finally { writeReadChannelLock.unlock(); } } /** * Reads to the file channel if the channel is open. If the channel is closed, the read will not * be done, and false will be returned. * * @param <A> * Type of attachment. * @param dst * Destination buffer. Buffer. * @param position * Position. * @param attachment * Attachment. * @param handler * Completion handler. * @return True if read succeeds. Fails if the channel is closed. */ public <A> boolean read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer, ? super A> handler) { writeReadChannelLock.lock(); try { if (this.isOpened()) { fileChannel.read(dst, position, attachment, handler); return true; } else { return false; } } finally { writeReadChannelLock.unlock(); } } /** * Reserves the writing position in this channel with the given size. This method is thread * safe. * * @param writeSize * Size of writing that has to be done. * @return Returns the position where file should be written. */ public long reserveWritingPosition(long writeSize) { while (true) { long writingPosition = nextWritingPosition.get(); if (nextWritingPosition.compareAndSet(writingPosition, writingPosition + writeSize)) { return writingPosition; } } } /** * {@inheritDoc} */ @Override public String toString() { ToStringBuilder toStringBuilder = new ToStringBuilder(this); toStringBuilder.append("path", path); toStringBuilder.append("opened", isOpened()); toStringBuilder.append("nextWritingPosition", nextWritingPosition.get()); return toStringBuilder.toString(); } }