You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/12/21 14:16:18 UTC

[isis] branch master updated: ISIS-3316: JaxbService: remove potentially ambiguous methods from API

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 8ead825d78 ISIS-3316: JaxbService: remove potentially ambiguous methods from API
8ead825d78 is described below

commit 8ead825d7841a81b72f904be89b8df48a656400f
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Dec 21 15:16:11 2022 +0100

    ISIS-3316: JaxbService: remove potentially ambiguous methods from API
    
    - consolidate jaxb low level stuff into JaxbUtils
    - if unmarshalled type does not match requested one, fallback to
    type-safe method
---
 .../causeway/applib/services/jaxb/JaxbService.java | 135 ++++++---------------
 .../org/apache/causeway/commons/io/JaxbUtils.java  |  45 ++++---
 .../bootstrap/GridMarshallerServiceBootstrap.java  |  32 ++---
 .../runtimeservices/jaxb/JaxbServiceDefault.java   |  55 +++------
 4 files changed, 89 insertions(+), 178 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java
index c5bfa12316..f0f29ee976 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java
@@ -18,18 +18,16 @@
  */
 package org.apache.causeway.applib.services.jaxb;
 
-import java.io.StringReader;
-import java.io.StringWriter;
 import java.util.Map;
 
 import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
 import javax.xml.bind.Unmarshaller;
 
 import org.springframework.lang.Nullable;
 
-import org.apache.causeway.commons.internal.base._Casts;
+import org.apache.causeway.applib.domain.DomainObjectList;
+import org.apache.causeway.commons.functional.Try;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.io.JaxbUtils;
 
