Injecting FacesContext as managed property of session-scoped managed bean - jsf

I had a request-scoped JSF 1.2 managed bean that I needed to refactor to session-scoped bean because it is doing some expensive operation on #PostConstruct and that is being called multiple times which really needs to be done only once. The side effect of changing the scope to session is now I cannot inject FacesContext anymore in faces-config.xml by doing like this:
<managed-property>
<property-name>context</property-name>
<value>#{facesContext}</value>
</managed-property>
where I have
setContext(FacesContext ctx) {}
in my managed bean.
In one of my action methods I need the context to access ExternalContext/HttpServletResponse. I don't want to invoke
FacesContext.getCurrentInstance();
inside my action method but somehow call setContext(FacesContext ctx) externally to allow isolation of context injection for ease of mocked testing. I tried putting the setContext() inside the #PostConstruct only to realize later that FacesContext is a per request thing and my ExternalContext was reset to null once a new request is being submitted.
How could I call setContext(FacesContext ctx) auto-magically every time I hit a new request although the managed bean itself is session scoped?

Keep your request scoped bean and inject the session scoped bean in it instead so that you can pass the FacesContext to it in the #PostConstruct of the request scoped bean. In the session scoped bean, perform lazy loading/executing.
E.g.
public class RequestBean {
private FacesContext context; // Managed property.
private SessionBean sessionBean; // Managed property.
#PostConstruct
public void init() {
sessionBean.init(context);
}
// ...
}
and
public class SessionBean {
private SomeObject initializedObject;
public void init(FacesContext context) {
if (initializedObject != null) {
return;
}
initializedObject = initializeObject(context);
}
// ...
}

Related

Getting JSF ServletContext in non-request environment in a CDI bean

I am using TomEE+ 1.7.1.
With JSF managed beans this code was working well:
#ManagedBean( eager = true )
#ApplicationScoped
public class AppBean {
#PostConstruct
public void init() {
ServletContext sc = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
if (GlobalSettings.TESTMODE) {
sc.getSessionCookieConfig().setDomain("." + GlobalSettings.APP_DOMAIN_TEST);
} else {
sc.getSessionCookieConfig().setDomain("." + GlobalSettings.APP_DOMAIN);
}
}
}
The init function ran at application startup and ServletContext was available.
I read everywhere that it's time to migrate to CDI beans instead of JSF beans. So I wanted to change #ManagedBean( eager = true ) to #Named #Eager (#Eager is from Omnifaces). Init function is running at application startup, but there is no FacesContext so I can't get ServletContext.
General question: How to get ServletContext in a non-request environment in CDI beans? (ServletContext is not a 'per request' object, so it should exist before the first request.)
Specific question: how to set the domain for the session cookies dynamically from code but before the first request occurs?
You should be using a ServletContextListener for the purpose of performing programmatic configuration on a servlet based application.
#WebListener
public class Config implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
// ...
}
#Override
public void contextDestroyed(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
// ...
}
}
A #WebListener is inherently also CDI managed and thus you can just use #Inject and friends in there.
An application scoped managed bean is intented for holding application scoped data/state which can be used/shared across requests/views/sessions.
Per the CDI spec, you can #Inject the ServletContext into a CDI bean. Just be sure to do it in a #PostConstruct, as injected fields are available only after construction:
#Inject ServletContext extCtxt;
#PostConstruct
public void doSomething(){
// do something with your injected field
}

Is it possible to inject a view scoped bean into a #FacesValidator in JSF?

