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
>> >
>> >
>> >
>
>

Reply via email to