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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 2ccc82aad5 ISIS-3012: decouples RO from javax.jdo
2ccc82aad5 is described below

commit 2ccc82aad55202b42ed3685b5b5e79864f557017
Author: Andi Huber <[email protected]>
AuthorDate: Thu Apr 21 10:07:19 2022 +0200

    ISIS-3012: decouples RO from javax.jdo
---
 .../exceprecog/ExceptionRecognizerAbstract.java    |  5 ++-
 .../services/exceprecog/RootCauseFinder.java       | 18 ++++++--
 .../isis/commons/internal/collections/_Lists.java  |  8 ++++
 .../commons/internal/exceptions/_Exceptions.java   | 26 ++++++++---
 .../jdo/spring/IsisModulePersistenceJdoSpring.java | 22 ++++++++++
 ...tsApplicationExceptionMapper_Test_Contract.java | 20 +++++----
 viewers/restfulobjects/viewer/pom.xml              | 11 -----
 .../viewer/mappers/ExceptionMapperAbstract.java    | 10 +++--
 .../viewer/mappers/entity/ExceptionDetail.java     | 50 +++++++++++-----------
 9 files changed, 110 insertions(+), 60 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerAbstract.java
 
b/api/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerAbstract.java
index 57ad5102d3..4c629c122b 100644
--- 
a/api/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerAbstract.java
+++ 
b/api/applib/src/main/java/org/apache/isis/applib/services/exceprecog/ExceptionRecognizerAbstract.java
@@ -123,8 +123,9 @@ public abstract class ExceptionRecognizerAbstract 
implements ExceptionRecognizer
                     return translatableMessage.translate(translationService, 
translationContext);
                 }
             }
-            final Throwable rootCause = _Exceptions.getRootCause(throwable);
-            final String formattedMessage = 
rootCauseMessageFormatter.apply(rootCause);
+            final String formattedMessage = _Exceptions.getRootCause(throwable)
+                .map(rootCauseMessageFormatter::apply)
+                .orElse("");
             return formattedMessage;
         })
         .filter(_NullSafe::isPresent)
diff --git 
a/persistence/jdo/spring/src/main/java/org/apache/isis/persistence/jdo/spring/IsisModulePersistenceJdoSpring.java
 
b/api/applib/src/main/java/org/apache/isis/applib/services/exceprecog/RootCauseFinder.java
similarity index 63%
copy from 
persistence/jdo/spring/src/main/java/org/apache/isis/persistence/jdo/spring/IsisModulePersistenceJdoSpring.java
copy to 
api/applib/src/main/java/org/apache/isis/applib/services/exceprecog/RootCauseFinder.java
index e68ae34762..61f69a0db0 100644
--- 
a/persistence/jdo/spring/src/main/java/org/apache/isis/persistence/jdo/spring/IsisModulePersistenceJdoSpring.java
+++ 
b/api/applib/src/main/java/org/apache/isis/applib/services/exceprecog/RootCauseFinder.java
@@ -16,11 +16,21 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.persistence.jdo.spring;
+package org.apache.isis.applib.services.exceprecog;
 
-import org.springframework.context.annotation.Configuration;
+import java.util.Optional;
+import java.util.function.Function;
 
