JSF receiving FacesContext outside request - jsf

As far as i know, FacesContext is avalible only in request scope. I've created a thread that tries to receive instance of FacesContext, but it is returning null.
My point is to update some application-scoped beans every 10 seconds.
Thread's run method:
#Override
public void run()
{
while (true)
{
try
{
TimeView timeView = (TimeView)FacesContext.getCurrentInstance().
getExternalContext().getApplicationMap().get("timeView");
// FacesContext.getCurrentInstalce() returns null
timeView.update();
Thread.sleep(10000);
}
catch (InterruptedException ex)
{
System.out.println(ex);
}
}
}
TimeView's header (I've skipped getters/setters):
#ManagedBean(eager=true, name="timeView")
#ApplicationScoped
public class TimeView implements Serializable
{
private int hour;
private int minutes;
private int seconds;
public TimeView()
{
update();
}
public void update()
{
Date date = new Date();
setHour(date.getHours());
setMinutes(date.getMinutes());
setSeconds(date.getSeconds());
}
faces-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0">
<managed-bean>
<managed-bean-name>timeView</managed-bean-name>
<managed-bean-class>foogame.viewBeans.TimeView</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
</faces-config>
So, is there a way to receive refference to my application-scoped beans in this thread?

As there is now way to access/construct FacesContext outside of Servlet environment, I recommend you to pass the application scoped object to the constructor of the worker thread (the thread that performs the batch job). Updating the reference in the thread will result in updating the application scoped reference because they both point to the same instance.
If you have an EJB3 environment you could use EJB timer + #Singleton bean without the need to deal with threads and scopes.

Related

Access ApplicationScoped bean through ServletContext

I have an ApplicationScoped bean that I'd like to access in a quartz job implementation.
That bean holds a hashmap through run-time and I'd like to populate the hashmap when the job runs.
However, the FacesContext is out of context inside the job.
I have access to the ServletContext. Is it possible to access my bean through the ServletContext?
My code to access the Servlet Context:
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
SchedulerContext schedulerContext=null;
try {
schedulerContext=context.getScheduler().getContext();
}
catch (SchedulerException e) {
e.printStackTrace();
}
ServletContext servletContext=(ServletContext)schedulerContext.get("QuartzServletContext");
BOCacheM bOCacheM = (BOCacheM) servletContext.getAttribute("bOCacheM");
}
My QuartzServletContext is defined in web.xml as:
<context-param>
<param-name>quartz:scheduler-context-servlet-context-key</param-name>
<param-value>QuartzServletContext</param-value>
</context-param>
<listener>
<listener-class>
org.quartz.ee.servlet.QuartzInitializerListener
</listener-class>
</listener>
Yes, it's stored as an attribute in ServletContext. Obtain it like any other attribute:
YourApplicationScopedBean bean = servletContext.getAttribute("yourApplicationScopedBeanName");
//use it...
If bean is null then looks like your bean wasn't created when the quartz job started. Make sure the bean is created by adding eager=true to its definition:
#ManagedBean(eager=true)
#ApplicationScoped
public class YourApplicationScopedBean {
//...
#PostConstruct
public void init() {
//initialize your shared resources here...
}
}
Note that eager=true only applies for #ApplicationScoped beans.
If this still doesn't work, seems like your quartz job is being fired even before the bean is created and stored in the application context. It would be better to initialize this resource in the ServletContextListener rather than in an #ApplicationScoped bean and provide access to this resource through another component.

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>

How to inject a CDI Bean in a ManagedBean?

