1   package org.mortbay.cometd.session;
2   
3   import java.io.ByteArrayInputStream;
4   import java.util.Map;
5   
6   import javax.servlet.ServletContextAttributeEvent;
7   import javax.servlet.ServletContextAttributeListener;
8   import javax.servlet.http.HttpServletRequest;
9   
10  import org.mortbay.cometd.AbstractBayeux;
11  import org.mortbay.jetty.Request;
12  import org.mortbay.jetty.security.B64Code;
13  import org.mortbay.jetty.servlet.AbstractSessionManager;
14  import org.mortbay.jetty.servlet.HashSessionManager;
15  import org.mortbay.log.Log;
16  import org.mortbay.util.ByteArrayOutputStream2;
17  
18  import dojox.cometd.Bayeux;
19  import dojox.cometd.Client;
20  import dojox.cometd.Extension;
21  import dojox.cometd.Message;
22  
23  /* ------------------------------------------------------------ */
24  /**
25   * This is an experimental session manager that uses Bayeux to send
26   * replicated session data to the client, that can be made available if the client
27   * switches nodes in a cluster.
28   * 
29   * Care must be taken when handling requests that do not have sessions, so that
30   * new sessions are not created.  Session should be created/restored by the bayeux handshake.
31   * 
32   * The client side needs to add in the dojox.cometd.session extension.
33   * 
34   * @author gregw
35   *
36   */
37  public class BayeuxSessionManager extends HashSessionManager
38  {
39      public final static String BAYEUX_SESSION=Bayeux.SERVICE+"/ext/session";
40      AbstractBayeux _bayeux;
41      private String _secret="Really Private";
42  
43      /* ------------------------------------------------------------ */
44      protected void initialize(Bayeux bayeux)
45      {
46          Log.info("Bayeux Session Manager initialized for "+_context.getContextPath());
47          _bayeux=(AbstractBayeux)bayeux;
48          _bayeux.setRequestAvailable(true);
49          _bayeux.addExtension(new SessionExt());
50      }
51      
52      /* ------------------------------------------------------------ */
53      protected AbstractSessionManager.Session newSession(HttpServletRequest request)
54      {
55          return new BayeuxSession(request);
56      }
57  
58      /* ------------------------------------------------------------ */
59      protected AbstractSessionManager.Session newSession(long created, String clusterId)
60      {
61          return new BayeuxSession(created,clusterId);
62      }
63  
64      /* ------------------------------------------------------------ */
65      /* (non-Javadoc)
66       * @see org.mortbay.jetty.servlet.HashSessionManager#doStart()
67       */
68      public void doStart() throws Exception
69      {
70          super.doStart();
71          
72          _context.getContextHandler().addEventListener(new ServletContextAttributeListener(){
73  
74              public void attributeAdded(ServletContextAttributeEvent scab)
75              {
76                  if (scab.getName().equals(Bayeux.DOJOX_COMETD_BAYEUX))
77                  {
78                      Bayeux bayeux=(Bayeux)scab.getValue();
79                      initialize(bayeux);
80                  }
81              }
82  
83              public void attributeRemoved(ServletContextAttributeEvent scab)
84              {
85              }
86  
87              public void attributeReplaced(ServletContextAttributeEvent scab)
88              {
89              }
90          });
91          
92      }
93  
94      /* ------------------------------------------------------------ */
95      /* ------------------------------------------------------------ */
96      protected class SessionExt implements Extension
97      {
98  
99          /* -------------------------------------------------------- */
100         public Message rcv(Message message)
101         {
102             return message;
103         }
104 
105         /* -------------------------------------------------------- */
106         public Message rcvMeta(Message message)
107         {
108             if (message.getChannel().equals(Bayeux.META_HANDSHAKE))
109             {
110                 Map<String,?> ext = (Map<String,?>)message.get(Bayeux.EXT_FIELD);
111                 String session=ext==null?null:(String)ext.get("session");
112                 if (session!=null)
113                 {
114                     byte[] buf = B64Code.decode(session.toCharArray());
115                     // TODO decrypt
116                     ByteArrayInputStream bin=new ByteArrayInputStream(buf);
117                     try
118                     {
119                         Session s =restoreSession(bin);
120                         ((BayeuxSession)s).access(System.currentTimeMillis());
121                         ((Request)_bayeux.getCurrentRequest()).setSession(s);
122                         String cid=message.getClientId();
123                         Client client = _bayeux.getClient(cid);
124                         ((BayeuxSession)s).setClient(client);
125                         addSession(s);
126                         Log.info("Restored session "+s.getId()+" for bayeux client "+cid);
127                     }
128                     catch(Exception e)
129                     {
130                         Log.warn(e);
131                     }
132                 }
133             }
134             return message;
135         }
136 
137         /* -------------------------------------------------------- */
138         public Message send(Message message)
139         {
140             return message;
141         }
142 
143         /* -------------------------------------------------------- */
144         public Message sendMeta(Message message)
145         {
146             if (message.getChannel().equals(Bayeux.META_HANDSHAKE))
147             {
148                 String cid=message.getClientId();
149                 Client client = _bayeux.getClient(cid);
150                 ((BayeuxSession)_bayeux.getCurrentRequest().getSession(true)).setClient(client);
151             }
152             return message;
153         }
154         
155     }
156 
157     /* ------------------------------------------------------------ */
158     private Object encode(Object o, String valueSecret)
159     {
160         return null;
161     }
162 
163     /* ------------------------------------------------------------ */
164     private Object decode(String value, String valueSecret)
165     {
166         return null;
167     }
168     
169     /* ------------------------------------------------------------ */
170     /* ------------------------------------------------------------ */
171     protected class BayeuxSession extends HashSessionManager.Session
172     {
173         Client _client;
174         boolean _dirty;
175         
176         /* ------------------------------------------------------------ */
177         protected BayeuxSession(HttpServletRequest request)
178         {
179             super(request);
180         }
181 
182         /* ------------------------------------------------------------ */
183         public BayeuxSession(long created, String clusterId)
184         {
185             super(created,clusterId);
186             _dirty=true;
187         }
188         
189         /* ------------------------------------------------------------ */
190         public void setClient(Client client)
191         {
192             _client=client;
193         }
194 
195 
196         /* ------------------------------------------------------------ */
197         /* (non-Javadoc)
198          */
199         protected void access(long time)
200         {
201             super.access(time);
202         }
203 
204         /* ------------------------------------------------------------ */
205         /* (non-Javadoc)
206          * @see org.mortbay.jetty.servlet.HashSessionManager.Session#invalidate()
207          */
208         public void invalidate() throws IllegalStateException
209         {
210             super.invalidate();
211             _dirty=true;
212         }
213 
214         /* ------------------------------------------------------------ */
215         /* (non-Javadoc)
216          * @see org.mortbay.jetty.servlet.AbstractSessionManager.Session#removeAttribute(java.lang.String)
217          */
218         public synchronized void removeAttribute(String name)
219         {
220             super.removeAttribute(name);
221             _dirty=true;
222         }
223 
224         /* ------------------------------------------------------------ */
225         /* (non-Javadoc)
226          * @see org.mortbay.jetty.servlet.AbstractSessionManager.Session#setAttribute(java.lang.String, java.lang.Object)
227          */
228         public synchronized void setAttribute(String name, Object value)
229         {
230             super.setAttribute(name,value);
231             _dirty=true;
232         }
233 
234         /* ------------------------------------------------------------ */
235         /* (non-Javadoc)
236          * @see org.mortbay.jetty.servlet.AbstractSessionManager.Session#complete()
237          */
238         protected void complete()
239         {
240             super.complete();
241             
242             if (_dirty && _client!=null)
243             {
244                 _dirty=false;
245                 Message message=_bayeux.newMessage();
246                 
247                 ByteArrayOutputStream2 bout = new ByteArrayOutputStream2();
248                 try
249                 {
250                     save(bout);
251                     
252                     // TODO real encryption....  
253                     byte[] buf = bout.toByteArray();
254                     String encoded=new String(B64Code.encode(buf));
255                     message.put(Bayeux.DATA_FIELD,encoded);
256                 }
257                 catch(Exception e)
258                 {
259                     Log.warn(e);
260                 }
261                 
262                 _bayeux.deliver(_client,_client,BAYEUX_SESSION,message);
263             }
264         }
265     }
266 }