Expose an EJB as JAX-RS from an EJB-Module
Question
I know I can expose a class inside a web-module as a restful interface using only annotations. Now I want to do something a little more complex.
I'm having a stateless EJB in an ejb-module inside my ear and want to expose this bean as a restful interface using jax-rs. In the first step I annotated the EJB class with @Path
and @GET
. It didn't work.
It works when I create an additional web-module with a web.xml
that contains
<context-param>
<param-name>resteasy.jndi.resources</param-name>
<param-value>myear/testService/no-interface,myear/testService2/no-interface</param-value>
</context-param>
<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>rest</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
I can expose a simple class with only annotations, so I can't quite believe that I have to explicitly configure every EJB that I want to expose in a config file.
Is there a simpler/better way?
I'm working with JBoss 6.1
Solution
There shouldn't be any config required. If you're willing to look at another container -- at least for reference purposes -- here's an example that exposes an @Singleton EJB as both a JAX-RS service and @LocalBean.
The bean itself uses Container-Managed Transactions and JPA, with the JPA @Entity objects used in the actual JAX-RS messages -- effectively turning the EntityManager into a transactional JAX-RS service.
Small chunk of the EJB class:
@Singleton
@Lock(LockType.WRITE)
@Path("/user")
@Produces(MediaType.APPLICATION_XML)
public class UserService {
@PersistenceContext
private EntityManager em;
@Path("/create")
@PUT
public User create(@QueryParam("name") String name,
@QueryParam("pwd") String pwd,
@QueryParam("mail") String mail) {
User user = new User();
user.setFullname(name);
user.setPassword(pwd);
user.setEmail(mail);
em.persist(user);
return user;
}
@Path("/list")
@GET
public List<User> list(@QueryParam("first") @DefaultValue("0") int first,
@QueryParam("max") @DefaultValue("20") int max) {
List<User> users = new ArrayList<User>();
List<User> found = em.createNamedQuery("user.list", User.class).setFirstResult(first).setMaxResults(max).getResultList();
for (User u : found) {
users.add(u.copy());
}
return users;
}
And here's a chunk of the unit test for it (uses the Embeddable EJBContainer API):
public class UserServiceTest {
private static Context context;
private static UserService service;
private static List<User> users = new ArrayList<User>();
@BeforeClass
public static void start() throws NamingException {
Properties properties = new Properties();
properties.setProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, "true");
context = EJBContainer.createEJBContainer(properties).getContext();
// create some records
service = (UserService) context.lookup("java:global/rest-on-ejb/UserService");
users.add(service.create("foo", "foopwd", "foo@foo.com"));
users.add(service.create("bar", "barpwd", "bar@bar.com"));
}
@Test
public void list() throws Exception {
String users = WebClient.create("http://localhost:4204")
.path("/user/list")
.get(String.class);
assertEquals(
"<users>" +
"<user>" +
"<email>foo@foo.com</email>" +
"<fullname>foo</fullname>" +
"<id>1</id>" +
"<password>foopwd</password>" +
"</user>" +
"<user>" +
"<email>bar@bar.com</email>" +
"<fullname>bar</fullname>" +
"<id>2</id>" +
"<password>barpwd</password>" +
"</user>" +
"</users>", users);
}
Full source of the example here. The entire example is just three classes (that includes the test) and a persistence.xml file.