-@Configuration
-public class IsisModulePersistenceJdoSpring {
+/**
+ * SPI to help {@link Exception} root cause finding.
+ * <p>
+ * Either returns a more specific {@link Throwable} or an {@link 
Optional#empty()}.
+ * @apiNote Specifically introduced to handle JDOException's {@code 
Throwable[] nested}.
+ *
+ * @since 2.0 {@index}
+ */
+@FunctionalInterface
+public interface RootCauseFinder
+extends Function<Throwable, Optional<Throwable>> {
 
 }
diff --git 
a/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java
 
b/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java
index 31d5075192..1dec99f051 100644
--- 
a/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java
+++ 
b/commons/src/main/java/org/apache/isis/commons/internal/collections/_Lists.java
@@ -24,6 +24,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -62,6 +63,13 @@ public final class _Lists {
         return list.get(list.size()-1);
     }
 
+    public static <T> Optional<T> lastElement(final @Nullable List<T> list) {
+        if(_NullSafe.isEmpty(list)) {
+            return Optional.empty();
+        }
+        return Optional.ofNullable(list.get(list.size()-1));
+    }
+
     // -- UNMODIFIABLE LIST
 
     /**
diff --git 
a/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
 
b/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
index 219fce305c..8a00655b0f 100644
--- 
a/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
+++ 
b/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Optional;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -344,8 +345,26 @@ public final class _Exceptions {
         return chain.stream();
     }
 
-    public static Throwable getRootCause(final @Nullable Throwable ex) {
-        return _Lists.lastElementIfAny(getCausalChain(ex));
+    public static Optional<Throwable> getRootCause(final @Nullable Throwable 
ex) {
+        return _Lists.lastElement(getCausalChain(ex));
+    }
+
+    /**
+     * Variant to support custom root cause finders.
+     * @param rootCauseFinders -
+     *      either returns a more specific {@link Throwable} or an {@link 
Optional#empty()}.
+     * @implNote non-recursive
+     */
+    public static Optional<Throwable> getRootCause(
+            final @Nullable Throwable ex,
+            final @Nullable List<? extends Function<Throwable, 
Optional<Throwable>>> rootCauseFinders) {
+        return getRootCause(ex)
+        .map(rootCause->_NullSafe.stream(rootCauseFinders)
+            .map(finder->finder.apply(rootCause))
+            .filter(Optional::isPresent)
+            .map(Optional::get)
+            .findFirst()
+            .orElse(rootCause));
     }
 
     // -- SWALLOW
@@ -480,7 +499,4 @@ public final class _Exceptions {
 
     }
 
-
-
-
 }
diff --git 
a/persistence/jdo/spring/src/main/java/org/apache/isis/persistence/jdo/spring/IsisModulePersistenceJdoSpring.java
 
b/persistence/jdo/spring/src/main/java/org/apache/isis/persistence/jdo/spring/IsisModulePersistenceJdoSpring.java
index e68ae34762..b7845aa6f6 100644
--- 
a/persistence/jdo/spring/src/main/java/org/apache/isis/persistence/jdo/spring/IsisModulePersistenceJdoSpring.java
+++ 
b/persistence/jdo/spring/src/main/java/org/apache/isis/persistence/jdo/spring/IsisModulePersistenceJdoSpring.java
@@ -18,9 +18,31 @@
  */
 package org.apache.isis.persistence.jdo.spring;
 
+import java.util.Optional;
+
+import javax.jdo.JDOException;
+
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import org.apache.isis.applib.services.exceprecog.RootCauseFinder;
+
 @Configuration
 public class IsisModulePersistenceJdoSpring {
 
+    @Bean
+    public RootCauseFinder getJdoExceptionRootCauseFinder() {
+        return (final Throwable ex) -> {
+            if (ex instanceof JDOException) {
+                final JDOException jdoException = (JDOException) ex;
+                final Throwable[] nestedExceptions = 
jdoException.getNestedExceptions();
+                return nestedExceptions != null
+                        && nestedExceptions.length > 0
+                        ? Optional.ofNullable(nestedExceptions[0])
+                        : Optional.empty();
+            }
+            return Optional.empty();
+        };
+    }
+
 }
diff --git 
a/viewers/restfulobjects/testing/src/main/java/org/apache/isis/viewer/restfulobjects/testing/RestfulObjectsApplicationExceptionMapper_Test_Contract.java
 
b/viewers/restfulobjects/testing/src/main/java/org/apache/isis/viewer/restfulobjects/testing/RestfulObjectsApplicationExceptionMapper_Test_Contract.java
index 3ecc924f65..b8b92faafb 100644
--- 
a/viewers/restfulobjects/testing/src/main/java/org/apache/isis/viewer/restfulobjects/testing/RestfulObjectsApplicationExceptionMapper_Test_Contract.java
+++ 
b/viewers/restfulobjects/testing/src/main/java/org/apache/isis/viewer/restfulobjects/testing/RestfulObjectsApplicationExceptionMapper_Test_Contract.java
@@ -38,6 +38,8 @@ import 
org.apache.isis.viewer.restfulobjects.applib.util.JsonMapper;
 import 