@@ -50,32 +48,6 @@ import lombok.val;
  */
 public interface JaxbService {
 
-    /**
-     * unmarshalls the XML into an instance of the class, using
-     * the provided {@link JAXBContext}.
-     *
-     * @param jaxbContext  - configured for the expected target class
-     * @param xml
-     */
-    default Object fromXml(
-            final JAXBContext jaxbContext,
-            final String xml) {
-        return fromXml(jaxbContext, xml, null);
-    }
-
-    /**
-     * unmarshalls the XML into an instance of the class, using the
-     * provided {@link JAXBContext} and the additional properties.
-     *
-     * @param jaxbContext - configured for the expected target class
-     * @param xml
-     * @param unmarshallerProperties
-     */
-    Object fromXml(
-            JAXBContext jaxbContext,
-            String xml,
-            @Nullable Map<String,Object> unmarshallerProperties);
-
     /**
      * Unmarshalls the XML to the specified domain class.
      */
@@ -129,22 +101,9 @@ public interface JaxbService {
             CausewaySchemas causewaySchemas);
 
 
+    // no injection point resolving
     class Simple implements JaxbService {
 
-        @Override
-        @SneakyThrows
-        @Nullable
-        public final Object fromXml(
-                final @NonNull JAXBContext jaxbContext,
-                final @Nullable String xml,
-                final @Nullable Map<String, Object> unmarshallerProperties) {
-            try {
-                return internalFromXml(jaxbContext, xml, unmarshallerProperties);
-            } catch (Exception e) {
-                throw JaxbUtils.verboseException("unmarshalling XML", null, e);
-            }
-        }
-
         @Override
         @SneakyThrows
         @Nullable
@@ -153,12 +112,21 @@ public interface JaxbService {
                 final @Nullable String xml,
                 final @Nullable Map<String, Object> unmarshallerProperties) {
 
-            try {
-                val jaxbContext = jaxbContextForClass(domainClass);
-                return _Casts.uncheckedCast(internalFromXml(jaxbContext, xml, unmarshallerProperties));
-            } catch (Exception e) {
-                throw JaxbUtils.verboseException("unmarshalling XML", domainClass, e);
+            if (xml == null) {
+                return null;
             }
+
+            return JaxbUtils.tryRead(domainClass, xml, opts->{
+                for (val entry : _NullSafe.entrySet(unmarshallerProperties)) {
+                    opts.property(entry.getKey(), entry.getValue());
+                }
+                opts.unmarshallerConfigurer(this::configure);
+                return opts;
+            })
+            .mapFailure(cause->JaxbUtils.verboseException("unmarshalling XML", domainClass, cause))
+            .ifFailureFail()
+            .getValue().orElse(null);
+
         }
 
         @Override
@@ -167,43 +135,31 @@ public interface JaxbService {
                 final @NonNull Object domainObject,
                 final @Nullable Map<String, Object> marshallerProperties) {
 
-            val domainClass = domainObject.getClass();
-            val jaxbContext = jaxbContextForObject(domainObject);
-            try {
-                val marshaller = jaxbContext.createMarshaller();
-                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+            val jaxbContext = Try.call(()->domainObject instanceof DomainObjectList
+                    ? jaxbContextForList((DomainObjectList)domainObject)
+                    : JaxbUtils.jaxbContextFor(domainObject.getClass(), true))
+                .mapFailure(cause->JaxbUtils.verboseException("creating JAXB context for domain object", domainObject.getClass(), cause))
+                .ifFailureFail()
+                .getValue().orElseThrow();
 
+            return Try.call(()->JaxbUtils.toStringUtf8(domainObject, opts->{
                 for (val entry : _NullSafe.entrySet(marshallerProperties)) {
-                    marshaller.setProperty(entry.getKey(), entry.getValue());
+                    opts.property(entry.getKey(), entry.getValue());
                 }
-
-                configure(marshaller);
-
-                val writer = new StringWriter();
-                marshaller.marshal(domainObject, writer);
-                val xml = writer.toString();
-
-                return xml;
-
-            } catch (Exception e) {
-                throw JaxbUtils.verboseException("marshalling domain object to XML", domainClass, e);
-            }
+                opts.marshallerConfigurer(this::configure);
+                opts.jaxbContextOverride(jaxbContext);
+                return opts;
+            }))
+            .mapFailure(cause->JaxbUtils.verboseException("marshalling domain object to XML", domainObject.getClass(), cause))
+            .ifFailureFail()
+            .getValue().orElse(null);
         }
 
         /**
          * Optional hook
          */
-        protected JAXBContext jaxbContextForObject(final @NonNull Object domainObject) {
-            val useCache = true;
-            return JaxbUtils.jaxbContextFor(domainObject.getClass(), useCache);
-        }
-
-        /**
-         * Optional hook
-         */
-        protected JAXBContext jaxbContextForClass(final @NonNull Class<?> domainObjectClass) {
-            val useCache = true;
-            return JaxbUtils.jaxbContextFor(domainObjectClass, useCache);
+        protected JAXBContext jaxbContextForList(final @NonNull DomainObjectList list) {
+            return JaxbUtils.jaxbContextFor(DomainObjectList.class, true);
         }
 
         /**
@@ -218,27 +174,6 @@ public interface JaxbService {
         protected void configure(final Marshaller marshaller) {
         }
 
-        @Nullable
-        protected Object internalFromXml(
-                final @NonNull JAXBContext jaxbContext,
-                final @Nullable String xml,
-                final @Nullable Map<String, Object> unmarshallerProperties) throws JAXBException {
-
-            if (xml == null) {
-                return null;
-            }
-
-            val unmarshaller = jaxbContext.createUnmarshaller();
-
-            for (val entry : _NullSafe.entrySet(unmarshallerProperties)) {
-                unmarshaller.setProperty(entry.getKey(), entry.getValue());
-            }
-
-            configure(unmarshaller);
-
-            val pojo = unmarshaller.unmarshal(new StringReader(xml));
-            return pojo;
-        }
 
         @Override
         @SneakyThrows
@@ -246,7 +181,7 @@ public interface JaxbService {
                 final @NonNull Object domainObject,
                 final @NonNull CausewaySchemas causewaySchemas) {
 
-            val jaxbContext = jaxbContextForObject(domainObject);
+            val jaxbContext = JaxbUtils.jaxbContextFor(domainObject.getClass(), true);
 
             val outputResolver = new CatalogingSchemaOutputResolver(causewaySchemas);
             jaxbContext.generateSchema(outputResolver);
diff --git a/commons/src/main/java/org/apache/causeway/commons/io/JaxbUtils.java b/commons/src/main/java/org/apache/causeway/commons/io/JaxbUtils.java
index fc31be7abe..b0a9ae5fd7 100644
--- a/commons/src/main/java/org/apache/causeway/commons/io/JaxbUtils.java
+++ b/commons/src/main/java/org/apache/causeway/commons/io/JaxbUtils.java
@@ -42,6 +42,7 @@ import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.internal.codec._DocumentFactories;
 import org.apache.causeway.commons.internal.collections._Maps;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.commons.internal.functions._Functions;
 import org.apache.causeway.commons.internal.reflection._Annotations;
 
 import lombok.Builder;
@@ -60,7 +61,7 @@ import lombok.experimental.UtilityClass;
 @UtilityClass
 public class JaxbUtils {
 
-    /** uses MOXy as default */
+    /** uses given factory as default */
     public void setDefaultJAXBContextFactory(final Class<?> jaxbContextFactoryClass, final boolean force) {
         if(force
                 || System.getProperty(JAXBContext.JAXB_CONTEXT_FACTORY)==null) {
@@ -77,6 +78,7 @@ public class JaxbUtils {
         setDefaultJAXBContextFactory(org.eclipse.persistence.jaxb.JAXBContextFactory.class, true);
     }
 
+    /** clears the system property override */
     public static void usePlatformDefault() {
         setDefaultJAXBContextFactory(null, true);
     }
@@ -87,6 +89,9 @@ public class JaxbUtils {
         private final @Builder.Default boolean allowMissingRootElement = false;
         private final @Builder.Default boolean formattedOutput = false;
         private final @Singular Map<String, Object> properties;
+        private final @Builder.Default @NonNull Consumer<Marshaller> marshallerConfigurer = _Functions.noopConsumer();
+        private final @Builder.Default @NonNull Consumer<Unmarshaller> unmarshallerConfigurer = _Functions.noopConsumer();
+        private final @Nullable JAXBContext jaxbContextOverride;
         public static JaxbOptions defaults() {
             return JaxbOptions.builder().build();
         }
@@ -99,7 +104,9 @@ public class JaxbUtils {
         }
         @SneakyThrows
         private JAXBContext jaxbContext(final Class<?> mappedType) {
-            return jaxbContextFor(mappedType, useContextCache);
+            return jaxbContextOverride!=null
+                    ? jaxbContextOverride
+                    : jaxbContextFor(mappedType, useContextCache);
         }
         @SneakyThrows
         private Marshaller marshaller(final JAXBContext jaxbContext, final Class<?> mappedType) {
@@ -126,15 +133,21 @@ public class JaxbUtils {
         }
         @SneakyThrows
         private <T> T unmarshal(final Unmarshaller unmarshaller, final Class<T> mappedType, final InputStream is) {
-            if(shouldMissingXmlRootElementBeHandledOn(mappedType)) {
-                val xsr = _DocumentFactories.xmlInputFactory().createXMLStreamReader(is);
-                final JAXBElement<T> userElement = unmarshaller.unmarshal(xsr, mappedType);
-                return userElement.getValue();
-            }
-            return _Casts.uncheckedCast(unmarshaller.unmarshal(is));
+            unmarshallerConfigurer.accept(unmarshaller);
+            return shouldMissingXmlRootElementBeHandledOn(mappedType)
+                    ? unmarshalTypesafe(unmarshaller, mappedType, is)
+                    : _Casts.castTo(mappedType, unmarshaller.unmarshal(is))
+                        .orElseGet(()->unmarshalTypesafe(unmarshaller, mappedType, is));
+        }
+        @SneakyThrows
+        private <T> T unmarshalTypesafe(final Unmarshaller unmarshaller, final Class<T> mappedType, final InputStream is) {
+            val xsr = _DocumentFactories.xmlInputFactory().createXMLStreamReader(is);
+            final JAXBElement<T> userElement = unmarshaller.unmarshal(xsr, mappedType);
+            return userElement.getValue();
         }
         @SneakyThrows
         private <T> void marshal(final Marshaller marshaller, final T pojo, final OutputStream os) {
+            marshallerConfigurer.accept(marshaller);
             @SuppressWarnings("unchecked")
             val mappedType = (Class<T>)pojo.getClass();
             if(shouldMissingXmlRootElementBeHandledOn(mappedType)) {
@@ -272,7 +285,7 @@ public class JaxbUtils {
 
     private static Map<Class<?>, JAXBContext> jaxbContextByClass = _Maps.newConcurrentHashMap();
 
-    public static <T> JAXBContext jaxbContextFor(final Class<T> dtoClass, final boolean useCache)  {
+    public static JAXBContext jaxbContextFor(final Class<?> dtoClass, final boolean useCache)  {
         return useCache
                 ? jaxbContextByClass.computeIfAbsent(dtoClass, JaxbUtils::contextOf)
                 : contextOf(dtoClass);
@@ -289,21 +302,21 @@ public class JaxbUtils {
 
     // -- ENHANCE EXCEPTION MESSAGE IF POSSIBLE
 
-    public static Exception verboseException(final String doingWhat, @Nullable final Class<?> dtoClass, final Exception e) {
+    public static Exception verboseException(final String doingWhat, @Nullable final Class<?> dtoClass, final Throwable cause) {
 
         val dtoClassName = Optional.ofNullable(dtoClass).map(Class::getName).orElse("unknown");
 
-        if(isIllegalAnnotationsException(e)) {
+        if(isIllegalAnnotationsException(cause)) {
             // report a better error if possible
             // this is done reflectively because on JDK 8 this exception type is only provided by Oracle JDK
             try {
 
                 val errors = _Casts.<List<? extends Exception>>uncheckedCast(
-                        e.getClass().getMethod("getErrors").invoke(e));
+                        cause.getClass().getMethod("getErrors").invoke(cause));
 
                 if(_NullSafe.size(errors)>0) {
 
-                    return _Exceptions.unrecoverable(e,
+                    return _Exceptions.unrecoverable(cause,
                             "Error %s, "
                             + "due to illegal annotations on object class '%s'; "
                             + "%d error(s) reported: %s",
@@ -320,13 +333,13 @@ public class JaxbUtils {
             }
         }
 
-        return _Exceptions.unrecoverable(e,
+        return _Exceptions.unrecoverable(cause,
                 "Error %s; object class is '%s'", doingWhat, dtoClassName);
     }
 
-    private static boolean isIllegalAnnotationsException(final Exception e) {
+    private static boolean isIllegalAnnotationsException(final Throwable cause) {
         /*sonar-ignore-on*/
-        return "com.sun.xml.bind.v2.runtime.IllegalAnnotationsException".equals(e.getClass().getName());
+        return "com.sun.xml.bind.v2.runtime.IllegalAnnotationsException".equals(cause.getClass().getName());
         /*sonar-ignore-off*/
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java
index f5df623423..ce37d1bd4a 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridMarshallerServiceBootstrap.java
@@ -24,7 +24,6 @@ import java.util.Objects;
 import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.xml.bind.JAXBContext;
 import javax.xml.bind.Marshaller;
 
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -36,15 +35,13 @@ import org.apache.causeway.applib.services.grid.GridMarshallerService;
 import org.apache.causeway.applib.services.jaxb.JaxbService;
 import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
 import org.apache.causeway.commons.functional.Try;
-import org.apache.causeway.commons.internal.base._Lazy;
 import org.apache.causeway.commons.internal.collections._Maps;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.commons.io.JaxbUtils;
 import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
 
 import lombok.Getter;
 import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
 import lombok.experimental.Accessors;
 
 /**
@@ -54,13 +51,19 @@ import lombok.experimental.Accessors;
 @Named(CausewayModuleCoreMetamodel.NAMESPACE + ".GridMarshallerServiceBootstrap")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
 //@Log4j2
 public class GridMarshallerServiceBootstrap
 implements GridMarshallerService<BSGrid> {
 
     private final JaxbService jaxbService;
-//    private final ServiceRegistry serviceRegistry;
+
+    @Inject
+    public GridMarshallerServiceBootstrap(final JaxbService jaxbService) {
+        super();
+        this.jaxbService = jaxbService;
+        // eagerly create a JAXBContext for this grid type (and cache it)
+        JaxbUtils.jaxbContextFor(BSGrid.class, true);
+    }
 
     @Getter(onMethod_={@Override}) @Accessors(fluent = true)
     private final EnumSet<CommonMimeType> supportedFormats =
@@ -92,7 +95,7 @@ implements GridMarshallerService<BSGrid> {
         throwIfFormatNotSupported(format);
         switch(format) {
         case XML:{
-            return Try.call(()->(BSGrid)jaxbService.fromXml(jaxbContext.get(), content));
+            return Try.call(()->jaxbService.fromXml(BSGrid.class, content));
         }
         default:
             throw _Exceptions.unsupportedOperation("supported format %s is not implemented", format.name());
@@ -107,19 +110,4 @@ implements GridMarshallerService<BSGrid> {
         }
     }
 
-    /** registers all discovered grid types */
-    private final _Lazy<JAXBContext> jaxbContext = _Lazy.threadSafe(this::createJaxbContext);
-    @SneakyThrows
-    private JAXBContext createJaxbContext() {
-//        final Class<?>[] supportedGridTypes =
-//                serviceRegistry.select(GridSystemService.class)
-//                .stream()
-//                .map(GridSystemService::gridImplementation)
-//                .collect(Can.toCan())
-//                .distinct()
-//                .stream()
-//                .collect(_Arrays.toArray(Class.class));
-        return JAXBContext.newInstance(BSGrid.class);
-    }
-
 }
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java
index f366617219..88cd94a59a 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java
@@ -18,13 +18,10 @@
  */
 package org.apache.causeway.core.runtimeservices.jaxb;
 
-import java.util.Map;
-
 import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
 import javax.xml.bind.Unmarshaller;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@@ -59,45 +56,23 @@ public class JaxbServiceDefault extends Simple {
     private final ServiceInjector serviceInjector;
     private final SpecificationLoader specLoader;
 
-    @Override @SneakyThrows
-    protected JAXBContext jaxbContextForObject(final @NonNull Object domainObject) {
-        if(domainObject instanceof DomainObjectList) {
-            val domainClass = domainObject.getClass();
-            val domainObjectList = (DomainObjectList) domainObject;
-            try {
-                val elementType = specLoader
-                        .specForType(_Context.loadClass(domainObjectList.getElementTypeFqcn()))
-                        .map(ObjectSpecification::getCorrespondingClass)
-                        .orElse(null);
-                if (elementType!=null
-                        && elementType.getAnnotation(XmlJavaTypeAdapter.class) == null) {
-
-                    return JAXBContext.newInstance(domainClass, elementType);
-                } else {
-                    return JAXBContext.newInstance(domainClass);
-                }
-            } catch (Exception e) {
-                throw JaxbUtils.verboseException("obtaining JAXBContext for a DomainObjectList", domainClass, e);
-            }
-        }
-        return super.jaxbContextForObject(domainObject);
-    }
-
+    @SneakyThrows
     @Override
-    protected Object internalFromXml(
-            final @NonNull JAXBContext jaxbContext,
-            final String xml,
-            final Map<String, Object> unmarshallerProperties) throws JAXBException {
-
-        val pojo = super.internalFromXml(jaxbContext, xml, unmarshallerProperties);
-        if(pojo instanceof DomainObjectList) {
-
-            // go around the loop again, so can properly deserialize the contents
-            val domainObjectList = (DomainObjectList) pojo;
-            val jaxbContextForList = jaxbContextForObject(domainObjectList);
-            return super.internalFromXml(jaxbContextForList, xml, unmarshallerProperties);
+    protected JAXBContext jaxbContextForList(@NonNull final DomainObjectList domainObjectList) {
+        try {
+            val elementType = specLoader
+                    .specForType(_Context.loadClass(domainObjectList.getElementTypeFqcn()))
+                    .map(ObjectSpecification::getCorrespondingClass)
+                    .orElse(null);
+            if (elementType!=null
+                    && elementType.getAnnotation(XmlJavaTypeAdapter.class) == null) {
+                return JAXBContext.newInstance(DomainObjectList.class, elementType);
+            } else {
+                return JaxbUtils.jaxbContextFor(DomainObjectList.class, true);
+            }
+        } catch (Exception e) {
+            throw JaxbUtils.verboseException("obtaining JAXBContext for a DomainObjectList", DomainObjectList.class, e);
         }
-        return pojo;
     }
 
     @Override