As the title implies, I'm trying to inject a view scoped bean into a validator decorated by #FacesValidator as follows.
#FacesValidator(value = "priceRangeValidator")
public final class PriceRangeValidator implements Validator {
#ManagedProperty(value="#{rangeSliderBean}")
private RangeSliderBean rangeSliderBean; //Setter only.
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
// The bean instance is unavailable here. It is null.
}
}
The target view scoped bean - RangeSliderBean is unavailable in the validate() method.
I'm temporarily following the following way to get an instance of that bean in the validate() method itself.
RangeSliderBean rangeSliderBean = context.getApplication().evaluateExpressionGet(context, "#{rangeSliderBean}", RangeSliderBean.class);
Is it possible to inject a view scoped JSF managed bean into a validator using the #ManagedProperty annotation or something else?
I'm using JSF 2.2.6.
UPDATE:
This does not work on Mojarra 2.3.0-m01. The bean instance still remains null as it did before. This time long after this post, I took a corresponding view scoped CDI bean (#ManagedProperty was replaced by #Inject).
Try dynamic Injection by evaluating an expression using Application like this.
FacesContext facesContext = FacesContext.getCurrentInstance();
RangeSliderBean rangeSliderBean = facesContext.getApplication().evaluateExpressionGet(facesContext, "#{rangeSliderBean}", RangeSliderBean .class);

How to pass bean property from one view to another view

I'm using JSF 2.1 and Primefaces:
I have a view scoped managed bean with a managed property and a method that set something on other view scoped managed bean and forward to other page referencing that managed bean:
#ManagedBean
#ViewScoped
public class HelloMB {
#ManagedProperty("otherMB")
private OtherMB other;
public String changeOtherMB() {
otherMB.setAnyObject(new Object());
return "otherPage.xhtml";
}
}
#ManagedBean
#ViewScoped
public class OtherMB {
private Object o;
public void setAnyObject(Object o) {
this.o = o;
}
}
So, when otherPage is rendered o is null.
You have idea how could I solve this? How can I retain an Object in a #ViewScoped managed bean and keep it live on other page without using #SessionScoped?
The view scope is destroyed and recreated once you navigate to a different JSF view. You know, the view scope lives as long as you're interacting with the same JSF view. In this particular case you effectively end up with two instances of the #{otherMB} managed bean during one request. One instance which is used by the source view and another instance which is used by the destination view.
As the second view is created within the very same request, you could just pass it as a request attribute.
#ManagedBean
#ViewScoped
public class HelloMB implements Serializable {
public String changeOtherMB() {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.getRequestMap().put("anyObject", anyObject);
return "otherPage.xhtml";
}
}
#ManagedBean
#ViewScoped
public class OtherMB {
private Object anyObject;
#PostConstruct
public void init() {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
this.anyObject = ec.getRequestMap().get("anyObject");
}
}
I however wonder if you're aware about the importance of idempotent vs non-idempotent requests. Perhaps you actually need a "plain vanilla" link without the need to invoke a view scoped bean action method. See the last "See also" link below for an extensive example on that.
See also:
How to choose the right bean scope?
How to navigate in JSF? How to make URL reflect current page (and not previous one)
Creating master-detail pages for entities, how to link them and which bean scope to choose

PrimeFaces <p:poll> refresh invalidates backing bean member if bean ViewScoped

I am using PrimeFaces UI library and JSF 2.
I have a backing bean:
public class JobMgmtBean extends ClientBeanBase implements Serializable
and
public class ClientBeanBase extends BeanBase
(so inheritance is JobMgmtBean:ClientBeanBase:BeanBase).
I wanted to set my JobMgmtBean from request scoped to view scoped, but after a while my sessionVars which is defined in BeanBase becomes null and the bean is not functional anymore.
I initialize sessionVars in the BeanBase like this:
protected Map<String,Object> sessionVars = null;
ex = FacesContext.getCurrentInstance().getExternalContext();
sessionVars = ex.getSessionMap();
I refresh some of my PrimeFaces UI components on the page every 5 seconds (using <p:poll interval="5"...>), and after a few refreshes sessionVars becomes null.
Why does this happen?
You can use View scope provided you can assemble the state of object during de-serialization.
Java provides method hooks for a serializable class where you can perform custom logic.
private void writeObject(java.io.ObjectOutputStream stream) throws java.io.IOException {
//custom logic
stream.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException {
stream.defaultReadObject();
// custom logic
}
Any bean reference you think you dont want serialize you can mark it as transient.
private transient Bean bean.
this bean wont get serialized but the problem is you are responsible
to set the reference back when it is deserailized in method hook
"readObject"
private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException {
stream.defaultReadObject();
// custom logic
this.bean = ................
}
ViewScoped beans require objects to be Serialized, and my class extends many classes with too many object which all need to be Serialized which is not possible. This means that I can not use ViewScoped at all here.

Can't #Inject a #ManagedBean in another #ManagedBean

Ok here is my session bean. I can always retrieve the currentUser from any Servlet or Filter. That's not the problem The problem is the fileList, and currentFile. I've tested with simple int's and Strings and its' the same effect. If I set a value from my view scoped bean I can get the data from another class.
#ManagedBean(name = "userSessionBean")
#SessionScoped
public class UserSessionBean implements Serializable, HttpSessionBindingListener {
final Logger logger = LoggerFactory.getLogger(UserSessionBean.class);
#Inject
private User currentUser;
#EJB
UserService userService;
private List<File> fileList;
private File currentFile;
public UserSessionBean() {
fileList = new ArrayList<File>();
currentFile = new File("");
}
#PostConstruct
public void onLoad() {
Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
String email = principal.getName();
if (email != null) {
currentUser = userService.findUserbyEmail(email);
} else {
logger.error("Couldn't find user information from login!");
}
}
Here is an example.
My view scoped bean. This is how it is decorated.
#ManagedBean
#ViewScoped
public class ViewLines implements Serializable {
#Inject
private UserSessionBean userSessionBean;
Now the code.
userSessionBean.setCurrentFile(file);
System.out.println("UserSessionBean : " + userSessionBean.getCurrentFile().getName());
I can see the current file name perfectly. This is actually being printed from a jsf action method. So obviously the currentFile is being set.
Now if I do this.
#WebFilter(value = "/Download")
public class FileFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpSession session = ((HttpServletRequest) request).getSession(false);
UserSessionBean userSessionBean = (UserSessionBean) session.getAttribute("userSessionBean");
System.out.println(userSessionBean.getCurrentUser().getUserId()); //works
System.out.println("File filter" + userSessionBean.getCurrentFile().getName()); //doesn't work
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
currentUser shows up fine but I can't see the file. It's just blank. The same thing happens with Strings, int's, etc.
Thanks for any help you can provide on this.
INFO: UserSessionBean : Line 3B--8531268875812004316.csv (value printed from view scoped bean)
INFO: File filter tester.csv (value printed when filter is ran.)
**EDIT**
This worked.
FacesContext context = FacesContext.getCurrentInstance();
userSessionBean = (UserSessionBean) context.getApplication().evaluateExpressionGet(context, "#{userSessionBean}", UserSessionBean.class);
I put this in the constructor of the ViewScoped and everything was fine. Now why isn't the inject doing what I thought? At first I thought maybe because I was using JSF managed beans instead of the new CDI beans. But I changed the beans to the new style(with named) and that was the same effect.
Does the inject only allow you to access the beans but not change their attributes?
You're mixing JSF and CDI. Your UserSessionBean is a JSF #ManagedBean, yet you're using CDI #Inject to inject it in another bean. CDI doesn't reuse the JSF managed one, it instead creates a brand new one. Use the one or the other, not both. The correct annotation to inject a JSF-managed bean is #ManagedProperty.
Replace
#Inject
private UserSessionBean userSessionBean;
by
#ManagedProperty(value="#{userSessionBean}")
private UserSessionBean userSessionBean;
and ensure that you don't have a import javax.enterprise.context anywhere in your code (which is the package of CDI annotations).
Alternatively, migrate all JSF bean management annotations to CDI bean management annotations.
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
#Named
#SessionScoped
public class UserSessionBean implements Serializable {}
import javax.inject.Named;
import javax.faces.view.ViewScoped;
#Named
#ViewScoped
public class ViewLines implements Serializable {}
Additional advantage is that you can just #Inject it inside a regular servlet or filter without the need to manually grab it as request/session/application attribute.
Moreover, JSF bean management annotations are deprecated since JSF 2.3. See also Backing beans (#ManagedBean) or CDI Beans (#Named)?
My best GUESS as to why this is happening, is because the variable file, is being set in view scope, and then passed by reference into the session scoped bean. Maybe this is happening because when the view scope bean is destroyed, it still has a reference to that variable, but doesn't bother to see if there's any other references to it in session scope, where it should be preserved. Hence, when it's destroyed, it's removed from both view and session scope in this case.
Could you try calling setCurrentFile with an object instantiated with 'new'? That might prove or disprove this hypothesis of mine.
Otherwise, my best advice would be to crack open the debugger, and see exactly where getCurrentFile is being changed.

Resources