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