| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import jakarta.persistence.EntityManager; |
|---|
| .. | .. |
|---|
| 17 | 20 | import net.curisit.securis.services.exception.SeCurisServiceException; |
|---|
| 18 | 21 | import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes; |
|---|
| 19 | 22 | |
|---|
| 23 | +/** |
|---|
| 24 | +* DefaultExceptionHandler |
|---|
| 25 | +* <p> |
|---|
| 26 | +* JAX-RS {@link ExceptionMapper} that normalizes error responses across the API. |
|---|
| 27 | +* It also makes a best-effort to rollback and close a request-scoped {@link EntityManager} |
|---|
| 28 | +* if still open. |
|---|
| 29 | +* |
|---|
| 30 | +* <p>Response strategy: |
|---|
| 31 | +* <ul> |
|---|
| 32 | +* <li>{@link ForbiddenException} → 401 UNAUTHORIZED with app-specific error headers.</li> |
|---|
| 33 | +* <li>{@link SeCurisServiceException} → 418 (custom) with app error headers.</li> |
|---|
| 34 | +* <li>Other exceptions → 500 with generic message and request context logging.</li> |
|---|
| 35 | +* </ul> |
|---|
| 36 | +* |
|---|
| 37 | +* Headers: |
|---|
| 38 | +* <ul> |
|---|
| 39 | +* <li>{@code X-SECURIS-ERROR-MSG}</li> |
|---|
| 40 | +* <li>{@code X-SECURIS-ERROR-CODE}</li> |
|---|
| 41 | +* </ul> |
|---|
| 42 | +* |
|---|
| 43 | +* @author JRA |
|---|
| 44 | +* Last reviewed by JRA on Oct 6, 2025. |
|---|
| 45 | +*/ |
|---|
| 20 | 46 | @Provider |
|---|
| 21 | 47 | public class DefaultExceptionHandler implements ExceptionMapper<Exception> { |
|---|
| 48 | + |
|---|
| 22 | 49 | private static final Logger LOG = LogManager.getLogger(DefaultExceptionHandler.class); |
|---|
| 23 | | - |
|---|
| 50 | + |
|---|
| 51 | + /** Default status code used for application-defined errors. */ |
|---|
| 24 | 52 | public static final int DEFAULT_APP_ERROR_STATUS_CODE = 418; |
|---|
| 53 | + |
|---|
| 54 | + /** Header name carrying a human-readable error message. */ |
|---|
| 25 | 55 | public static final String ERROR_MESSAGE_HEADER = "X-SECURIS-ERROR-MSG"; |
|---|
| 56 | + |
|---|
| 57 | + /** Header name carrying a symbolic application error code. */ |
|---|
| 26 | 58 | public static final String ERROR_CODE_MESSAGE_HEADER = "X-SECURIS-ERROR-CODE"; |
|---|
| 27 | 59 | |
|---|
| 60 | + /** Default constructor (logs instantiation). */ |
|---|
| 28 | 61 | public DefaultExceptionHandler() { |
|---|
| 29 | 62 | LOG.info("Creating DefaultExceptionHandler "); |
|---|
| 30 | 63 | } |
|---|
| 31 | 64 | |
|---|
| 65 | + // Context objects injected by the runtime |
|---|
| 32 | 66 | @Context |
|---|
| 33 | 67 | HttpServletRequest request; |
|---|
| 34 | 68 | @Context |
|---|
| .. | .. |
|---|
| 36 | 70 | @Context |
|---|
| 37 | 71 | EntityManager em; |
|---|
| 38 | 72 | |
|---|
| 73 | + /** |
|---|
| 74 | + * toResponse |
|---|
| 75 | + * <p> |
|---|
| 76 | + * Map a thrown exception to an HTTP {@link Response}, releasing the {@link EntityManager} |
|---|
| 77 | + * if present. |
|---|
| 78 | + */ |
|---|
| 39 | 79 | @Override |
|---|
| 40 | 80 | public Response toResponse(Exception e) { |
|---|
| 41 | 81 | releaseEntityManager(); |
|---|
| .. | .. |
|---|
| 57 | 97 | return Response.serverError().header(ERROR_MESSAGE_HEADER, "Unexpected error: " + e.toString()).type(MediaType.APPLICATION_JSON).build(); |
|---|
| 58 | 98 | } |
|---|
| 59 | 99 | |
|---|
| 100 | + /** |
|---|
| 101 | + * releaseEntityManager |
|---|
| 102 | + * <p> |
|---|
| 103 | + * Best-effort cleanup: rollback active transaction (if joined) and close the {@link EntityManager}. |
|---|
| 104 | + */ |
|---|
| 60 | 105 | private void releaseEntityManager() { |
|---|
| 61 | 106 | try { |
|---|
| 62 | 107 | if (em != null && em.isOpen()) { |
|---|