KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > cluster > session > JvmRouteBinderValve


1 /*
2  * Copyright 1999,2004 The Apache Software Foundation.
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  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.catalina.cluster.session;
17
18 import java.io.IOException JavaDoc;
19
20 import javax.servlet.ServletException JavaDoc;
21 import javax.servlet.http.Cookie JavaDoc;
22 import javax.servlet.http.HttpSession JavaDoc;
23
24 import org.apache.catalina.Container;
25 import org.apache.catalina.Context;
26 import org.apache.catalina.Globals;
27 import org.apache.catalina.Host;
28 import org.apache.catalina.Lifecycle;
29 import org.apache.catalina.LifecycleException;
30 import org.apache.catalina.LifecycleListener;
31 import org.apache.catalina.Manager;
32 import org.apache.catalina.Session;
33 import org.apache.catalina.cluster.CatalinaCluster;
34 import org.apache.catalina.cluster.ClusterMessage;
35 import org.apache.catalina.connector.Request;
36 import org.apache.catalina.connector.Response;
37 import org.apache.catalina.session.ManagerBase;
38 import org.apache.catalina.util.LifecycleSupport;
39 import org.apache.catalina.util.StringManager;
40 import org.apache.catalina.valves.ValveBase;
41
42 /**
43  * Valve to handle Tomcat jvmRoute takeover using mod_jk module after node
44  * failure. After a node crashed the next request going to other cluster node.
45  * Now the answering from apache is slower ( make some error handshaking. Very
46  * bad with apache at my windows.). We rewrite now the cookie jsessionid
47  * information to the backup cluster node. After the next response all client
48  * request goes direct to the backup node. The change sessionid send also to all
49  * other cluster nodes. Well, now the session stickyness work directly to the
50  * backup node and traffic don't go back too restarted cluster nodes!
51  *
52  * At all cluster node you must configure the
53  * {@link org.apache.catalina.cluster.session.JvmRouteSessionIDBinderListener JvmRouteSessionIDBinderListener}
54  * with
55  * {@link org.apache.catalina.cluster.session.JvmRouteSessionIDBinderListenerLifecycle JvmRouteSessionIDBinderListenerLifecycle}
56  *
57  *
58  * Add this Valve to your clustered application or setup it to context default
59  * conf/enginename/hostname/context.xml.default for all host application
60  *
61  * <pre>
62  * &lt;Context&gt;
63  * &lt;Valve className=&quot;org.apache.catalina.cluster.session.JvmRouteBinderValve&quot; /&gt;
64  * &lt;/Context&gt;
65  * </pre>
66  *
67  *
68  * @author Peter Rossbach
69  * @version 1.1
70  */

