is it possible to use a CDI producer method defined in module A in order to CDI inject into a bean in a second module B?
Is there any description on the relation between CDI and the JBoss Modules System?
In producer.jar:
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import java.util.logging.Logger;
public class Producer {
#Produces
public static Logger produceLog(InjectionPoint injectionPoint) {
return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
}
In consumer.war:
import javax.inject.Inject;
import java.util.logging.Logger;
public class Consumer {
#Inject
Logger logger;
public void doLog() {
logger.info("Hello CDI with JBoss Modules");
}
}
module B has a Manifest Dependency on module A:
Manifest-Version: 1.0
Dependencies: deployment.producer.jar
this approach leads to an weld unsatisfied dependency problem:
"JBAS014671: Failed services" => {"jboss.deployment.unit.\"consumer.war\".WeldService" => "org.jboss.msc.service.StartException in service jboss.deployment.unit.\"consumer.war\".WeldService: org.jboss.weld.exceptions.DeploymentException: WELD-001408 Unsatisfied dependencies for type [Logger] with qualifiers [#Default] at injection point [[field] #Inject question.Consumer.logger]"
I posted a sample project on Github: https://github.com/sistar/jboss-cdi-demo
TIA
Ralf
Related
I have two managed Java beans:
import javax.annotation.PostConstruct;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.ws.rs.Path;
#Path("/sync")
#ManagedBean(name="syncService", eager=true)
#ApplicationScoped
public class SyncService {
#ManagedProperty(value="#{ldapDirectoryAccess}")
private DirectoryAccess directoryAccess;
public void setDirectoryAccess(DirectoryAccess directoryAccess) {
System.out.println("SyncService.setDirectoryAccess()");
this.directoryAccess = directoryAccess;
}
public SyncService() {
System.out.println("SyncService() - constructed: " + this);
}
#PostConstruct
public void init() {
System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
}
...
}
#ManagedBean(name="ldapDirectoryAccess", eager=true)
#ApplicationScoped
public class LdapDirectoryAccess implements DirectoryAccess {
public LdapDirectoryAccess() {
System.out.println("LdapDirectoryAccess constructed: " + this);
}
...
}
When I deploy the application in Tomcat, I get the following output in catalina.out:
SyncService() - constructed: ...SyncService#705ebb4d
...
LdapDirectoryAccess constructed: ...LdapDirectoryAccess#3c1fd5aa
SyncService.setDirectoryAccess()
DirectoryAccess injected: ...LdapDirectoryAccess#3c1fd5aa in:
...SyncService#705ebb4d
LdapDirectoryAccess constructed: ...LdapDirectoryAccess#59d6a4d1
So, first an instance of each bean is constructed as expected, and the second bean is injected into the first. But then, another instance of the second bean class is created. How is this possible? In this tutorial I found the following:
#ApplicationScoped
Bean lives as long as the web application lives. It gets created upon
the first HTTP request involving this bean in the application (or when
the web application starts up and the eager=true attribute is set in
#ManagedBean) and gets destroyed when the web application shuts down.
So I would expected that one instance of each bean is created when the application is started, and that both instances are destroyed when the application is shut down. But LdapDirectoryAccess is constructed twice.
Moreover, when I open the page that is served by SyncService I see:
SyncService() - constructed: ... SyncService#1cb4a09c
so a second instance of SyncService is built as well, and I cannot understand why. Also, no directoryAccess property is injected this time, and the service throws a null pointer exception.
This means that the first instance of SyncService is built correctly, but then
A second instance of SyncService is created (why?)
No LdapDirectoryAccess is injected into it (why?)
This second instance of SyncService is used to serve the call to my REST API. Why this instance and not the first one that was created?
I have looked at this question and its answers, however:
I am using Mojarra 2.2.18
My application's web.xml does not contain any tag mentioning com.sun.faces.config.ConfigureListener
So I after several hours investigation I am completely out of ideas. Do you have any hints?
A second instance of SyncService is created (why?)
Because two different frameworks, completely unaware of each other, are being instructed to manage (instantiate and use) it.
JAX-RS, via #Path
JSF, via #ManagedBean
So, in effects, you have one instance of SyncService which is managed by JAX-RS, and you have another instance of SyncService which is managed by JSF, and only in this instance, the also JSF-specific #ManagedProperty is recognized. JAX-RS doesn't understand the #ManagedProperty and thus does nothing with it.
Basically, you're here tight-coupling a JAX-RS resource and a JSF managed bean in the very same class. Tight-coupling is a bad programming practice. You need to split out SyncService into one independent JAX-RS resource and one independent JSF managed bean. And you'd need to convert the LdapDirectoryAccess to use another bean management framework which is recognized by both JAX-RS and JSF, so that a single instance can be injected in both. In modern Java EE 8, that would be a bean managed by CDI's #javax.enterprise.context.ApplicationScoped. In legacy Java EE 6/7, you could abuse EJB's #javax.ejb.Singleton for that.
Given that you're still using the deprecated #ManagedBean instead of its replacement #Named, I'll assume that you're still on legacy Java EE, so I'll show only the EJB approach.
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
#Singleton
public class LdapDirectoryAccessService implements DirectoryAccess {
#PostConstruct
public void init() {
System.out.println("LdapDirectoryAccess constructed: " + this);
}
}
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ws.rs.Path;
#Path("/sync")
public class SyncResource {
#EJB
private DirectoryAccess directoryAccess;
#PostConstruct
public void init() {
System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
}
}
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.RequestScoped;
import javax.faces.bean.ManagedBean;
#ManagedBean
#RequestScoped
public class SyncBacking {
#EJB
private DirectoryAccess directoryAccess;
#PostConstruct
public void init() {
System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
}
}
Note that being able to inject an EJB in a JAX-RS resource might require additional configuration in Java EE 6/7, for that see the first link of the list below. And, in case you want to LdapDirectoryAccessService to eagerly initialize during server's startup, add the #javax.ejb.Startup annotation.
See also:
Inject an EJB into JAX-RS (RESTful service)
JSF Controller, Service and DAO
Backing beans (#ManagedBean) or CDI Beans (#Named)?
How to choose the right bean scope?
I'm beggining with Hibernate Envers. I'm already able to properly annotate classes with #Audited and create revisions, but I'm unable to record logged user data with my revisions.
My JSF 2.0 test application is running on CDI, JPA/Hibernate in a jbossEAP6 / wildfly server. I'm neither using Spring or Seam.
Here is some code:
revisionEntity.java
#Entity
#RevisionEntity(AuditListener.class)
public class RevisionEntity {
#Id
#GeneratedValue
#RevisionNumber
private int id;
#RevisionTimestamp
private long timestamp;
private String username;
LoginBean.java
#Named
#Stateful
#SessionScoped
public class LoginBean implements Serializable{
private String username;
...
AuditListener.java
import javax.ejb.Stateful;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.hibernate.envers.RevisionListener;
import br.test.login.filterlogin.beans.LoginBean;
#Named
#Stateful
#SessionScoped
public class AuditListener implements RevisionListener {
#Inject
private LoginBean loginBean;
public void newRevision(Object revisionEntity) {
RevisionEntityEx RevEntity = (RevisionEntityEx) revisionEntity;
RevEntity.setUsername(loginBean.getUsername());
}
The loginBean injection fails, giving me a NullPointerException. Any ideas?
Sorry about my terrible grammar.
Regards,
Marcelo.
The listener is not managed by the container so your loginBean will not be injected.
We need to lookup for it...
Notice that UsuarioService should be changed to your type: LoginBean.
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.CDI;
BeanManager beanManager = CDI.current().getBeanManager();
Bean<UsuarioService> bean = (Bean<UsuarioService>) beanManager.getBeans(UsuarioService.class).iterator().next();
CreationalContext<UsuarioService> context = beanManager.createCreationalContext(bean);
this.usuarioService = (UsuarioService) beanManager.getReference(bean, UsuarioService.class, context);
You didn't give any stacktrace, so I'm guessing. AFAIK you cannot combine together both
#Stateful
#SessionScoped
Because the first annotation is for making a class an EJB stateful session bean, but the second one is for making class a CDI managed bean, with scope Session.
It seems to me that you are trying to use technologies you don't understand at all. Please read a specification or at least some CDI tutorials/ sample GitHub projects.
Personal advice: most of the time you should prefer to use #Stateless over #Stateful for EJB beans. Then all data related to HTTP session you can store e.g. in some additional #SessionScoped CDI bean.
I was reading/testing the following tutorial about CDI: [link].
And I got an exception when I added a producer class to the code.
Basically, there's an interface with a default implementation:
public interface ATMTransport {
public void communicateWithBank(byte[] datapacket);
}
#Default
public class StandardAtmTransport implements ATMTransport {
public void communicateWithBank(byte[] datapacket) {
System.out.println("communicating with bank via Standard transport");
}
}
Next, there's another class which injects the ATMTransport interface:
#Named("atm")
public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {
#Inject
private ATMTransport transport;
public void deposit(BigDecimal bd) {
transport.communicateWithBank(null);
}
}
Everything is OK so far. So in the section about 'producers', they show a new class:
public class TransportFactory {
#Produces ATMTransport createTransport() {
System.out.println("ATMTransport created with producer");
return new StandardAtmTransport();
}
}
Then, by adding the producer class I got this exception:
WELD-001409: Ambiguous dependencies for type ATMTransport with qualifiers #Default
at injection point [BackedAnnotatedField] #Inject private AutomatedTellerMachineImpl.transport at AutomatedTellerMachineImpl.transport(AutomatedTellerMachineImpl.java:0)
Possible dependencies:
- Managed Bean [class StandardAtmTransport] with qualifiers [#Default #Any],
- Producer Method [ATMTransport] with qualifiers [#Any #Default] declared as [[BackedAnnotatedMethod] #Produces TransportFactory.createTransport()]
I solved the problem by using qualifiers, but I really don't know why.
My question is, why does the producer class cause that exception? (They didn't mention anything about it on their tutorial).
I really need a little explanation.
Based on the code you supplied, you ended up creating a default implementation of ATMTransport that is a managed bean. The exception is caused by having both beans available with the same injection targets. #Default is the qualifier added to all CDI beans. Basically, there is no need for the producer method since the one provided by the class definition by itself is the same.
I guess the bigger question - what were you trying to do with the producer method?
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.
This my interceptor study case, but it doesn't fire.
The annotation for interceptor
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
#Inherited
#InterceptorBinding
#Retention(RUNTIME)
#Target({METHOD, TYPE})
public #interface LogInterceptor {
}
The Implementation for interceptor
import javax.annotation.Priority;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
#Interceptor
#LogInterceptor
#Priority(Interceptor.Priority.APPLICATION)
public class LogInterceptorImpl {
#AroundInvoke
public Object log(InvocationContext context) {
Object o = null;
try {
System.out.println("START");
o = context.proceed();
System.out.println("END");
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
}
The controller
import modelo.Post;
import interceptor.LogInterceptor;
import java.io.Serializable;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
#ManagedBean(name = "postController")
#SessionScoped
public class PostController implements Serializable {
private List<Post> items = null;
private Post selected;
public PostController() {
MyFacade facade = new MyFacade();
items = facade.getItems();
}
#LogInterceptor
public List<Post> getItems() {
return items;
}
}
The glassfish log
Informações: EclipseLink, version: Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd
Informações: file:/C:/Users/f9921257/Documents/NetBeansProjects/estudo/build/web/WEB-INF/classes/_estudoPU login successful
Informações: Portable JNDI names for EJB PostFacade: [java:global/estudo/PostFacade!facade.PostFacade, java:global/estudo/PostFacade]
Informações: Portable JNDI names for EJB CategoriaFacade: [java:global/estudo/CategoriaFacade!facade.CategoriaFacade, java:global/estudo/CategoriaFacade]
Informações: WELD-000900: 2.2.2 (Final)
WARN: WELD-001700: Interceptor annotation class javax.ejb.PostActivate not found, interception based on it is not enabled
WARN: WELD-001700: Interceptor annotation class javax.ejb.PrePassivate not found, interception based on it is not enabled
WARN: WELD-000411: Observer method [BackedAnnotatedMethod] org.glassfish.sse.impl.ServerSentEventCdiExtension.processAnnotatedType(#Observes ProcessAnnotatedType<Object>, BeanManager) receives events for all annotated types. Consider restricting events using #WithAnnotations or a generic type with bounds.
WARN: WELD-000411: Observer method [BackedAnnotatedMethod] private org.glassfish.jersey.gf.cdi.internal.CdiComponentProvider.processAnnotatedType(#Observes ProcessAnnotatedType<Object>) receives events for all annotated types. Consider restricting events using #WithAnnotations or a generic type with bounds.
WARN: WELD-000411: Observer method [BackedAnnotatedMethod] public org.glassfish.jms.injection.JMSCDIExtension.processAnnotatedType(#Observes ProcessAnnotatedType<Object>) receives events for all annotated types. Consider restricting events using #WithAnnotations or a generic type with bounds.
Informações: Inicializando Mojarra 2.2.7 ( 20140610-1547 https://svn.java.net/svn/mojarra~svn/tags/2.2.7#13362) para o contexto '/estudo'
Informações: Monitoring jndi:/server/estudo/WEB-INF/faces-config.xml for modifications
Informações: Running on PrimeFaces 5.0
Informações: Loading application [estudo] at [/estudo]
Informações: estudo was successfully deployed in 13.298 milliseconds.
The marks "START" and "END" put on LogInterceptorImpl does not appear in the log file. Why they doesn't fire?
You need to use #javax.inject.Named annotation instead of #ManagedBean:
#Named("postController")
#SessionScoped
public class PostController
Also add #AroundInvoke to mark the interceptor method:
#Interceptor
#LogInterceptor
public class LogInterceptorImpl {
#AroundInvoke
public Object log(InvocationContext context) {
...
}
}
And the last step, you need to enable the interceptor in beans.xml, otherwise the interceptor would be ignored:
beans.xml
<beans>
<interceptors>
<class>com.nameofyourpackage.LogInterceptorImpl </class>
</interceptors>
</beans>
Explanation for using #Named instead of #ManagedBean
Both annotations mean essentially the same - they give a textual name to a bean, so that it may be accessed in a JSF page. However, beans marked #ManagedBean are created by the JSF subsystem and CDI annotations are ignored (such as SessionScoped or interceptor qualifiers, such as your LogInterceptor). Beans marked #Named can take full advantage of CDI, while you cannot use JSF specific annotations, like #ManagedProperty (you should use #Inject with CDI). This is not a problem as #ManagedProperty and #ManagedBean are both deprecated and there is always a way how to accomplish the same with CDI. This is discussed in more detail here: Backing beans (#ManagedBean) or CDI Beans (#Named)?