Repository: tomee Updated Branches: refs/heads/master 58ec24151 -> 59e366a86
TOMEE-1649 adding eviction for 'cdi' websockets Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/59e366a8 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/59e366a8 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/59e366a8 Branch: refs/heads/master Commit: 59e366a86f7b9455701ef520873b7656d9626537 Parents: 58ec241 Author: Romain Manni-Bucau <rmann...@gmail.com> Authored: Mon Nov 2 10:08:13 2015 -0800 Committer: Romain Manni-Bucau <rmann...@gmail.com> Committed: Mon Nov 2 10:08:29 2015 -0800 ---------------------------------------------------------------------- .../org/apache/openejb/core/WebContext.java | 37 ++++- .../tomee/catalina/JavaeeInstanceManager.java | 16 +- .../tomee/catalina/OpenEJBContextConfig.java | 36 +++- .../tomee/catalina/TomEEWebappClassLoader.java | 15 ++ .../JavaEEDefaultServerEnpointConfigurator.java | 166 ++++++++++++++++++- 5 files changed, 258 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java b/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java index 8674bfa..55d98c1 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java @@ -53,7 +53,7 @@ public class WebContext { private Context jndiEnc; private final AppContext appContext; private Map<String, Object> bindings; - private final Map<Object, CreationalContext<?>> creatonalContexts = new ConcurrentHashMap<Object, CreationalContext<?>>(); + private final Map<Object, CreationalContext<?>> creationalContexts = new ConcurrentHashMap<>(); private WebBeansContext webbeansContext; private String contextRoot; private String host; @@ -129,8 +129,7 @@ public class WebContext { return appContext; } - public Object newInstance(final Class beanClass) throws OpenEJBException { - + public <T> Instance newWeakableInstance(final Class<T> beanClass) throws OpenEJBException { final WebBeansContext webBeansContext = getWebBeansContext(); final ConstructorInjectionBean<Object> beanDefinition = getConstructorInjectionBean(beanClass, webBeansContext); final CreationalContext<Object> creationalContext; @@ -156,10 +155,16 @@ public class WebContext { if (webBeansContext != null) { final InjectionTargetBean<Object> bean = InjectionTargetBean.class.cast(beanDefinition); bean.getInjectionTarget().inject(beanInstance, creationalContext); + } + return new Instance(beanInstance, creationalContext); + } - creatonalContexts.put(beanInstance, creationalContext); + public Object newInstance(final Class beanClass) throws OpenEJBException { + final Instance instance = newWeakableInstance(beanClass); + if (instance.getCreationalContext() != null) { + creationalContexts.put(instance.getValue(), instance.getCreationalContext()); } - return beanInstance; + return instance.getValue(); } private ConstructorInjectionBean<Object> getConstructorInjectionBean(final Class beanClass, final WebBeansContext webBeansContext) { @@ -227,7 +232,7 @@ public class WebContext { // if the bean is dependent simply cleanup the creational context once it is created final Class<? extends Annotation> scope = beanDefinition.getScope(); if (scope == null || Dependent.class.equals(scope)) { - creatonalContexts.put(beanInstance, creationalContext); + creationalContexts.put(beanInstance, creationalContext); } } @@ -262,9 +267,27 @@ public class WebContext { } public void destroy(final Object o) { - final CreationalContext<?> ctx = creatonalContexts.remove(o); + final CreationalContext<?> ctx = creationalContexts.remove(o); if (ctx != null) { ctx.release(); } } + + public static class Instance { + private final Object value; + private final CreationalContext<?> cc; + + public Instance(final Object value, final CreationalContext<?> cc) { + this.value = value; + this.cc = cc; + } + + public Object getValue() { + return value; + } + + public CreationalContext<?> getCreationalContext() { + return cc; + } + } } http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java ---------------------------------------------------------------------- diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java index 506bd00..3a4540e 100644 --- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java +++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java @@ -25,6 +25,7 @@ import org.apache.webbeans.exception.WebBeansCreationException; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.naming.NamingException; +import javax.servlet.ServletContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -40,6 +41,10 @@ public class JavaeeInstanceManager implements InstanceManager { this.webContext = webContext; } + public ServletContext getServletContext() { + return webContext == null ? null : webContext.getServletContext(); + } + @Override public Object newInstance(final Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException { try { @@ -51,6 +56,16 @@ public class JavaeeInstanceManager implements InstanceManager { } } + public WebContext.Instance newWeakableInstance(final Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException { + try { + final WebContext.Instance object = webContext.newWeakableInstance(clazz); + postConstruct(object.getValue(), clazz); + return object; + } catch (final OpenEJBException | WebBeansCreationException | WebBeansConfigurationException e) { + throw (InstantiationException) new InstantiationException(e.getMessage()).initCause(e); + } + } + @Override public Object newInstance(final String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException { final ClassLoader classLoader = webContext.getClassLoader(); @@ -178,5 +193,4 @@ public class JavaeeInstanceManager implements InstanceManager { preDestroy.setAccessible(accessibility); } } - } http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java ---------------------------------------------------------------------- diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java index 357a912..7e2e410 100644 --- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java +++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java @@ -63,6 +63,13 @@ import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.web.context.WebConversationFilter; import org.apache.xbean.finder.IAnnotationFinder; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.ws.rs.core.Application; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -83,9 +90,6 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; -import javax.servlet.ServletContainerInitializer; -import javax.servlet.http.HttpServlet; -import javax.ws.rs.core.Application; public class OpenEJBContextConfig extends ContextConfig { private static Logger logger = Logger.getInstance(LogCategory.OPENEJB, OpenEJBContextConfig.class); @@ -427,6 +431,32 @@ public class OpenEJBContextConfig extends ContextConfig { final ClassLoader classLoader = context.getLoader().getClassLoader(); + try { + classLoader.loadClass("org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator"); + context.addServletContainerInitializer(new ServletContainerInitializer() { + @Override + public void onStartup(final Set<Class<?>> c, final ServletContext ctx) throws ServletException { + ctx.addListener(new ServletContextListener() { + @Override + public void contextInitialized(final ServletContextEvent sce) { + //no -op + } + + @Override + public void contextDestroyed(final ServletContextEvent sce) { // ensure we cleanup our "eviction" processes + try { + org.apache.tomee.catalina.websocket.JavaEEDefaultServerEnpointConfigurator.unregisterProcesses(sce.getServletContext()); + } catch (final Throwable th) { + // no-op + } + } + }); + } + }, null); + } catch (final Throwable noWebSocket) { + // no-op: ok + } + // add myfaces auto-initializer if mojarra is not present try { classLoader.loadClass("com.sun.faces.context.SessionMap"); http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java ---------------------------------------------------------------------- diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java index 1b672b6..17fd006 100644 --- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java +++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java @@ -430,6 +430,21 @@ public class TomEEWebappClassLoader extends ParallelWebappClassLoader { } return Collections.enumeration(list); } + if ("META-INF/services/javax.websocket.ContainerProvider".equals(name)) { + final Collection<URL> list = new ArrayList<>(Collections.list(super.getResources(name))); + final Iterator<URL> it = list.iterator(); + while (it.hasNext()) { + final URL next = it.next(); + final File file = Files.toFile(next); + if (!file.isFile() && NewLoaderLogic.skip(next)) { + it.remove(); + } + } + if (list.size() == 1) { + return Collections.enumeration(list); + } + return Collections.enumeration(list); + } return URLClassLoaderFirst.filterResources(name, super.getResources(name)); } http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java ---------------------------------------------------------------------- diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java index 34df1dc..6613c70 100644 --- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java +++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java @@ -16,14 +16,51 @@ */ package org.apache.tomee.catalina.websocket; +import org.apache.openejb.core.WebContext; import org.apache.openejb.loader.SystemInstance; import org.apache.tomcat.InstanceManager; +import org.apache.tomcat.websocket.BackgroundProcess; +import org.apache.tomcat.websocket.BackgroundProcessManager; +import org.apache.tomcat.websocket.WsWebSocketContainer; +import org.apache.tomcat.websocket.pojo.PojoEndpointBase; +import org.apache.tomcat.websocket.server.Constants; import org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator; +import org.apache.tomee.catalina.JavaeeInstanceManager; import org.apache.tomee.catalina.TomcatWebAppBuilder; +import javax.servlet.ServletContext; +import javax.websocket.Endpoint; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedList; import java.util.Map; +import java.util.Set; public class JavaEEDefaultServerEnpointConfigurator extends DefaultServerEndpointConfigurator { + private static final String BG_PROCESSES_LIST = JavaEEDefaultServerEnpointConfigurator.class.getName() + ".bgProcesses"; + + // for websocket eviction + private static final Field END_POINT_SESSION_MAP_LOCK; + private static final Field ENDPOINT_SESSION_MAP; + private static final Method GET_POJO; + static { + try { + ENDPOINT_SESSION_MAP = WsWebSocketContainer.class.getDeclaredField("endpointSessionMap"); + ENDPOINT_SESSION_MAP.setAccessible(true); + END_POINT_SESSION_MAP_LOCK = WsWebSocketContainer.class.getDeclaredField("endPointSessionMapLock"); + END_POINT_SESSION_MAP_LOCK.setAccessible(true); + + GET_POJO = PojoEndpointBase.class.getDeclaredMethod("getPojo"); + GET_POJO.setAccessible(true); + } catch (final Exception e) { + throw new IllegalStateException("Toncat not compatible with tomee", e); + } + } + private final Map<ClassLoader, InstanceManager> instanceManagers; public JavaEEDefaultServerEnpointConfigurator() { @@ -50,7 +87,38 @@ public class JavaEEDefaultServerEnpointConfigurator extends DefaultServerEndpoin } try { - return clazz.cast(instanceManager.newInstance(clazz)); + final Object instance; + if (JavaeeInstanceManager.class.isInstance(instanceManager)) { + final JavaeeInstanceManager javaeeInstanceManager = JavaeeInstanceManager.class.cast(instanceManager); + final WebContext.Instance cdiInstance = javaeeInstanceManager.newWeakableInstance(clazz); + instance = cdiInstance.getValue(); + if (cdiInstance.getCreationalContext() != null) { // TODO: if we manage to have better listeners on tomcat we can use it rather than it + final ServletContext sc = javaeeInstanceManager.getServletContext(); + if (sc != null) { + Collection<CdiCleanUpBackgroundProcess> processes; + synchronized (sc) { + processes = (Collection<CdiCleanUpBackgroundProcess>) sc.getAttribute(BG_PROCESSES_LIST); + if (processes == null) { + processes = new LinkedList<>(); + sc.setAttribute(BG_PROCESSES_LIST, processes); + } + } + + final WebSocketContainer wsc = WebSocketContainer.class.cast(sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE)); + final Object lock = END_POINT_SESSION_MAP_LOCK.get(wsc); + if (wsc != null && WsWebSocketContainer.class.isInstance(wsc)) { + final CdiCleanUpBackgroundProcess process = new CdiCleanUpBackgroundProcess(wsc, cdiInstance, lock); + synchronized (processes) { + processes.add(process); + } + BackgroundProcessManager.getInstance().register(process); + } + } + } + } else { + instance = instanceManager.newInstance(clazz); + } + return clazz.cast(instance); } catch (final Exception e) { if (InstantiationException.class.isInstance(e)) { throw InstantiationException.class.cast(e); @@ -58,4 +126,100 @@ public class JavaEEDefaultServerEnpointConfigurator extends DefaultServerEndpoin throw new InstantiationException(e.getMessage()); } } + + public static void unregisterProcesses(final ServletContext sc) { // no sync needed at this point - no more "runtime" + final Collection<CdiCleanUpBackgroundProcess> processes = (Collection<CdiCleanUpBackgroundProcess>) sc.getAttribute(BG_PROCESSES_LIST); + if (processes == null) { + return; + } + for (final CdiCleanUpBackgroundProcess p : processes) { + try { + p.stop(); + } catch (final RuntimeException e) { + // no-op + } + } + } + + private static class CdiCleanUpBackgroundProcess implements BackgroundProcess { + private volatile int period = 1; // 1s by default + private volatile int acceptRetries = 3; // in case there is latency between this call and registerSession() + private volatile Set<Session> sessions; + private volatile boolean stopped; + + private final WebSocketContainer container; + private final Object lock; + private final WebContext.Instance cdiInstance; + + private CdiCleanUpBackgroundProcess(final WebSocketContainer wsc, final WebContext.Instance cdiInstance, final Object lock) { + this.container = wsc; + this.cdiInstance = cdiInstance; + this.lock = lock; + } + + @Override + public void backgroundProcess() { + if (!hasSession() && --acceptRetries > 0) { + stop(); + } + } + + @Override + public void setProcessPeriod(final int period) { + this.period = period; + } + + @Override + public int getProcessPeriod() { + return period; + } + + private boolean hasSession() { + try { + if (sessions == null) { // needs to be lazy cause tomcat register sessions after + final Map<Endpoint, Set<Session>> sessionsByEndpoint = (Map<Endpoint, Set<Session>>) ENDPOINT_SESSION_MAP.get(container); + if (sessionsByEndpoint != null) { // find sessions + synchronized (lock) { + for (final Map.Entry<Endpoint, Set<Session>> e : sessionsByEndpoint.entrySet()) { + if (e.getKey() == cdiInstance.getValue()) { + sessions = e.getValue(); + break; + } + if (PojoEndpointBase.class.isInstance(e.getKey())) { + try { + final Object pojo = GET_POJO.invoke(e.getKey()); + if (pojo == cdiInstance.getValue()) { + sessions = e.getValue(); + break; + } + } catch (final InvocationTargetException e1) { + // no-op + } + } + } + } + } + } + + synchronized (lock) { + return sessions != null && !sessions.isEmpty(); + } + } catch (final IllegalAccessException e) { + // no-op + } + return false; + } + + public void stop() { + if (stopped) { + return; + } + stopped = true; + try { + cdiInstance.getCreationalContext().release(); + } finally { + BackgroundProcessManager.getInstance().unregister(this); + } + } + } }