71 public class JvmRouteBinderValve extends ValveBase implements Lifecycle {
72
73     /*--Static Variables----------------------------------------*/
74     public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
75             .getLog(JvmRouteBinderValve.class);
76
77     /**
78      * The descriptive information about this implementation.
79      */

80     protected static final String JavaDoc info = "org.apache.catalina.cluster.session.JvmRouteBinderValve/1.1";
81
82     /*--Instance Variables--------------------------------------*/
83
84     /**
85      * the cluster
86      */

87     protected CatalinaCluster cluster;
88
89     /**
90      * Session Manager for and app <code>manager</code>
91      */

92     protected Manager manager;
93
94     /**
95      * is manager a distrbuted
96      */

97     protected boolean managerCheck = false;
98
99     /**
100      * The string manager for this package.
101      */

102     protected StringManager sm = StringManager.getManager(Constants.Package);
103
104     /**
105      * Has this component been started yet?
106      */

107     protected boolean started = false;
108
109     /**
110      * enabled this component
111      */

112     protected boolean enabled = true;
113
114     /**
115      * number of session that no at this tomcat instanz hosted
116      */

117     protected long numberOfSessions = 0;
118
119     protected String JavaDoc sessionIdAttribute = "org.apache.catalina.cluster.session.JvmRouteOrignalSessionID";
120
121     /**
122      * The lifecycle event support for this component.
123      */

124     protected LifecycleSupport lifecycle = new LifecycleSupport(this);
125
126     /*--Logic---------------------------------------------------*/
127
128     /**
129      * Return descriptive information about this implementation.
130      */

131     public String JavaDoc getInfo() {
132
133         return (info);
134
135     }
136
137     /**
138      * set session id attribute to failed node for request.
139      *
140      * @return Returns the sessionIdAttribute.
141      */

142     public String JavaDoc getSessionIdAttribute() {
143         return sessionIdAttribute;
144     }
145
146     /**
147      * get name of failed reqeust session attribute
148      *
149      * @param sessionIdAttribute
150      * The sessionIdAttribute to set.
151      */

152     public void setSessionIdAttribute(String JavaDoc sessionIdAttribute) {
153         this.sessionIdAttribute = sessionIdAttribute;
154     }
155
156     /**
157      * @return Returns the numberOfSessions.
158      */

159     public long getNumberOfSessions() {
160         return numberOfSessions;
161     }
162
163     /**
164      * @return Returns the enabled.
165      */

166     public boolean getEnabled() {
167         return enabled;
168     }
169
170     /**
171      * @param enabled
172      * The enabled to set.
173      */

174     public void setEnabled(boolean enabled) {
175         this.enabled = enabled;
176     }
177
178     /**
179      * Detect possible the JVMRoute change at cluster backup node..
180      *
181      * @param request
182      * tomcat request being processed
183      * @param response
184      * tomcat response being processed
185      * @exception IOException
186      * if an input/output error has occurred
187      * @exception ServletException
188      * if a servlet error has occurred
189      */

190     public void invoke(Request request, Response response) throws IOException JavaDoc,
191             ServletException JavaDoc {
192
193         // FIXME manager starts after valve!
194
getManager();
195         if (getEnabled() && manager != null) {
196             handlePossibleTurnover(request, response);
197         }
198         // Pass this request on to the next valve in our pipeline
199
getNext().invoke(request, response);
200     }
201
202     /**
203      * handle possible session turn over.
204      *
205      * @see JvmRouteBinderValve#handleJvmRoute(String, String, Request,
206      * Response)
207      * @param request
208      * @param response
209      */

210     protected void handlePossibleTurnover(Request request, Response response) {
211         HttpSession JavaDoc session = request.getSession(false);
212         if (session != null) {
213             long t1 = System.currentTimeMillis();
214             String JavaDoc jvmRoute = getLocalJvmRoute();
215             if (jvmRoute == null) {
216                 if (log.isWarnEnabled())
217                     log.warn(sm.getString("jvmRoute.missingJvmRouteAttribute"));
218                 return;
219             }
220             if (request.isRequestedSessionIdFromURL()) {
221                 if (log.isDebugEnabled())
222                     log.debug(sm.getString("jvmRoute.skipURLSessionIDs"));
223             } else {
224                 handleJvmRoute(session.getId(), jvmRoute, request, response);
225             }
226             if (log.isInfoEnabled()) {
227                 long t2 = System.currentTimeMillis();
228                 long time = t2 - t1;
229                 log.info(sm.getString("jvmRoute.turnoverInfo", new Long JavaDoc(time)));
230             }
231         }
232     }
233
234     /**
235      * get jvmroute from engine
236      *
237      * @return
238      */

239     protected String JavaDoc getLocalJvmRoute() {
240         return ((ManagerBase) manager).getJvmRoute();
241     }
242
243     /**
244      * get Cluster DeltaManager
245      *
246      * @return
247      */

248     protected Manager getManager() {
249         if (!managerCheck) {
250             managerCheck = true;
251             if (container.getManager() instanceof DeltaManager) {
252                 manager = container.getManager();
253                 if (log.isDebugEnabled())
254                     log.debug(sm.getString("jvmRoute.foundManager", container
255                             .getManager(), getContainer().getName()));
256             } else if (log.isDebugEnabled())
257                 log.debug(sm.getString("jvmRoute.notFoundManager", container
258                         .getManager(), getContainer().getName()));
259         }
260         return manager;
261     }
262
263     /**
264      * Handle jvmRoute stickyness after tomcat instance failed. After this
265      * correction a new Cookie send to client with new jvmRoute and the
266      * SessionID change propage to the other cluster nodes.
267      *
268      * @param sessionId
269      * request SessionID from Cookie
270      * @param localJvmRoute
271      * local jvmRoute
272      * @param response
273      * Tomcat Response
274      */

275     protected void handleJvmRoute(String JavaDoc sessionId, String JavaDoc localJvmRoute,
276             Request request, Response response) {
277         // get requested jvmRoute.
278
String JavaDoc requestJvmRoute = null;
279         int index = sessionId.indexOf(".");
280         if (index > 0) {
281             requestJvmRoute = sessionId
282                     .substring(index + 1, sessionId.length());
283         }
284         if (requestJvmRoute != null && !requestJvmRoute.equals(localJvmRoute)) {
285             if (log.isDebugEnabled()) {
286                 log.debug(sm.getString("jvmRoute.failover", requestJvmRoute,
287                         localJvmRoute, sessionId));
288             }
289             // OK - turnover the session ?
290
String JavaDoc newSessionID = sessionId.substring(0, index) + "."
291                     + localJvmRoute;
292             Session JavaDoc catalinaSession = null;
293             try {
294                 catalinaSession = manager.findSession(sessionId);
295             } catch (IOException JavaDoc e) {
296                 // Hups!
297
}
298             if (catalinaSession != null) {
299                 changeSessionID(sessionId, request, response, newSessionID,
300                         catalinaSession);
301                 numberOfSessions++;
302             } else {
303                 if (log.isDebugEnabled()) {
304                     log.debug(sm.getString("jvmRoute.cannotFindSession",
305                             sessionId));
306                 }
307             }
308         }
309     }
310
311     /**
312      * change session id and send to all cluster nodes
313      *
314      * @param sessionId
315      * original session id
316      * @param request
317      * @param response
318      * @param newSessionID
319      * new session id for node migration
320      * @param catalinaSession
321      * current session with original session id
322      */

323     protected void changeSessionID(String JavaDoc sessionId, Request request,
324             Response response, String JavaDoc newSessionID, Session JavaDoc catalinaSession) {
325         lifecycle.fireLifecycleEvent("Before session migration",
326                 catalinaSession);
327         request.setRequestedSessionId(newSessionID);
328         catalinaSession.setId(newSessionID);
329         if (catalinaSession instanceof DeltaSession)
330             ((DeltaSession) catalinaSession).resetDeltaRequest();
331         setNewSessionCookie(newSessionID, request, response);
332         // set orginal sessionid at request, to allow application detect the
333
// change
334
if (sessionIdAttribute != null && !"".equals(sessionIdAttribute)) {
335             if (log.isDebugEnabled()) {
336                 log.debug("Set Orginal Session id at request attriute " + sessionIdAttribute+ " value: " + sessionId);
337             }
338             request.setAttribute(sessionIdAttribute, sessionId);
339         }
340         // now sending the change to all other clusternode!
341
sendSessionIDClusterBackup(sessionId, newSessionID);
342         lifecycle
343                 .fireLifecycleEvent("After session migration", catalinaSession);
344         if (log.isDebugEnabled()) {
345             log.debug(sm.getString("jvmRoute.changeSession", sessionId,
346                     newSessionID));
347         }
348     }
349
350     /**
351      * Send the changed Sessionid to all clusternodes.
352      *
353      * @see JvmRouteSessionIDBinderListener#messageReceived(ClusterMessage)
354      * @param sessionId
355      * current failed sessionid
356      * @param newSessionID
357      * new session id, bind to the new cluster node
358      */

359     protected void sendSessionIDClusterBackup(String JavaDoc sessionId,
360             String JavaDoc newSessionID) {
361         SessionIDMessage msg = new SessionIDMessage();
362         msg.setOrignalSessionID(sessionId);
363         msg.setBackupSessionID(newSessionID);
364         Context context = (Context) getContainer();
365         msg.setContextPath(context.getPath());
366         cluster.send(msg);
367     }
368
369     /**
370      * Sets a new cookie for the given session id and response and see
371      * {@link org.apache.catalina.connector.Request#configureSessionCookie(javax.servlet.http.Cookie)
372      *
373      * @param sessionId
374      * The session id
375      * @param response
376      * Tomcat Response
377      */

378
379     protected void setNewSessionCookie(String JavaDoc sessionId, Request request,
380             Response response) {
381         if (response != null) {
382             Context context = (Context) getContainer();
383             if (context.getCookies()) {
384                 // set a new session cookie
385
Cookie JavaDoc newCookie = new Cookie JavaDoc(Globals.SESSION_COOKIE_NAME,
386                         sessionId);
387                 newCookie.setMaxAge(-1);
388                 String JavaDoc contextPath = null;
389                 if (!response.getConnector().getEmptySessionPath()
390                         && (context != null)) {
391                     contextPath = context.getEncodedPath();
392                 }
393                 if ((contextPath != null) && (contextPath.length() > 0)) {
394                     newCookie.setPath(contextPath);
395                 } else {
396                     newCookie.setPath("/");
397                 }
398                 if (request.isSecure()) {
399                     newCookie.setSecure(true);
400                 }
401                 if (log.isDebugEnabled()) {
402                     log.debug(sm.getString("jvmRoute.newSessionCookie",
403                             sessionId, Globals.SESSION_COOKIE_NAME, newCookie
404                                     .getPath(), new Boolean JavaDoc(newCookie
405                                     .getSecure())));
406                 }
407                 response.addCookie(newCookie);
408             }
409         }
410     }
411
412     // ------------------------------------------------------ Lifecycle Methods
413

414     /**
415      * Add a lifecycle event listener to this component.
416      *
417      * @param listener
418      * The listener to add
419      */

420     public void addLifecycleListener(LifecycleListener listener) {
421
422         lifecycle.addLifecycleListener(listener);
423
424     }
425
426     /**
427      * Get the lifecycle listeners associated with this lifecycle. If this
428      * Lifecycle has no listeners registered, a zero-length array is returned.
429      */

430     public LifecycleListener[] findLifecycleListeners() {
431
432         return lifecycle.findLifecycleListeners();
433
434     }
435
436     /**
437      * Remove a lifecycle event listener from this component.
438      *
439      * @param listener
440      * The listener to add
441      */

442     public void removeLifecycleListener(LifecycleListener listener) {
443
444         lifecycle.removeLifecycleListener(listener);
445
446     }
447
448     /**
449      * Prepare for the beginning of active use of the public methods of this
450      * component. This method should be called after <code>configure()</code>,
451      * and before any of the public methods of the component are utilized.
452      *
453      * @exception LifecycleException
454      * if this component detects a fatal error that prevents this
455      * component from being used
456      */

457     public void start() throws LifecycleException {
458
459         // Validate and update our current component state
460
if (started)
461             throw new LifecycleException(sm
462                     .getString("jvmRoute.valve.alreadyStarted"));
463         lifecycle.fireLifecycleEvent(START_EVENT, null);
464         started = true;
465         Container container = getContainer().getParent();
466         if (container instanceof Host
467                 && ((Host) container).getCluster() != null)
468             cluster = (CatalinaCluster) ((Host) container).getCluster();
469         if (cluster == null) {
470             throw new RuntimeException JavaDoc("No clustering support at container "
471                     + container.getName());
472         }
473
474         if (log.isInfoEnabled())
475             log.info(sm.getString("jvmRoute.valve.started"));
476
477     }
478
479     /**
480      * Gracefully terminate the active use of the public methods of this
481      * component. This method should be the last one called on a given instance
482      * of this component.
483      *
484      * @exception LifecycleException
485      * if this component detects a fatal error that needs to be
486      * reported
487      */

488     public void stop() throws LifecycleException {
489
490         // Validate and update our current component state
491
if (!started)
492             throw new LifecycleException(sm
493                     .getString("jvmRoute.valve.notStarted"));
494         lifecycle.fireLifecycleEvent(STOP_EVENT, null);
495         started = false;
496
497         cluster = null;
498         manager = null;
499         managerCheck = false;
500
501         numberOfSessions = 0;
502         if (log.isInfoEnabled())
503             log.info(sm.getString("jvmRoute.valve.stopped"));
504
505     }
506
507 }
Popular Tags