JBoss Forge – Speedup your enterprise development – Part II RESTful Webservices


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


This is the second part of my JBoss Forge series:

In the first part, we installed JBoss Tools to get JBoss Forge eclipse integration. Then we created a Java EE project with the entities Book and Author and generated a web interface based on these entities.

In this second part, we will add a RESTful webservice API to our project.

Webservice generation

We will use JBoss Forge to generate webservice endpoints for our entities. Therefore we need to setup the rest plugin:

[ForgeExample] Author.java $ rest setup --activatorType APP_CLASS;
 ? What root path do you want to use for your resources? [/rest] 
 ? In what package do you want to store the Application class?
 [blog.thoughts.on.java.forge.rest] 
 ? How do you want to name the Application class? [RestApplication] 
***SUCCESS*** Installed [forge.spec.jaxrs.applicationclass] successfully.
***SUCCESS*** Installed [forge.spec.jaxrs] successfully.
***SUCCESS*** Rest Web Services (JAX-RS) is installed.
Wrote D:\/dev/wrk4/forge/ForgeExample/src/main/java/blog/thoughts/on/
java/forge/rest/RestApplication.java
Wrote D:\/dev/wrk4/forge/ForgeExample/pom.xml

We use the application class as activator instead of the web.xml file. Before starting the generation process, Forge asks which root path we want to use for our resources and the name of the package and application class. I used the suggested defaults here. If you want to create a real application, you might want to change these values. Now we can use Forge to generate our webservice endpoints. This is done similar to the generation of the web interface described in part one:

[ForgeExample] Author.java $ rest endpoint-from-entity --contentType 
application/xml blog.thoughts.on.java.forge.model.*;
***SUCCESS*** Generated REST endpoint for 
[blog.thoughts.on.java.forge.model.Author]
***SUCCESS*** Generated REST endpoint for 
[blog.thoughts.on.java.forge.model.Book]
Wrote D:\/dev/wrk4/forge/ForgeExample/src/main/java/blog/thoughts/on/java
/forge/model/Author.java
Wrote D:\/dev/wrk4/forge/ForgeExample/src/main/java/blog/thoughts/on/java
/forge/rest/AuthorEndpoint.java
Wrote D:\/dev/wrk4/forge/ForgeExample/src/main/java/blog/thoughts/on/java
/forge/model/Book.java
Wrote D:\/dev/wrk4/forge/ForgeExample/src/main/java/blog/thoughts/on/java
/forge/rest/BookEndpoint.java

As we can see in the console output, Forge added the JAXB annotations to our entities and generated the classes AuthorEndpoint and BookEndpoint for us. So lets have a look at the AuthorEndpoint:

@Stateless
@Path("/authors")
public class AuthorEndpoint
{
   @PersistenceContext(unitName = "forge-default")
   private EntityManager em;

   @POST
   @Consumes("application/xml")
   public Response create(Author entity)
   {
      em.persist(entity);
      return Response.created(UriBuilder.fromResource(AuthorEndpoint.class).path(String.valueOf(entity.getId())).build()).build();
   }

   @DELETE
   @Path("/{id:[0-9][0-9]*}")
   public Response deleteById(@PathParam("id") Long id)
   {
      Author entity = em.find(Author.class, id);
      if (entity == null)
      {
         return Response.status(Status.NOT_FOUND).build();
      }
      em.remove(entity);
      return Response.noContent().build();
   }

   @GET
   @Path("/{id:[0-9][0-9]*}")
   @Produces("application/xml")
   public Response findById(@PathParam("id") Long id)
   {
      TypedQuery<Author> findByIdQuery = em.createQuery("SELECT a FROM Author a LEFT JOIN FETCH a.books WHERE a.id = :entityId", Author.class);
      findByIdQuery.setParameter("entityId", id);
      Author entity = findByIdQuery.getSingleResult();
      if (entity == null)
      {
         return Response.status(Status.NOT_FOUND).build();
      }
      return Response.ok(entity).build();
   }

   @GET
   @Produces("application/xml")
   public List<Author> listAll()
   {
      final List<Author> results = em.createQuery("SELECT a FROM Author a LEFT JOIN FETCH a.books", Author.class).getResultList();
      return results;
   }

   @PUT
   @Path("/{id:[0-9][0-9]*}")
   @Consumes("application/xml")
   public Response update(@PathParam("id") Long id, Author entity)
   {
      entity.setId(id);
      entity = em.merge(entity);
      return Response.noContent().build();
   }
}

