lefed
05 Jul 2011 / posted by Nikku

In my Jersey + Spring + JPA stack I recently came across an interesting issue: Throwing an exception inside a @Transactional annotated Spring bean method would cause a org.springframework.transaction.UnexpectedRollbackException in the outer container.

The result was that the request ended screwed up and the following stack trace appearing in the server logs:

org.springframework.transaction.UnexpectedRollbackException: JPA transaction unexpectedly rolled back (maybe marked rollback-only after a failed operation); nested exception is javax.persistence.RollbackException: Error while commiting the transaction
Caused by:
  javax.persistence.RollbackException: Error while commiting the transaction
  ...

After doing some research, it got apparent that, while there were many varieties, many people had this issue. Some said this was due to limitation in JPA's exception hierarchy, others just claimed it is a Spring or Hibernate or whatever bug.

Wondering, why everyone pointed on the issue, no solved it I created a small test case to investigate what is actually happening.

Test Setup

At first, I got the NewsManager which does some database action when asked to #aggregateNews. If it finds an error, it will throw an exception which itself makes the changes rolled back (pretty clear so far).

@Component
public NewsManager {

  @Transactional
  public News aggregateNews(Date date) {
    // Does some random action here
    // e.g. updating stuff in the database

    // May throw a (unchecked) operation failed
    // exception on error, rolling back the transaction
    if (error) {
      throw new OperationFailedException(
                    "Could not aggregate due to error");
    }
  }
}

The root controller of my Jersey application (a Spring bean, too) will ask the NewsManager to aggregate news and will catch the expected exception in case one is thrown. The reason for choosing this two-layer approach is that roll-backs can cleanly be done without propagating an exception to the presentational layer.

@Path("/")
@Component
public RootController {

  @Inject
  private NewsManager newsManager; // Spring bean

  @GET
  @Transactional
  public Object news() {
    try {
      // Call Spring bean
      return newsManager.aggregateNews(new Date());
    } catch (OperationFailedException e) {
      return null;
    }
  }
}

What happens when a OperationFailedException is thrown is that this exception will be wrapped nicely with a javax.persistence.RollbackException and then with a org.springframework.transaction.UnexpectedRollbackException and so forth. Your application is not be able to handle it and you read the above mentioned stack trace in your server logs.

Problem deduction

The actual cause of the problem is not the badness of the exception thrown, but the fact that the previous example used nested @Transactional contexts to handle the business logic. Both, the controller and the news bean had @Transactional annotations specified on their business methods. Thus they were instrumented by Spring to use transactional logic.

@Path("/")
@Component
public RootController {

  @GET
  public Object news() {
    try {
      // Call Spring bean
      return newsManager.aggregateNews(new Date());
    } catch (OperationFailedException e) {
      return null;
    }
  }
}

If you simply remove the outer @Transactional annotation (which might be unnecessary anyway) everything works fine.

Conclusion

I am not convinced, that the above fix may help everyone posting about UnexpectedRollbackException and Spring + JPA. Neither am I hundred percent sure why this happens. What I got is that deliberately throwing exceptions inside nested transactional contexts may not be good for your Spring + JPA applications. Too many miracles happen there, eventually screwing things up and stealing one day of your time to figure out what and why.

Tagged as [ java, web, spring, jpa, jersey, transactions ]

20 Nov 2010 / posted by Nikku

Nearly every modern web framework features MVC, the separation of program parts into model, view and controller. This article discusses how Jersey, the reference implementation of JSR 311 (JAX-RS) can be extended to do MVC as supported by Ruby on Rails, Spring MVC and many other web frameworks, too.

This post showcases how a simple MVC extension can be built for Jersey, too.

MVC, what is it?

When talking about MVC in the context of web development the controller is typically the object which is responsible for processing the request in one of its methods (sometimes referred to action invoked on controller).

