Expire specific managed bean instance after time interval
Asked Answered
H

1

5

I have 2 JSF managed beans A and B and I need to expire/destruct/destroy A after 2 minutes and B after 5 minutes. I checked this related question Timing out from a bean, but it is expiring whole session. I do not want to expire whole session.

How can I achieve this with a custom scope?

Higdon answered 10/6, 2015 at 6:32 Comment(7)
You cant do that if the container is managing your beansBorn
@Dummy they are JSF/manage bean not ejbs !! I am managing the scopes of JSF beans !!Higdon
Well you didnt say explicitly, you can use session beans as your jsf backing beans technically. If so, havent you thought about timer service?Born
Do you actually need to destroy them? Or is there a different usescase for which you think destroying them is the only solution? It's the first time in many years I hear this requirementHildahildagard
@Hildahildagard I really want to destroy the beans.Higdon
Ok, good luck (sorry no idea how to do that other than creating a custom scope)Hildahildagard
For such a requirement, I would create a custom scope (assuming you're using CDI) (sorry for the overlap @Hildahildagard ;)Luann
I
7

Given that you're using JSF bean management facility (and thus not CDI, which would require a completely different answer), you can achieve this with @CustomScoped. The @CustomScoped value must refer a Map implementation in a broader, usually existing, scope.

Something like:

@ManagedBean
@CustomScoped("#{timeoutScope}")
public class TimeoutBean {}

As the @CustomScoped annotation doesn't support passing additional arguments, setting the timeout can only be done via an additional custom annotation like below:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Timeout {

    /** Minutes. */
    int value();

}
@ManagedBean
@CustomScoped("#{timeoutScope}")
@Timeout(5) // Expires after 5 minutes.
public class TimeoutBean {}

Now, here's a kickoff example of how the #{timeoutScope} looks like, including @PostConstruct support (automatically) and @PreDestroy support (manually):

@ManagedBean
@SessionScoped
public class TimeoutScope extends HashMap<String, Object> {

    private static final long serialVersionUID = 1L;

    @Override
    public Object put(String name, Object bean) {
        Timeout timeout = bean.getClass().getAnnotation(Timeout.class);

        if (timeout == null) {
            throw new IllegalArgumentException("@Timeout annotation is required on bean " + name);
        }

        Long endtime = System.nanoTime() + (timeout.value() * (long) 6e10);
        Object[] beanAndEndtime = new Object[] { bean, endtime };
        return super.put(name, beanAndEndtime);
    }

    @Override
    public Object get(Object key) {
        Object[] beanAndEndtime = (Object[]) super.get(key);

        if (beanAndEndtime == null) {
            return null;
        }

        Object bean = beanAndEndtime[0];
        Long endtime = (Long) beanAndEndtime[1];

        if (System.nanoTime() > endtime) {
            String name = (String) key;
            ScopeContext scope = new ScopeContext("timeoutScope", Collections.singletonMap(name, bean));
            FacesContext context = FacesContext.getCurrentInstance();
            context.getApplication().publishEvent(context, PreDestroyCustomScopeEvent.class, scope);
            return null;
        }

        return bean;
    }

}

You see, it's session scoped and implements Map. As to the scope, this way it's tied to a specific user session, not to the whole application. If you actually want to share the bean across all user sessions in the application, then make it application scoped instead. As to the Map, henever JSF needs to find a managed bean, it first tries get(). If it returns null (i.e. bean doesn't exist yet), then it will auto-create the managed bean instance and perform a put().

Inside the put(), it's a matter of extracting and calculating the timeout and store it in the map. Inside the get(), you just check the timeout and return null to indicate JSF that bean doesn't exist anymore. JSF will then simply auto-create it and come back at put(), etc.

Do note that I'm using System#nanoTime() instead of System#currentTimeMillis() as the latter is tied to OS (operating system) time, not to hardware time (and it's thus sensitive to a.o. DST and enduser-controlled changes in time).

Inviolable answered 12/6, 2015 at 8:16 Comment(5)
So there is no way @PreDestroy support (automatically) ? I actually want that @PreDestroy calls as soon as bean's time ends !!Higdon
Perhaps you missed the updated answer or didn't actually try it or didn't read the code line by line?Inviolable
Well I read your updated answer and code line-byline !! What I understand is that @PreDestroy support is manually NOT automatically !! Can it be automatically, like whenever Bean's time out it will call get or @PreDestroy automatically ?? Please let me know if I missed anything from your answer. ThanksHigdon
As a tip: Never use "!!" or "??", nor overuse bold formatting in formal English. This way you come over like a jerk and I'm very tempted to just delete the answer and ignore your future questions. As to the question in the comment, the code doesn't programmatically trigger @PostConstruct as that's already done by JSF automatically. The code only manually triggers @PreDestroy as that's not done by JSF automatically. The line with publishEvent() does that. Give it a try before asking new questions.Inviolable
Thanks for reply and tip. I will take care of it in future.Higdon

© 2022 - 2024 — McMap. All rights reserved.