3

I'm writing a REST application using WildFly 8 (JAX-RS 2.0, EJB 3.2 and JPA 2.1).

I have a JAX-RS resource which is also an EJB (stateless session bean with implicit container-managed transactions):

@Path("myresources")
@Stateless
public class MyResource {
    @PersistenceContext(name = "MyDataSource")
    private EntityManager em;

    @POST
    public Response create() {
        MyEntity invalidBean = new MyEntity();
        em.persit(invalidBean);
        ...
    }
}

MyEntity is decorated with bean validation annotations:

@Entity
public class MyEntity {
    @NotNull
    private String field;
    ...
}

Finally, I have defined an ExceptionMapper for ConstraintViolationException:

@Provider
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
    @Override
    public Response toResponse(ConstraintViolationException exception) {
        ...
    }
}

When MyResource.create is called, I expect the following behavior:

  1. The invalid bean is saved using JPA
  2. The end of my service implementation is reached without any exception
  3. A commit is triggered by the container (container-managed transaction)
  4. JTA launches bean validation to check constraints on the saved bean
  5. A ConstraintViolationException is thrown
  6. JAX-RS dispatches it to my ConstraintViolationExceptionMapper

However, this does not work properly: the ConstraintViolationException is actually thrown, but deeply wrapped inside an EJBTransactionRolledbackException. So, my ExceptionMapper does not catch it.

I already configured ConstraintViolationException to be an ApplicationException (following this guide). Thus, I'm now able to catch ConstraintViolationException when thrown from my own code, but still not when thrown by JTA.

This thread seems related, but I would prefer to avoid:

  • explicit transaction handling
  • vendor-specific solutions

Any idea to solve this problem?

Thanks!

Community
  • 1
  • 1
manchot
  • 33
  • 1
  • 5
  • Can't you simply map your ExceptionMapper to an `EJBTransactionRolledbackException` and look if the cause was a `ConstraintViolationException`? – lefloh Jun 27 '14 at 06:13
  • Yes, of course, but it looks a bit ugly since the `ConstraintViolationException` is not the direct cause of `EJBTransactionRolledbackException`: `javax.ejb.EJBTransactionRolledbackException` caused by `javax.transaction.RollbackException` caused by `javax.persistence.PersistenceException` caused by `javax.validation.ConstraintViolationException`. – manchot Jun 27 '14 at 07:19
  • Just iterate over the causes and check if one is a `ConstraintViolationException`. Think there is no other alternative. – lefloh Jun 27 '14 at 12:25
  • 1
    Ok, thanks. But why [`ApplicationException`](http://docs.oracle.com/javaee/7/api/javax/ejb/ApplicationException.html) does not work as expected in this situation? – manchot Jun 27 '14 at 13:54

2 Answers2

1

I think the problem is what your expectation is. There are potentially two points where validation can occur. Your example triggers Bean Validation as part of JPA life cycle events (persist in this case). Since we are at this stage already talking to a database it is wrapped in a rollback exception.

The other approach is to use the JAX-RS validation of the input. For example I would have expected your example to look like:

@Path("myresources")
@Stateless
public class MyResource {
    @PersistenceContext(name = "MyDataSource")
    private EntityManager em;

    @POST
    @ValidateOnExecution
    public Response create(@Valid MyEntity entity) {
        em.persit(entity);
        ...
    }
}

Now your posted parameters are converted into a MyEntity instance and validated as part of the request processing.

Hardy
  • 18,659
  • 3
  • 49
  • 65
  • 3
    I also considered this approach. It looks really good for simple processes, but my project actually uses [DTO](http://en.wikipedia.org/wiki/Data_transfer_object)s to disconnect the format of API inputs from the structure of my database entities. Thus, validating the input object does not necessarily mean that the whole backend entity will be valid. For instance, a partial update (`PATCH`) may break the global coherence of an entity, even if all the input fields were valid. – manchot Jul 03 '14 at 08:04
  • If this is the case, you might need validation at JPA event level. However, you will in this case also have to deal with the type of exception returned, aka EJBTransactionRolledbackException – Hardy Jul 03 '14 at 11:06
0

I have faced exactly the same issue with Glassfish 4 (Jersey 2 + EclipseLink). As @Hardy mentioned, the problem here is that JPA is throwing the exception as part of its lifecycle - in my case, because of the <validation-mode>CALLBACK</validation-mode> in my persistence.xml.

The @ApplicationException mechanism with the ejb-jar.xml file is working indeed, so the validation is not triggered inside your EJB method but at commit time. Then, the behaviour is:

  1. The container starts the transaction.
  2. The EJB method persist the invalid entity without errors (There is no exception to map).
  3. The container commits the transaction, and the JPA lifecycle validates the entity.
  4. A ConstraintViolationException is thrown, it causes a RollbackException, which in turn causes the final EJB Exception. Note that this exception is not thrown by your EJB.

The solution I found was to synchronize the persistence context in the method execution via EntityManager#flush(). This triggers the JPA validation and I am able to map the exception with the ExceptionMapper provider, since the ConstraintViolationException is always thrown inside my EJB code.

In my opinion, this solution is cleaner than unwrapping the EJB exception until the cause is the ConstraintViolationException you are looking for.

I have uploaded a sample project to GitHub to reproduce the solution, the important files are:

If you have any doubts, please let me know.

A. Rodas
  • 20,171
  • 8
  • 62
  • 72
  • 1
    Thanks a lot, it worked! What's more, I was able to automate the process by using [`@ArroundInvoke`](http://docs.oracle.com/javaee/6/tutorial/doc/gkedm.html). Now, I can properly catch `ConstraintViolationException` without having to care about calling `EntityManager#flush()`. – manchot Jul 07 '14 at 07:38