/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.web.tomcat.service.session; import java.io.IOException; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import org.apache.catalina.*; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.util.LifecycleSupport; import org.apache.catalina.valves.ValveBase; import org.jboss.logging.Logger; import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingDistributableSessionData; /** * Web request valve to specifically handle Tomcat jvmRoute using mod_jk(2) * module. We assume that the session id has a format of id.jvmRoute where * jvmRoute is used by JK module to determine sticky session during * load balancing. Checks for failover by matching session and request jvmRoute * to the session manager's, updating the session and session cookie if a * failover is detected. * * This valve is inserted in the pipeline only when mod_jk is configured. * * @author Ben Wang * @author Brian Stansberry * * @version $Revision: 87304 $ */ public class JvmRouteValve extends ValveBase implements Lifecycle { // The info string for this Valve private static final String info = "JvmRouteValve/1.0"; protected static Logger log_ = Logger.getLogger(JvmRouteValve.class); // Valve-lifecycle_ helper object protected LifecycleSupport support = new LifecycleSupport(this); protected AbstractJBossManager manager_; /** * Create a new Valve. * */ public JvmRouteValve(AbstractJBossManager manager) { super(); manager_ = manager; } /** * Get information about this Valve. */ public String getInfo() { return info; } public void invoke(Request request, Response response) throws IOException, ServletException { // Need to check it before let request through. checkJvmRoute(request, response); // let the servlet invocation go through getNext().invoke(request, response); } public void checkJvmRoute(Request req, Response res) throws IOException, ServletException { String requestedId = req.getRequestedSessionId(); HttpSession session = req.getSession(false); if (session != null) { String sessionId = session.getId(); // Obtain JvmRoute // FIXME do this once in constructor String jvmRoute = manager_.getJvmRoute(); if (log_.isTraceEnabled()) { log_.trace("checkJvmRoute(): check if need to re-route based on JvmRoute. Session id: " + sessionId + " jvmRoute: " + jvmRoute); } // FIXME do this once in the session manager's start() if (jvmRoute == null) { throw new RuntimeException("JvmRouteValve.checkJvmRoute(): Tomcat JvmRoute is null. " + "Need to assign a value in Tomcat server.xml for load balancing."); } // Check if incoming session id has JvmRoute appended. If not, append it. boolean setCookie = !req.isRequestedSessionIdFromURL(); handleJvmRoute(requestedId, sessionId, jvmRoute, res, setCookie); } } protected void handleJvmRoute(String requestedId, String sessionId, String jvmRoute, HttpServletResponse response, boolean setCookie) throws IOException { // The new id we'll give the session if we detect a failover String newId = null; // First, check if the session object's jvmRoute matches ours // TODO. The current format is assumed to be id.jvmRoute. Can be generalized later. String sessionJvmRoute = null; int index = sessionId.indexOf('.', 0); if (index > -1 && index < sessionId.length() -1) { sessionJvmRoute = sessionId.substring(index + 1, sessionId.length()); } if (!jvmRoute.equals(sessionJvmRoute)) { if (index < 0) { // If this valve is turned on, we assume we have an appendix of jvmRoute. // So this request is new. newId = new StringBuilder(sessionId).append('.').append(jvmRoute).toString(); } else { // We just had a failover since jvmRoute does not match. // We will replace the old one with the new one. if (log_.isTraceEnabled()) { log_.trace("handleJvmRoute(): We have detected a failover with different jvmRoute." + " old one: " + sessionJvmRoute + " new one: " + jvmRoute + ". Will reset the session id."); } String base = sessionId.substring(0, index); newId = base + "." + jvmRoute; } // Fix the session's id resetSessionId(sessionId, newId); } // Now we know the session object has a correct id // Also need to ensure any session cookie is correct if (setCookie) { // Check if the jvmRoute of the requested session id matches ours. // Only bother if we haven't already spotted a mismatch above if (newId == null) { String requestedJvmRoute = null; if (requestedId != null) { int reqIndex = requestedId.indexOf('.', 0); if (reqIndex > -1 && reqIndex < requestedId.length() - 1) { requestedJvmRoute = requestedId.substring(reqIndex + 1, requestedId.length()); } } if (!jvmRoute.equals(requestedJvmRoute)) { if (log_.isTraceEnabled()) { log_.trace("handleJvmRoute(): We have detected a failover with different jvmRoute." + " received one: " + requestedJvmRoute + " new one: " + jvmRoute + ". Will resent the session id."); } String base = index > -1 ? sessionId.substring(0, index) : sessionId; newId = new StringBuilder(base).append('.').append(jvmRoute).toString(); } } /* Change the sessionid cookie if needed */ if (newId != null) manager_.setNewSessionCookie(newId, response); } } /** * Update the id of the given session * * @param oldId id of the session to change * @param newId new session id the session object should have */ private void resetSessionId(String oldId, String newId) throws IOException { @SuppressWarnings("unchecked") ClusteredSession<? extends OutgoingDistributableSessionData> session = (ClusteredSession) manager_.findSession(oldId); // change session id with the new one using local jvmRoute. if( session != null ) { // Note this will trigger a session remove from the super Tomcat class. session.resetIdWithRouteInfo(newId); if (log_.isTraceEnabled()) { log_.trace("resetSessionId(): changed catalina session to= [" + newId + "] old one= [" + oldId + "]"); } } else if (log_.isTraceEnabled()) { log_.trace("resetSessionId(): no session with id " + newId + " found"); } } // Lifecycle Interface public void addLifecycleListener(LifecycleListener listener) { support.addLifecycleListener(listener); } public void removeLifecycleListener(LifecycleListener listener) { support.removeLifecycleListener(listener); } public LifecycleListener[] findLifecycleListeners() { return support.findLifecycleListeners(); } public void start() throws LifecycleException { support.fireLifecycleEvent(START_EVENT, this); } public void stop() throws LifecycleException { support.fireLifecycleEvent(STOP_EVENT, this); } }