/* * Copyright 2009-2010 Brian S O'Neill * * 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 org.cojen.dirmi.io; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.InputStream; import java.io.IOException; import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.cojen.util.ThrowUnchecked; import org.cojen.dirmi.ClosedException; import org.cojen.dirmi.RejectedException; import org.cojen.dirmi.RemoteTimeoutException; import org.cojen.dirmi.util.Timer; /** * Paired with {@link BasicChannelBrokerConnector} to adapt a ChannelAcceptor * into a ChannelBrokerAcceptor. * * @author Brian S O'Neill */ public class BasicChannelBrokerAcceptor implements ChannelBrokerAcceptor { static final byte OPEN_REQUEST = 1, ACCEPT_REQUEST = 3, // deprecated in favor of ACCEPT_CONFIRM_REQUEST CONNECT_REQUEST = 5, CONNECT_RESPONSE = 6, PING_REQUEST = 7, PING_RESPONSE = 8, ACCEPT_CONFIRM_REQUEST = 9, ACCEPT_SUCCESS_RESPONSE = 10, ACCEPT_FAILED_RESPONSE = 11; private final IOExecutor mExecutor; private final ChannelAcceptor mAcceptor; private final SecureRandom mRandom; private final ChannelAcceptor.Listener mBrokerListener; // Access these fields while synchronized on mAcceptedBrokers. private final Map<Long, Broker> mAcceptedBrokers; private boolean mClosed; private final ListenerQueue<ChannelBrokerAcceptor.Listener> mAcceptListenerQueue; private boolean mNotListening; public BasicChannelBrokerAcceptor(IOExecutor executor, ChannelAcceptor acceptor) { mExecutor = executor; mAcceptor = acceptor; mRandom = new SecureRandom(); mAcceptedBrokers = new HashMap<Long, Broker>(); mAcceptListenerQueue = new ListenerQueue<ChannelBrokerAcceptor.Listener> (mExecutor, ChannelBrokerAcceptor.Listener.class); mBrokerListener = new ChannelAcceptor.Listener() { public void accepted(Channel channel) { mAcceptor.accept(this); final ChannelBroker broker; try { broker = BasicChannelBrokerAcceptor.this.accepted(channel); } catch (IOException e) { channel.disconnect(); mAcceptListenerQueue.dequeue().failed(e); return; } if (broker != null) { mAcceptListenerQueue.dequeue().accepted(broker); } } public void rejected(RejectedException e) { notListening(); mAcceptListenerQueue.dequeue().rejected(e); } public void failed(IOException e) { notListening(); mAcceptListenerQueue.dequeue().failed(e); } public void closed(IOException e) { notListening(); mAcceptListenerQueue.dequeueForClose().closed(e); } }; mAcceptor.accept(mBrokerListener); } @Override public Object getLocalAddress() { return mAcceptor.getLocalAddress(); } @Override public ChannelBroker accept() throws IOException { AcceptListener listener = new AcceptListener(); accept(listener); return listener.waitForBroker(); } @Override public ChannelBroker accept(long timeout, TimeUnit unit) throws IOException { AcceptListener listener = new AcceptListener(); accept(listener); return listener.waitForBroker(timeout, unit); } @Override public ChannelBroker accept(Timer timer) throws IOException { return accept(RemoteTimeoutException.checkRemaining(timer), timer.unit()); } @Override public void accept(Listener listener) { synchronized (this) { if (mNotListening) { mNotListening = false; try { mAcceptor.accept(mBrokerListener); } catch (Throwable e) { mNotListening = true; ThrowUnchecked.fire(e); } } } try { mAcceptListenerQueue.enqueue(listener); } catch (RejectedException e) { mAcceptListenerQueue.dequeue().rejected(e); } } synchronized void notListening() { mNotListening = true; } @Override public void close() { mAcceptor.close(); { Map<Long, Broker> copy; synchronized (mAcceptedBrokers) { mClosed = true; copy = new HashMap<Long, Broker>(mAcceptedBrokers); mAcceptedBrokers.clear(); } for (Broker broker : copy.values()) { broker.close(); } } } ChannelBroker accepted(Channel channel) throws IOException { ChannelTimeout timeout = new ChannelTimeout(mExecutor, channel, 15, TimeUnit.SECONDS); int op; long id; try { InputStream in = channel.getInputStream(); op = in.read(); if (op == OPEN_REQUEST) { Broker broker; synchronized (mAcceptedBrokers) { if (mClosed) { throw new ClosedException("ChannelBrokerAcceptor is closed"); } do { id = mRandom.nextLong(); } while (mAcceptedBrokers.containsKey(id)); broker = new Broker(id, channel); mAcceptedBrokers.put(id, broker); } try { DataOutputStream dout = new DataOutputStream(channel.getOutputStream()); dout.writeLong(id); dout.flush(); return broker; } catch (IOException e) { broker.close(); throw e; } } if (op != ACCEPT_REQUEST && op != CONNECT_RESPONSE && op != ACCEPT_CONFIRM_REQUEST) { if (op < 0) { throw new ClosedException("Accepted channel is closed"); } else { throw new IOException("Invalid operation from accepted channel: " + op); } } id = new DataInputStream(in).readLong(); } finally { timeout.cancel(); } Broker broker; synchronized (mAcceptedBrokers) { broker = mAcceptedBrokers.get(id); if (broker == null && mClosed) { throw new ClosedException("ChannelBrokerAcceptor is closed"); } } if (broker == null) { if (op == CONNECT_RESPONSE) { throw new IOException("Reverse connect refers to an unknown session: " + id); } if (op == ACCEPT_CONFIRM_REQUEST) { channel.getOutputStream().write(ACCEPT_FAILED_RESPONSE); channel.getOutputStream().flush(); } throw new IOException("Accepted connection refers to an unknown session: " + id); } if (op == CONNECT_RESPONSE) { broker.connected(channel); } else if (op == ACCEPT_CONFIRM_REQUEST) { channel.getOutputStream().write(ACCEPT_SUCCESS_RESPONSE); channel.getOutputStream().flush(); broker.accepted(channel); } else { broker.accepted(channel); } return null; } void removeBroker(final long id, final Broker broker, boolean immediate) { synchronized (mAcceptedBrokers) { if (mAcceptedBrokers.get(id) == broker) { if (!immediate) { // Keep it around for a bit, to avoid "No broker found" // exceptions resulting from race conditions. try { mExecutor.schedule(new Runnable() { public void run() { removeBroker(id, broker, true /* immediate*/); } }, 10, TimeUnit.SECONDS); return; } catch (RejectedException e) { // Fall through and remove now. } } mAcceptedBrokers.remove(id); } } } private class Broker extends BasicChannelBroker { private final ListenerQueue<ChannelConnector.Listener> mListenerQueue; Broker(long id, Channel control) throws RejectedException { super(mExecutor, id, control); mListenerQueue = new ListenerQueue<ChannelConnector.Listener> (mExecutor, ChannelConnector.Listener.class); mControl.inputNotify(new Channel.Listener() { public void ready() { Channel control = mControl; int op; try { op = control.getInputStream().read(); if (cPingLogger != null) { logPingMessage("Ping response from " + control + ": " + op); } if (op == PING_RESPONSE) { pinged(); } else if (op < 0) { throw new ClosedException("Control channel is closed"); } else { throw new IOException ("Invalid operation from control channel: " + op); } control.inputNotify(this); } catch (IOException e) { closed(e); } } public void rejected(RejectedException e) { // Safer to close if no threads. closed(e); } public void closed(IOException e) { Broker.this.close(e); if (cPingLogger != null) { logPingMessage("Ping check stopping for " + mControl + ": " + e); } } }); } @Override public Channel connect() throws IOException { return connect(15, TimeUnit.SECONDS); } @Override public Channel connect(long timeout, TimeUnit unit) throws IOException { mAllChannels.checkClosed(); ChannelConnectWaiter listener = new ChannelConnectWaiter(); connect(listener); return listener.waitForChannel(timeout, unit); } @Override public void connect(final ChannelConnector.Listener listener) { if (mAllChannels.isClosed()) { listener.closed(new ClosedException()); return; } try { mListenerQueue.enqueue(listener); } catch (RejectedException e) { dequeueConnectListener().rejected(e); return; } mControl.outputNotify(new Channel.Listener() { public void ready() { try { mControl.getOutputStream().write(CONNECT_REQUEST); mControl.flush(); } catch (IOException e) { dequeueConnectListener().failed(e); return; } } public void rejected(RejectedException e) { dequeueConnectListener().rejected(e); } public void closed(IOException e) { dequeueConnectListenerForClose().closed(e); } }); } @Override protected void close(IOException cause) { removeBroker(mId, this, false); if (!mAllChannels.isClosed()) { dequeueConnectListenerForClose().failed( cause != null ? cause : new ClosedException()); super.close(cause); } } @Override protected boolean requirePingTask() { return true; } @Override protected void doPing() throws IOException { if (cPingLogger != null) { logPingMessage("Ping request to " + mControl); } try { mControl.getOutputStream().write(PING_REQUEST); mControl.flush(); } catch (IOException e) { if (cPingLogger != null) { logPingMessage("Ping response failure from " + mControl + ": " + e); } throw e; } } ChannelConnector.Listener dequeueConnectListener() { return mListenerQueue.dequeue(); } ChannelConnector.Listener dequeueConnectListenerForClose() { return mListenerQueue.dequeueForClose(); } void connected(Channel channel) { channel.register(mAllChannels); dequeueConnectListener().connected(channel); } } private static class AcceptListener implements Listener { private final Waiter<ChannelBroker> mWaiter = Waiter.create(); public void accepted(ChannelBroker broker) { mWaiter.available(broker); } public void rejected(RejectedException e) { mWaiter.rejected(e); } public void failed(IOException e) { mWaiter.failed(e); } public void closed(IOException e) { mWaiter.closed(e); } ChannelBroker waitForBroker() throws IOException { return mWaiter.waitFor(); } ChannelBroker waitForBroker(long timeout, TimeUnit unit) throws IOException { return mWaiter.waitFor(timeout, unit); } } }