I want to inject a CDI Bean in a ManagedBean either with the annotation #Inject or #Produce. The CDI Bean which I use is:
#Named
#Startup
#ApplicationScoped
public class BaseBean {
private List<String> custs;
public List<String> getCusts() {
return custs;
}
public void setCusts(List<String> emps) {
this.custs = emps;
}
public BaseBean(){
}
#PostConstruct
void init() {
custs = new ArrayList<String>();
custs.add("Cust1");
custs.add("Cust3");
custs.add("Cust2");
custs.add("Cust4");
}
}
The ManagedBean, in which I want to inject the CDI Bean is:
#SessionScoped
#ManagedBean
public class Hello implements Serializable {
#Inject
private BaseBean dBean;
private static final long serialVersionUID = 1L;
private List<String> customers;
private List<String> customersSelect;
public Hello() {
}
#PostConstruct
void init() {
// dBean = new BaseBean();
customers = dBean.getCusts();
}
public List<String> getCustomers() {
return customers;
}
public List<String> getCustomersSelect() {
return customersSelect;
}
public void setCustomersSelect(List<String> customersSelect) {
this.customersSelect = customersSelect;
}
}
In the init function however, it throws NullPointerException. If I use the annotation #Produces instead of #Inject, the result is the same: NullPointerException. Is anything wrong with the CDI Bean (improper annotations)? Do I try to inject it with a wrong way? Does my code lack of something? How can I get it work? Here is the JSF code:
<h:form id ="f">
<h:selectManyCheckbox layout="pageDirection" border="1" value="#{hello.customersSelect}">
<f:selectItems value="#{hello.customers}"></f:selectItems>
</h:selectManyCheckbox><br />
<h:commandButton action="response.xhtml" value="Click me" />
</h:form>
PS: If I use a Stateless Bean as BaseBean and I inject it with the annotation #EJB, it works with no problem.
UPDATE: I have tried it with the annotations #SessionScoped (javax.enterprise.context.SessionScoped) and #Named on the Hello class. Although I do not receive a NullPointerException, the h:selectManyCheckbox is empty. moreover, it strikes me, that when I add the beans.xml file under META-INF folder, I receive a StartException, although the file is there supposed to be. I think my application lacks the proper configuration to be capable of Dependency Injection. What is likely to need additional configuration?
UPDATE 2: This error appears when I add the beans.xml file in the WEB-INF folder. The beans.xml file is empty, it contains only the line :
<?xml version="1.0" encoding="UTF-8"?>
The error is:
Services which failed to start: service jboss.deployment.unit."JSF1.war".PARSE: org.jboss.msc.service.StartException in service jboss.deployment.unit."JSF1.war".PARSE: Failed to process phase PARSE of deployment "JSF1.war"
12:51:11,482 ERROR [org.jboss.as.server.deployment.scanner] (DeploymentScanner-threads - 1) {"JBAS014653: Composite operation failed and was rolled back. Steps that failed:" => {"Operation step-2" => {"JBAS014671: Failed services" => {"jboss.deployment.unit.\"JSF1.war\".PARSE" => "org.jboss.msc.service.StartException in service jboss.deployment.unit.\"JSF1.war\".PARSE: Failed to process phase PARSE of deployment \"JSF1.war\""}}}}
What #patlov is suggesting will work if you use #Named on your CDI beans. However, if you're working in an environment that supports CDI, do not use #ManagedBean. Instead, use CDI all the way. See this answer and I'm sure you could find numerous other ones that strongly advise against what you're trying to do.
Just switch from javax.faces.bean.SessionScoped to javax.enterprise.context.SessionScoped and everything will magically work. What you may run into is the absense of #ViewScoped from CDI however, in which case use something like JBoss Seam or Apache Deltaspike that implement it for you. As an added benefit, they will also automatically replace all of the JSF scopes with CDI scopes automatically if you already have existing code written for JSF.
Update:
This should be the content of your beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>
Make sure you have enabled CDI by putting a WEB-INF/beans.xml file in your application.
If you are using #ManagedBean use #ManagedProperty to inject properties:
#ManagedProperty(value = "#{baseBean}")
private BaseBean dBean;
// getter and setter

How to retrieve a session scoped managed bean in a PhaseListener?

