FacesContext resolves to null in ApplicationScoped bean - jsf

I have a filter: UserSessionFilter.java, that I use to handle user sessions and a ApplicationScoped bean: Config.java that primarily gives me access to a DAO factory.
Config.java
#ManagedBean (eager = true)
#ApplicationScoped
public class Config implements Serializable {...
The filter calls a method in Config to get a DAOFactory object:
Filter method
#Override
public void init(FilterConfig filterConfig) {
daoFactory = Config.getInstance().getDAOFactory();
}
Config method
public static Config getInstance() {
FacesContext facesContext = FacesContext.getCurrentInstance();
return (Config) facesContext.getApplication().evaluateExpressionGet(
facesContext, "#{config}", Config.class);
}
My problem is that the facesContext gets set to null. This problem started occurring after switch from Mojarra to MyFaces, although it seems weird that that would cause it.

FacesContext cannot be used inside the Filter. See this or this answers. Reason:
The FacesContext is created by the FacesServlet and thus only available within any Java code which is processed by the FacesServlet, which covers all JSF artifacts, such as managed beans and phase listeners

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
}

Can I look up a web app context url from a eager ApplicationScoped managed bean?

I want to get a web application context url (ex.: http://myserver:8080/myApp) and store it in the database at startup.
I know how to hook a method call in the startup by using: #ApplicationScoped combined with #ManagedBean(eager=true) and #PostConstruct
And ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() will give me the context path.
However, since the method annotated with #PostConstruct is not being triggered by a request (since it's eager) getRequestContextPath() is giving me null.
As stated in your question, eager #ApplicationScoped bean cannot access to the context in #PostConstruct since there's no request-response cycle. Instead, use ServletContextListener to listen when the application is deployed/undeployed.
public class MyAppListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
//here the application has been deployed
ServletContext servletContext = sce.getServletContext();
String contextPath = servletContext.getContextPath();
//do what you want/need with context path
//...
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
//here the application is being undeployed
}
}
Then just configure the listener properly in web.xml
<listener>
<listener-class>the.package.of.your.MyAppListener</listener-class>
</listener>

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

Injecting FacesContext as managed property of session-scoped managed bean

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);
}
// ...
}

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