Hello again everyone,
I apologize for the wall of text last in the last email.
Gerhard,
I looked into the implementation of WindowContext and I like the idea
of using a session scoped bean that would destroy view scoped beans in
its @PreDestroy. Boo me, I forgot I can pass the bean manager to the
context at AfterBeanDiscovery, then use it to access such a bean. So I
implemented it, see Listing 1. In this implementation I don't listen
for PreDestroyViewMapEvents, since I don't think this adds much
benefit at this point, but we might want to implement it before JSF
spec 2.2 when they're actually fired.
But, I also like the idea of weak references. Consider for example a
very long running session during which a user navigates through
numerous views, i.e. creates a lot of bean instances. With the
implementation in Listing 1, @PostConstruct in those instances won't
be called until the session ends.
If we could destroy the instances created on the first view, after it
is navigated from, but before the session is destroyed, that would be
an improvement imo. Suppose one the first view, a bean opens a
connection, but relies on the @PostConstruct to close it. If the
session lives for 30 minutes, with the implementation from Listing 1,
that connection would stay open for 30 minutes. Listing 2,
demonstrates the use of weak references to facilitate destroying view
scoped beans, on the first invocation of one of the view context
methods, right after the next GC cycle. This implementation does not
create threads.
Let me know what you guys think, I'd like to try working on a patch if
any of this sounds good.
---=== Listing 1 ===---
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
public class ViewContext implements Context {
private final BeanManager beanManager;
public ViewContext(BeanManager beanManager) {
this.beanManager = beanManager;
}
private UIViewRoot getViewRoot() {
FacesContext fc = FacesContext.getCurrentInstance();
return fc == null ? null : fc.getViewRoot();
}
@Override
public Class<? extends Annotation> getScope() {
return ViewScoped.class;
}
private ViewBeanManager getViewBeanManager() {
Set<Bean<?>> beans =
this.beanManager.getBeans(ViewBeanManager.class);
Bean<? extends Object> bean = this.beanManager.resolve(beans);
CreationalContext<? extends Object> creationalContext =
this.beanManager.createCreationalContext(bean);
return (ViewBeanManager) this.beanManager.getReference(
bean, ViewBeanManager.class, creationalContext);
}
@Override
public <T> T get(final Contextual<T> contextual,
final CreationalContext<T> creationalContext) {
UIViewRoot viewRoot = this.getViewRoot();
ViewBeanManager storage = this.getViewBeanManager();
if (viewRoot == null || storage == null) {
throw new ContextNotActiveException();
}
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
Map<String, Object> viewMap = viewRoot.getViewMap();
if (viewMap.containsKey(name)) {
return (T) viewMap.get(name);
} else {
T instance = storage.create(contextual, creationalContext);
viewMap.put(name, instance);
return instance;
}
} else {
return null;
}
}
@Override
public <T> T get(Contextual<T> contextual) {
UIViewRoot viewRoot = this.getViewRoot();
if (viewRoot == null) {
throw new ContextNotActiveException();
}
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
Map<String, Object> viewMap = viewRoot.getViewMap();
return (T) viewMap.get(name);
} else {
return null;
}
}
@Override
public boolean isActive() {
return this.getViewRoot() != null;
}
}
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
@SessionScoped
public class ViewBeanManager implements Serializable {
public class Info<T> implements Serializable {
private final T instance;
private final Contextual<T> contextual;
private final CreationalContext<T> creationalContext;
private Info(Contextual<T> contextual,
CreationalContext<T> creationalContext) {
this.contextual = contextual;
this.creationalContext = creationalContext;
this.instance = this.contextual.create(this.creationalContext);
}
}
private transient Set<Info> storage;
@PostConstruct
protected void postConstruct() {
this.storage = new HashSet<Info>();
}
public synchronized <T> T create(Contextual<T> contextual,
CreationalContext<T> creationalContext) {
Info<T> info = new Info<T>(
contextual, creationalContext);
this.storage.add(info );
return info .instance;
}
@PreDestroy
protected void preDestroy() {
for (Info info : this.storage) {
info .contextual.destroy(
info .instance, info .creationalContext);
}
}
}
---===Listing 2 ===---
import java.io.Serializable;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.faces.component.UIViewRoot;
@SessionScoped
public class ViewReferenceManager implements Serializable {
// identifier for the tracker object
private static final String TRACKER =
ViewReferenceManager.class.getName() + '.' + "TRACKER";
// info is now a weak reference
private class Info<T> extends WeakReference<Object> implements
Serializable {
private final T instance;
private final Contextual<T> contextual;
private final CreationalContext<T> creationalContext;
private Info(Object tracker,
ReferenceQueue<Object> queue,
Contextual<T> contextual,
CreationalContext<T> creationalContext) {
super(tracker, queue);
this.contextual = contextual;
this.creationalContext = creationalContext;
this.instance = this.contextual.create(this.creationalContext);
}
}
private transient ReferenceQueue<Object> queue;
private transient Set<Info> storage;
@PostConstruct
protected void postConstruct() {
this.queue = new ReferenceQueue<Object>();
this.storage = new HashSet<Info>();
}
// called during every get() of the context object
protected synchronized void removeStaleReferences() {
Info info = (Info) this.queue.poll();
while (info != null) {
this.storage.remove(info);
info.contextual.destroy(info.instance, info.creationalContext);
info = (Info) this.queue.poll();
}
}
protected synchronized <T> T create(UIViewRoot viewRoot,
Contextual<T> contextual, CreationalContext<T> creationalContext) {
// get or create marker object
Object tracker;
Map<String, Object> attributes = viewRoot.getAttributes();
if (attributes.containsKey(ViewReferenceManager.TRACKER)) {
tracker = attributes.get(ViewReferenceManager.TRACKER);
} else {
tracker = System.currentTimeMillis();
attributes.put(ViewReferenceManager.TRACKER, tracker);
}
Info<T> info = new Info<T>(tracker, this.queue, contextual,
creationalContext);
this.storage.add(info);
return info.instance;
}
@PreDestroy
protected void preDestroy() {
for (Info info : this.storage) {
info.contextual.destroy(
info.instance, info.creationalContext);
}
}
}
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
public class ViewContext implements Context {
private final BeanManager beanManager;
public ViewContext(BeanManager beanManager) {
this.beanManager = beanManager;
}
private UIViewRoot getViewRoot() {
FacesContext fc = FacesContext.getCurrentInstance();
return fc == null ? null : fc.getViewRoot();
}
@Override
public Class<? extends Annotation> getScope() {
return ViewScoped.class;
}
private ViewReferenceManager getReferenceManager() {
Set<Bean<?>> beans =
this.beanManager.getBeans(ViewReferenceManager.class);
Bean<? extends Object> bean = this.beanManager.resolve(beans);
CreationalContext<? extends Object> creationalContext =
this.beanManager.createCreationalContext(bean);
return (ViewReferenceManager) this.beanManager.getReference(
bean, ViewReferenceManager.class, creationalContext);
}
@Override
public <T> T get(final Contextual<T> contextual,
final CreationalContext<T> creationalContext) {
UIViewRoot viewRoot = this.getViewRoot();
ViewReferenceManager referenceManager = this.getReferenceManager();
if (viewRoot == null || referenceManager == null) {
throw new ContextNotActiveException();
} else {
referenceManager.removeStaleReferences();
}
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
Map<String, Object> viewMap = viewRoot.getViewMap();
if (viewMap.containsKey(name)) {
return (T) viewMap.get(name);
} else {
T instance = referenceManager.create(
viewRoot, contextual, creationalContext);
viewMap.put(name, instance);
return instance;
}
} else {
return null;
}
}
@Override
public <T> T get(Contextual<T> contextual) {
UIViewRoot viewRoot = this.getViewRoot();
ViewReferenceManager referenceManager = this.getReferenceManager();
if (viewRoot == null || referenceManager == null) {
throw new ContextNotActiveException();
} else {
referenceManager.removeStaleReferences();
}
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
Map<String, Object> viewMap = viewRoot.getViewMap();
return (T) viewMap.get(name);
} else {
return null;
}
}
@Override
public boolean isActive() {
return this.getViewRoot() != null;
}
}
Radu Creanga
On Mon, Nov 19, 2012 at 7:50 PM, Radu Creanga <[email protected]> wrote:
> Hello everyone,
>
> Pete,
>> We aren't firing servlet events in CDI 1.1 due to backwards incompatibility
>> issues with extensions.
> Hmm, I was going off of [1]. There it stills says that it will be done, but
> ok.
>
> [1] http://jcp.org/en/jsr/detail?id=346
>
> Mark,
>> Yes, we already discussed this and agreed to import it.
> Sounds good.
>> Txs 4 your report!
> Happy to help, this is fun.
>
> Gerhard,
>> #1
> This should work.
>
>> #2
> I need to familiarize myself better with view-access-scope.
>
>> #3
> Also with window-scoped.
>
> Intuitively all should work just fine, which one we'll pick will prob
> depend on ease / reliability.
>
> I've been working on this since last night for our internal project
> since I can't move on without a reliable implementation, so I'd like
> to add the idea to the list.
>
> #4 I thought it would be a good idea to make a BeanInfo object
> extending WeakReference that would store the bean instance, the
> Contextual object, and the CreationalContext object with strong
> references, but store a weak reference to the view map itself and
> place the bean instance (not the BeanInfo) in the view map. Also, have
> the Context keep a reference queue and use a clean up thread that
> blocks on the queue until some weak reference has been cleared -
> which, won't happen until the view map would be up for GC. I thought
> that, even though JSF doesn't fire the PreDestroyViewMapEvent, it
> would at least release the map for GC, but it appears that this is not
> the case (the implementation reuses them?). Anyhow, I store a marker
> object in the view attributes map and link a weak reference to that
> object while still placing all the bean instances in the view map.
> Once the view goes out of scope, all the associated bean instances get
> properly destroyed right after the next CG cycle. This works for my
> needs for now as I don't need them to be destroyed right away although
> it is important that they get disposed properly eventually. It may not
> be a good general purpose implementation since depending on the server
> load, the next GC cycle may not come for quite some time. Also, I
> don't know how this play out with serialization, I'm guessing not so
> well however.
>
> Here's a rough copy / paste from my implementation:
>
> package com.concensus.athena.framework.cdi.extension;
>
> import java.lang.annotation.Annotation;
> import java.lang.ref.ReferenceQueue;
> import java.lang.ref.WeakReference;
> import java.util.HashSet;
> import java.util.Map;
> import java.util.Set;
> import javax.enterprise.context.ContextNotActiveException;
> import javax.enterprise.context.spi.Context;
> import javax.enterprise.context.spi.Contextual;
> import javax.enterprise.context.spi.CreationalContext;
> import javax.enterprise.inject.spi.Bean;
> import javax.faces.component.UIViewRoot;
> import javax.faces.context.FacesContext;
>
> public class ViewContext implements Context {
>
> private class BeanInfo<T> extends WeakReference<Object> {
>
> private final T instance;
> private final Contextual<T> contextual;
> private final CreationalContext<T> creationalContext;
>
> public BeanInfo(Object pin,
> ReferenceQueue<Object> queue,
> Contextual<T> contextual,
> CreationalContext<T> creationalContext) {
> super(pin, queue);
> this.contextual = contextual;
> this.creationalContext = creationalContext;
> this.instance = this.contextual.create(this.creationalContext);
> }
>
> public T getInstance() {
> return this.instance;
> }
>
> public void dispose() {
> this.contextual.destroy(this.instance, this.creationalContext);
> }
>
> }
>
> private boolean run;
> // needed to make sure the weak references themselves don't become
> stale before they serve their purpose
> private final Set<BeanInfo> set;
> private final ReferenceQueue<BeanInfo> queue;
> private final Thread disposer;
>
> public ViewContext() {
> this.run = true;
> this.set = new HashSet<BeanInfo>();
> this.queue = new ReferenceQueue<BeanInfo>();
> this.disposer = new Thread(new Runnable() {
>
> @Override
> public void run() {
> BeanInfo beanInfo;
> while (run) {
> try {
> beanInfo = (BeanInfo) queue.remove();
> set.remove(beanInfo);
> beanInfo.dispose();
> } catch (InterruptedException e) {
> // log
> }
> }
> }
>
> });
> this.disposer.start();
> }
>
> private UIViewRoot getViewRoot() {
> FacesContext fc = FacesContext.getCurrentInstance();
> return fc == null ? null : fc.getViewRoot();
> }
>
> private static final String MARKER =
> ViewContext.class.getName() + '.' + "MARKER";
>
> // make sure to use one marker per view for all of it's instances
> private Object getMarker(Map<String, Object> attributes) {
> if (attributes.containsKey(ViewContext.MARKER)) {
> return attributes.get(ViewContext.MARKER);
> } else {
> Long marker = System.currentTimeMillis();
> attributes.put(ViewContext.MARKER, marker);
> return marker;
> }
> }
>
> @Override
> public Class<? extends Annotation> getScope() {
> return ViewScoped.class;
> }
>
> @Override
> public <T> T get(final Contextual<T> contextual,
> final CreationalContext<T> creationalContext) {
> UIViewRoot viewRoot = this.getViewRoot();
> if (viewRoot == null) {
> throw new ContextNotActiveException();
> }
> if (contextual instanceof Bean) {
> Bean bean = (Bean) contextual;
> String name = bean.getName();
> Map<String, Object> viewMap = viewRoot.getViewMap();
> if (viewMap.containsKey(name)) {
> return (T) viewMap.get(name);
> } else {
> Object marker = this.getMarker(viewRoot.getAttributes());
> BeanInfo<T> beanInfo = new BeanInfo(
> marker, this.queue, contextual, creationalContext);
> T instance = beanInfo.getInstance();
> viewMap.put(name, instance);
> this.set.add(beanInfo);
> return instance;
> }
> } else {
> return null;
> }
> }
>
> @Override
> public <T> T get(Contextual<T> contextual) {
> UIViewRoot viewRoot = this.getViewRoot();
> if (viewRoot == null) {
> throw new ContextNotActiveException();
> }
> if (contextual instanceof Bean) {
> Bean bean = (Bean) contextual;
> String name = bean.getName();
> Map<String, Object> viewMap = viewRoot.getViewMap();
> return (T) viewMap.get(name);
> } else {
> return null;
> }
> }
>
> @Override
> public boolean isActive() {
> return this.getViewRoot() != null;
> }
>
> // this is bad, yes, does @PreDestroy work on Context objects?
> @Override
> protected void finalize() throws Throwable {
> try {
> this.run = false;
> } finally {
> super.finalize();
> }
> }
>
> }
>
> Cheers,
> Radu Creanga
>
>
> On Mon, Nov 19, 2012 at 5:35 PM, Gerhard Petracek
> <[email protected]> wrote:
>> hi radu,
>>
>> imo we have 3 easy options:
>>
>> #1
>> we continue to rely on jsf-events and just add a session-scoped bean which
>> triggers the cleanup via its @PreDestroy callback (if the cleanup wasn't
>> done already).
>> (we also need one to cleanup the window-context.)
>>
>> #2
>> since we also need to track the view-ids for the view-access-scope, we just
>> use a similar implementation (which follows the rules of the view-scope) and
>> map it to @ViewScoped.
>>
>> #3
>> like #2 but with the help of the window-context (instead of the view-map) ->
>> similar to [1].
>>
>> regards,
>> gerhard
>>
>> [1]
>> http://os890.blogspot.co.at/2011/06/session-based-view-scope-with-codi.html
>>
>>
>>
>>
>> 2012/11/19 Mark Struberg <[email protected]>
>>>
>>>
>>>
>>> Hi Radu!
>>>
>>> Yes, we already discussed this and agreed to import it.
>>> Txs 4 your report!
>>>
>>> LieGrue,
>>> strub
>>>
>>>
>>>
>>>
>>>
>>> >________________________________
>>> > From: Radu Creanga <[email protected]>
>>> >To: [email protected]
>>> >Sent: Sunday, November 18, 2012 11:56 PM
>>> >Subject: Re: [jira] [Created] (DELTASPIKE-293) Improve the
>>> > ViewScopedContext by observing ServletContext and HttpSession lifecycle
>>> > events.
>>> >
>>> >Hello everyone,
>>> >
>>> >It seems this will require ServletContext events and Session events to
>>> >be published to the CDI event bus, which in turn requires adding the
>>> >appropriate listeners to web.xml until CDI 1.1. I know Seam Solder has
>>> >these listeners implemented. Are there plans to import it into
>>> >DeltaSpike?
>>> >
>>> >Radu Creanga
>>> >
>>> >
>>> >On Sun, Nov 18, 2012 at 4:32 PM, Radu Creanga (JIRA) <[email protected]>
>>> > wrote:
>>> >> Radu Creanga created DELTASPIKE-293:
>>> >> ---------------------------------------
>>> >>
>>> >> Summary: Improve the ViewScopedContext by observing
>>> >> ServletContext and HttpSession lifecycle events.
>>> >> Key: DELTASPIKE-293
>>> >> URL:
>>> >> https://issues.apache.org/jira/browse/DELTASPIKE-293
>>> >> Project: DeltaSpike
>>> >> Issue Type: Improvement
>>> >> Components: JSF-Module
>>> >> Affects Versions: 0.4-incubating
>>> >> Reporter: Radu Creanga
>>> >> Fix For: 0.5-incubating
>>> >>
>>> >>
>>> >> The CDI specification states that Context implementations are
>>> >> responsible for destroying instances it creates. The current
>>> >> ViewScopedContext relies on PreDestroyViewMapEvents to be notified when a
>>> >> view map is destroyed. But, the JSF 2.0 and 2.1 spec only fire this event
>>> >> when a view map is replaced. This means that in most cases, instances
>>> >> created by ViewScopedContext are not properly destroyed. The
>>> >> ViewScopedContext should be observing ServletContext and HttpSession
>>> >> lifecycle events instead in order to ensure that all instances it creates
>>> >> are properly destroyed. Visible improvements resulting out of this would
>>> >> be
>>> >> that the @PostConstruct method of @ViewScoped beans is invoked.
>>> >> Additionally, this will probably result in better memory usage, since
>>> >> instances that are not properly destroyed are not eligible for GC.
>>> >>
>>> >> --
>>> >> This message is automatically generated by JIRA.
>>> >> If you think it was sent incorrectly, please contact your JIRA
>>> >> administrators
>>> >> For more information on JIRA, see:
>>> >> http://www.atlassian.com/software/jira
>>> >
>>> >
>>> >
>>
>>