Access ApplicationScoped bean through ServletContext - jsf

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.

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>

How to inject value of initParam into managed bean?

I have a context-param in web.xml:
<context-param>
<description>Version number will be prefixed to url of requests</description>
<param-name>version_id</param-name>
<param-value>11</param-value>
</context-param>
I want to inject this into a ManagedBean
This bean has a scope None
I tried the code below but it's not working, I'm getting an exception on startup with this error:
The scope of the object referenced by expression #{initParam[version_id]}, application, is shorter than the referring managed beans (responseData) scope of none
#ManagedBean
#NoneScoped
public class ResponseData implements Serializable {
#ManagedProperty(value = "#{initParam.version_id}")
private String version;
public ResponseData() {
}
/**
* #param version the version to set
*/
public void setVersion(String version) {
this.version = version;
}
}
What is the right way to inject value of context-param into a managed bean as a managed property?
What you are attempting is that you are trying to get the value of a managed property that has a smaller scope than the ResponseData managed bean that is NoneScoped.
You should be able to get the context params from the ServletContext however without needing to reference another bean.
ServletContext servletContext = (ServletContext) FacesContext
.getCurrentInstance().getExternalContext().getContext();
servletContext.getInitParameter("version_id");

Rollback transaction inside managed bean

I would like to rollback transaction not inside EJB but inside JSF managed bean. Inside EJB we can use SessionContext.setRollBackOnly() but what can I use in managed bean ?
#Stateless
#Local(AccountLocal.class)
public class AccountBean implements AccountLocal {
public void test1() throws CustomException(){
...
}
public void test2() throws CustomException(){
...
throw new CustomException();
}
public void test3() throws CustomException(){
...
}
public void all() throws CustomException(){
test1();
test2();
test3();
}
}
In my managed bean :
#SessionScoped
public class LoginBean implements Serializable{
public void test(){
try{
accountBean.test1();
accountBean.test2();
accountBean.test3();
}catch(CustomException e){
// WHAT HERE TO ROLLBACK TRANSACTION ?
}
}
}
EDIT : How can I ensure that if one of the test1, test2 or test3 rolls back, others will roll back too ?
I tested this code and accountBean.test1(); is validated even if accountBean.test2(); rolls back.
Could the solution be only to nest this 3 methods inside one EJB method ?
#SessionScoped
public class LoginBean implements Serializable{
public void test(){
try{
accountBean.all();
}catch(CustomException e){
...
}
}
}
Transactions are automatically rolled back by the EJB container if an unchecked exception is thrown (note that JPA's PersistenceException is such one). Your CustomException seems to be a checked exception. If changing it to extend RuntimeException as follows
public class CustomException extends RuntimeException {
// ...
}
or creating a new one is not an option, then you need to set the #ApplicationException annotation on the class with the rollback attribute set to true.
E.g.
#ApplicationException(rollback=true)
public class CustomException extends Exception {
// ...
}
Please note that the concrete problem has nothing to do with JSF. The service layer and managing transactions is completely outside the responsibility of JSF. It's the responsibility of EJB instead. JSF should merely act as "view" in this perspective.
See also:
JSF Service Layer
Handling service layer exception in Java EE frontend method
I'm playing the Devil's advocate here, since BalusC's advice that you should not let your backing beans act as services is absolutely true.
But, purely as a technical excersise, it -is- possible to start a JTA transaction in a backing bean and then control start and commit or rollback programmatically.
You can do this by injecting a UserTransaction via #Resource. Prior to calling your EJB methods, call start on this instance, and after the last call either commit or rollback.
Again, this is a purely theoretical answer. In practice, don't do this and let the backing bean call 1 EJB method that calls out to other EJB beans if needed.

Invoking action method in the application startup (JSF)

We need to call a action method while invoking the first page of the application. For example, the first page is index.jsp, when we are directly calling this page the action method is not called. To achieve that, we have written another page where it uses java script to click on the button and call the action method, that navigates to the index.jsp.
I feel that there should be proper way in JSF to achieve this task. What is the bet way to do that? I have told the team that we can call the action method in the constructor while loading the page. Is it the correct way? What are the possible solutions?
Just do the job in #PostConstruct method of an application scoped bean which is is eagerly constructed or which is at least bound to the page.
#ManagedBean(eager=true)
#ApplicationScoped
public class Bean {
#PostConstruct
public void init() {
// Here.
}
}
Alternatively, if JSF (read: the FacesContext) has no relevant role in the actual job, you can also use a ServletContextListener.
#WebListener
public class Config implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
// Do stuff during webapp startup.
}
public void contextDestroyed(ServletContextEvent event) {
// Do stuff during webapp shutdown.
}
}
If you're not on Servlet 3.0 yet, register it in web.xml as follows.
<listener>
<listener-class>com.example.Config</listener-class>
</listener>
See also:
Using special auto start servlet to initialize on startup and share application data
Using JSF 2.0
If you want to take some action when your application starts (even if is not yet accesed ), you can use a SystemEventListener and subscribe it to PostConstructApplicationEvent.
Example of the listener:
package listeners;
import javax.faces.application.Application;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ListenerFor;
import javax.faces.event.PostConstructApplicationEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
public class MySystemListener implements SystemEventListener{
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
System.out.println("started");
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof Application;
}
}
To suscribe you have to include this fragment in the faces-config.xml
<application>
<system-event-listener>
<system-event-listener-class>
listeners.MySystemListener
</system-event-listener-class>
<system-event-class>
javax.faces.event.PostConstructApplicationEvent
</system-event-class>
</system-event-listener>
</application>
And if you want to take the action when the user enters to a specific page, you could use another system event and f:event tag to receive a notification before the page is displayed.
For example:
...
<h:body>
<f:event type="preRenderView" listener="#{bean.action}"/>
<h:form>
<!--components-->
</h:form>
</h:body>
...
Here are more details on using system events: http://andyschwartz.wordpress.com/2009/07/31/whats-new-in-jsf-2/#system-events.
In JSF 1.2, one way I think you could receive a notification will be with PhaseListener's and check the id of the view currently rendering.

Resources