/**
*
* Copyright 2004 Hiram Chirino
*
* 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.activeio.oneport;
import java.io.IOException;
import java.net.URI;
import java.util.Iterator;
import org.activeio.AcceptListener;
import org.activeio.AsynchChannel;
import org.activeio.AsynchChannelListener;
import org.activeio.AsynchChannelServer;
import org.activeio.Channel;
import org.activeio.FilterAsynchChannel;
import org.activeio.FilterAsynchChannelServer;
import org.activeio.Packet;
import org.activeio.SynchChannel;
import org.activeio.adapter.AsynchToSynchChannelAdapter;
import org.activeio.adapter.SynchToAsynchChannelAdapter;
import org.activeio.filter.PushbackSynchChannel;
import org.activeio.packet.AppendedPacket;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
/**
* Allows multiple protocols share a single ChannelServer. All protocols sharing the server
* must have a distinct magic number at the beging of the client's request.
*
* TODO: handle the case where a client opens a connection but sends no data down the stream. We need
* to timeout that client.
*
* @version $Revision$
*/
final public class OnePortAsynchChannelServer extends FilterAsynchChannelServer {
/**
* The OnePortAsynchChannelServer listens for incoming connection
* from a normal AsynchChannelServer. This s the listner used
* to receive the accepted channels.
*/
final private class OnePortAcceptListener implements AcceptListener {
public void onAccept(Channel channel) {
try {
AsynchChannel asynchChannel = SynchToAsynchChannelAdapter.adapt(channel);
ProtocolInspectingAsynchChannel inspector = new ProtocolInspectingAsynchChannel(asynchChannel);
inspector.start();
} catch (IOException e) {
onAcceptError(e);
}
}
public void onAcceptError(IOException error) {
dispose();
}
}
/**
* This channel filter sniffs the first few bytes of the byte stream
* to see if a ProtocolRecognizer recognizes the protocol. If it does not
* it just closes the channel, otherwise the associated SubPortAsynchChannelServer
* is notified that it accepted a channel.
*
*/
final private class ProtocolInspectingAsynchChannel extends FilterAsynchChannel {
private Packet buffer;
public ProtocolInspectingAsynchChannel(AsynchChannel next) throws IOException {
super(next);
setAsynchChannelListener(new AsynchChannelListener() {
public void onPacket(Packet packet) {
if (buffer == null) {
buffer = packet;
} else {
buffer = AppendedPacket.join(buffer, packet);
}
findMagicNumber();
}
public void onPacketError(IOException error) {
dispose();
}
});
}
private void findMagicNumber() {
for (Iterator iter = recognizerMap.keySet().iterator(); iter.hasNext();) {
ProtocolRecognizer recognizer = (ProtocolRecognizer) iter.next();
if (recognizer.recognizes(buffer.duplicate())) {
if( UnknownRecognizer.UNKNOWN_RECOGNIZER == recognizer ) {
// Dispose the channel.. don't know what to do with it.
dispose();
}
SubPortAsynchChannelServer onePort = (SubPortAsynchChannelServer) recognizerMap.get(recognizer);
if( onePort == null ) {
// Dispose the channel.. don't know what to do with it.
dispose();
}
// Once the magic number is found:
// Stop the channel so that a decision can be taken on what to
// do with the
// channel. When the channel is restarted, the buffered up
// packets wiil get
// delivered.
try {
stop(NO_WAIT_TIMEOUT);
setAsynchChannelListener(null);
} catch (IOException e) {
getAsynchChannelListener().onPacketError(e);
}
Channel channel = getNext();
channel = AsynchToSynchChannelAdapter.adapt(channel);
channel = new PushbackSynchChannel((SynchChannel) channel, buffer);
channel = SynchToAsynchChannelAdapter.adapt(channel);
onePort.onAccept(channel);
break;
}
}
}
}
/**
* Clients bind against the OnePortAsynchChannelServer and get
* SubPortAsynchChannelServer which can be used to accept connections.
*/
final private class SubPortAsynchChannelServer implements AsynchChannelServer {
private final ProtocolRecognizer recognizer;
private AcceptListener acceptListener;
private boolean started;
/**
* @param recognizer
*/
public SubPortAsynchChannelServer(ProtocolRecognizer recognizer) {
this.recognizer = recognizer;
}
public void setAcceptListener(AcceptListener acceptListener) {
this.acceptListener = acceptListener;
}
public URI getBindURI() {
return next.getBindURI();
}
public URI getConnectURI() {
return next.getConnectURI();
}
public void dispose() {
started = false;
recognizerMap.remove(recognizer);
}
public void start() throws IOException {
started = true;
}
public void stop(long timeout) throws IOException {
started = false;
}
void onAccept(Channel channel) {
if( started && acceptListener!=null ) {
acceptListener.onAccept(channel);
} else {
// Dispose the channel.. don't know what to do with it.
channel.dispose();
}
}
public Object narrow(Class target) {
if( target.isAssignableFrom(getClass()) ) {
return this;
}
return OnePortAsynchChannelServer.this.narrow(target);
}
}
private final ConcurrentHashMap recognizerMap = new ConcurrentHashMap();
public OnePortAsynchChannelServer(AsynchChannelServer server) throws IOException {
super(server);
super.setAcceptListener(new OnePortAcceptListener());
}
public void setAcceptListener(AcceptListener acceptListener) {
throw new IllegalAccessError("Not supported");
}
public AsynchChannelServer bindAsynchChannel(ProtocolRecognizer recognizer) throws IOException {
if( recognizerMap.contains(recognizer) )
throw new IOException("That recognizer is allredy bound.");
SubPortAsynchChannelServer server = new SubPortAsynchChannelServer(recognizer);
Object old = recognizerMap.put(recognizer, server);
return server;
}
}