View Javadoc

1   package org.mortbay.terracotta.servlet;
2   
3   import java.security.NoSuchAlgorithmException;
4   import java.security.SecureRandom;
5   import java.util.Collections;
6   import java.util.HashSet;
7   import java.util.Random;
8   import java.util.Set;
9   
10  import javax.servlet.http.HttpServletRequest;
11  import javax.servlet.http.HttpSession;
12  
13  import org.mortbay.component.AbstractLifeCycle;
14  import org.mortbay.jetty.Handler;
15  import org.mortbay.jetty.Server;
16  import org.mortbay.jetty.SessionIdManager;
17  import org.mortbay.jetty.SessionManager;
18  import org.mortbay.jetty.servlet.AbstractSessionManager;
19  import org.mortbay.jetty.servlet.AbstractSessionManager.Session;
20  import org.mortbay.jetty.webapp.WebAppContext;
21  import org.mortbay.log.Log;
22  
23  /**
24   * A specialized SessionIdManager to be used with <a href="http://www.terracotta.org">Terracotta</a>.
25   *
26   * @see TerracottaSessionManager
27   */
28  public class TerracottaSessionIdManager extends AbstractLifeCycle implements SessionIdManager
29  {
30      private final static String __NEW_SESSION_ID = "org.mortbay.jetty.newSessionId";
31      private final static String SESSION_ID_RANDOM_ALGORITHM = "SHA1PRNG";
32      private final static String SESSION_ID_RANDOM_ALGORITHM_ALT = "IBMSecureRandom";
33  
34      private final Server _server;
35      private String _workerName;
36      private Random _random;
37      private boolean _weakRandom;
38      private Set<String> _sessionIds;
39  
40      public TerracottaSessionIdManager(Server server)
41      {
42          _server = server;
43      }
44  
45      public void doStart()
46      {
47          if (_random == null)
48          {
49              try
50              {
51                  _random = SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM);
52              }
53              catch (NoSuchAlgorithmException e)
54              {
55                  try
56                  {
57                      _random = SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM_ALT);
58                      _weakRandom = false;
59                  }
60                  catch (NoSuchAlgorithmException e_alt)
61                  {
62                      Log.warn("Could not generate SecureRandom for session-id randomness", e);
63                      _random = new Random();
64                      _weakRandom = true;
65                  }
66              }
67          }
68          _random.setSeed(_random.nextLong() ^ System.currentTimeMillis() ^ hashCode() ^ Runtime.getRuntime().freeMemory());
69          _sessionIds = newSessionIdsSet();
70      }
71  
72      private Set<String> newSessionIdsSet()
73      {
74          // We need a synchronized data structure to have node-local synchronization.
75          // Terracotta handles collections classes transparently, so concurrent adds
76          // from different nodes are safe with respect to locking.
77          return Collections.synchronizedSet(new HashSet<String>());
78      }
79  
80      public void doStop()
81      {
82      }
83  
84      public void addSession(HttpSession session)
85      {
86          String clusterId = ((TerracottaSessionManager.Session)session).getClusterId();
87          _sessionIds.add(clusterId);
88      }
89  
90      public String getWorkerName()
91      {
92          return _workerName;
93      }
94  
95      public void setWorkerName(String workerName)
96      {
97          _workerName = workerName;
98      }
99  
100     public boolean idInUse(String clusterId)
101     {
102         return _sessionIds.contains(clusterId);
103     }
104 
105     /**
106      * When told to invalidate all session instances that share the same id, we must
107      * tell all contexts on the server for which it is defined to delete any session
108      * object they might have matching the id.
109      */
110     public void invalidateAll(String clusterId)
111     {
112         Handler[] contexts = _server.getChildHandlersByClass(WebAppContext.class);
113         for (int i = 0; contexts != null && i < contexts.length; i++)
114         {
115             WebAppContext webAppContext = (WebAppContext)contexts[i];
116             SessionManager sessionManager = webAppContext.getSessionHandler().getSessionManager();
117             if (sessionManager instanceof AbstractSessionManager)
118             {
119                 Session session = ((AbstractSessionManager)sessionManager).getSession(clusterId);
120                 if (session != null) session.invalidate();
121             }
122         }
123     }
124 
125     public String newSessionId(HttpServletRequest request, long created)
126     {
127         // Generate a unique cluster id. This id must be unique across all nodes in the cluster,
128         // since it is stored in the distributed shared session ids set.
129 
130         // A requested session ID can only be used if it is in use already.
131         String requested_id = request.getRequestedSessionId();
132         if (requested_id != null && idInUse(requested_id))
133             return requested_id;
134 
135         // Else reuse any new session ID already defined for this request.
136         String new_id = (String)request.getAttribute(__NEW_SESSION_ID);
137         if (new_id != null && idInUse(new_id))
138             return new_id;
139 
140         // pick a new unique ID!
141         String id = null;
142         while (id == null || id.length() == 0 || idInUse(id))
143         {
144             long r = _weakRandom
145                     ? (hashCode() ^ Runtime.getRuntime().freeMemory() ^ _random.nextInt() ^ (((long)request.hashCode()) << 32))
146                     : _random.nextLong();
147             r ^= created;
148             if (request.getRemoteAddr() != null) r ^= request.getRemoteAddr().hashCode();
149             if (r < 0) r = -r;
150             id = Long.toString(r, 36);
151         }
152 
153         request.setAttribute(__NEW_SESSION_ID, id);
154         return id;
155     }
156 
157     public void removeSession(HttpSession session)
158     {
159         String clusterId = ((TerracottaSessionManager.Session)session).getClusterId();
160         _sessionIds.remove(clusterId);
161     }
162 
163     public String getClusterId(String nodeId)
164     {
165         int dot = nodeId.lastIndexOf('.');
166         return (dot > 0) ? nodeId.substring(0, dot) : nodeId;
167     }
168 
169     public String getNodeId(String clusterId, HttpServletRequest request)
170     {
171         if (_workerName != null) return clusterId + '.' + _workerName;
172         return clusterId;
173     }
174 }