package org.rzo.netty.ahessian.session; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineCoverage; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.rzo.netty.ahessian.Constants; import org.rzo.netty.ahessian.stopable.StopablePipeline; /** * Handles sessions on the client side. * A typical setup for a protocol in a TCP/IP socket would be: * * <pre> * // client session filter is an attribute of the ChannelPipelineFactory Class * // it should not be created with each call to getPipeline() * // _mixinFactory is a ChannelPipelineFactory which returns MixinPipeline * _sessionFilter = new ClientSessionFilter(_mixinFactory); * * {@link ChannelPipeline} pipeline = ...; * * pipeline.addLast("sessionFilter", _sessionFilter); * </pre> */ @ChannelPipelineCoverage("all") public class ClientSessionFilter extends SimpleChannelUpstreamHandler { /** The current session. */ private Session _session = null; /** Indicates if we have received a session. */ private boolean _hasSession = false; /** String to read in the session id from the server */ private String _sessionId = ""; /** Factory for creating session objects. */ private SessionFactory _factory = new SessionFactory(); /** Connected events are intercepted and sent upstream once a session has been established. */ private ChannelStateEvent _connectedEvent; /** The factory for getting a MixinPipeline for a new session */ private ChannelPipelineFactory _mixinFactory; /** Assignment of session-id to pipelines created. //TODO destroy a pipeline if a session is timed out */ private static Map<String, MixinPipeline> _sessionPipelines = Collections.synchronizedMap(new HashMap<String, MixinPipeline>()); private List<Runnable> _sessionClosedListeners = Collections.synchronizedList(new ArrayList()); private List<Runnable> _sessionNewListeners = Collections.synchronizedList(new ArrayList()); /** * Instantiates a new client session filter. * * @param mixinFactory the mixin factory */ public ClientSessionFilter(ChannelPipelineFactory mixinFactory) { _mixinFactory = mixinFactory; } /* (non-Javadoc) * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#channelConnected(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelStateEvent) */ @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { // remeber the event. it will be sent upstream when session has been created _connectedEvent = e; String id = _session == null ? "?" : _session.getId(); // send the session id to client ChannelFuture future = Channels.future(ctx.getChannel()); Channels.write(ctx, future, ChannelBuffers.wrappedBuffer(id.getBytes())); } /* (non-Javadoc) * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#messageReceived(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent) */ public void messageReceived( ChannelHandlerContext ctx, MessageEvent e) throws Exception { // if session established forward all messages if (_hasSession) ctx.sendUpstream(e); else { ChannelBuffer b = (ChannelBuffer) e.getMessage(); _sessionId += b.toString("UTF-8"); checkSession(ctx); } } private void checkSession(ChannelHandlerContext ctx) { if (_sessionId.length() == _factory.getSessionIdLength()*2) { if (_session == null) newSession(ctx); else if (_session.getId().equals(_sessionId)) confirmSession(ctx); else changedSession(ctx); } } private void changedSession(ChannelHandlerContext ctx) { closeSession(_session); newSession(ctx); } private void closeSession(Session session) { for (Runnable listener : _sessionClosedListeners) { try { listener.run(); } catch (Throwable e) { Constants.ahessianLogger.warn("", e); } } ChannelPipeline p = _sessionPipelines.remove(session.getId()); if (p instanceof StopablePipeline) ((StopablePipeline)p).stop(); } private void confirmSession(ChannelHandlerContext ctx) { MixinPipeline pipeline = _sessionPipelines.get(_session.getId()); handleSession(ctx, pipeline); } private void newSession(ChannelHandlerContext ctx) { _session = _factory.createSession(_sessionId); MixinPipeline pipeline = null; try { pipeline = (MixinPipeline) _mixinFactory.getPipeline(); _sessionPipelines.put(_session.getId(), pipeline); } catch (Exception e) { Constants.ahessianLogger.warn("", e); } handleSession(ctx, pipeline); for (Runnable listener : _sessionNewListeners) { try { listener.run(); } catch (Throwable ex) { Constants.ahessianLogger.warn("", ex); } } } private void handleSession(ChannelHandlerContext ctx, MixinPipeline pipeline) { _hasSession = true; // now that we have a session extend the pipeline ChannelPipeline currentPipeline = ctx.getPipeline(); pipeline.mixin(currentPipeline); ctx.setAttachment(_session); ctx.sendUpstream(_connectedEvent); } @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) { _hasSession = false; _sessionId = ""; ctx.sendUpstream(e); } @Override public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { // } public void addSessionClosedListener(Runnable listener) { _sessionClosedListeners.add(listener); } public void removeSessionClosedListener(Runnable listener) { _sessionClosedListeners.remove(listener); } public void addSessionNewListener(Runnable listener) { _sessionNewListeners.add(listener); } }