Hello everyone,

Gerhard, you're right, it's way simpler than that. I looked over the
rules again like you suggested and sure I was complicating things.

So, the rules:

1. A @ViewScoped bean will live as long as you're submitting the form
to the same view again and again. In other words, as long as when the
action method(s) returns null or even void, the bean will be there in
the next request. Once you navigate to a different view, then the bean
will be trashed.

For this we can continue to rely on JSF events, or use a navigation
observer like in #3.

2. The case when that event is not fired is when the session expires
but no navigation has been performed. For example, 2 get requests to
the same URL will result in the view for the first request to never
get cleaned up.

For this, a session bean would be enough.

The implementation currently present in DS destroys instances in 1,
but not in 2. So, assuming these are the only requirements, the
attached patch adds support for 2 (using a session bean, no weak
references or anything like that.).

Anyhow, this may not be what you all are looking for but it is quite
simple and working (basically #1).

Gerhard, I considered both #2 and #3, and I understand how it would
work given your article and reading documentation. But, I don't yet
have a good grasp of the code base to implement them. I'll continue
looking into it, maybe something good comes out.

Happy Thanksgiving to all the people who celebrate it tomorrow. :)

Radu Creanga


On Wed, Nov 21, 2012 at 9:22 AM, Pete Muir <[email protected]> wrote:
>
> On 20 Nov 2012, at 00:50, Radu Creanga 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.
>
> This was proposed but in the end the EG decided not to include it.
>
> The JCP page reflects what is proposed, not what is actually included :-)
>
>>
>> [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
>>>>>
>>>>>
>>>>>
>>>
>>>
>

Reply via email to