From 9a3b91111a75d679b2999cc7cd8417071748e23e Mon Sep 17 00:00:00 2001 From: Paul Ferraro Date: Thu, 1 Dec 2016 20:15:05 -0500 Subject: [PATCH] Initial HodRod-based Tomcat session manager implementation. --- .gitignore | 44 ++- hotrod/pom.xml | 51 +++ .../tomcat/hotrod/HotRodManager.java | 252 ++++++++++++++ .../tomcat/hotrod/HotRodMarshaller.java | 42 +++ pom.xml | 98 ++++++ tomcat/pom.xml | 55 ++++ .../tomcat/IdentifierFactoryAdapter.java | 54 +++ .../TomcatIdentifierExternalizerProvider.java | 40 +++ .../session/ContextClassLoaderAction.java | 64 ++++ .../tomcat/session/DistributableManager.java | 203 ++++++++++++ .../tomcat/session/DistributableSession.java | 308 ++++++++++++++++++ .../tomcat/session/HttpSessionAdapter.java | 122 +++++++ .../tomcat/session/LocalSessionContext.java | 66 ++++ .../session/LocalSessionContextFactory.java | 37 +++ .../tomcat/session/TomcatManager.java | 260 +++++++++++++++ .../session/TomcatSessionDestroyAction.java | 74 +++++ .../TomcatSessionExpirationListener.java | 49 +++ 17 files changed, 1807 insertions(+), 12 deletions(-) create mode 100644 hotrod/pom.xml create mode 100644 hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodManager.java create mode 100644 hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodMarshaller.java create mode 100644 pom.xml create mode 100644 tomcat/pom.xml create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/IdentifierFactoryAdapter.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/TomcatIdentifierExternalizerProvider.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/ContextClassLoaderAction.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableManager.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableSession.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/HttpSessionAdapter.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContext.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContextFactory.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatManager.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionDestroyAction.java create mode 100644 tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionExpirationListener.java diff --git a/.gitignore b/.gitignore index 32858aad..77b2ae3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,32 @@ -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +# ignore Maven generated target folders +~ +target +# ignore eclipse files +.project +.classpath +.settings +.metadata +.checkstyle +# ignore m2e annotation processing files +.factorypath +# ignore IDEA files +*.iml +*.ipr +*.iws +.idea +# ignore NetBeans files +nbactions.xml +nb-configuration.xml +catalog.xml +# vim files +*.swp +/.gitk-tmp.* +atlassian-ide-plugin.xml +# temp files +*~ +# maven versions plugin +pom.xml.versionsBackup +# hprof dumps +/*.hprof +# ignore java crashes +hs_err_pid*.log diff --git a/hotrod/pom.xml b/hotrod/pom.xml new file mode 100644 index 00000000..21296cf4 --- /dev/null +++ b/hotrod/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + org.wildfly.clustering + wildfly-clustering-tomcat-parent + 1.0.0.Alpha1-SNAPSHOT + + + wildfly-clustering-tomcat-hotrod + jar + + WildFly: Tomcat integration with HotRod-based session manager + + + + org.wildfly.clustering + wildfly-clustering-tomcat + + + org.wildfly + wildfly-clustering-web-hotrod + + + + diff --git a/hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodManager.java b/hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodManager.java new file mode 100644 index 00000000..ec10ee78 --- /dev/null +++ b/hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodManager.java @@ -0,0 +1,252 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.hotrod; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.Serializable; +import java.util.Properties; +import java.util.function.Function; + +import javax.servlet.ServletContext; + +import org.apache.catalina.Context; +import org.apache.catalina.Host; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleState; +import org.apache.catalina.Session; +import org.apache.catalina.session.ManagerBase; +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.Configuration; +import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; +import org.infinispan.client.hotrod.configuration.NearCacheMode; +import org.infinispan.commons.marshall.jboss.DefaultContextClassResolver; +import org.jboss.marshalling.MarshallingConfiguration; +import org.wildfly.clustering.ee.Batch; +import org.wildfly.clustering.ee.Recordable; +import org.wildfly.clustering.marshalling.jboss.ExternalizerObjectTable; +import org.wildfly.clustering.marshalling.jboss.MarshallingContext; +import org.wildfly.clustering.marshalling.jboss.SimpleClassTable; +import org.wildfly.clustering.marshalling.jboss.SimpleMarshalledValueFactory; +import org.wildfly.clustering.marshalling.jboss.SimpleMarshallingConfigurationRepository; +import org.wildfly.clustering.marshalling.jboss.SimpleMarshallingContextFactory; +import org.wildfly.clustering.marshalling.spi.MarshalledValueFactory; +import org.wildfly.clustering.tomcat.IdentifierFactoryAdapter; +import org.wildfly.clustering.tomcat.session.DistributableManager; +import org.wildfly.clustering.tomcat.session.LocalSessionContext; +import org.wildfly.clustering.tomcat.session.LocalSessionContextFactory; +import org.wildfly.clustering.tomcat.session.TomcatManager; +import org.wildfly.clustering.tomcat.session.TomcatSessionExpirationListener; +import org.wildfly.clustering.web.IdentifierFactory; +import org.wildfly.clustering.web.LocalContextFactory; +import org.wildfly.clustering.web.hotrod.session.HotRodSessionManager; +import org.wildfly.clustering.web.hotrod.session.HotRodSessionManagerFactory; +import org.wildfly.clustering.web.hotrod.session.HotRodSessionManagerFactoryConfiguration; +import org.wildfly.clustering.web.session.ImmutableSession; +import org.wildfly.clustering.web.session.SessionExpirationListener; +import org.wildfly.clustering.web.session.SessionManager; +import org.wildfly.clustering.web.session.SessionManagerConfiguration; +import org.wildfly.clustering.web.session.SessionManagerFactory; +import org.wildfly.clustering.web.session.SessionManagerFactoryConfiguration; +import org.wildfly.clustering.web.session.SessionManagerFactoryConfiguration.SessionAttributePersistenceStrategy; + +/** + * Distributed Manager implementation that configures a HotRod client. + * @author Paul Ferraro + */ +public class HotRodManager extends ManagerBase { + + enum MarshallingVersion implements Function { + VERSION_1() { + @Override + public MarshallingConfiguration apply(ClassLoader loader) { + MarshallingConfiguration config = new MarshallingConfiguration(); + config.setClassResolver(new DefaultContextClassResolver(loader)); + config.setClassTable(new SimpleClassTable(Serializable.class, Externalizable.class)); + config.setObjectTable(new ExternalizerObjectTable(loader)); + return config; + } + }, + ; + static final MarshallingVersion CURRENT = VERSION_1; + } + + private final Properties properties = new Properties(); + + private volatile RemoteCacheManager container; + private volatile TomcatManager manager; + private volatile SessionAttributePersistenceStrategy persistenceStrategy = SessionAttributePersistenceStrategy.COARSE; + + public String getProperty(String name) { + return this.properties.getProperty(name); + } + + public void setProperty(String name, String value) { + this.properties.setProperty(name, value); + } + + public void setPersistenceStrategy(SessionAttributePersistenceStrategy strategy) { + this.persistenceStrategy = strategy; + } + + public void setPersistenceStrategy(String strategy) { + this.setPersistenceStrategy(SessionAttributePersistenceStrategy.valueOf(strategy)); + } + + @Override + protected void startInternal() throws LifecycleException { + super.startInternal(); + + Configuration configuration = new ConfigurationBuilder() + .withProperties(this.properties) + .nearCache().mode(NearCacheMode.INVALIDATED).maxEntries(this.getMaxActiveSessions()) + .marshaller(new HotRodMarshaller(HotRodSessionManager.class.getClassLoader())) + .build(); + + RemoteCacheManager container = new RemoteCacheManager(configuration, false); + this.container = container; + this.container.start(); + + Context context = this.getContext(); + ClassLoader loader = context.getLoader().getClassLoader(); + Host host = (Host) context.getParent(); + // Deployment name = host name + context path + version + String deploymentName = host.getName() + context.getName(); + int maxActiveSessions = this.getMaxActiveSessions(); + SessionAttributePersistenceStrategy strategy = this.persistenceStrategy; + MarshallingContext marshallingContext = new SimpleMarshallingContextFactory().createMarshallingContext(new SimpleMarshallingConfigurationRepository(MarshallingVersion.class, MarshallingVersion.CURRENT, loader), loader); + MarshalledValueFactory marshallingFactory = new SimpleMarshalledValueFactory(marshallingContext); + + SessionManagerFactoryConfiguration sessionManagerFactoryConfig = new SessionManagerFactoryConfiguration() { + @Override + public int getMaxActiveSessions() { + return maxActiveSessions; + } + + @Override + public SessionAttributePersistenceStrategy getAttributePersistenceStrategy() { + return strategy; + } + + @Override + public String getDeploymentName() { + return deploymentName; + } + + @Override + public String getCacheName() { + return null; + } + + @Override + public MarshalledValueFactory getMarshalledValueFactory() { + return marshallingFactory; + } + + @Override + public MarshallingContext getMarshallingContext() { + return marshallingContext; + } + }; + + HotRodSessionManagerFactoryConfiguration hotrodSessionManagerFactoryConfig = new HotRodSessionManagerFactoryConfiguration() { + @Override + public SessionManagerFactoryConfiguration getSessionManagerFactoryConfiguration() { + return sessionManagerFactoryConfig; + } + + @Override + public RemoteCacheManager getCacheContainer() { + return container; + } + }; + + SessionManagerFactory sessionManagerFactory = new HotRodSessionManagerFactory<>(hotrodSessionManagerFactoryConfig); + + ServletContext servletContext = context.getServletContext(); + SessionExpirationListener expirationListener = new TomcatSessionExpirationListener(context); + LocalContextFactory contextFactory = new LocalSessionContextFactory(); + IdentifierFactory identifierFactory = new IdentifierFactoryAdapter(this.getSessionIdGenerator()); + + SessionManagerConfiguration sessionManagerConfiguration = new SessionManagerConfiguration() { + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public IdentifierFactory getIdentifierFactory() { + return identifierFactory; + } + + @Override + public SessionExpirationListener getExpirationListener() { + return expirationListener; + } + + @Override + public LocalContextFactory getLocalContextFactory() { + return contextFactory; + } + + @Override + public Recordable getInactiveSessionRecorder() { + return null; + } + }; + SessionManager sessionManager = sessionManagerFactory.createSessionManager(sessionManagerConfiguration); + + this.manager = new DistributableManager(sessionManager, context, marshallingContext); + this.manager.start(); + + this.setState(LifecycleState.STARTING); + } + + @Override + protected void stopInternal() throws LifecycleException { + this.setState(LifecycleState.STOPPING); + + this.manager.stop(); + this.container.stop(); + } + + @Override + public Session createSession(String sessionId) { + return this.manager.createSession(sessionId); + } + + @Override + public Session findSession(String id) throws IOException { + return this.manager.findSession(id); + } + + @Override + public void load() throws ClassNotFoundException, IOException { + // Do nothing + } + + @Override + public void unload() throws IOException { + // Do nothing + } +} diff --git a/hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodMarshaller.java b/hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodMarshaller.java new file mode 100644 index 00000000..9dee7fe5 --- /dev/null +++ b/hotrod/src/main/java/org/wildfly/clustering/tomcat/hotrod/HotRodMarshaller.java @@ -0,0 +1,42 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.hotrod; + +import org.infinispan.commons.marshall.jboss.AbstractJBossMarshaller; +import org.infinispan.commons.marshall.jboss.DefaultContextClassResolver; +import org.wildfly.clustering.marshalling.jboss.DynamicClassTable; +import org.wildfly.clustering.marshalling.jboss.ExternalizerObjectTable; + +/** + * @author Paul Ferraro + */ +public class HotRodMarshaller extends AbstractJBossMarshaller { + + public HotRodMarshaller(ClassLoader loader) { + super(); + super.baseCfg.setClassExternalizerFactory(null); + super.baseCfg.setClassResolver(new DefaultContextClassResolver(loader)); + super.baseCfg.setClassTable(new DynamicClassTable(loader)); + super.baseCfg.setObjectTable(new ExternalizerObjectTable(loader)); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..4a686f15 --- /dev/null +++ b/pom.xml @@ -0,0 +1,98 @@ + + + + + 4.0.0 + + + org.jboss + jboss-parent + 21 + + + org.wildfly.clustering + wildfly-clustering-tomcat-parent + 1.0.0.Alpha1-SNAPSHOT + pom + + WildFly: Web session clustering: Tomcat integration + + + tomcat + hotrod + + + + 8.0.39 + 1.7 + 11.0.0.Alpha1-SNAPSHOT + + + + + + org.wildfly.clustering + wildfly-clustering-tomcat + ${project.version} + + + org.wildfly.clustering + wildfly-clustering-tomcat-hotrod + ${project.version} + + + org.wildfly + wildfly-clustering-web-hotrod + ${version.org.wildfly} + + + org.wildfly + wildfly-clustering-web-spi + ${version.org.wildfly} + + + org.wildfly + wildfly-clustering-common + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.1_spec + + + + + org.apache.tomcat + tomcat-catalina + ${version.org.apache.tomcat} + + + org.kohsuke.metainf-services + metainf-services + ${version.org.kohsuke.metainf-services} + + + + + diff --git a/tomcat/pom.xml b/tomcat/pom.xml new file mode 100644 index 00000000..aca11335 --- /dev/null +++ b/tomcat/pom.xml @@ -0,0 +1,55 @@ + + + + + 4.0.0 + + + org.wildfly.clustering + wildfly-clustering-tomcat-parent + 1.0.0.Alpha1-SNAPSHOT + + + wildfly-clustering-tomcat + jar + + WildFly: Web session clustering: Tomcat integration + + + + org.wildfly + wildfly-clustering-web-spi + + + org.apache.tomcat + tomcat-catalina + + + org.kohsuke.metainf-services + metainf-services + + + + diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/IdentifierFactoryAdapter.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/IdentifierFactoryAdapter.java new file mode 100644 index 00000000..51525481 --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/IdentifierFactoryAdapter.java @@ -0,0 +1,54 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat; + +import org.apache.catalina.SessionIdGenerator; +import org.wildfly.clustering.web.IdentifierFactory; + +/** + * An identifier factory that uses Tomcat's SessionIdGenerator. + * @author Paul Ferraro + */ +public class IdentifierFactoryAdapter implements IdentifierFactory { + + private final SessionIdGenerator generator; + + public IdentifierFactoryAdapter(SessionIdGenerator generator) { + this.generator = generator; + // Prevent Tomcat's session id generator from auto-appending the route + this.generator.setJvmRoute(null); + } + + @Override + public String createIdentifier() { + return this.generator.generateSessionId(); + } + + @Override + public void start() { + } + + @Override + public void stop() { + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/TomcatIdentifierExternalizerProvider.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/TomcatIdentifierExternalizerProvider.java new file mode 100644 index 00000000..39c46341 --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/TomcatIdentifierExternalizerProvider.java @@ -0,0 +1,40 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat; + +import org.kohsuke.MetaInfServices; +import org.wildfly.clustering.marshalling.Externalizer; +import org.wildfly.clustering.web.IdentifierExternalizer; +import org.wildfly.clustering.web.IdentifierExternalizerProvider; + +/** + * @author Paul Ferraro + */ +@MetaInfServices(IdentifierExternalizerProvider.class) +public class TomcatIdentifierExternalizerProvider implements IdentifierExternalizerProvider { + + @Override + public Externalizer getExternalizer() { + return IdentifierExternalizer.HEX; + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/ContextClassLoaderAction.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/ContextClassLoaderAction.java new file mode 100644 index 00000000..632aef5b --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/ContextClassLoaderAction.java @@ -0,0 +1,64 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Consumer; + +/** + * Performs some action with a given context class loader. + * @author Paul Ferraro + */ +public class ContextClassLoaderAction implements Consumer { + + private final ClassLoader loader; + + public ContextClassLoaderAction(ClassLoader loader) { + this.loader = loader; + } + + @Override + public void accept(Runnable task) { + ClassLoader loader = getContextClassLoader(); + setContextClassLoader(this.loader); + try { + task.run(); + } finally { + setContextClassLoader(loader); + } + } + + private static ClassLoader getContextClassLoader() { + PrivilegedAction contextClassLoaderAction = () -> Thread.currentThread().getContextClassLoader(); + return AccessController.doPrivileged(contextClassLoaderAction); + } + + private static void setContextClassLoader(ClassLoader loader) { + PrivilegedAction action = () -> { + Thread.currentThread().setContextClassLoader(loader); + return null; + }; + AccessController.doPrivileged(action); + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableManager.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableManager.java new file mode 100644 index 00000000..5e2f57ce --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableManager.java @@ -0,0 +1,203 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.io.IOException; +import java.time.Duration; +import java.util.OptionalLong; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.apache.catalina.Context; +import org.apache.catalina.Engine; +import org.apache.catalina.LifecycleException; +import org.wildfly.clustering.ee.Batch; +import org.wildfly.clustering.ee.Batcher; +import org.wildfly.clustering.marshalling.spi.Marshallability; +import org.wildfly.clustering.web.session.ImmutableHttpSessionAdapter; +import org.wildfly.clustering.web.session.ImmutableSession; +import org.wildfly.clustering.web.session.Session; +import org.wildfly.clustering.web.session.SessionManager; + +/** + * Adapts a WildFly distributable SessionManager to Tomcat's Manager interface. + * @author Paul Ferraro + */ +public class DistributableManager implements TomcatManager { + private static final char ROUTE_DELIMITER = '.'; + + private final SessionManager manager; + private final Context context; + private final Consumer invalidateAction; + private final Marshallability marshallability; + private final String route; + private final StampedLock lifecycleLock = new StampedLock(); + + // Guarded by this + private OptionalLong lifecycleStamp = OptionalLong.empty(); + + public DistributableManager(SessionManager manager, Context context, Marshallability marshallability) { + this.manager = manager; + this.marshallability = marshallability; + this.context = context; + this.route = ((Engine) context.getParent().getParent()).getJvmRoute(); + this.invalidateAction = new TomcatSessionDestroyAction(context); + + this.manager.setDefaultMaxInactiveInterval(Duration.ofMinutes(context.getSessionTimeout())); + } + + @Override + public SessionManager getSessionManager() { + return this.manager; + } + + @Override + public Marshallability getMarshallability() { + return this.marshallability; + } + + /** + * Strips routing information from requested session identifier. + */ + private static String parseSessionId(String requestedSesssionId) { + int index = requestedSesssionId.indexOf(ROUTE_DELIMITER); + return (index < 0) ? requestedSesssionId : requestedSesssionId.substring(0, index); + } + + /** + * Appends routing information to session identifier. + */ + private org.apache.catalina.Session getSession(Session session, Runnable closeTask) { + String id = session.getId(); + String internalId = (this.route != null) ? new StringBuilder(id.length() + this.route.length() + 1).append(id).append(ROUTE_DELIMITER).append(this.route).toString() : id; + return new DistributableSession(this, session, internalId, this.manager.getBatcher().suspendBatch(), () -> this.invalidateAction.accept(session), closeTask); + } + + @Override + public void init() throws LifecycleException { + } + + @Override + public void start() throws LifecycleException { + this.lifecycleStamp.ifPresent(stamp -> this.lifecycleLock.unlock(stamp)); + this.manager.setDefaultMaxInactiveInterval(Duration.ofMinutes(this.context.getSessionTimeout())); + this.manager.start(); + } + + @Override + public void stop() throws LifecycleException { + if (!this.lifecycleStamp.isPresent()) { + try { + this.lifecycleStamp = OptionalLong.of(this.lifecycleLock.writeLockInterruptibly()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + this.manager.stop(); + } + } + + @Override + public void destroy() throws LifecycleException { + } + + @Override + public org.apache.catalina.Session createSession(String sessionId) { + String id = (sessionId != null) ? parseSessionId(sessionId) : this.manager.createIdentifier(); + Runnable closeTask = this.getSessionCloseTask(); + boolean close = true; + try { + Batcher batcher = this.manager.getBatcher(); + // Batch will be closed by Session.close(); + @SuppressWarnings("resource") + Batch batch = batcher.createBatch(); + try { + Session session = this.manager.createSession(id); + HttpSessionEvent event = new HttpSessionEvent(new ImmutableHttpSessionAdapter(session, this.context.getServletContext())); + Stream.of(this.context.getApplicationEventListeners()).filter(listener -> listener instanceof HttpSessionListener).map(listener -> (HttpSessionListener) listener).forEach(listener -> { + try { + listener.sessionCreated(event); + } catch (Throwable e) { + this.context.getLogger().warn(e.getMessage(), e); + } + }); + org.apache.catalina.Session result = this.getSession(session, closeTask); + close = false; + return result; + } catch (RuntimeException | Error e) { + batch.discard(); + batch.close(); + throw e; + } + } finally { + if (close) { + closeTask.run(); + } + } + } + + @Override + public org.apache.catalina.Session findSession(String id) throws IOException { + Runnable closeTask = this.getSessionCloseTask(); + boolean close = true; + try { + Session session = this.manager.findSession(parseSessionId(id)); + if (session == null) { + return null; + } + org.apache.catalina.Session result = this.getSession(session, closeTask); + close = false; + return result; + } finally { + if (close) { + closeTask.run(); + } + } + } + + private Runnable getSessionCloseTask() { + long stamp = this.lifecycleLock.tryReadLock(); + if (stamp == 0L) { + throw new IllegalStateException("Session manager was stopped"); + } + return () -> this.lifecycleLock.unlock(stamp); + } + + @Override + public void changeSessionId(org.apache.catalina.Session session) { + this.changeSessionId(session, this.manager.createIdentifier()); + } + + @Override + public void changeSessionId(org.apache.catalina.Session session, String id) { + session.tellChangedSessionId(id, session.getId(), true, true); + } + + @Override + public Context getContext() { + return this.context; + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableSession.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableSession.java new file mode 100644 index 00000000..268682ce --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/DistributableSession.java @@ -0,0 +1,308 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.security.Principal; +import java.time.Duration; +import java.time.Instant; +import java.util.Iterator; +import java.util.stream.Stream; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; + +import org.apache.catalina.Context; +import org.apache.catalina.Manager; +import org.apache.catalina.SessionListener; +import org.wildfly.clustering.ee.Batch; +import org.wildfly.clustering.ee.BatchContext; +import org.wildfly.clustering.ee.Batcher; +import org.wildfly.clustering.web.session.Session; +import org.wildfly.clustering.web.session.SessionManager; + +/** + * Adapts a WildFly distributable Session to Tomcat's Session interface. + * @author Paul Ferraro + */ +public class DistributableSession implements org.apache.catalina.Session { + + private final TomcatManager manager; + private final String internalId; + private final Batch batch; + private final Runnable invalidateAction; + private final Runnable closeTask; + + private volatile Session session; + + public DistributableSession(TomcatManager manager, Session session, String internalId, Batch batch, Runnable invalidateAction, Runnable closeTask) { + this.manager = manager; + this.session = session; + this.internalId = internalId; + this.batch = batch; + this.invalidateAction = invalidateAction; + this.closeTask = closeTask; + } + + @Override + public String getAuthType() { + return this.session.getLocalContext().getAuthType(); + } + + @Override + public void setAuthType(String authType) { + this.session.getLocalContext().setAuthType(authType); + } + + @Override + public long getCreationTime() { + try (BatchContext context = this.manager.getSessionManager().getBatcher().resumeBatch(this.batch)) { + return this.session.getMetaData().getCreationTime().toEpochMilli(); + } + } + + @Override + public long getCreationTimeInternal() { + return this.getCreationTime(); + } + + @Override + public void setCreationTime(long time) { + // Do nothing + } + + @Override + public String getId() { + return this.session.getId(); + } + + @Override + public String getIdInternal() { + return this.internalId; + } + + @Override + public void setId(String id) { + // Do nothing + } + + @Override + public void setId(String id, boolean notify) { + // Do nothing + } + + @Override + public long getThisAccessedTime() { + return this.getLastAccessedTime(); + } + + @Override + public long getThisAccessedTimeInternal() { + return this.getThisAccessedTime(); + } + + @Override + public long getLastAccessedTime() { + try (BatchContext context = this.manager.getSessionManager().getBatcher().resumeBatch(this.batch)) { + return this.session.getMetaData().getLastAccessedTime().toEpochMilli(); + } + } + + @Override + public long getLastAccessedTimeInternal() { + return this.getLastAccessedTime(); + } + + @Override + public long getIdleTime() { + return Instant.now().toEpochMilli() - this.getLastAccessedTime(); + } + + @Override + public long getIdleTimeInternal() { + return this.getIdleTime(); + } + + @Override + public Manager getManager() { + return this.manager; + } + + @Override + public void setManager(Manager manager) { + // Do nothing + } + + @Override + public int getMaxInactiveInterval() { + try (BatchContext context = this.manager.getSessionManager().getBatcher().resumeBatch(this.batch)) { + return (int) this.session.getMetaData().getMaxInactiveInterval().getSeconds(); + } + } + + @Override + public void setMaxInactiveInterval(int interval) { + try (BatchContext context = this.manager.getSessionManager().getBatcher().resumeBatch(this.batch)) { + this.session.getMetaData().setMaxInactiveInterval((interval > 0) ? Duration.ofSeconds(interval) : Duration.ZERO); + } + } + + @Override + public void setNew(boolean isNew) { + // Ignore + } + + @Override + public Principal getPrincipal() { + return this.session.getLocalContext().getPrincipal(); + } + + @Override + public void setPrincipal(Principal principal) { + this.session.getLocalContext().setPrincipal(principal); + } + + @Override + public HttpSession getSession() { + return new HttpSessionAdapter(this.session, this.manager.getContext(), this.invalidateAction); + } + + @Override + public void setValid(boolean isValid) { + // Ignore + } + + @Override + public boolean isValid() { + return this.session.isValid(); + } + + @Override + public void access() { + // Do nothing + } + + @Override + public void addSessionListener(SessionListener listener) { + this.session.getLocalContext().getSessionListeners().add(listener); + } + + @Override + public void endAccess() { + try { + if (this.session.isValid()) { + Batcher batcher = this.manager.getSessionManager().getBatcher(); + try (BatchContext context = batcher.resumeBatch(this.batch)) { + // If batch was discarded, close it + if (this.batch.getState() == Batch.State.DISCARDED) { + this.batch.close(); + } + // If batch is closed, close session in a new batch + try (Batch batch = (this.batch.getState() == Batch.State.CLOSED) ? batcher.createBatch() : this.batch) { + this.session.close(); + } + } catch (Throwable e) { + this.manager.getContext().getLogger().warn(e.getLocalizedMessage(), e); + } + } + } finally { + this.closeTask.run(); + } + } + + @Override + public void expire() { + this.session.invalidate(); + } + + @Override + public Object getNote(String name) { + return this.session.getLocalContext().getNotes().get(name); + } + + @Override + public Iterator getNoteNames() { + return this.session.getLocalContext().getNotes().keySet().iterator(); + } + + @Override + public void recycle() { + // Do nothing + } + + @Override + public void removeNote(String name) { + this.session.getLocalContext().getNotes().remove(name); + } + + @Override + public void removeSessionListener(SessionListener listener) { + this.session.getLocalContext().getSessionListeners().remove(listener); + } + + @Override + public void setNote(String name, Object value) { + this.session.getLocalContext().getNotes().put(name, value); + } + + @Override + public void tellChangedSessionId(String newId, String oldId, boolean notifySessionListeners, boolean notifyContainerListeners) { + SessionManager manager = this.manager.getSessionManager(); + Session oldSession = this.session; + try (BatchContext context = this.manager.getSessionManager().getBatcher().resumeBatch(this.batch)) { + Session newSession = manager.createSession(newId); + for (String name: this.session.getAttributes().getAttributeNames()) { + newSession.getAttributes().setAttribute(name, oldSession.getAttributes().getAttribute(name)); + } + newSession.getMetaData().setMaxInactiveInterval(oldSession.getMetaData().getMaxInactiveInterval()); + newSession.getMetaData().setLastAccessedTime(oldSession.getMetaData().getLastAccessedTime()); + newSession.getLocalContext().setAuthType(oldSession.getLocalContext().getAuthType()); + newSession.getLocalContext().setPrincipal(oldSession.getLocalContext().getPrincipal()); + this.session = newSession; + oldSession.invalidate(); + } + + // Invoke listeners outside of the context of the batch associated with this session + Context context = this.manager.getContext(); + + if (notifyContainerListeners) { + context.fireContainerEvent(Context.CHANGE_SESSION_ID_EVENT, new String[] { oldId, newId }); + } + + if (notifySessionListeners) { + HttpSessionEvent event = new HttpSessionEvent(this.getSession()); + Stream.of(context.getApplicationEventListeners()).filter(listener -> listener instanceof HttpSessionIdListener).map(listener -> (HttpSessionIdListener) listener).forEach(listener -> { + try { + listener.sessionIdChanged(event, oldId); + } catch (Throwable e) { + context.getLogger().warn(e.getMessage(), e); + } + }); + } + } + + @Override + public boolean isAttributeDistributable(String name, Object value) { + return this.manager.getMarshallability().isMarshallable(value); + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/HttpSessionAdapter.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/HttpSessionAdapter.java new file mode 100644 index 00000000..ca34bb82 --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/HttpSessionAdapter.java @@ -0,0 +1,122 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.time.Duration; +import java.util.stream.Stream; + +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.apache.catalina.Context; +import org.wildfly.clustering.web.session.ImmutableHttpSessionAdapter; +import org.wildfly.clustering.web.session.Session; + +/** + * Adapts a WildFly distributable Session to an HttpSession. + * @author Paul Ferraro + */ +public class HttpSessionAdapter extends ImmutableHttpSessionAdapter { + + private final Session session; + private final Context context; + private final Runnable invalidateAction; + + public HttpSessionAdapter(Session session, Context context, Runnable invalidateAction) { + super(session, context.getServletContext()); + this.session = session; + this.context = context; + this.invalidateAction = invalidateAction; + } + + @Override + public void invalidate() { + this.invalidateAction.run(); + this.session.invalidate(); + } + + @Override + public void setMaxInactiveInterval(int interval) { + this.session.getMetaData().setMaxInactiveInterval((interval > 0) ? Duration.ofSeconds(interval) : Duration.ZERO); + } + + @Override + public void setAttribute(String name, Object value) { + if (value == null) { + this.removeAttribute(name); + return; + } + Object old = this.session.getAttributes().setAttribute(name, value); + if (old != value) { + if (value instanceof HttpSessionBindingListener) { + HttpSessionBindingListener listener = (HttpSessionBindingListener) value; + try { + listener.valueBound(new HttpSessionBindingEvent(this, name)); + } catch (Throwable e) { + this.context.getLogger().warn(e.getMessage(), e); + } + } + if (old instanceof HttpSessionBindingListener) { + HttpSessionBindingListener listener = (HttpSessionBindingListener) old; + try { + listener.valueUnbound(new HttpSessionBindingEvent(this, name)); + } catch (Throwable e) { + this.context.getLogger().warn(e.getMessage(), e); + } + } + HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name, (old != null) ? old : value); + Stream.of(this.context.getApplicationEventListeners()).filter(listener -> listener instanceof HttpSessionAttributeListener).map(listener -> (HttpSessionAttributeListener) listener).forEach(listener -> { + try { + if (old == null) { + listener.attributeAdded(event); + } else { + listener.attributeReplaced(event); + } + } catch (Throwable e) { + this.context.getLogger().warn(e.getMessage(), e); + } + }); + } + } + + @Override + public void removeAttribute(String name) { + Object value = this.session.getAttributes().removeAttribute(name); + + if (value != null) { + if (value instanceof HttpSessionBindingListener) { + HttpSessionBindingListener listener = (HttpSessionBindingListener) value; + listener.valueUnbound(new HttpSessionBindingEvent(this, name)); + } + HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name, value); + Stream.of(this.context.getApplicationEventListeners()).filter(listener -> listener instanceof HttpSessionAttributeListener).map(listener -> (HttpSessionAttributeListener) listener).forEach(listener -> { + try { + listener.attributeRemoved(event); + } catch (Throwable e) { + this.context.getLogger().warn(e.getMessage(), e); + } + }); + } + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContext.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContext.java new file mode 100644 index 00000000..d18745f4 --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContext.java @@ -0,0 +1,66 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.security.Principal; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.catalina.SessionListener; + +/** + * Local (i.e. non-persistent) context for a Tomcat session. + * @author Paul Ferraro + */ +public class LocalSessionContext { + private final Map notes = new ConcurrentHashMap<>(); + private final List listeners = new CopyOnWriteArrayList<>(); + private volatile String authType; + private volatile Principal principal; + + public String getAuthType() { + return this.authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public Principal getPrincipal() { + return this.principal; + } + + public void setPrincipal(Principal principal) { + this.principal = principal; + } + + public Map getNotes() { + return this.notes; + } + + public List getSessionListeners() { + return this.listeners; + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContextFactory.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContextFactory.java new file mode 100644 index 00000000..c6834cf2 --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/LocalSessionContextFactory.java @@ -0,0 +1,37 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import org.wildfly.clustering.web.LocalContextFactory; + +/** + * Create a local (i.e. non-persistent) context for a Tomcat session. + * @author Paul Ferraro + */ +public class LocalSessionContextFactory implements LocalContextFactory { + + @Override + public LocalSessionContext createLocalContext() { + return new LocalSessionContext(); + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatManager.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatManager.java new file mode 100644 index 00000000..3948321c --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatManager.java @@ -0,0 +1,260 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.beans.PropertyChangeListener; +import java.io.IOException; + +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.LifecycleState; +import org.apache.catalina.Manager; +import org.apache.catalina.SessionIdGenerator; +import org.wildfly.clustering.ee.Batch; +import org.wildfly.clustering.marshalling.spi.Marshallability; +import org.wildfly.clustering.web.session.SessionManager; + +/** + * Enhances Tomcat's Manager interface, providing default implementations for deprecated methods and methods we currently ignore. + * @author Paul Ferraro + */ +public interface TomcatManager extends Manager, Lifecycle { + + /** + * Returns underlying distributable session manager implementation. + * @return a session manager + */ + SessionManager getSessionManager(); + + /** + * Returns a mechanism for determining the marshallability of a session attribute. + * @return + */ + Marshallability getMarshallability(); + + // We don't care about any of the methods below + + @Override + default void setContext(Context context) { + } + + @Override + default SessionIdGenerator getSessionIdGenerator() { + return null; + } + + @Override + default void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) { + } + + @Override + default long getSessionCounter() { + return 0; + } + + @Override + default void setSessionCounter(long sessionCounter) { + } + + @Override + default int getMaxActive() { + return 0; + } + + @Override + default void setMaxActive(int maxActive) { + } + + @Override + default int getActiveSessions() { + return 0; + } + + @Override + default long getExpiredSessions() { + return 0; + } + + @Override + default void setExpiredSessions(long expiredSessions) { + } + + @Override + default int getRejectedSessions() { + return 0; + } + + @Override + default int getSessionMaxAliveTime() { + return 0; + } + + @Override + default void setSessionMaxAliveTime(int sessionMaxAliveTime) { + } + + @Override + default int getSessionAverageAliveTime() { + return 0; + } + + @Override + default int getSessionCreateRate() { + return 0; + } + + @Override + default int getSessionExpireRate() { + return 0; + } + + @Override + default void add(org.apache.catalina.Session session) { + } + + @Override + default void addPropertyChangeListener(PropertyChangeListener listener) { + } + + @Override + default void changeSessionId(org.apache.catalina.Session session) { + } + + @Override + default void changeSessionId(org.apache.catalina.Session session, String newId) { + } + + @Override + default org.apache.catalina.Session createEmptySession() { + return null; + } + + @Override + default org.apache.catalina.Session[] findSessions() { + return null; + } + + @Override + default void load() throws ClassNotFoundException, IOException { + } + + @Override + default void remove(org.apache.catalina.Session session) { + } + + @Override + default void remove(org.apache.catalina.Session session, boolean update) { + } + + @Override + default void removePropertyChangeListener(PropertyChangeListener listener) { + } + + @Override + default void unload() throws IOException { + } + + @Override + default void backgroundProcess() { + } + + @Override + default boolean willAttributeDistribute(String name, Object value) { + return false; + } + + @Override + default void addLifecycleListener(LifecycleListener listener) { + } + + @Override + default LifecycleListener[] findLifecycleListeners() { + return null; + } + + @Override + default void removeLifecycleListener(LifecycleListener listener) { + } + + @Override + default LifecycleState getState() { + return null; + } + + @Override + default String getStateName() { + return null; + } + + // Provide default impls for deprecated methods + + @Deprecated + @Override + default Container getContainer() { + return null; + } + + @Deprecated + @Override + default void setContainer(Container container) { + // Ignore + } + + @Deprecated + @Override + default boolean getDistributable() { + return this.getContext().getDistributable(); + } + + @Deprecated + @Override + default void setDistributable(boolean distributable) { + // Ignore + } + + @Deprecated + @Override + default int getMaxInactiveInterval() { + return this.getContext().getSessionTimeout() * 60; + } + + @Deprecated + @Override + default void setMaxInactiveInterval(int interval) { + // Ignore + } + + @Override + @Deprecated + default int getSessionIdLength() { + return this.getSessionIdGenerator().getSessionIdLength(); + } + + @Override + @Deprecated + default void setSessionIdLength(int length) { + this.getSessionIdGenerator().setSessionIdLength(length); + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionDestroyAction.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionDestroyAction.java new file mode 100644 index 00000000..dc27324a --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionDestroyAction.java @@ -0,0 +1,74 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.util.function.Consumer; +import java.util.stream.Stream; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.apache.catalina.Context; +import org.wildfly.clustering.web.session.ImmutableHttpSessionAdapter; +import org.wildfly.clustering.web.session.ImmutableSession; +import org.wildfly.clustering.web.session.ImmutableSessionAttributes; + +/** + * Defines an action to be performed prior to destruction of a session. + * @author Paul Ferraro + */ +public class TomcatSessionDestroyAction implements Consumer { + private final Context context; + + public TomcatSessionDestroyAction(Context context) { + this.context = context; + } + + @Override + public void accept(ImmutableSession session) { + HttpSession httpSession = new ImmutableHttpSessionAdapter(session, this.context.getServletContext()); + HttpSessionEvent event = new HttpSessionEvent(httpSession); + Stream.of(this.context.getApplicationEventListeners()).filter(listener -> listener instanceof HttpSessionListener).map(listener -> (HttpSessionListener) listener).forEach(listener -> { + try { + listener.sessionDestroyed(event); + } catch (Throwable e) { + this.context.getLogger().warn(e.getMessage(), e); + } + }); + ImmutableSessionAttributes attributes = session.getAttributes(); + for (String name : attributes.getAttributeNames()) { + Object attribute = attributes.getAttribute(name); + if (attribute instanceof HttpSessionBindingListener) { + HttpSessionBindingListener listener = (HttpSessionBindingListener) attribute; + try { + listener.valueUnbound(new HttpSessionBindingEvent(httpSession, name, attribute)); + } catch (Throwable e) { + this.context.getLogger().warn(e.getMessage(), e); + } + } + } + } +} diff --git a/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionExpirationListener.java b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionExpirationListener.java new file mode 100644 index 00000000..1cd51e24 --- /dev/null +++ b/tomcat/src/main/java/org/wildfly/clustering/tomcat/session/TomcatSessionExpirationListener.java @@ -0,0 +1,49 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016, Red Hat, Inc., 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.wildfly.clustering.tomcat.session; + +import java.util.function.Consumer; + +import org.apache.catalina.Context; +import org.wildfly.clustering.web.session.ImmutableSession; +import org.wildfly.clustering.web.session.SessionExpirationListener; + +/** + * Invokes following timeout of a session. + * @author Paul Ferraro + */ +public class TomcatSessionExpirationListener implements SessionExpirationListener { + + private final Consumer expireAction; + private final Consumer classLoaderAction; + + public TomcatSessionExpirationListener(Context context) { + this.expireAction = new TomcatSessionDestroyAction(context); + this.classLoaderAction = new ContextClassLoaderAction(context.getLoader().getClassLoader()); + } + + @Override + public void sessionExpired(ImmutableSession session) { + this.classLoaderAction.accept(() -> this.expireAction.accept(session)); + } +}