Forge implemented a standard RESTful webservice for our Author entity with the required annotations and Java code. This looks like all the work is already done and the only thing we need to do is build and deploy the application. Unfortunately this is not the case. If we run our application as it is now, JAXB throws an Exception because we created a cycle in our object graph. This is because we created a bidirectional association between our entities Book and Author in part one. To fix this, we need to add javax.xml.bind.annotation.XmlTransient to one side of the association. You can choose either side. I added it to the Author entity:

@Entity
@XmlRootElement
public class Author implements Serializable
{
   ...

   @XmlTransient
   public Set<Book> getBooks()
   {
      return this.books;
   }

   ...
}

OK, now we can run our application without getting an Exception. But there is still one problem left. The queries in the findById(Long id) and listAll() methods of the AuthorEndpoint class are not correct. They join the author entity with the book entity. Due to this the query returns an author multiple times if the database contains multiple books for him/her. Because I added the @XmlTransient annotation to the book association of the author, the books are not part of the XML message of an author. Therefore I can simply remove the join:

@Stateless
@Path("/authors")
public class AuthorEndpoint
{
   ...

   @GET
   @Path("/{id:[0-9][0-9]*}")
   @Produces("application/xml")
   public Response findById(@PathParam("id") Long id)
   {
      TypedQuery<Author> findByIdQuery = em.createQuery("SELECT a FROM Author a WHERE a.id = :entityId", Author.class);
      findByIdQuery.setParameter("entityId", id);
      Author entity = findByIdQuery.getSingleResult();
      if (entity == null)
      {
         return Response.status(Status.NOT_FOUND).build();
      }
      return Response.ok(entity).build();
   }

   @GET
   @Produces("application/xml")
   public List<Author> listAll()
   {
      final List<Author> results = em.createQuery("SELECT a FROM Author a", Author.class).getResultList();
      return results;
   }

   ...
}

Now we are done. We have fixed all issues and can build and deploy our application by calling build and as7 deploy.
So, let’s have a look at our webservices:

book
list_authors
list_books

Conclusion

This time the code generation with JBoss Forge did not work as perfect as in the first part of this series. But we still got a good result. There were only two small things we needed to change and JBoss Forge did most of the work for us.

JBoss Forge Series

4 Comments

  1. Hello Vineet,

    thanks for reading it 🙂

    I will check the SQL queries and the strategy options within the next days. But it might be next week before I have time for it. I have to meet some deadlines at work first.

    Regards,
    Thorben

  2. Hey Thorben,

    Thanks for writing this up. We don't usually come across people using this part of Forge so it's quite interesting to read up on how they are used and what they expect.

    I would recommend looking at Forge 1.4.0+ for the SQL query fixes; I believe this issue was fixed in FORGE-1084: https://issues.jboss.org/browse/FORGE-1084. If the queries are not what you desired, please file a bug in the Forge JIRA and we'll improve them.

    Also, as someone very familiar with this part of Forge, I would recommend not using this feature to generate RESTful web-services for complex JPA-entity graphs. Most of the issues revolve around support for lazy-fetching and bi-directional/cyclic relationships, like you're already discovered. The problem with such graphs is not that the webservices cannot be fixed, but that the end-result would be heavily opinionated and the service itself may not be RESTful in nature. For example, using XmlTransient here ensures that the books are omitted in the XML representation for the author. Unfortunately it also leaves you with the possibility that in some cases existing books for an Author can be lost, when the Author is updated via a PUT request (since the deserialized Author.books collection would be empty).

    There are a few cases like these that resulted in us introducing support for DTOs in a very specific manner. Forge 1.4.0 has a strategy option in the rest plugin. I'd like to have your feedback on it. While this option was tested heavily with JSON based REST APIs, I believe it would work for XML APIs as well.

    We'll be supporting a different and improved model on the lines of this strategy option, to enable users to build better RESTful APIs in a customizable manner in upcoming versions of Forge (Forge 2.x).

    Vineet

  3. Hello Ronny,

    thank you for your reply!
    Yes, adding Arquillian tests to the project will be the topic of next week. So stay tuned and check back next week 🙂

  4. The Forge is by far the best rapid prototyping tool for Maven based Java EE projects I know. Unfortunately you missed the Arquilian part you promised in the last posting. Maybe in the next posting? Waiting hopefully for a follow up.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.