Call #RequestScoped Bean from #PreDestroy method - cdi

I have application logic to write a protocol file in a #RequestScoped bean. I would like to write the protocol just before a #SessionScoped bean expires.
#SessionScoped
class Anybean implements Serializable {
#Inject
private ProtocolBean protocolBean;
#PreDestroy
private void writeFinalProtocol() {
protocolBean.writeProtocol();
}
}
I get the error: WELD-000019 Error destroying an instance Managed Bean. Is it generally disallowed to call other beans from #PreDestroy?

OK I found out: As soon as I change the ProtocolBean to #SessionScoped or #ApplicationScoped everything works fine. It seems as if you cannot instantiate a RequestScoped bean wihtin a #PreDestroy method and call a method on it.
The underlaying exception is: com.sun.jdi.InvocationException occurred invoking method

Related

Get new instance of session scoped bean with other name

I have a session scoped bean for a UI to edit some data. It is annotated with #Named and #SessionScoped and all runs in JBoss 6.2. Now I got the requirement for a nearly similar edit UI. The problem is that the two UIs can exist in parallel. So for a perfect reuse it would be nice to create a new instance of the bean with another name. Unfortunately I have no clue how to do this in a clean CDI way.
I don't like it so much to inherit from the bean and give another name. This was one of my ideas.
Another idea was to implement in the managed bean only the business logic and keep the data encapsulated from them and set the data object inside the managed bean when it is needed in the specific context. But maybe there is another CDI way with producers or something?
Changing the scope of the bean to ViewScope makes no sense in my case.
Thanks
Oliver
But maybe there is another CDI way with producers or something
Indeed, you could use a producer.
Kickoff example:
#SessionScoped
public class SessionBean {
#Produces
#Named("foo")
#SessionScoped
public SessionBean getAsFoo() {
return new SessionBean();
}
#Produces
#Named("bar")
#SessionScoped
public SessionBean getAsBar() {
return new SessionBean();
}
// ...
}
(method names are free to your choice)
Usage:
#Inject
#Named("foo")
private SessionBean foo;
#Inject
#Named("bar")
private SessionBean bar;

Injecting ResourceBundle via #ManagedProperty doesn't seem to work inside #Named

