/** * Copyright 2014 Ricardo Padilha * * Licensed 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 net.dsys.snio.impl.channel; import java.io.IOException; import java.net.SocketAddress; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.concurrent.Callable; import javax.annotation.Nonnull; import net.dsys.commons.api.exception.Bug; import net.dsys.commons.api.future.CallbackFuture; import net.dsys.commons.impl.future.MergingCallbackFuture; import net.dsys.commons.impl.future.SettableCallbackFuture; import net.dsys.snio.api.buffer.MessageBufferConsumer; import net.dsys.snio.api.buffer.MessageBufferProducer; import net.dsys.snio.api.buffer.MessageBufferProvider; import net.dsys.snio.api.pool.KeyProcessor; import net.dsys.snio.api.pool.SelectionType; import net.dsys.snio.api.pool.SelectorExecutor; import net.dsys.snio.api.pool.SelectorThread; /** * @author Ricardo Padilha */ abstract class AbstractProcessor<T> implements KeyProcessor<T> { private final SettableCallbackFuture<Void> connectReadFuture; private final SettableCallbackFuture<Void> connectWriteFuture; private final MergingCallbackFuture<Void> connectFuture; private final SettableCallbackFuture<Void> closeReadFuture; private final SettableCallbackFuture<Void> closeWriteFuture; private final SettableCallbackFuture<Void> shutdownFuture; private final MergingCallbackFuture<Void> closeFuture; private final MessageBufferProvider<T> provider; private final MessageBufferProducer<T> appOut; private final MessageBufferConsumer<T> chnIn; private final MessageBufferProducer<T> chnOut; private final MessageBufferConsumer<T> appIn; private SelectorThread thread; private SelectionKey readKey; private SelectionKey writeKey; protected AbstractProcessor(@Nonnull final MessageBufferProvider<T> provider) { if (provider == null) { throw new NullPointerException("provider == null"); } this.connectReadFuture = new SettableCallbackFuture<>(); this.connectWriteFuture = new SettableCallbackFuture<>(); this.connectFuture = MergingCallbackFuture.<Void>builder() .add(connectReadFuture).add(connectWriteFuture).build(); this.closeReadFuture = new SettableCallbackFuture<>(); this.closeWriteFuture = new SettableCallbackFuture<>(); this.shutdownFuture = new SettableCallbackFuture<>(); this.closeFuture = MergingCallbackFuture.<Void>builder() .add(shutdownFuture).add(closeReadFuture).add(closeWriteFuture).build(); this.provider = provider; this.appOut = provider.getAppOutput(this); this.chnIn = provider.getChannelInput(); this.chnOut = provider.getChannelOutput(); this.appIn = provider.getAppInput(); } /** * {@inheritDoc} */ @Override public final CallbackFuture<Void> getConnectionFuture() { return connectFuture; } @Nonnull protected final SettableCallbackFuture<Void> getConnectReadFuture() { return connectReadFuture; } /** * {@inheritDoc} */ @Override public final void registered(final SelectorThread thread, final SelectionKey key, final SelectionType type) { switch (type) { case OP_READ: { this.readKey = key; readRegistered(key); if (connectReadFuture.isDone()) { throw new Bug("connectFuture.isDone() while register"); } connectReadFuture.success(null); break; } case OP_WRITE: { this.thread = thread; this.writeKey = key; writeRegistered(key); if (connectWriteFuture.isDone()) { throw new Bug("connectFuture.isDone() while register"); } connectWriteFuture.success(null); break; } case OP_CONNECT: { this.readKey = key; break; } default: { throw new Bug("Unsupported SelectionType registered: " + String.valueOf(type)); } } } protected abstract void readRegistered(@Nonnull SelectionKey key); protected abstract void writeRegistered(@Nonnull SelectionKey key); /** * {@inheritDoc} */ @Override public final void wakeupWriter() { if (writeKey != null && writeKey.isValid()) { thread.enableKey(writeKey); } } /** * Only called from within the selector thread. */ protected final void disableWriter() { writeKey.interestOps(writeKey.interestOps() & ~SelectionKey.OP_WRITE); } /** * {@inheritDoc} */ @Override public final MessageBufferConsumer<T> getInputBuffer() { return appIn; } /** * {@inheritDoc} */ @Override public final MessageBufferProducer<T> getOutputBuffer() { return appOut; } @Nonnull protected final MessageBufferConsumer<T> getChannelInput() { return chnIn; } @Nonnull protected final MessageBufferProducer<T> getChannelOutput() { return chnOut; } /** * {@inheritDoc} */ @Override public final void close(final SelectorExecutor executor, final Callable<Void> closeTask) { final Callable<Void> task = new Callable<Void>() { @Override public Void call() throws Exception { shutdown(executor); closeTask.call(); return null; } }; // tell subclasses to shutdown shutdown(shutdownFuture, task); } final void shutdown(@Nonnull final SelectorExecutor executor) { provider.close(); executor.cancelConnect(readKey, closeReadFuture, writeKey, closeWriteFuture); } /** * Subclasses need to start shutdown upon call, then once done run the task, * and set the output of the future. */ protected abstract void shutdown(@Nonnull SettableCallbackFuture<Void> future, @Nonnull Callable<Void> task); /** * {@inheritDoc} */ @Override public final CallbackFuture<Void> getCloseFuture() { return closeFuture; } /** * {@inheritDoc} */ @Override public final String toString() { return String.format("%s{read=%s, write=%s, channel=%s, local=%s, remote=%s}", this.getClass().getSimpleName(), Boolean.valueOf(readKey != null), Boolean.valueOf(writeKey != null), getChannel(), getLocal(), getRemote()); } private SelectableChannel getChannel() { if (readKey != null) { return readKey.channel(); } if (writeKey != null) { return writeKey.channel(); } return null; } private SocketAddress getLocal() { final SelectableChannel channel = getChannel(); if (channel == null) { return null; } try { return ((SocketChannel) channel).getLocalAddress(); } catch (final ClassCastException | IOException e) { return null; } } private Object getRemote() { final SelectableChannel channel = getChannel(); if (channel == null) { return null; } try { return ((SocketChannel) channel).getRemoteAddress(); } catch (final ClassCastException | IOException e) { return null; } } }