org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
 import 
org.apache.isis.viewer.restfulobjects.viewer.mappers.ExceptionMapperForRestfulObjectsApplication;
 
+import lombok.val;
+
 /**
  * contract test.
  */
@@ -127,9 +129,9 @@ public abstract class 
RestfulObjectsApplicationExceptionMapper_Test_Contract {
 
         // given
         context.allowing(mockHttpHeaders);
-        final Exception rootCause = new Exception("bozfoz");
-        final Exception cause = new Exception("barfoo", rootCause);
-        final RestfulObjectsApplicationException ex = 
RestfulObjectsApplicationException.createWithCauseAndMessage(HttpStatusCode.BAD_REQUEST,
 cause, "foobar");
+        val cause = new Exception("barfoo", new 
Exception("root-cause-message"));
+        val ex = RestfulObjectsApplicationException
+                .createWithCauseAndMessage(HttpStatusCode.BAD_REQUEST, cause, 
"foobar");
 
         // when
         final Response response = exceptionMapper.toResponse(ex);
@@ -140,12 +142,12 @@ public abstract class 
RestfulObjectsApplicationExceptionMapper_Test_Contract {
         // then
         assertThat((String) response.getMetadata().get("Warning").get(0), 
is("199 RestfulObjects foobar"));
         assertThat(jsonRepr.getString("message"), is("foobar"));
-        final JsonRepresentation detail = jsonRepr.getRepresentation("detail");
-        assertThat(detail, is(not(nullValue())));
-        assertThat(detail.getString("message"), is("foobar"));
-        final JsonRepresentation causedBy = 
detail.getRepresentation("causedBy");
-        assertThat(causedBy, is(not(nullValue())));
-        assertThat(causedBy.getString("message"), is(cause.getMessage()));
+        val detailJson = jsonRepr.getRepresentation("detail");
+        assertThat(detailJson, is(not(nullValue())));
+        assertThat(detailJson.getString("message"), is("foobar"));
+        val causedByJson = detailJson.getRepresentation("causedBy");
+        assertThat(causedByJson, is(not(nullValue())));
+        assertThat(causedByJson.getString("message"), 
is("root-cause-message"));
     }
 
 }
diff --git a/viewers/restfulobjects/viewer/pom.xml 
b/viewers/restfulobjects/viewer/pom.xml
index c8c1de5a83..5db682ce50 100644
--- a/viewers/restfulobjects/viewer/pom.xml
+++ b/viewers/restfulobjects/viewer/pom.xml
@@ -52,17 +52,6 @@
             <artifactId>isis-viewer-restfulobjects-rendering</artifactId>
         </dependency>
 
-               <!-- JDO API (non transient, provided by plugin) -->
-               <dependency>
-                       <!-- v3.2 not available yet, use datanucleus staging 
for now -->
-                       <!-- <groupId>javax.jdo</groupId> -->
-                       <!-- <artifactId>jdo-api</artifactId> -->
-                       <groupId>org.datanucleus</groupId>
-                       <artifactId>javax.jdo</artifactId>
-                       <!-- provided by plugins -->
-                       <scope>provided</scope>
-               </dependency>
-
         <dependency>
             <groupId>com.fasterxml.jackson.module</groupId>
             <artifactId>jackson-module-jaxb-annotations</artifactId>
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/ExceptionMapperAbstract.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/ExceptionMapperAbstract.java
index 769fd69663..065e7593f3 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/ExceptionMapperAbstract.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/ExceptionMapperAbstract.java
@@ -28,7 +28,10 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.ext.ExceptionMapper;
 
+import org.springframework.beans.factory.annotation.Autowired;
+
 import org.apache.isis.applib.exceptions.RecoverableException;
+import org.apache.isis.applib.services.exceprecog.RootCauseFinder;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
@@ -43,8 +46,8 @@ import static 
org.apache.isis.commons.internal.base._NullSafe.stream;
 
 public abstract class ExceptionMapperAbstract<T extends Throwable> implements 
