Cleaning up CDI WebSocketScope behavior

This commit is contained in:
Joakim Erdfelt 2015-03-17 10:22:23 -07:00
parent 1e3fe991e3
commit bb3243182e
18 changed files with 215 additions and 117 deletions

View File

@ -34,17 +34,12 @@ public class SimpleBeanStore
public Map<Contextual<?>, List<ScopedInstance<?>>> beans = new HashMap<>();
public List<ScopedInstance<?>> getBeans(Contextual<?> contextual)
{
if (LOG.isDebugEnabled())
LOG.debug("getBeans({})",contextual);
return beans.get(contextual);
}
public void addBean(ScopedInstance<?> instance)
{
if (LOG.isDebugEnabled())
{
LOG.debug("addBean({})",instance);
}
List<ScopedInstance<?>> instances = getBeans(instance.bean);
if (instances == null)
{
@ -54,20 +49,46 @@ public class SimpleBeanStore
instances.add(instance);
}
public void clear()
{
beans.clear();
}
public void destroy()
{
if (LOG.isDebugEnabled())
LOG.debug("destroy() - {} beans", beans.size());
{
LOG.debug("destroy() - {} beans",beans.size());
}
for (List<ScopedInstance<?>> instances : beans.values())
{
if (LOG.isDebugEnabled())
LOG.debug("destroying - {} instance(s)", instances.size());
{
LOG.debug("destroying - {} instance(s)",instances.size());
}
for (ScopedInstance<?> instance : instances)
{
if (LOG.isDebugEnabled())
LOG.debug("destroying {}",instance);
{
LOG.debug("destroying instance {}",instance);
}
instance.destroy();
}
}
}
public List<ScopedInstance<?>> getBeans(Contextual<?> contextual)
{
if (LOG.isDebugEnabled())
{
LOG.debug("getBeans({})",contextual);
}
return beans.get(contextual);
}
@Override
public String toString()
{
return String.format("%s@%X[size=%d]",this.getClass().getSimpleName(),hashCode(),beans.size());
}
}

View File

@ -47,12 +47,14 @@ public class JettyWeldInitializer
webapp.addSystemClass("org.jboss.classfilewriter.");
webapp.addSystemClass("org.jboss.logging.");
webapp.addSystemClass("com.google.common.");
webapp.addSystemClass("org.eclipse.jetty.cdi.websocket.annotation.");
// don't hide weld classes from webapps (allow webapp to use ones from system classloader)
webapp.addServerClass("-org.jboss.weld.");
webapp.addServerClass("-org.jboss.classfilewriter.");
webapp.addServerClass("-org.jboss.logging.");
webapp.addServerClass("-com.google.common.");
webapp.addServerClass("-org.eclipse.jetty.cdi.websocket.annotation.");
}
public static void initContext(ContextHandler handler) throws NamingException

View File

