package rocks.inspectit.shared.cs.storage.nio;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Resource;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Abstract class for all channel managers.
*
* @author Ivan Senic
*
*/
public abstract class AbstractChannelManager {
/**
* All channels.
*/
private Map<Path, CustomAsyncChannel> writingChannelsMap = new ConcurrentHashMap<>(64, 0.75f, 1);
/**
* Opened channels queue.
*/
private ConcurrentLinkedQueue<CustomAsyncChannel> openedChannelsQueue = new ConcurrentLinkedQueue<>();
/**
* Executor service for IO operations.
*/
@Autowired
@Resource(name = "IOExecutorService")
private ExecutorService executorService;
/**
* Count of opened channels.
* <p>
* Note that the count does not have to be in the constant equality to the
* {@link #openedChannelsQueue} size. It's not critical to have the ACID relation here, because
* this count is just for us to know when do we need to close one channel.
*/
private AtomicInteger openedChannelsCount = new AtomicInteger(0);
/**
* Subclasses should provide the max number of opened channels.
*
* @return Returns the number of maximum opened channels.
*/
protected abstract int getMaxOpenedChannels();
/**
* Returns the channel. If necessary channel will be open.
*
* @param channelPath
* Path for the channel.
* @return {@link CustomAsyncChannel} that is already open.
* @throws IOException
* If exception occurs during channel creation.
*/
protected CustomAsyncChannel getChannel(Path channelPath) throws IOException {
CustomAsyncChannel channel = writingChannelsMap.get(channelPath);
if (channel == null) {
channel = createNewChannel(channelPath);
}
return channel;
}
/**
* Creates a new channel. We need to do this in synchronized method since the channel also has
* to be opened, thus we can not use putIfAbsent.
*
* @param channelPath
* Path.
* @return Created channel.
* @throws IOException
* If exception occurs during channel creation.
*/
private synchronized CustomAsyncChannel createNewChannel(Path channelPath) throws IOException {
CustomAsyncChannel channel = writingChannelsMap.get(channelPath);
if (channel == null) {
channel = new CustomAsyncChannel(channelPath);
this.openAsyncChannel(channel);
writingChannelsMap.put(channelPath, channel);
}
return channel;
}
/**
* Opens the {@link CustomAsyncChannel} and adds it to the beginning of the
* {@link #openedChannelsQueue}. If limit for opened channels is reached, one channel will be
* closed.
*
* @param customAsyncChannel
* Channel to open.
* @throws IOException
* If {@link IOException} occurs during opening.
*/
protected void openAsyncChannel(CustomAsyncChannel customAsyncChannel) throws IOException {
boolean channelOpened = customAsyncChannel.openChannel(executorService);
if (channelOpened) {
openedChannelsQueue.add(customAsyncChannel);
int channelsOpened = openedChannelsCount.incrementAndGet();
// don't excess the max number of allowed channels
while (channelsOpened > getMaxOpenedChannels()) {
closeOldestAsyncChannel();
channelsOpened = openedChannelsCount.get();
}
}
}
/**
* Closes the last opened channel in the queue.
*
* @throws IOException
* If {@link IOException} happens.
*/
protected void closeOldestAsyncChannel() throws IOException {
CustomAsyncChannel channelToClose = openedChannelsQueue.poll();
if (null != channelToClose) {
if (channelToClose.closeChannel()) {
openedChannelsCount.decrementAndGet();
}
}
}
/**
* Closes the {@link CustomAsyncChannel} and removes it from the {@link #openedChannelsQueue}.
*
* @param channelToClose
* Channel to close.
* @throws IOException
* If {@link IOException} occurs during closing.
*/
protected void closeAsyncChannel(CustomAsyncChannel channelToClose) throws IOException {
openedChannelsQueue.remove(channelToClose);
if (channelToClose.closeChannel()) {
openedChannelsCount.decrementAndGet();
}
}
/**
* Finalize all open channels. Before closing the channels, force will be performed, so that all
* pending writing on the file channel are performed.
*
* @throws IOException
* When {@link IOException} occurs.
*/
public void finalizeAllChannels() throws IOException {
for (CustomAsyncChannel channel : writingChannelsMap.values()) {
if (channel.isOpened()) {
closeAsyncChannel(channel);
}
writingChannelsMap.values().remove(channel);
}
}
/**
* Finalize the file channel with specified channel's file path. Before closing the channel,
* force will be performed, so that all pending writing on the file channel are performed.
*
* @param channelPath
* Path to the channel's file.
* @throws IOException
* When {@link IOException} occurs.
*/
public void finalizeChannel(Path channelPath) throws IOException {
CustomAsyncChannel channel = writingChannelsMap.remove(channelPath);
if ((channel != null) && channel.isOpened()) {
closeAsyncChannel(channel);
}
}
/**
* Returns executor service status. This methods just returns the result of
* {@link #executorService#toString()} method.
*
* @return Returns executor service status. This methods just returns the result of
* {@link #executorService#toString()} method.
*/
public String getExecutorServiceStatus() {
return executorService.toString();
}
/**
* Sets {@link #executorService}.
*
* @param executorService
* New value for {@link #executorService}
*/
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
ToStringBuilder toStringBuilder = new ToStringBuilder(this);
toStringBuilder.append("currentOpenedChannels", openedChannelsCount.get());
toStringBuilder.append("executorService", executorService);
toStringBuilder.append("writingChannelsMap", writingChannelsMap);
return toStringBuilder.toString();
}
}