With Apache CXF 3.0 just being released a couple of weeks ago, the project makes yet another important step to fulfill the JAX-RS 2.0 specification requirements: integration with CDI 1.1. In this blog post we are going to look on a couple of examples of how Apache CXF 3.0 and Apache CXF 3.0 work together.
Starting from version 3.0, Apache CXF includes a new module, named cxf-integration-cdi which could be added easily to your Apache Maven POM file:
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-integration-cdi</artifactId> <version>3.0.0</version> </dependency>
This new module brings just two components (in fact, a bit more but those are the key ones):
- CXFCdiServlet: the servlet to bootstrap Apache CXF application, serving the same purpose as CXFServlet and CXFNonSpringJaxrsServlet, ...
- JAXRSCdiResourceExtension: portable CDI 1.1 extension where all the magic happens
We are going to build a very simple JAX-RS 2.0 application to manage people using Apache CXF 3.0 and JBoss Weld 2.1, the CDI 1.1 reference implementation. The Person class we are going to use for a person representation is just a simple Java bean:
package com.example.model; public class Person { private String email; private String firstName; private String lastName; public Person() { } public Person( final String email, final String firstName, final String lastName ) { this.email = email; this.firstName = firstName; this.lastName = lastName; } // Getters and setters are ommited // ... }
As it is quite common now, we are going to run our application inside embedded Jetty 9.1 container and our Starter class does exactly that:
package com.example; import org.apache.cxf.cdi.CXFCdiServlet; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.jboss.weld.environment.servlet.BeanManagerResourceBindingListener; import org.jboss.weld.environment.servlet.Listener; public class Starter { public static void main( final String[] args ) throws Exception { final Server server = new Server( 8080 ); // Register and map the dispatcher servlet final ServletHolder servletHolder = new ServletHolder( new CXFCdiServlet() ); final ServletContextHandler context = new ServletContextHandler(); context.setContextPath( "/" ); context.addEventListener( new Listener() ); context.addEventListener( new BeanManagerResourceBindingListener() ); context.addServlet( servletHolder, "/rest/*" ); server.setHandler( context ); server.start(); server.join(); } }
Please notice the presence of CXFCdiServlet and two mandatory listeners which were added to the context:
- org.jboss.weld.environment.servlet.Listener is responsible for CDI injections
- org.jboss.weld.environment.servlet.BeanManagerResourceBindingListener binds the reference to the BeanManager to JNDI location java:comp/env/BeanManager to make it accessible anywhere from the application
With that, the full power of CDI 1.1 is at your disposal. Let us introduce the PeopleService class annotated with @Named annotation and with an initialization method declared and annotated with @PostConstruct just to create one person.
@Named public class PeopleService { private final ConcurrentMap< String, Person > persons = new ConcurrentHashMap< String, Person >(); @PostConstruct public void init() { persons.put( "a@b.com", new Person( "a@b.com", "Tom", "Bombadilt" ) ); } // Additional methods // ... }
Up to now we have said nothing about configuring JAX-RS 2.0 applications and resources in CDI 1.1 enviroment. The reason for that is very simple: depending on the application, you may go with zero-effort configuration or fully customizable one. Let us go through both approaches.
With zero-effort configuration, you may define an empty JAX-RS 2.0 application and any number of JAX-RS 2.0 resources: Apache CXF 3.0 implicitly will wire them together by associating each resource class with this application. Here is an example of JAX-RS 2.0 application:
package com.example.rs; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath( "api" ) public class JaxRsApiApplication extends Application { }
And here is a JAX-RS 2.0 resource PeopleRestService which injects the PeopleService managed bean:
package com.example.rs; import java.util.Collection; import javax.inject.Inject; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import com.example.model.Person; import com.example.services.PeopleService; @Path( "/people" ) public class PeopleRestService { @Inject private PeopleService peopleService; @Produces( { MediaType.APPLICATION_JSON } ) @GET public Collection< Person > getPeople( @QueryParam( "page") @DefaultValue( "1" ) final int page ) { // ... } @Produces( { MediaType.APPLICATION_JSON } ) @Path( "/{email}" ) @GET public Person getPerson( @PathParam( "email" ) final String email ) { // ... } @Produces( { MediaType.APPLICATION_JSON } ) @POST public Response addPerson( @Context final UriInfo uriInfo, @FormParam( "email" ) final String email, @FormParam( "firstName" ) final String firstName, @FormParam( "lastName" ) final String lastName ) { // ... } // More HTTP methods here // ... }Nothing else is required: Apache CXF 3.0 application could be run like that and be fully functional. The complete source code of the sample project is available on GitHub. Please keep in mind that if you are following this style, only single empty JAX-RS 2.0 application should be declared.
With customizable approach more options are available but a bit more work have to be done. Each JAX-RS 2.0 application should provide non-empty getClasses() or/and getSingletons() collections implementation. However, JAX-RS 2.0 resource classes stay unchanged. Here is an example (which basically leads to the same application configuration we have seen before):
package com.example.rs; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.enterprise.inject.Produces; import javax.inject.Inject; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; @ApplicationPath( "api" ) public class JaxRsApiApplication extends Application { @Inject private PeopleRestService peopleRestService; @Produces private JacksonJsonProvider jacksonJsonProvider = new JacksonJsonProvider(); @Override public Set< Object > getSingletons() { return new HashSet<>( Arrays.asList( peopleRestService, jacksonJsonProvider ) ); } }Please notice, that JAXRSCdiResourceExtension portable CDI 1.1 extension automatically creates managed beans for each JAX-RS 2.0 applications (the ones extending Application) and resources (annotated with @Path). As such, those are immediately available for injection (as for example PeopleRestService in the snippet above). The class JacksonJsonProvider is annotated with @Provider annotation and as such will be treated as JAX-RS 2.0 provider. There are no limit on JAX-RS 2.0 applications which could be defined in this way. The complete source code of the sample project using this appoarch is available on GitHub
No matter which approach you have chosen, our sample application is going to work the same. Let us build it and run:
> mvn clean package > java -jar target/jax-rs-2.0-cdi-0.0.1-SNAPSHOT.jar
Calling the couple of implemented REST APIs confirms that application is functioning and configured properly. Let us issue the GET command to ensure that the method of PeopleService annotated with @PostConstruct has been called upon managed bean creation.
> curl http://localhost:8080/rest/api/people HTTP/1.1 200 OK Content-Type: application/json Date: Thu, 29 May 2014 22:39:35 GMT Transfer-Encoding: chunked Server: Jetty(9.1.z-SNAPSHOT) [{"email":"a@b.com","firstName":"Tom","lastName":"Bombadilt"}]
And here is the example of POST command:
> curl -i http://localhost:8080/rest/api/people -X POST -d "email=a@c.com&firstName=Tom&lastName=Knocker" HTTP/1.1 201 Created Content-Type: application/json Date: Thu, 29 May 2014 22:40:08 GMT Location: http://localhost:8080/rest/api/people/a@c.com Transfer-Encoding: chunked Server: Jetty(9.1.z-SNAPSHOT) {"email":"a@c.com","firstName":"Tom","lastName":"Knocker"}
In this blog post we have just scratched the surface of what is possible now with Apache CXF and CDI 1.1 integration. Just to mention that embedded Apache Tomcat 7.x / 8.x as well as WAR-based deployments of Apache CXF with CDI 1.1 are possible on most JEE application servers and servlet containers.
Please take a look on official documentation and give it a try!
The complete source code is available on GitHub.
15 comments:
Hi, could you please provide an alternate to this sollution with defining cxf contextConfig in xml and not by coding the JaxRsApiApplication extends Application, where the cdi could work in maven-jetty-plugin? thank you
Hi!
Sure, please checkout this branch https://github.com/reta/jax-rs-2.0-cdi/tree/jetty-maven-plugin-war from GitHub. It contains the example of this application packaged as WAR and runnable using maven-jetty-plugin. Please note, because of this bug https://jira.codehaus.org/browse/MNG-5620, you have to use Maven 3.0.5 (or 3.2.2 when released).
Please let me know if you have more questions.
Thank you.
oh great! that bug was the exception I was getting over and over in mvn-3.2.1 and couldn't figure out what I was doing wrong! Even google did not point me to the bug.. big THANK YOU! :)
okey, i got i working with the empty JaxRsApiApplication but now it is the only way how to define rest service endpoints.. I used the xml definition in separate cxf xml file.
<jaxrs:server id="consumerServer" address="/services/v1">
<jaxrs:serviceBeans>
<ref bean="userResource" />
</jaxrs:serviceBeans>
<jaxrs:extensionMappings>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</jaxrs:extensionMappings>
<jaxrs:providers>
<ref bean="jsonProvider" />
</jaxrs:providers>
</jaxrs:server>
<jaxrs:server id="otherServer" address="/other/v1">
<jaxrs:serviceBeans>
<ref bean="userResourceLimited" />
</jaxrs:serviceBeans>
</jaxrs:server>
is it possible now? if I define this with the web.xml entry
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/cxf-servlet.xml</param-value>
</context-param>
there is no endpoint created... thank you
Hi there!
I would like to apologize but the use case with XML-based configuration is not yet possible at the moment. The reason is that XML-based configuration is not a part of JSR-339 / JAX-RS 2.0 specification and as such was not a number one priority. But it is on the list and there are some ongoing discussions towards seamless support of XML-based descriptors and CDI to ease off the migration process of the existing applications.
Thank you.
Best Regards,
Andriy Redko
ok, thank you for your time, you were really helpful.
its great disadvantage as now we cannot define even json provider, so I need to revert all back to spring :( or is there any other way?
oh, mea culpa, now I see
@Override
public Set< Object > getSingletons() {
return new HashSet<>(
Arrays.asList(
peopleRestService,
jacksonJsonProvider
)
);
}
thank you
Hi there!
Exactly, you are on a right track. You can pass providers, resources and features through the application class and they should be properly recognized by Apache CXF. Please let me know if you need any help, but you are picking the right path.
Thank you.
Best Regards,
Andriy Redko
By any chance this can be done for JAX-WS? I can see most of stuff fro JAX_RS.
Hi Prashant,
Thanks for you comment. Unfortunately, the CDI support for JAX-WS is not there yet. However, I expect some work to be done in this direction soon.
Thank you.
Hi Andriy,
I have been following your example and the one you have on GITHUB mentioned above. I have tried several configuration for my project but all I could get to work was with the CXFNonSpringJaxrsServlet and not with CXFCdiServlet. In my project, we cannot use Spring nor Jersey or any technologies aside of: CXF 3.1.0, JBoss Weld CDI-1.1, Apache Deltaspike and few other small ones. We also have to use Tomcat 8. What I find troubling is that if I make this work with CXFNonSpringJaxrsServlet, just changing the in web.xml to org.apache.cxf.cdi.CXFCdiServlet should work if I declared the org.jboss.weld.environment.servlet.Listener and also add proper config for it into context.xml. But it didn't.
Then you mentioned in this post the need for BeanManagerResourceBindingListener and noticed that your GITHUB example declares it in the web.xml. So I did as well but tomcat complains that the context is read only and so had to remove it.
With all the configuration I tried, I always get:
org.apache.cxf.transport.servlet.ServletController invoke
WARNING: Can't find the the request for http://localhost:8080/edmf/rest/rse's Observe
I also don't see the:
org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /api
in tomcat log which means that the initDestination is not getting called and don't know why. I am suspecting it is caused by the missing registration of BeanManagerResourceBindingListener. Would you know how I can "register" the BeanManagerResourceBindingListener for tomcat in a web.xml or any other ways?
Thank you for your time.
Hi Jocelyn,
Thank you very much for your comment. I am wondering, have you tried this branch https://github.com/reta/jax-rs-2.0-cdi/tree/tomcat-war, specifically created to show off WAR-based deployments on Tomcat 8? Please let me know if this is what you are looking for. For sure, I would be happy to help you out with your project if you run into issues with that.
Please let me know if the branch mentioned above works for you.
Thank you.
Best Regards,
Andriy Redko
Hi Andriy,
I have a problem with JAXRSCdiResourceExtension, since it is not able to discover my REST API service endpoint classes. The problem is that I use a WADL XML file for generating Java interfaces for the service API.
The service endpoint implementation classes are supposed to inherit all @Path, etc. annotations from their corresponding interfaces. This does not work, because the "isAnnotationPresent" method of "BackedAnnotatedType" apparently does not look for inherited annotations from implemented interfaces.
When I add a @Path annotation directly to the service implementation classes, then everything works as expected.
Is there any workaround possible, so I do not have to duplicate the @Path annotations?
Hi Thomas,
Thank you very much for your comment. I am not sure at the moment, but it looks like a desired behavior, at least with respect to CDI beans discovery. I will try to find evidence of that in the specification, but meanwhile I think you are right, the unfortunate workaround would be to duplicate @Path annotations on the implementation classes.
Thank you.
Best Regards,
Andriy Redko
Post a Comment