The controller will return one or more objects containing the result of this process, the so called model. The model is not tied to any graphical representation — it merely acts as a container for the computational results returned by the controller.

A view however gives a model its graphical form. Typically it is written in some kind of markup language to produce HTML (or any other kind of output) from the model.

And in Jersey?

Jersey is the reference implementation for JAX-RS áka JSR 311. As such, it (simply|beauti)fies the development of REST-ful web applications with Java.

It provides MVC to ease application development, however its approach differs slightly from what is considered standard in web development.

Given that a controller c processes a request in one of its methods m most web frameworks will choose a view based on the controller name and the invoked method. The resulting view the framework will use to display the model is based on both, controller and executed method inside the controller (action). In most cases, a web framework might search for the view found as [c]/[m] in a given directory.

In contrast, JAX-RS connects the view to the model without considering the executing controller at all: The graphical representation (XML/JSON/HTML) of a model depends only on the model itself and the mime type requested by the client. Consequently Jerseys way of doing MVC does not consider the controller when selecting a view, too. Considering the JAX-RS philosophy this makes perfectly sense.

However when trying to develop classical web applications this approach seems a bit inappropriate: When a request is processed by UserController#info, which returns an instance of com.sample.User Jersey will try to allocate a view com/sample/User/index.jsp. The same happens if a user gets logged in (done by UserController#info) or if he wants to change his profile (called by UserController#editProfile). All requests will yield the same view, no different functionality can be provided to the user.

Fortunately we can adapt this behaviour by implementing our own MVC component which we will then plug in into Jersey.

Implementing a MVC MessageBodyWriter

To extend Jersey to support selecting views based on controller and executed action we can plug into Jerseys view provider mechanism. This is as easy as defining a class annotated with @Provider which implements MessageBodyWriter. A stub for the class can look somewhat like this:

@Provider
@Produces("text/html")
public class MVCViewMessageBodyWriter implements
  MessageBodyWriter<Object> {

  @Override
  public boolean isWriteable(
    Class<?> type, Type genericType,
    Annotation[] annotations, MediaType mediaType) {

    // TODO: Check if view like [controller]/[action].jsp
    // exists under /WEB-INF/views
  }

  @Override
  public void writeTo(
    Object o, Class<?> type, Type genericType,
    Annotation[] annotations, MediaType mediaType,
    MultivaluedMap<String, Object> httpHeaders,
    OutputStream out) throws IOException {

    // Redirect to view placed in
    // /WEB-INF/views/[controller]/[action].jsp
  }

  @Override
  public long getSize(
    Object o, Class<?> type, Type genericType,
    Annotation[] annotations, MediaType mediaType) {

    // Return -1 to indicate that no explicit
    // content lenght can be assigned
    // (frankly, we do not know about the length)
    return -1;
  }
}

Implementing the interface MessageBodyWriter<Object> tells Jersey that, whenever an instance of Object should be rendered to the client, MessageBodyWriter#isWritable should be called by Jersey to check if the writer can write the specified object. If that is the case, MessageBodyWriter#writeTo will be invoked by the framework and the writer should write the body to the passed output stream. Optionally the writer may specify the size of the output data in MessageBodyWriter#getSize.

Nasty details

Our MVC implementation will check in MVCViewMessageBodyWriter#isWritable, if there exists a view to display for the current controller / action. Inside the writer it could do so by invoking


private String getViewPath(HttpContext context) {
  AbstractResourceMethod m =
    context.getUriInfo().getMatchedMethod();

  String controller =
    m.getMethod().getDeclaringClass().getSimpleName();
  String action = m.getMethod().getName();

  if (controller.endsWith("Controller")) {
    controller = controller.substring(0,
      controller.length() - "Controller".length());
  }

  controller = controller.toLowerCase();

  String path = "/WEB-INF/views" +
                controller + "/" + action + ".jsp";
  return path;
}

in conjunction with

