View Javadoc

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