@ -22,26 +22,35 @@ import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.websocket.Session;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* Producer of {@link javax.websocket.Session} instances
*/
public class JavaWebSocketSessionProducer
{
private ThreadLocal<Session> sessionInstance;
public JavaWebSocketSessionProducer()
{
sessionInstance = new ThreadLocal<Session>();
}
public void setSession(Session sess)
{
sessionInstance.set(sess);
}
private static final Logger LOG = Log.getLogger(JavaWebSocketSessionProducer.class);
@Produces
public Session getSession(InjectionPoint injectionPoint)
{
return this.sessionInstance.get();
if (LOG.isDebugEnabled())
{
LOG.debug("getSession({})",injectionPoint);
}
org.eclipse.jetty.websocket.api.Session sess = WebSocketScopeContext.current().getSession();
if (sess == null)
{
throw new IllegalStateException("No Session Available");
}
if (sess instanceof javax.websocket.Session)
{
return (Session)sess;
}
throw new IllegalStateException("Incompatible Session, expected <" + javax.websocket.Session.class.getName() + ">, but got <"
+ sess.getClass().getName() + "> instead");
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.cdi.websocket;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import org.eclipse.jetty.cdi.websocket.annotation.WebSocketScope;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
@ -31,24 +32,24 @@ import org.eclipse.jetty.websocket.api.Session;
public class JettyWebSocketSessionProducer
{
private static final Logger LOG = Log.getLogger(JettyWebSocketSessionProducer.class);
private ThreadLocal<Session> sessionInstance;
public JettyWebSocketSessionProducer()
{
LOG.debug("ctor<>");
sessionInstance = new ThreadLocal<Session>();
}
public void setSession(Session sess)
{
LOG.debug("setSession()");
sessionInstance.set(sess);
}
@Produces
public Session getSession(InjectionPoint injectionPoint)
{
LOG.debug("getSession(" + injectionPoint + ")");
return this.sessionInstance.get();
if (LOG.isDebugEnabled())
{
LOG.debug("getSession({})",injectionPoint);
}
WebSocketScopeContext ctx = WebSocketScopeContext.current();
if (ctx == null)
{
throw new IllegalStateException("Not in a " + WebSocketScope.class.getName());
}
org.eclipse.jetty.websocket.api.Session sess = ctx.getSession();
if (sess == null)
{
throw new IllegalStateException("No Session Available");
}
return sess;
}
}

View File

@ -22,7 +22,6 @@ import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
@ -33,6 +32,7 @@ import javax.inject.Inject;
import org.eclipse.jetty.cdi.core.AnyLiteral;
import org.eclipse.jetty.cdi.core.ScopedInstance;
import org.eclipse.jetty.cdi.core.SimpleBeanStore;
import org.eclipse.jetty.cdi.websocket.annotation.WebSocketScope;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
@ -42,42 +42,40 @@ import org.eclipse.jetty.websocket.api.Session;
* <p>
* A CDI Context definition for how CDI will use objects defined to belong to the WebSocketScope
*/
@Dependent
public class WebSocketScopeContext implements Context
{
private static final Logger LOG = Log.getLogger(WebSocketScopeContext.class);
public static ThreadLocal<SimpleBeanStore> state = new ThreadLocal<>();
private static ThreadLocal<WebSocketScopeContext> current = new ThreadLocal<>();
public static WebSocketScopeContext current()
{
return current.get();
}
private SimpleBeanStore beanStore;
@Inject
private BeanManager beanManager;
@Inject
private JettyWebSocketSessionProducer jettySessionProducer;
@Inject
private JavaWebSocketSessionProducer javaSessionProducer;
private ThreadLocal<org.eclipse.jetty.websocket.api.Session> session = new ThreadLocal<>();
public void begin()
{
if (LOG.isDebugEnabled())
{
LOG.debug("begin()");
LOG.debug("{} begin()",this);
}
if (state.get() != null)
{
return;
}
state.set(beanStore);
current.set(this);
}
public void create()
{
if (LOG.isDebugEnabled())
{
LOG.debug("create()");
LOG.debug("{} create()",this);
}
current.set(this);
beanStore = new SimpleBeanStore();
}
@ -85,7 +83,7 @@ public class WebSocketScopeContext implements Context
{
if (LOG.isDebugEnabled())
{
LOG.debug("destroy()");
LOG.debug("{} destroy()",this);
}
beanStore.destroy();
@ -95,13 +93,9 @@ public class WebSocketScopeContext implements Context
{
if (LOG.isDebugEnabled())
{
LOG.debug("end()");
LOG.debug("{} end()",this);
}
if (state.get() == null)
{
return;
}
state.remove();
beanStore.clear();
}
@SuppressWarnings({ "unchecked" })
@ -110,15 +104,22 @@ public class WebSocketScopeContext implements Context
{
if (LOG.isDebugEnabled())
{
LOG.debug("get({})",contextual);
LOG.debug("{} get({})",this,contextual);
}
SimpleBeanStore store = state.get();
if (store == null)
Bean<T> bean = (Bean<T>)contextual;
if (bean.getBeanClass().isAssignableFrom(Session.class))
{
return (T)this.session;
}
if (beanStore == null)
{
return null;
}
List<ScopedInstance<?>> beans = store.getBeans(contextual);
List<ScopedInstance<?>> beans = beanStore.getBeans(contextual);
if ((beans != null) && (!beans.isEmpty()))
{
@ -134,25 +135,28 @@ public class WebSocketScopeContext implements Context
{
if (LOG.isDebugEnabled())
{
LOG.debug("get({},{})",contextual,creationalContext);
LOG.debug("{} get({},{})",this,contextual,creationalContext);
}
Bean<T> bean = (Bean<T>)contextual;
SimpleBeanStore store = state.get();
if (store == null)
if (bean.getBeanClass().isAssignableFrom(Session.class))
{
store = new SimpleBeanStore();
state.set(store);
return (T)this.session;
}
List<ScopedInstance<?>> beans = store.getBeans(contextual);
if (beanStore == null)
{
beanStore = new SimpleBeanStore();
}
List<ScopedInstance<?>> beans = beanStore.getBeans(contextual);
if ((beans != null) && (!beans.isEmpty()))
{
for (ScopedInstance<?> instance : beans)
{
// TODO: need to work out the creational context comparison logic better
if (instance.creationalContext.equals(creationalContext))
if (instance.bean.equals(bean))
{
return (T)instance.instance;
}
@ -165,7 +169,7 @@ public class WebSocketScopeContext implements Context
customInstance.bean = bean;
customInstance.creationalContext = creationalContext;
customInstance.instance = t;
store.addBean(customInstance);
beanStore.addBean(customInstance);
return t;
}
@ -199,13 +203,28 @@ public class WebSocketScopeContext implements Context
return (T)beanManager.getReference(bean,clazz,cc);
}
public void setSession(Session sess)
public void setSession(org.eclipse.jetty.websocket.api.Session sess)
{
LOG.debug("setSession({})",sess);
jettySessionProducer.setSession(sess);
if (sess instanceof javax.websocket.Session)
if (LOG.isDebugEnabled())
{
javaSessionProducer.setSession((javax.websocket.Session)sess);
LOG.debug("{} setSession({})",this,sess);
}
current.set(this);
this.session.set(sess);
}
public org.eclipse.jetty.websocket.api.Session getSession()
{
if (LOG.isDebugEnabled())
{
LOG.debug("{} getSession()",this);
}
return this.session.get();
}
@Override
public String toString()
{
return String.format("%s@%X[%s]",this.getClass().getSimpleName(),hashCode(),beanStore);
}
}

View File

@ -18,16 +18,19 @@
package org.eclipse.jetty.cdi.websocket;
import javax.enterprise.context.Destroyed;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import org.eclipse.jetty.cdi.websocket.annotation.WebSocketScope;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* Register the various WebSocket specific components for CDI
* Register the various WebSocket specific components for CDI
*/
public class WebSocketScopeExtension implements Extension
{
@ -52,4 +55,20 @@ public class WebSocketScopeExtension implements Extension
// Register our context
event.addContext(new WebSocketScopeContext());
}
public void logWsScopeInit(@Observes @Initialized(WebSocketScope.class) org.eclipse.jetty.websocket.api.Session sess)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Initialized @WebSocketScope - {}",sess);
}
}
public void logWsScopeDestroyed(@Observes @Destroyed(WebSocketScope.class) org.eclipse.jetty.websocket.api.Session sess)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Destroyed @WebSocketScope - {}",sess);
}
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.cdi.websocket;
package org.eclipse.jetty.cdi.websocket.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;

View File

@ -1 +1 @@
org.eclipse.jetty.demo.ws.WebSocketScopeExtension
org.eclipse.jetty.cdi.websocket.WebSocketScopeExtension

View File

@ -42,7 +42,6 @@ import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
public class CdiAppTest
@ -156,7 +155,6 @@ public class CdiAppTest
}
@Test
@Ignore
public void testWebSocket_Info_DataFromCdi() throws Exception
{
WebSocketClient client = new WebSocketClient();

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.cdi.websocket.cdiapp;
import javax.inject.Inject;
import org.eclipse.jetty.cdi.websocket.annotation.WebSocketScope;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
@ -29,6 +30,7 @@ public class DataMaker
private static final Logger LOG = Log.getLogger(DataMaker.class);
@Inject
@WebSocketScope
private Session session;
public void processMessage(String msg)

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.cdi.websocket.scope;
package org.eclipse.jetty.cdi.websocket.wsscope;
import java.io.IOException;
import java.net.InetSocketAddress;
@ -45,6 +45,12 @@ public class BogusSession implements Session
{
this.id = id;
}
@Override
public String toString()
{
return String.format("BogusSession[id=%s]",id);
}
public String getId()
{

View File

@ -16,17 +16,17 @@
// ========================================================================
//
package org.eclipse.jetty.cdi.websocket.scope;
package org.eclipse.jetty.cdi.websocket.wsscope;
import javax.inject.Inject;
import org.eclipse.jetty.cdi.websocket.WebSocketScope;
import org.eclipse.jetty.cdi.websocket.annotation.WebSocketScope;
import org.eclipse.jetty.websocket.api.Session;
public class BogusSocket
{
@WebSocketScope
@Inject
@WebSocketScope
private Session session;
public Session getSession()

View File

@ -16,12 +16,12 @@
// ========================================================================
//
package org.eclipse.jetty.cdi.websocket.scope;
package org.eclipse.jetty.cdi.websocket.wsscope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.eclipse.jetty.cdi.websocket.WebSocketScope;
import org.eclipse.jetty.cdi.websocket.annotation.WebSocketScope;
@WebSocketScope
public class Food
@ -30,6 +30,16 @@ public class Food
private boolean destroyed = false;
private String name;
public Food()
{
// default constructor (for CDI use)
}
public Food(String name)
{
this.name = name;
}
@PreDestroy
void destroy()
{
@ -100,4 +110,10 @@ public class Food
{
this.name = name;
}
@Override
public String toString()
{
return String.format("%s@%X[%s]",Food.class.getSimpleName(),hashCode(),name);
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.cdi.websocket.scope;
package org.eclipse.jetty.cdi.websocket.wsscope;
import javax.inject.Inject;

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.cdi.websocket.scope;
package org.eclipse.jetty.cdi.websocket.wsscope;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -29,6 +29,7 @@ import org.eclipse.jetty.cdi.core.AnyLiteral;
import org.eclipse.jetty.cdi.core.ScopedInstance;
import org.eclipse.jetty.cdi.core.logging.Logging;
import org.eclipse.jetty.cdi.websocket.WebSocketScopeContext;
import org.eclipse.jetty.cdi.websocket.annotation.WebSocketScope;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
import org.junit.AfterClass;
@ -54,6 +55,11 @@ public class WebSocketScopeBaselineTest
weld.shutdown();
}
/**
* Test behavior of {@link WebSocketScope} in basic operation.
* <p>
* Food is declared as part of WebSocketScope, and as such, only 1 instance of it can exist.
*/
@Test
public void testScopeBehavior() throws Exception
{
@ -75,11 +81,12 @@ public class WebSocketScopeBaselineTest
assertThat("Meal 1 Entree Constructed",meal1.getEntree().isConstructed(),is(true));
assertThat("Meal 1 Side Constructed",meal1.getSide().isConstructed(),is(true));
/* TODO: enable when CreationalContext lookup is fixed
assertThat("Meal parts not the same",meal1.getEntree(),not(sameInstance(meal1.getSide())));
assertThat("Meal entrees are the same",meal1.getEntree(),not(sameInstance(meal2.getEntree())));
assertThat("Meal sides are the same",meal1.getSide(),not(sameInstance(meal2.getSide())));
*/
/* Since Food is annotated with @WebSocketScope, there can only be one instance of it
* in use with the 2 Meal objects.
*/
assertThat("Meal parts not the same",meal1.getEntree(),sameInstance(meal1.getSide()));
assertThat("Meal entrees are the same",meal1.getEntree(),sameInstance(meal2.getEntree()));
assertThat("Meal sides are the same",meal1.getSide(),sameInstance(meal2.getSide()));
meal1Bean.destroy();
meal2Bean.destroy();
@ -88,15 +95,16 @@ public class WebSocketScopeBaselineTest
{
wsScope.end();
}
Food entree1 = meal1.getEntree();
Food side1 = meal1.getSide();
assertThat("Meal 1 entree destroyed",meal1.getEntree().isDestroyed(),is(false));
assertThat("Meal 1 side destroyed",meal1.getSide().isDestroyed(),is(false));
assertThat("Meal 1 entree destroyed",entree1.isDestroyed(),is(false));
assertThat("Meal 1 side destroyed",side1.isDestroyed(),is(false));
wsScope.destroy();
/*
assertThat("Meal 1 entree destroyed",meal1.getEntree().isDestroyed(),is(true));
assertThat("Meal 1 side destroyed",meal1.getSide().isDestroyed(),is(true));
*/
// assertThat("Meal 1 entree destroyed",entree1.isDestroyed(),is(true));
// assertThat("Meal 1 side destroyed",side1.isDestroyed(),is(true));
wsScopeBean.destroy();
}

View File

@ -16,7 +16,10 @@
// ========================================================================
//
package org.eclipse.jetty.cdi.websocket.scope;
package org.eclipse.jetty.cdi.websocket.wsscope;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Set;
import java.util.concurrent.Callable;
@ -26,6 +29,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.Bean;
import org.eclipse.jetty.cdi.core.AnyLiteral;
@ -57,7 +61,7 @@ public class WebSocketScopeSessionTest
{
weld.shutdown();
}
@Test
public void testSessionActivation() throws Exception
{
@ -73,8 +77,7 @@ public class WebSocketScopeSessionTest
wsScope.setSession(sess);
ScopedInstance<BogusSocket> sock1Bean = newInstance(BogusSocket.class);
BogusSocket sock1 = sock1Bean.instance;
/* TODO: when CreationalContext is fixed
assertThat("Socket 1 Session",sock1.getSession(),sameInstance((Session)sess)); */
assertThat("Socket 1 Session",sock1.getSession().toString(),is(sess.toString()));
sock1Bean.destroy();
}
@ -105,8 +108,7 @@ public class WebSocketScopeSessionTest
wsScope1.setSession(sess);
ScopedInstance<BogusSocket> sock1Bean = newInstance(BogusSocket.class);
BogusSocket sock1 = sock1Bean.instance;
/* TODO: when CreationalContext is fixed
assertThat("Socket 1 Session",sock1.getSession(),sameInstance((Session)sess)); */
assertThat("Socket 1 Session",sock1.getSession(),sameInstance((Session)sess));
sock1Bean.destroy();
}
finally
@ -126,8 +128,7 @@ public class WebSocketScopeSessionTest
wsScope2.setSession(sess);
ScopedInstance<BogusSocket> sock2Bean = newInstance(BogusSocket.class);
BogusSocket sock2 = sock2Bean.instance;
/* TODO: when CreationalContext is fixed
assertThat("Socket 2 Session",sock2.getSession(),sameInstance((Session)sess)); */
assertThat("Socket 2 Session",sock2.getSession(),sameInstance((Session)sess));
sock2Bean.destroy();
}
finally
@ -166,8 +167,7 @@ public class WebSocketScopeSessionTest
ScopedInstance<BogusSocket> sock1Bean = newInstance(BogusSocket.class);
BogusSocket sock1 = sock1Bean.instance;
/* TODO: when CreationalContext is fixed
assertThat("Socket 1 Session",sock1.getSession(),sameInstance((Session)sess)); */
assertThat("Socket 1 Session",sock1.getSession(),sameInstance((Session)sess));
ret = sock1.getSession();
sock1Bean.destroy();
}
@ -207,8 +207,7 @@ public class WebSocketScopeSessionTest
BogusSocket sock2 = sock2Bean.instance;
ret = sock2.getSession();
/* TODO: when CreationalContext is fixed
assertThat("Socket 2 Session",sock2.getSession(),sameInstance((Session)sess)); */
assertThat("Socket 2 Session",sock2.getSession(),sameInstance((Session)sess));
sock2Bean.destroy();
}
finally
@ -230,8 +229,7 @@ public class WebSocketScopeSessionTest
Session sess1 = fut1.get(1,TimeUnit.SECONDS);
Session sess2 = fut2.get(1,TimeUnit.SECONDS);
/* TODO: when CreationalContext is fixed
assertThat("Sessions are different", sess1, not(sameInstance(sess2))); */
assertThat("Sessions are different", sess1, not(sameInstance(sess2)));
}
@SuppressWarnings({ "rawtypes", "unchecked" })

View File

@ -1 +0,0 @@
org.eclipse.jetty.cdi.websocket.WebSocketScopeExtension

View File

@ -7,7 +7,7 @@ org.jboss.LEVEL=INFO
# org.eclipse.jetty.websocket.common.LEVEL=DEBUG
# org.eclipse.jetty.util.DecoratedObjectFactory.LEVEL=DEBUG
org.eclipse.jetty.cdi.LEVEL=DEBUG
# org.eclipse.jetty.cdi.LEVEL=DEBUG
# org.eclipse.jetty.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=DEBUG