I'm having troubles in setting an Authorization Listener (that implements PhaseListener) to manage authentication and autorization.
More specifically, I set a session scoped bean called SessionBean:
#ManagedBean
#SessionScoped
public class SessionBean{
private String loggedUser;
public SessionBean(){
logger.info("Sono nel costruttore di SessionBean()");
}
public String login(){
....
}
...
}
And in my sun-web.xml:
<managed-bean>
<managed-bean-name>SessionBean</managed-bean-name>
<managed-bean-class>it.uniroma3.acme.auction.bean.SessionBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
In login() I make controls on username/password and if successful I set "loggedUser".
My AuthorizationListener is:
public class AuthorizationListener implements PhaseListener {
private String currentUser;
private SessionBean sessionBean;
public void afterPhase(PhaseEvent event) {
SessionBean sessionBean = (SessionBean)event.getFacesContext().getExternalContext().getSessionMap().get("SessionBean");
String currentUser = sessionBean.getLoggedUser();
if (sessionBean != null) {
...
String currentUser = sessionBean.getLoggedUser();
}
else {
...
}
}
...
}
And in the sun-web.xml:
<!-- Authentication and authorization block starting -->
<lifecycle>
<phase-listener>AuthorizationListener</phase-listener>
</lifecycle>
<navigation-rule>
<from-view-id>/*</from-view-id>
<navigation-case>
<from-outcome>loginPage</from-outcome>
<to-view-id>login.jsf</to-view-id>
</navigation-case>
</navigation-rule>
<!-- Authentication and authorization block ending -->
But I receive a null pointe sessionBean.getLoggedUser(). So, the SessionBean is still not created when the AuthorizationListener check the user. This is why I added a "if SessionBean doesn't exists, create it and put it in SessionMap", but still not working.
I'm not forced to use this approach for authentication and authorization, but what I need is to avoid "session.setAttribute("username", username). So any other strategy would be really apreciated.
Thanks,
Andrea
EDIT: as BalusC suggested, I edited the afterPhase method. Still having troubles with always null SessionBean.
The #ManagedProperty works in #ManagedBean classes only. Your PhaseListener isn't.
You need to manually get it from the session map.
SessionBean sessionBean = (SessionBean) event.getFacesContext()
.getExternalContext().getSessionMap().get("SessionBean");
Note that it can be still null on the very first HTTP request.
The common approach, however, is to use a servlet Filter for the job, not a PhaseListener. The session scoped managed bean is available as a session attribute.
SessionBean sessionBean = (SessionBean) session.getAttribute("sessionBean");
Here in your code, in your phaseListener, you did not tell in which Phase exactly you would implement the code and you should call your managed bean. You should do this in RESTORE_VIEW Phase in AuthorizationListener override method getPhaseId(). See the following:
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
If your managed bean is still null, remove managed bean annotations, register it in faces_config, and use this helper method in phase listener to call the managed bean:
public static Object resolveExpression(String expression) {
FacesContext ctx = FacesContext.getCurrentInstance();
Application app = ctx.getApplication();
ValueBinding bind = app.createValueBinding(expression);
return bind.getValue(ctx);
}
To call it in after phase method, use:
public void afterPhase(PhaseEvent event) {
SessionBean sessionBean =(SessionBean)resolveExpression("#{SessionBean}");
String currentUser = sessionBean.getLoggedUser();

Detect JSF session timeout/refresh/back/forward and return to login screen

I have a JSF 1.2 application that has a session going on and whenever the session timeouts or the user presses the browse back or refresh page, the session gets messed up and things start to behave unexpectedly.
I would like to simply bring the user back to a predefined login screen whenever that happens.
Authentication is handled inside the JSF application.
Thanks in Advance!
Try using PhaseListener
Example:
MyPhaseListener.java
public class MyPhaseListener implements PhaseListener {
public void afterPhase(PhaseEvent event) {
//If you have a login, so you have a user in session. Try to retrieve this value
//and it will return null if the user is not logged in or theres no more session
//and...
if (null == FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("myUserInSession") {
//Redirect to login using mapped navigation configs in faces-config.xml
try {
NavigationHandler nh = FacesContext.getCurrentInstance().getApplication().getNavigationHandler();
nh.handleNavigation(FacesContext.getCurrentInstance(), null, "stringToReturnLogin");
} catch (Exception e) {
}
//OR using redirect
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("http://localhost:xxxx/App_Context/Page.xhtml");
} catch (Exception e) {
}
}
}
public void beforePhase(PhaseEvent event) {
//Do nothing
}
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
}
Configuring the listener in faces-config.xml
<faces-config version="1.2" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
(...)
<lifecycle>
<phase-listener>yourPackage.MyPhaseListener</phase-listener>
</lifecycle>
(...)
</faces-config>
Yes, you dont need to put ".java".
I know this is late but here goes anyway.
You probably want to put the following in your web.xml
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/faces/sales/index.xhtml</location>
</error-page>
That will redirect the ViewExpiredException. I guess that will hold for any other exceptions that come up.

Resources