ExceptionMapper<T> {
 
-    @Context
-    protected HttpHeaders httpHeaders;
+    @Context protected HttpHeaders httpHeaders;
+    @Autowired(required = false) protected List<RootCauseFinder> 
rootCauseFinders;
 
     Response buildResponse(final T ex) {
         return buildResponse(ex, determineStatusCode(ex));
@@ -132,10 +135,11 @@ public abstract class ExceptionMapperAbstract<T extends 
Throwable> implements Ex
     private ExceptionDetail detailIfRequired(
             final RestfulResponse.HttpStatusCode httpStatusCode,
             final Throwable ex) {
+
         return httpStatusCode == RestfulResponse.HttpStatusCode.NOT_FOUND ||
                 httpStatusCode == RestfulResponse.HttpStatusCode.OK
                 ? null
-                        : new ExceptionDetail(ex);
+                : new ExceptionDetail(ex, rootCauseFinders);
     }
 
     private Response buildResponse(
diff --git 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/entity/ExceptionDetail.java
 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/entity/ExceptionDetail.java
index e51abe7af1..8c0a6abc3e 100644
--- 
a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/entity/ExceptionDetail.java
+++ 
b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/mappers/entity/ExceptionDetail.java
@@ -19,8 +19,8 @@
 package org.apache.isis.viewer.restfulobjects.viewer.mappers.entity;
 
 import java.util.List;
+import java.util.stream.Stream;
 
-import javax.jdo.JDOException;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
@@ -28,9 +28,14 @@ import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
 
+import org.springframework.lang.Nullable;
+
+import org.apache.isis.applib.services.exceprecog.RootCauseFinder;
 import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
 
 import lombok.Getter;
+import lombok.NoArgsConstructor;
 
 @XmlRootElement(
         name = "exceptionDetail"
@@ -45,12 +50,9 @@ import lombok.Getter;
         }
         )
 @XmlAccessorType(XmlAccessType.FIELD)
+@NoArgsConstructor
 public class ExceptionDetail {
 
-    private static String format(final StackTraceElement stackTraceElement) {
-        return stackTraceElement.toString();
-    }
-
     @Getter private String className;
     @Getter private String message;
 
@@ -60,32 +62,28 @@ public class ExceptionDetail {
 
     @Getter private ExceptionDetail causedBy;
 
-    public ExceptionDetail() {
-    }
-
-    public ExceptionDetail(final Throwable ex) {
+    public ExceptionDetail(
+            final Throwable ex,
+            final @Nullable List<RootCauseFinder> rootCauseFinders) {
         this.className = ex.getClass().getName();
         this.message = ex.getMessage();
-        final StackTraceElement[] stackTraceElements = ex.getStackTrace();
-        for (final StackTraceElement stackTraceElement : stackTraceElements) {
-            this.stackTrace.add(format(stackTraceElement));
-        }
 
-        final Throwable cause = causeOf(ex);
-        if (cause != null && cause != ex) {
-            this.causedBy = new ExceptionDetail(cause);
-        }
+        Stream.of(ex.getStackTrace())
+            .map(ExceptionDetail::format)
+            .forEach(stackTrace::add);
+
+        this.causedBy = _Exceptions.getRootCause(ex, rootCauseFinders)
+            .filter(cause->cause!=ex)
+            .map(cause->new ExceptionDetail(cause, rootCauseFinders))
+            .orElse(null);
     }
 
-    private static Throwable causeOf(Throwable ex) {
-        if (ex instanceof JDOException) {
-            final JDOException jdoException = (JDOException) ex;
-            final Throwable[] nestedExceptions = 
jdoException.getNestedExceptions();
-            return nestedExceptions != null && nestedExceptions.length > 0? 
nestedExceptions[0]: null;
-        }
-        else {
-            return ex.getCause();
-        }
+    // -- HELPER
+
+    private static String format(final StackTraceElement stackTraceElement) {
+        return stackTraceElement.toString();
     }
 
+
+
 }

Reply via email to