This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch ejb-exception-mapper-generic-message in repository https://gitbox.apache.org/repos/asf/tomee.git
commit bf74338760d505646ef161039338beb3242346f0 Author: Richard Zowalla <[email protected]> AuthorDate: Fri Oct 9 10:16:09 2026 +0200 do not return EJB exception messages from the default JAX-RS mapper Log the cause as a warning instead. Setting the property openejb.jaxrs.ejb-exception-mapper.expose-message=true restores it. --- .../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; + } +}