How can I access messages bundle from java code to get message according to current locale?
I tried using #ManagedProperty like below:
#Named
#SessionScoped
public class UserBean implements Serializable {
#ManagedProperty("#{msg}")
private ResourceBundle bundle;
// ...
public void setBundle(ResourceBundle bundle) {
this.bundle = bundle;
}
}
However, it remains null. It seems that it doesn't work inside a #Named.
This is how I registered the resource bundle in faces-context.xml:
<application>
<message-bundle>validator.messages</message-bundle>
<locale-config>
<supported-locale>en_US</supported-locale>
<supported-locale>ua_UA</supported-locale>
</locale-config>
<resource-bundle>
<base-name>lang.messages</base-name>
<var>msg</var>
</resource-bundle>
</application>
updated by author:
I get error
16:29:10,968 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/WEBSearchPrime_JB_lang].[Faces Servlet]] (http-localhost-127.0.0.1-8080-1) Servlet.service() for servlet Faces Servlet threw exception: org.jboss.weld.exceptions.IllegalProductException: WELD-000054 Producers cannot produce non-serializable instances for injection into non-transient fields of passivating beans\\n\\nProducer\: Producer Method [PropertyResourceBundle] with qualifiers [#Any #Default] declared as [[method] #Produces public util.BundleProducer.getBundle()]\\nInjection Point\: [field] #Inject private model.UserBean.bundle
note, that I also put Serializable interface
You can't use #javax.faces.bean.ManagedProperty in a CDI managed bean as annotated with #Named. You can only use it in a JSF managed bean as annotated with #ManagedBean.
You need use #javax.faces.annotation.ManagedProperty instead, along with an #Inject. This was introduced in JSF 2.3.
#Inject #javax.faces.annotation.ManagedProperty("#{msg}")
private ResourceBundle bundle;
Noted should be that this gets injected as a #Dependent. So be aware that when you inject this into a #SessionScoped bean, then it would basically become #SessionScoped too and thus stick to the originally injected value forever. So any potential locale changes later on in the session won't be reflected there. If this is a blocker, then you should really inject it into a #RequestScoped or #ViewScoped only, or make use of a #Producer as shown below.
CDI doesn't have native annotations to inject the evaluation result of an EL expression. The CDI approach is using a "CDI producer" with #Produces wherein you return the concrete type, which is PropertyResourceBundle in case of .properties file based resource bundles.
So, if you cannot upgrade to JSF 2.3, then just drop this class somewhere in your WAR:
#RequestScoped
public class BundleProducer {
#Produces
public PropertyResourceBundle getBundle() {
FacesContext context = FacesContext.getCurrentInstance();
return context.getApplication().evaluateExpressionGet(context, "#{msg}", PropertyResourceBundle.class);
}
}
With this, can inject it as below:
#Inject
private PropertyResourceBundle bundle;
In addition to BalusC's answer:
Since JSF 2.3 it is also possible to inject a resource bundle defined in faces-config.xml without the use of a producer method. There is a new annotation javax.faces.annotation.ManagedProperty (note it is in the ...annotation package, not the ...bean package) that works with #Inject:
// ...
import javax.faces.annotation.ManagedProperty;
// ...
#Named
#SessionScoped
public class UserBean implements Serializable {
// ...
#Inject
#ManagedProperty("#{msg}")
private ResourceBundle bundle;
// ...
}
Perhaps I'm getting something wrong, but actually neither of the solutions provided worked for my use case. So I'm providing another answer that worked for me.
With the answer provided by BalusC I encountered the following problems:
As I'm using ajaxbased validation, my beans are #ViewScoped, and
must be serializable. As neither ResourceBundle nor
PropertyResourceBundle are serializable they can't be injected with
#Dependent scope.
If I try to change the producer to use #RequestScoped it also fails
because ResourceBundle is not proxyable as it defines final
methods.
As I'm using JSF 2.3 (JBoss 7.2EAP) I went with the solution provided by Jören Haag and initially it seemed to work. Like the original question, I also have multiple supported locales, and the user can change between locales (in my case ca and es)
The problems I faced with Jören answer are
The ResourceBundle returned is evaluated when the bean is created. In his example he is using a #SessionScoped bean. The ResourceBundle will be resolved with the current locale when the session is created. If the user later changes the locale, the ResourceBundle will not get updated. This also happens with #ViewScoped beans if the user can change the language without changing the view.
I also encountered another issue with beans that need to preload data with a <f:viewAction>. In this case, the bean is instantiated earlier, so the ResourceBundle gets injected before the user locale is set with <f:view locale="#{sessionBean.locale}">. So If the user browser is using es, but the user changed the locale to ca, the bundle will be loaded with es instead, because the view locale is not set to ca with the sessionBean.locale until the render phase.
To overcome this issues that's the solution that worked for me for the use case of the question using injection would be:
#SessionScoped
public class UserBean implements Serializable {
#Inject
#ManagedProperty("#{msg}")
private Instance<ResourceBundle> bundle;
// ....
public void someAction() {
String message = bundle.get().getString("someLabel");
// ...
}
}
As the original question doesn't require injection, only asks how to access a resource bundle with the current locale, a solution without injection, and without the overhead of evaluating EL expression #{msg} every time bundle.get() is called would be:
#SessionScoped
public class UserBean implements Serializable {
#Inject
private FacesContext context;
// ...
public void someAction() {
ResourceBundle bundle = context.getApplication().getResourceBundle(context, "msg");
String message = bundle.getString("someLabel");
// ...
}
}

Injecting a view scoped bean into another view scoped bean

A view scoped bean remains alive as long as the user interacts with the same view (or until it is navigated to a different view).
Suppose a view scoped managed bean is injected into another view scoped bean like so,
#ManagedBean
#ViewScoped
public final class SharableManagedBean implements Serializable
{
private static final long serialVersionUID = 1L;
#EJB
private SharableBean sharableService;
//...Do something.
}
#ManagedBean
#ViewScoped
public final class TestManagedBean implements Serializable
{
private static final long serialVersionUID = 1L;
#EJB
private TestBean testBean;
#ManagedProperty(value="#{sharableManagedBean}")
private SharableManagedBean sharableManagedBean ;
//... Do something with the injected bean.
}
In this case, is it necessary for the SharableManagedBean to have a view scoped bean?
What happens, if it is a request scoped bean (SharableManagedBean)? Is it initialized only once, when TestManagedBean comes into picture and destroyed, when TestManagedBean is destroyed?
Even it's technically possible to do that (JSF allows you to inject beans which are at the same or wider scope) I don't see the point of doing that with #ViewScoped beans. From my point of view, a well designed JSF web application should have a single #ViewScoped bean tied to each specific view. Then, how to solve your issue? You can do it in two ways:
If SharableManagedBean is a utility bean which contain static methods which are not tied to JSF, just define this class as abstract and statically invoke its methods everytime you need.
If SharableManagedBean itself has to be a managed bean which access the FacesContext and has common code that is shared along all the view beans, just create an abstract class and make your #ViewScoped beans extend it.
For your last question (SharableManagedBean being #RequestScoped), JSF doesn't allow you doing that. You'll get an exception because of trying to inject a narrower scoped managed bean.
According to the Oracle docs:
Another important point about managed beans referencing each other is that a managed bean can only refer to other beans provide their scope is equal or has a longer lifespan than the calling object.
Update
If using CDI, it's possible to inject a #RequestScoped bean into a #ViewScoped one too, using the Proxy pattern. Have it a look.

Injecting one view scoped bean in another view scoped bean causes it to be recreated

I need to use some data saved in a view scoped bean in an other view scoped bean.
#ManagedBean
#ViewScoped
public class Attivita implements Serializable {
//
}
and
#ManagedBean
#ViewScoped
public class Nota implements Serializable {
#ManagedProperty("#{attivita}")
private Attivita attivita;
// Getter and setter.
}
Now, maybe my theory about it is still quite poor, I have noticed that when #{attivita} is injected, the Attivita constructor is invoked and thus creating another instance. Is it the right behaviour? What about if I want to reference the same instance and not create a new one?
This will happen if you're navigating from one to the other view on a postback. A view scoped bean is not tied to a request, but to a view. So when you navigate to a new view, it will get a brand new instance of the view scoped bean. It won't reuse the same bean instance which is associated with a previous view.
I understand that the attivita bean is created on the initial view and reused on postback. I understand that nota bean is associated with the new view where you're navigating to. When injecting attivita in it, it will simply get a new and distinct instance even though there's another instance in the very same request. This is all expected (and admittedly a bit unintuitive) behaviour.
There is no standard JSF solution for this. CDI solves this with #ConversationScoped (the bean lives as long as you explicitly tell it to live) and the CDI extension MyFaces CODI goes a bit further with #ViewAccessScoped (the bean lives as long as the navigated view references it).
You could however workaround this by storing the bean as an attribute in the request scope.
#ManagedBean
#ViewScoped
public class Attivita implements Serializable {
public String submit() {
FacesContext.getCurrentInstance().getExternalContext()
.getRequestMap().put("attivita", this);
return "nota";
}
}
and
#ManagedBean
#ViewScoped
public class Nota implements Serializable {
private Attivita attivita;
#PostConstruct
public void init() {
attivita = (Attivita) FacesContext.getCurrentInstance().getExternalContext()
.getRequestMap().get("attivita");
}
}
Note that this is rather hacky. There may be better solutions depending on the concrete functional requirement. Also note that you should in the nota view reference the desired Attivita bean instance as #{nota.attivita} and not as #{attivita}, because it would give you a new and different instance, for the reasons already explained before.
Your attivita bean is #ViewScoped and that doesn't guarantee that your instance will be hold in session. You need a #SessionScoped bean. However, if you need attivita for some reason to be #ViewScoped, then you can pass params through them in other ways, e.g. using viewParam or using other #SessionScoped bean between them.
Page Params
http://mkblog.exadel.com/2010/07/learning-jsf2-page-params-and-page-actions/
JSF 2 Managed Bean Scopes
http://balusc.blogspot.com.es/2011/09/communication-in-jsf-20.html#ManagedBeanScopes

Inject Bean that uses #Named with value

how can I Inject a Bean, that uses a #Named annotation along with a value?
#Named
public class LanguageService{
...
}
public class SomeOtherBean{
#Inject
private LanguageService languageService
}
works without Problem - but how to inject, if i'm using:
#Named("lang")
public class LanguageService{
...
}
#Inject can't have a value as #ManagedProperty does. (But I wan't to stay with CDI)
Edit:
I noticed that it doesn't matter how the bean is named. My Fault that leads to an NPE was simple that i created SomeOtherBean manually, and ofc. no Injection was done. My fault.
CDI selects injectable beans by type (and qualifiers) and not by the annotation parameter. The name is used to address a CDI bean from views, e.g. facelets.

Resources