package org.rzo.netty.ahessian.session; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; 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.jboss.netty.util.Timeout; import org.jboss.netty.util.Timer; import org.jboss.netty.util.TimerTask; import org.rzo.netty.ahessian.Constants; /** * Handles sessions on the server side. A typical setup for a * protocol in a TCP/IP socket would be: * * <pre> * // _mixinFactory is a ChannelPipelineFactory which returns MixinPipeline * {@link ChannelPipeline} pipeline = ...; * * pipeline.addLast("sessionFilter", new ServerSessionFilter(_mixinFactory)); * </pre> */ public class ServerSessionFilter extends SimpleChannelUpstreamHandler { /** Indicates if session has been assigned to the current channel */ private boolean _hasSession = false; /** String for reading in a session id */ private String _sessionId = ""; /** Factory for creating new session objects */ private SessionFactory _factory = new SessionFactory(); /** Connected event is intercepted. It is sent upstream only after a session has been established*/ private ChannelStateEvent _connectedEvent; /** A pipeline factory which returns a MixinPipeline */ private ChannelPipelineFactory _mixinFactory; /** Assignment of session-id to the associated MixinPipeline */ private static Map<String, MixinPipeline> _sessionPipelines = Collections.synchronizedMap(new HashMap<String, MixinPipeline>()); private long _sessionTimeout = -1; private Timer _timer = null; private volatile Channel _channel = null; private volatile boolean _valid = true; /** * Instantiates a new server session filter. * * @param mixinFactory a pipeline factory which returns MixinPipeline */ public ServerSessionFilter(ChannelPipelineFactory mixinFactory, Timer timer, long sessionTimeout) { _mixinFactory = mixinFactory; _timer = timer; _sessionTimeout = sessionTimeout; } public ServerSessionFilter(ChannelPipelineFactory mixinFactory) { this(mixinFactory, null, -1); } /* (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"); if (_sessionId.equals("?")) newSession(ctx); else checkSession(ctx); } } private void checkSession(ChannelHandlerContext ctx) { if (_sessionId.length() == _factory.getSessionIdLength() * 2) { Session session = _factory.getSession(_sessionId); if (session == null) newSession(ctx); else confirmSession(ctx); } } private void newSession(ChannelHandlerContext ctx) { Session session = _factory.createSession(null); Constants.ahessianLogger.info(ctx.getChannel()+" new session #"+session.getId()); MixinPipeline pipeline = null; try { pipeline = (MixinPipeline) _mixinFactory.getPipeline(); _sessionPipelines.put(session.getId(), pipeline); } catch (Exception e) { Constants.ahessianLogger.warn("", e); } handleSession(ctx, session, pipeline); } private void confirmSession(ChannelHandlerContext ctx) { Session session = _factory.getSession(_sessionId); Constants.ahessianLogger.info(ctx.getChannel()+" reuse session #"+session.getId()); MixinPipeline pipeline = _sessionPipelines.get(_sessionId); handleSession(ctx, session, pipeline); } private void handleSession(ChannelHandlerContext ctx, Session session, MixinPipeline pipeline) { _hasSession = true; // if we have a session timeout set, cancel it. Timeout timeOut = session.removeTimeout(); if (timeOut != null) timeOut.cancel(); // check if the session is already connected to a channel Channel c = pipeline.getChannel(); if (c != null && c.isOpen()) { Constants.ahessianLogger.warn(ctx.getChannel()+" session already attached -> close connection"); c.close(); } // now that we have a session extend the pipeline ChannelPipeline currentPipeline = ctx.getPipeline(); pipeline.mixin(currentPipeline); ctx.setAttachment(session); _channel = ctx.getChannel(); // first send session and wait until it has been transmitted ChannelFuture future = Channels.future(ctx.getChannel()); Channels.write(ctx, future, ChannelBuffers.wrappedBuffer(session.getId().getBytes())); try { future.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // only then inform the mixin pipeline ctx.sendUpstream(_connectedEvent); } /** * Helper Method: returns the session of associated with the pipeline of a given context * * @param ctx the context * * @return the session */ public static Session getSession(ChannelHandlerContext ctx) { ChannelHandlerContext handler = ctx.getPipeline().getContext(ServerSessionFilter.class); if (handler == null) return null; return (Session) handler.getAttachment(); } /* (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; } /* (non-Javadoc) * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#channelDisconnected(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelStateEvent) */ @Override public void channelDisconnected(final ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { _hasSession = false; final String sessionId = ((Session)ctx.getAttachment()).getId(); Constants.ahessianLogger.info("Session disconnected: "+ sessionId); _sessionId = ""; _connectedEvent = null; _channel = null; if (_sessionTimeout > 0) { Timeout timeOut = _timer.newTimeout(new TimerTask() { public void run(Timeout arg0) throws Exception { _factory.removeSession(sessionId); _sessionPipelines.remove(sessionId); _valid = false; Constants.ahessianLogger.warn(ctx.getChannel()+" session timed out: "+sessionId); } }, _sessionTimeout, TimeUnit.MILLISECONDS); ((Session)ctx.getAttachment()).setTimeOut(timeOut); } ctx.sendUpstream(e); } public long getSessionTimeout() { return _sessionTimeout; } public void setSessionTimeout(long sessionTimeout) { _sessionTimeout = sessionTimeout; } public boolean isValid() { return _valid; } public Channel getChannel() { return _channel; } public static ServerSessionFilter getServerSessionFilter(ChannelHandlerContext ctx) { return (ServerSessionFilter) ctx.getPipeline().getContext(ServerSessionFilter.class).getHandler(); } }