3

The EJB method (using CMT) that updates an entity supplied :

@Override
@SuppressWarnings("unchecked")
public boolean update(Entity entity) throws OptimisticLockException {
    // Code to merge the entity.
    return true;
}

This will throw the javax.persistence.OptimisticLockException, if concurrent update is detected which is to be handled precisely by the caller (a managed bean).

public void onRowEdit(RowEditEvent event) {
    try {
        service.update((Entity) event.getObject())
    } catch(OptimisticLockException e) {
        // Add a user-friendly faces message.
    }
}

But doing so makes an additional dependency from the javax.persistence API on the presentation layer compulsory which is a design smell leading to tight-coupling.

In which exception should it be wrapped so that the tight-coupling issue can be omitted in its entirely? Or is there a standard way to handle this exception which in turn does not cause any service layer dependencies to be enforced on the presentation layer?

By the way, I found it clumsy to catch this exception in the EJB (on the service layer itself) and then return a flag value to the client (JSF).

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Tiny
  • 27,221
  • 105
  • 339
  • 599

1 Answers1

7

Create a custom service layer specific runtime exception which is annotated with @ApplicationException with rollback=true.

@ApplicationException(rollback=true)
public abstract class ServiceException extends RuntimeException {}

Create some concrete subclasses for general business exceptions, such as constraint violation, required entity, and of course optimistic lock.

public class DuplicateEntityException extends ServiceException {}
public class EntityNotFoundException extends ServiceException {}
public class EntityAlreadyModifiedException extends ServiceException {}

Some of them can be thrown directly.

public void register(User user) {
    if (findByEmail(user.getEmail()) != null) {
        throw new DuplicateEntityException();
    }

    // ...
}
public void addToOrder(OrderItem item, Long orderId) {
    Order order = orderService.getById(orderId);

    if (order == null) {
        throw new EntityNotFoundException();
    }

    // ...
}

Some of them need a global interceptor.

@Interceptor
public class ExceptionInterceptor implements Serializable {

    @AroundInvoke
    public Object handle(InvocationContext context) throws Exception {
        try {
            return context.proceed();
        }
        catch (javax.persistence.EntityNotFoundException e) { // Can be thrown by Query#getSingleResult().
            throw new EntityNotFoundException(e);
        }
        catch (OptimisticLockException e) {
            throw new EntityAlreadyModifiedException(e);
        }
    }

}

Which is registered as default interceptor (on all EJBs) as below in ejb-jar.xml.

<interceptors>
    <interceptor>
        <interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
    </interceptor>
</interceptors>
<assembly-descriptor>
    <interceptor-binding>
        <ejb-name>*</ejb-name>
        <interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
    </interceptor-binding>
</assembly-descriptor>

As a general hint, in JSF you can also have a global exception handler which just adds a faces message. When starting with this kickoff example, you could do something like this in YourExceptionHandler#handle() method:

if (exception instanceof EntityAlreadyModifiedException) { // Unwrap if necessary.
    // Add FATAL faces message and return.
}
else {
    // Continue as usual.
}
Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • This is a bit off-topic and perhaps, I will go with a separate question, if I am unable to get a solution. I have a test application containing only an interceptor (`javax.interceptor.Interceptor`) with configurations all mentioned here and a single EJB. I always get an exception on deployment - `java.lang.IllegalStateException: Interceptor binding contains an interceptor class name = com.example.MyInterceptor that is not defined as an interceptor`. What might be the probable cause at a glance? – Tiny Jul 09 '15 at 20:40
  • See updated answer for explicit registration. I guess GF4 won't do that if there's no `@InterceptorBinding`. – BalusC Jul 09 '15 at 20:45
  • This works now. Thanks. (I did not expect explicitly registering an interceptor this way). – Tiny Jul 09 '15 at 20:51
  • `Throwable exception = iter.next().getContext().getException();` in the `handle()` method in `ExceptionHandlerWrapper` always returns `javax.faces.FacesException` (`exception.getClass().getName()`). Therefore, a conditional check like `exception instanceof EntityAlreadyModifiedException` always fails even though `EntityAlreadyModifiedException` is re-thrown from the interceptor specified. – Tiny Jul 09 '15 at 21:35
  • Comment says "Unwrap if necessary". If you're using [OmniFaces](http://showcase.omnifaces.org/utils/Exceptions), use `Exceptions#unwrap()` or, better, `Exceptions#is()`. – BalusC Jul 09 '15 at 21:40
  • Working well. Thank you. – Tiny Jul 09 '15 at 22:25
  • Can exception stack-traces be suppressed so that they are not displayed on the server log? They are unnecessary at the point an appropriate `FacesMessage` is added. – Tiny Jul 11 '15 at 17:58
  • Yes, override `FullAjaxExceptionHandler#logException()` to pass `null` as `exception` argument (as per [your comment on another question](http://stackoverflow.com/questions/10534187/why-use-a-jsf-exceptionhandlerfactory-instead-of-error-page-redirection/10564549#comment50668743_10564549), you're most likely extending `FullAjaxExceptionHandler` for the purpose). – BalusC Jul 11 '15 at 17:59