Letting the presentation layer (JSF) handle business exceptions from service layer (EJB)
Solution 1:
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.
}