package org.jboss.web.tomcat.service.session; /* * JBoss, Home of Professional Open Source. * Copyright 2006, 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. */ import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleListener; 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; import org.mobicents.servlet.sip.core.session.ConvergedSessionFacade; import org.mobicents.servlet.sip.startup.StaticServiceHolder; import org.mobicents.servlet.sip.startup.failover.SipStandardBalancerNodeService; /** * Web request valve to specifically handle Tomcat jvmRoute using mod_jk(2) * module. We assume that the session is set by cookie only for now, i.e., no * support of that from URL. Furthermore, the session id has a format of * id.jvmRoute where jvmRoute is used by JK module to determine sticky session * during load balancing. * * @author Ben Wang * @author vralev * @version $Revision: 59035 $ */ public class ConvergedJvmRouteValve extends ValveBase implements Lifecycle { // The info string for this Valve private static final String info = "JvmRouteValve/1.0"; protected static Logger log_ = Logger.getLogger(ConvergedJvmRouteValve.class); // Valve-lifecycle_ helper object protected LifecycleSupport support = new LifecycleSupport(this); protected AbstractJBossManager manager_; /** * Create a new Valve. * */ public ConvergedJvmRouteValve(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 it through. This is ok because this // valve is inserted only when mod_jk option is configured. checkJvmRoute(request, response); // let the servlet invokation go through getNext().invoke(request, response); } public void checkJvmRoute(Request req, Response res) throws IOException, ServletException { String oldsessionId = req.getRequestedSessionId(); HttpSession session = req.getSession(false); if (session != null) { String sessionId = session.getId(); // Obtain JvmRoute String jvmRoute = manager_.getJvmRoute(); if (log_.isDebugEnabled()) { log_.debug("checkJvmRoute(): check if need to re-route based on JvmRoute. Session id: " + sessionId + " jvmRoute: " + jvmRoute); } 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(oldsessionId, sessionId, jvmRoute, res, setCookie, session); } } protected void handleJvmRoute(String oldsessionId, String sessionId, String jvmRoute, HttpServletResponse response, boolean setCookie, HttpSession session) { int indexReceivedJvmRoute; // Get requested jvmRoute. // TODO. The current format is assumed to be id.jvmRoute. Can be generalized later. String receivedJvmRoute = null; if (oldsessionId != null) { indexReceivedJvmRoute = sessionId.indexOf('.', 0); if (indexReceivedJvmRoute > -1 && indexReceivedJvmRoute < sessionId.length() -1) { receivedJvmRoute = sessionId.substring(indexReceivedJvmRoute + 1, sessionId.length()); } } int indexRequestedJvmRoute; String requestedJvmRoute = null; indexRequestedJvmRoute = sessionId.indexOf('.', 0); if (indexRequestedJvmRoute > -1 && indexRequestedJvmRoute < sessionId.length() -1) { requestedJvmRoute = sessionId.substring(indexRequestedJvmRoute + 1, sessionId.length()); } String newId = null; if (!jvmRoute.equals(requestedJvmRoute)) { if (indexRequestedJvmRoute < 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_.isInfoEnabled()) { log_.info("handleJvmRoute(): We have detected a failover with different jvmRoute." + " old one: " + requestedJvmRoute + " new one: " + jvmRoute + ". Will reset the session id."); } String base = sessionId.substring(0, indexRequestedJvmRoute); newId = base + "." + jvmRoute; if(session instanceof ConvergedSessionFacade) { ConvergedSessionFacade sessionFacade = (ConvergedSessionFacade) session; // Change the jvmRoute in case of failover sessionFacade.getApplicationSession(true).setJvmRoute(jvmRoute); if(StaticServiceHolder.sipStandardService instanceof SipStandardBalancerNodeService) { SipStandardBalancerNodeService service = (SipStandardBalancerNodeService) StaticServiceHolder.sipStandardService; service.sendSwitchoverInstruction(requestedJvmRoute, jvmRoute); } else { log_.error("The tomcat service is not a SipStandardBalancerNodeService service!!!!!"); } } } resetSessionId(sessionId, newId); } /* Also check the jvmRoute received (via req.getRequestedSessionId()) */ if (!jvmRoute.equals(receivedJvmRoute)) { if (log_.isDebugEnabled()) { log_.debug("handleJvmRoute(): We have detected a failover with different jvmRoute." + " received one: " + receivedJvmRoute + " new one: " + jvmRoute + ". Will resent the session id."); } String base = sessionId.substring(0, indexRequestedJvmRoute); newId = base + "." + jvmRoute; } /* Change the sessionid cookie if needed */ if (setCookie && newId != null) manager_.setNewSessionCookie(newId, response); } private void resetSessionId(String oldId, String newId) { try { @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_.isDebugEnabled()) { log_.debug("resetSessionId(): changed catalina session to= [" + newId + "] old one= [" + oldId + "]"); } } else if (log_.isDebugEnabled()) { log_.debug("resetSessionId(): no session with id " + newId + " found"); } } catch (IOException e) { if (log_.isDebugEnabled()) { log_.debug("resetSessionId(): manager_.findSession() unable to find session= [" + oldId + "]", e); } throw new RuntimeException("JvmRouteValve.resetSessionId(): cannot find session [" + oldId + "]", e); } } // 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); } }