I've built a simple ear application consisting of an EJB and a WAR module. I'm using CDI interceptor to intercept the call to JSF actions to do some operation. Everything is working fine except for a strange behaviour that arises when I annotate the action method using the interceptorbiding annotation. To be more precise, here is the code:
This is the interceptor biding class
#Inherited
#Documented
#InterceptorBinding
#Retention(RUNTIME)
#Target({METHOD, TYPE})
public #interface SecurityCheck {
}
This is the interceptor class:
#Interceptor
#SecurityCheck
#Priority(Interceptor.Priority.APPLICATION)
public class SecurityCheckInterceptor implements Serializable {
#Inject
SecurityService secutiyService;
#AroundInvoke
public Object securityIntercept(InvocationContext ic) throws Exception {
String target = // build the fully qualified method name from ic
System.out.println("---- ENTER: ["+target+"]");
Object out = ic.proceed();
System.out.println("---- EXIT: ["+target+"]");
return out;
}
}
And these are the two alternative of usage of the interceptor in my JSF bean class:
// METHOD N. 1
#Named
#ViewScoped
public class IndexController implements Serializable {
...
#Interceptors({SecurityCheckInterceptor.class})
public void doAction() {
System.out.println("indexController.doAction()");
}
}
and
// METHOD N. 2
#Named
#ViewScoped
public class IndexController implements Serializable {
...
#SecurityCheck
public void doAction() {
System.out.println("indexController.doAction()");
}
}
when I use the method N. 1 the output is as follow:
---- ENTER: [it.univaq.we2018.tutor.controller.IndexController.doAction()]
indexController.doAction()
---- EXIT: [it.univaq.we2018.tutor.controller.IndexController.doAction()]
so everything is as expected, but using Method n.2 (which I prefer) the output is as follow:
---- ENTER: [it.univaq.we2018.tutor.controller.IndexController.doAction()]
---- ENTER: [it.univaq.we2018.tutor.controller.IndexController.doAction()]
indexController.doAction()
---- EXIT: [it.univaq.we2018.tutor.controller.IndexController.doAction()]
---- EXIT: [it.univaq.we2018.tutor.controller.IndexController.doAction()]
The interceptor is called twice in a "nested" fashion.
Is this a bug? Have I missed something?
I'm using Java 1.8_172, Payara 5.181, NetBeans 8.2 and JavaEE 7 profile. The application is based on the javaee7 maven archetype. All the annotation are CDI and not the JSF ones (eg. ViewScoped).
Thank you.
Related
In a JSF project, we wrote our own PartialViewContext to listen to some events fired by pages beans:
#RequestScoped
public class OurPartialViewContext extends PartialViewContextWrapper
{
...
// called by cdi
#SuppressWarnings("unused")
private void listenForUpdate(#Observes OurRefreshEvent event)
{
...
And we wrote the matching factory, injecting it:
public class OurPartialViewContextFactory extends PartialViewContextFactory
{
#Inject
private OurPartialViewContext customPartialViewContext;
...
Problem is that in the newest versions of JSF, the empty constructor for PartialViewContextWrapper is deprecated, asking us to use another constructor with the wrapped object in parameter.
Currently, our PartialViewContext needs to be tied to the request scope, in order to be modified during the request by the observed events and to be used by a custom PartialResponseWriter we also wrote.
So our PartialViewContext currently both:
must have an empty constructor, as it is a #RequestScoped bean;
should not have an empty constructor, as it is deprecated for PartialViewContextWrapper which it inherits from.
How could we find a solution there?
We tried to remove it from the scope and build it in the Factory with a simple new OurPartialViewContext(), but then the #Observes methods are never called.
You are required to pass the wrapped instance into the constructor and to use getWrapped() over all place in delegate methods. Otherwise your application will most probably not work when you install other JSF libraries which also ship with their own PartialViewContext implementation such as OmniFaces and PrimeFaces. You would be effectively completely skipping the functionality of their PartialViewContext implementation. This mistake was previously observed in too many custom implementations of factory-provided classes. Hence the urge to mark the default constructor as #Deprecated so that the developers are forced to use the proper design pattern.
Your specific issue can be solved by simply refactoring the listenForUpdate() method into a fullworthy request scoped CDI bean, which you then inject in the factory who in turn ultimately passes it into the constructor of your PartialViewContext implementation.
Thus, so:
#RequestScoped
public class OurEventObserver {
public void listenForUpdate(#Observes OurRefreshEvent event) {
// ...
}
}
public class OurPartialViewContextFactory extends PartialViewContextFactory {
#Inject
private OurEventObserver observer;
public OurPartialViewContextFactory(PartialViewContextFactory wrapped) {
super(wrapped);
}
#Override
public PartialViewContext getPartialViewContext(FacesContext context) {
PartialViewContext wrapped = getWrapped().getPartialViewContext(context);
return new OurPartialViewContext(wrapped, observer);
}
}
public class OurPartialViewContext extends PartialViewContextWrapper {
private OurEventObserver observer;
public OurPartialViewContext(PartialViewContext wrapped, OurEventObserver observer) {
super(wrapped);
this.observer = observer;
}
// ...
}
Inside any of the overridden methods of OurPartialViewContext you can simply access the state of the observer, provided that the listenForUpdate() modifies some instance variables representing the state.
While running a unit test with Arquillian using Glassfish embedded plugin, I get the following CDI error :
2015-09-18 06:25:24,376 DEBUG | main | org.jboss.weld.Reflection | WELD-000620: interface com.SupportedLocales is not declared #Target(METHOD, FIELD, PARAMETER, TYPE). Weld will use this annotation, however this may make the application unportable.
sept. 18, 2015 6:25:24 AM com.sun.enterprise.v3.server.ApplicationLifecycle deploy
GRAVE: Exception during lifecycle processing
org.glassfish.deployment.common.DeploymentException: CDI deployment failure:WELD-001408: Unsatisfied dependencies for type Set<Locale> with qualifiers #SupportedLocales
at injection point [BackedAnnotatedParameter] Parameter 1 of [BackedAnnotatedConstructor] #Inject protected com.MyClass(#SupportedLocales Set<Locale>)
at com.MyClass.<init>(MyClass.java:0)
Set(Locale) with qualifier #SupportedLocales is defined in a module deployed in the tested WebArchive. The Archive content is :
/WEB-INF/
/WEB-INF/lib/
/WEB-INF/lib/commons-lang3-3.3.2.jar
/WEB-INF/lib/commons-configuration-1.10.jar
/WEB-INF/lib/reflections-0.9.9-RC2.jar
/WEB-INF/lib/jcl-over-slf4j-1.7.10.jar
/WEB-INF/lib/slf4j-api-1.7.10.jar
/WEB-INF/lib/deltaspike-core-api-1.5.0.jar
/WEB-INF/lib/commons-util-1.0.0-SNAPSHOT.jar
/WEB-INF/lib/commons-io-2.4.jar
/WEB-INF/lib/guava-16.0.1.jar
/WEB-INF/lib/log4j-over-slf4j-1.7.10.jar
/WEB-INF/lib/javassist-3.18.2-GA.jar
/WEB-INF/lib/logback-classic-1.1.2.jar
/WEB-INF/lib/logback-core-1.1.2.jar
/WEB-INF/lib/jul-to-slf4j-1.7.10.jar
/WEB-INF/lib/commons-cdi-1.0.0-SNAPSHOT.jar
/WEB-INF/lib/xml-apis-1.0.b2.jar
/WEB-INF/lib/deltaspike-core-impl-1.5.0.jar
/WEB-INF/lib/dom4j-1.6.1.jar
/WEB-INF/lib/commons-codec-1.9.jar
/WEB-INF/lib/commons-lang-2.6.jar
/WEB-INF/lib/annotations-2.0.1.jar
/WEB-INF/lib/libphonenumber-7.0.3.jar
/WEB-INF/classes/
/WEB-INF/classes/com/
/WEB-INF/classes/com/timm/
/WEB-INF/classes/com/timm/common/
/WEB-INF/classes/com/timm/common/cdi/
/WEB-INF/classes/com/timm/common/cdi/web/
/WEB-INF/classes/com/timm/common/cdi/web/i18n/
/WEB-INF/classes/com/timm/common/cdi/web/i18n/ShiroCurrentLocale.class
/WEB-INF/beans.xml
This object is provided by a producer method located in "common-cdi" module. The same module provide CDI extension feature like ThreadScoped. This producer is not discovered by Weld during test startup and Weld does not discover beans from "commons-cdi" module. How is it possible? Can we provide CDI extension features and CDI bean in the same module ?
#SupportedLocales is declares in "commons-cdi" with:
#Qualifier
#Target({
ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD
})
#Retention(RetentionPolicy.RUNTIME)
public #interface SupportedLocales {
}
The producer is declared in "commons-cdi" with:
#Dependent
public class I18NProducer {
#Produces
#ApplicationScoped
#Default
#SupportedLocales
public Set<Locale> getSupportedLocales() {
Set<Locale> supportedLocales;
supportedLocales = new HashSet<Locale>();
supportedLocales.add(Locale.US);
return supportedLocales;
}
}
JUnit test case definition:
#RunWith(Arquillian.class)
public class LocaleInjectionTest {
#Deployment
public static Archive<?> deploy() {
final String moduleName = LocaleInjectionTest.class.getSimpleName();
PomEquippedResolveStage resolver = Maven.resolver().loadPomFromFile("pom.xml");
File[] libs = resolver.resolve("com.myname:commons-cdi").withTransitivity().asFile();
WebArchive testWar = ShrinkWrap
.create(WebArchive.class, moduleName + ".war")
.addClass(MyCurrentLocale.class)
.addAsLibraries(libs)
.addAsWebInfResource(ArchiveUtils.getResourceBeanXMLWithAlternativeAndDiscoveryModeAnnotated(MyCurrentLocale.class),
"beans.xml");
return testWar;
}
#Inject
private CurrentLocale bean;
#Test
public void testInjection() throws Exception {
...
}
}
MyCurrentLocale class definition:
#SessionScoped
#Alternative
public class MyCurrentLocale extends DefaultCurrentLocale implements Serializable {
#Inject
protected MyCurrentLocale(#SupportedLocales Set<Locale> supportedLocales) {
super(supportedLocales);
}
...
}
Is there something wrong in declaration ?
Looks like you're using GlassFish v3, so you'll need the beans.xml file in the jar as well as it is a bean archive too.
I'm writing Java SE application that uses CDI.
I have bean definition:
public class BeanA {
#PostConstruct
public void init() {
System.out.println("INIT");
}
public void receive(#Observes String test) {
System.out.println("received: " + test);
}
}
Requirements:
- I need to have many instances of BeanA in application
- I'd like to use Event CDI mechanism to communicate with that objects
When I use #Dependent scope, then #PostConstruct of BeanA is called everytime a new message is received. When I use #Singleton or #ApplicationScope then I can't have many objects of BeanA type.
What is solution to my problem?
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.
Java:
public class MyBean {
...
public Handler getHandler(){
return new Handler(){
public void handle(ActionEvent e){...}
}
}
...
}
public interface Handler{
void handle(ActionEvent e);
}
xhtml:
<h:commandButton ... actionListener="#{myBean.handler.handle}"/>
I'm in a tomcat 6.0 environment. This is a common pattern in java, but it seems not to work with EL method bindings. I get an exception:
javax.faces.event.MethodExpressionActionListener processAction SEVERE): Received 'java.lang.IllegalAccessException' when invoking action listener '#{myBean.handler.handle}' for component 'j_id115'
javax.faces.event.MethodExpressionActionListener processAction SEVERE): java.lang.IllegalAccessException: Class org.apache.el.parser.AstValue can not access a member of class MyBean$1 with modifiers "public"
...
Yes it can, but you did a few things wrong.
First of all the #handle()-method has to be declared as public, because it is an implementation of the public method of your interface.
public class MyBean {
...
public Handler getHandler(){
return new Handler(){
public void handle(){...}
};
}
}
Second point is, that you are calling the Handler as your actionListener, but what you want is to call the #handle()-method:
<h:commandButton actionListener="#{myBean.handler.handle}"/>
You also should omit the ActionEvent from the method-signature in your interface (and implementation)
public interface Handler {
public void handle();
}
This was more subtle than I thought...
From java, there is no problem calling the public method in the inner class:
MyBean myBean = getMyBean();
Handler handler = myBean.getHandler();
handler.handle(event); // OK
Using reflection, it depends on how it's done. The method can be invoked as declared (1):
Method getHandlerMethod = MyBean.class.getMethod("getHandler");
Method handleMethod = getHandlerMethod.getReturnType().getMethod("handle", ActionEvent.class);
handleMethod.invoke(handler, event); // OK, invoking declared method works
Or it can be invoked as defined in the inner class (2):
Method handleMethod = handler.getClass().getMethod("handle", ActionEvent.class);
handleMethod.invoke(handler, event) // throws IllegalAccessException
Obviously, there's a third option, and it works (3):
Method handleMethod = handler.getClass().getDeclaredMethod("handle", ActionEvent.class);
handleMethod.invoke(handler, event) // OK
Unfortunately, my JSF environment (Tomcat 6.0 with JSF mojarra 1.2 and icefaces 1.8.2) implements approach (2) instead of (3) and therefore my example doesn't work.
It should work if you specify the handle() method as ActionListener e.g.
<h:commandButton ... actionListener="#{myBean.handler.handle}"/>
To specify an implementation class of ActionListener interface you can use the "f:actionListener" tag e.g.
<h:commandButton>
<f:actionListener type="com.bla.SomeActionListenerImplementation" />
</h:commandButton>
But this wouldn't work in your case because your MyBean class doesn't implement the ActionListener interface...