public boolean templateExists(String path) {
  try {
    return servletContext.getResource(path) != null;
  } catch (MalformedURLException e) {
    // TODO: log
    return false;
  }
}

The required writer attributes context and servletContext can be automatically injected into the writer using Jerseys @Context injection mechanism:

// Injecting both HttpContext and current ServletContext into
// the writer...
@Context
private HttpContext context;

@Context
private ServletContext servletContext;

In the MVCViewMessageBodyWriter#writeTo method the servlet contexts dispatch mechanisms are used to forward the request to our view:

// way to obtain request via @Context injection
@Context
private ThreadLocal<HttpServletRequest> requestInvoker;

// way to obtain response via @Context injection
@Context
private ThreadLocal<HttpServletResponse> responseInvoker;

private void dispatch(Object model) throws IOException {
  String viewPath = getViewPath(context);

  RequestDispatcher d =
    servletContext.getRequestDispatcher(viewPath);

  if (d == null) { /* could not be obtained */ }

  HttpServletRequest request = requestInvoker.get();

  // Set model to be accessible as it variable in view
  request.setAttribute("it", model);
  try {
    d.forward(request, responseInvoker.get());
  } catch (ServletException e) {
    throw new RuntimeException("Dispatch to view failed", e);
  }
}

The final Solution

That's it! Putting all the pieces together, our do-MVC-as-other-frameworks-do-it-MessageBodyWriter looks like this:

@Provider
@Produces("text/html")
public class MVCViewMessageBodyWriter implements
        MessageBodyWriter<Object> {

    // Injecting both HttpContext and current ServletContext into
    // the writer...
    @Context
    private HttpContext context;
    @Context
    private ServletContext servletContext;

    // way to obtain request via @Context injection
    @Context
    private ThreadLocal<HttpServletRequest> requestInvoker;

    // way to obtain response via @Context injection
    @Context
    private ThreadLocal<HttpServletResponse> responseInvoker;

    private void dispatch(Object model) throws IOException {
        String viewPath = getViewPath(context);

        RequestDispatcher d =
                servletContext.getRequestDispatcher(viewPath);

        if (d == null) { /* could not be obtained */ }

        HttpServletRequest request = requestInvoker.get();

        // Set model to be accessible as it variable in view
        request.setAttribute("it", model);
        try {
            d.forward(request, responseInvoker.get());
        } catch (ServletException e) {
            throw new RuntimeException("Dispatch to view failed", e);
        }
    }

    @Override
    public boolean isWriteable(
            Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {

        // Check if view like [controller]/[action].jsp
        // exists under /WEB-INF/views

        return templateExists(getViewPath(context));
    }

    @Override
    public void writeTo(
            Object o, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders,
            OutputStream out) throws IOException {

        // Redirect to view placed in
        // /WEB-INF/views/[controller]/[action].jsp

        dispatch(getViewPath(context));
    }

    @Override
    public long getSize(
            Object o, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {

        // Return -1 to indicate that no explicit
        // content lenght can be assigned
        // (frankly, we do not know about the length)
        return -1;
    }

    private String getViewPath(HttpContext context) {
        AbstractResourceMethod m =
                context.getUriInfo().getMatchedMethod();

        String controller =
                  m.getMethod().getDeclaringClass().getSimpleName();
        String action = m.getMethod().getName();

        if (controller.endsWith("Controller")) {
            controller = controller.substring(0,
                    controller.length() - "Controller".length());
        }

        controller = controller.toLowerCase();

        String path = "/WEB-INF/views"
                + controller + "/" + action + ".jsp";
        return path;
    }

    public boolean templateExists(String path) {
        try {
            return servletContext.getResource(path) != null;
        } catch (MalformedURLException e) {
            // TODO: log
            return false;
        }
    }
}

Voilà!

Don't forget to put this provider class somewhere where Jersey can find it (specify package scanning accordingly).

More Resources

Tagged as [ jersey, mvc, jax-rs, java ]