You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rz...@apache.org on 2026/10/13 07:52:43 UTC

(tomee) 15/18: Do not return EJB exception messages from the default JAX-RS mapper (#3083)

This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 68ab3f5ea81ed276ec61ad8403f96b0b6a6d327a
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Tue Oct 13 08:49:40 2026 +0200

    Do not return EJB exception messages from the default JAX-RS mapper (#3083)
    
    Log the cause as a warning instead. Setting the property
    openejb.jaxrs.ejb-exception-mapper.expose-message=true restores it.
    
    (cherry picked from commit 22162c6b132ec50d47da1b77048b13c12721f759)
---
 .../openejb/server/cxf/rs/EJBExceptionMapper.java  | 25 +++++-
 .../server/cxf/rs/EJBExceptionMapperTest.java      | 89 ++++++++++++++++++++++
 2 files changed, 112 insertions(+), 2 deletions(-)

diff --git a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/EJBExceptionMapper.java b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/EJBExceptionMapper.java
index c4fa6e9d19..97019b637e 100644
--- a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/EJBExceptionMapper.java
+++ b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/EJBExceptionMapper.java
@@ -23,8 +23,22 @@ import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.ExceptionMapper;
 import jakarta.ws.rs.ext.Providers;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
 
+/**
+ * Maps {@link EJBException}s to the mapper of their cause when one is registered.
+ * Otherwise a 500 response is returned. The exception message is only written to the
+ * response body when {@value #EXPOSE_MESSAGE} is set to {@code true}, it is logged otherwise.
+ */
 public class EJBExceptionMapper implements ExceptionMapper<EJBException> {
+    public static final String EXPOSE_MESSAGE = "openejb.jaxrs.ejb-exception-mapper.expose-message";
+
+    private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_RS, EJBExceptionMapper.class);
+
+    private final boolean exposeMessage = SystemInstance.get().getOptions().get(EXPOSE_MESSAGE, false);
+
     @Context
     private Providers providers;
 
@@ -44,7 +58,14 @@ public class EJBExceptionMapper implements ExceptionMapper<EJBException> {
         return defaultResponse(ejbException);
     }
 
-    private Response defaultResponse(Exception cause) {
-        return Response.serverError().type(MediaType.TEXT_PLAIN_TYPE).entity(cause.getMessage()).build();
+    private Response defaultResponse(final Exception cause) {
+        if (exposeMessage) {
+            return Response.serverError().type(MediaType.TEXT_PLAIN_TYPE).entity(cause.getMessage()).build();
+        }
+        LOGGER.warning("Unmapped EJB exception, returning a 500 response: {0}", String.valueOf(cause));
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Unmapped EJB exception", cause);
+        }
+        return Response.serverError().build();
     }
 }
diff --git a/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/EJBExceptionMapperTest.java b/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/EJBExceptionMapperTest.java
new file mode 100644
index 0000000000..57c6566104
--- /dev/null
+++ b/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/EJBExceptionMapperTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.server.cxf.rs;
+
+import jakarta.ejb.EJBAccessException;
+import jakarta.ejb.EJBException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Providers;
+import org.apache.openejb.loader.SystemInstance;
+import org.junit.After;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Proxy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class EJBExceptionMapperTest {
+    private static final String DETAIL = "jdbc:db://internal-host/schema";
+
+    @After
+    public void reset() {
+        SystemInstance.reset();
+    }
+
+    @Test
+    public void defaultResponseHasNoMessage() throws Exception {
+        final Response response = newMapper(null).toResponse(new EJBException(new IllegalStateException(DETAIL)));
+        assertEquals(500, response.getStatus());
+        assertNull(response.getEntity());
+    }
+
+    @Test
+    public void defaultResponseWithoutCauseHasNoMessage() throws Exception {
+        final Response response = newMapper(null).toResponse(new EJBException(DETAIL));
+        assertEquals(500, response.getStatus());
+        assertNull(response.getEntity());
+    }
+
+    @Test
+    public void messageExposedWhenEnabled() throws Exception {
+        SystemInstance.get().setProperty(EJBExceptionMapper.EXPOSE_MESSAGE, "true");
+        final Response response = newMapper(null).toResponse(new EJBException(new IllegalStateException(DETAIL)));
+        assertEquals(500, response.getStatus());
+        assertEquals(DETAIL, response.getEntity());
+    }
+
+    @Test
+    public void accessDeniedIsForbidden() throws Exception {
+        final Response response = newMapper(null).toResponse(new EJBAccessException(DETAIL));
+        assertEquals(403, response.getStatus());
+        assertNull(response.getEntity());
+    }
+
+    @Test
+    public void causeMapperIsUsed() throws Exception {
+        final ExceptionMapper<IllegalArgumentException> mapper = e -> Response.status(234).entity(e.getMessage()).build();
+        final Response response = newMapper(mapper).toResponse(new EJBException(new IllegalArgumentException("oops")));
+        assertEquals(234, response.getStatus());
+        assertEquals("oops", response.getEntity());
+    }
+
+    private static EJBExceptionMapper newMapper(final ExceptionMapper<?> causeMapper) throws Exception {
+        final EJBExceptionMapper mapper = new EJBExceptionMapper();
+        final Providers providers = (Providers) Proxy.newProxyInstance(
+                EJBExceptionMapperTest.class.getClassLoader(), new Class<?>[]{Providers.class},
+                (proxy, method, args) -> "getExceptionMapper".equals(method.getName()) ? causeMapper : null);
+        final Field field = EJBExceptionMapper.class.getDeclaredField("providers");
+        field.setAccessible(true);
+        field.set(mapper, providers);
+        return mapper;
+    }
+}