You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/02/28 10:08:08 UTC
[causeway] branch master updated: CAUSEWAY-3304: JaxbUtils: don't use JAXBContext#newInstance
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/causeway.git
The following commit(s) were added to refs/heads/master by this push:
new 15d7311f15 CAUSEWAY-3304: JaxbUtils: don't use JAXBContext#newInstance
15d7311f15 is described below
commit 15d7311f15352f8562b85fd157f154fc62455eb2
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Feb 28 11:08:02 2023 +0100
CAUSEWAY-3304: JaxbUtils: don't use JAXBContext#newInstance
- instead of using JAXBContext#newInstance(Class...), which does lookup
the JaxbContextFactory on each call, and which - depending on
system-properties - could change during the lifetime of an application,
we rather utilize the com.sun.xml.bind.v2.ContextFactory directly
---
commons/src/main/java/module-info.java | 8 +--
.../org/apache/causeway/commons/io/JaxbUtils.java | 83 ++++++++++++++--------
.../internal/resources/XmlRoundTripTest.java | 30 +-------
.../apache/causeway/commons/io/JaxbUtilsTest.java | 10 ++-
4 files changed, 68 insertions(+), 63 deletions(-)
diff --git a/commons/src/main/java/module-info.java b/commons/src/main/java/module-info.java
index 64d6499f39..be3c4b56be 100644
--- a/commons/src/main/java/module-info.java
+++ b/commons/src/main/java/module-info.java
@@ -74,10 +74,10 @@ module org.apache.causeway.commons {
requires transitive spring.core;
requires java.inject;
requires java.annotation;
- requires org.eclipse.persistence.moxy;
+ requires com.sun.xml.bind;
+ requires org.apache.causeway.core.privileged;
- // JAXB JUnit test
- opens org.apache.causeway.commons.internal.resources to java.xml.bind;
- opens org.apache.causeway.commons.io to java.xml.bind;
+ opens org.apache.causeway.commons.internal.resources to java.xml.bind, com.sun.xml.bind; // JUnit test
+ opens org.apache.causeway.commons.io to java.xml.bind, com.sun.xml.bind;
}
\ No newline at end of file
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 193893e9df..f049a9b690 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
@@ -20,6 +20,10 @@ package org.apache.causeway.commons.io;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -28,7 +32,9 @@ import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBContextFactory;
import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
@@ -40,7 +46,6 @@ import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.codec._DocumentFactories;
import org.apache.causeway.commons.internal.collections._Arrays;
-import org.apache.causeway.commons.internal.collections._Lists;
import org.apache.causeway.commons.internal.collections._Maps;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.commons.internal.functions._Functions;
@@ -56,32 +61,25 @@ import lombok.experimental.UtilityClass;
/**
* Utilities to convert from and to JAXB-XML format.
+ * @implNote instead of using {@link JAXBContext#newInstance(Class...)},
+ * which does lookup the JaxbContextFactory on each call,
+ * and which - depending on system-properties - could change during the lifetime of an application,
+ * we rather utilize the com.sun.xml.bind.v2.ContextFactory directly.
*
* @since 2.0 {@index}
*/
@UtilityClass
public class JaxbUtils {
- /** uses given factory as default */
- public void setDefaultJAXBContextFactory(final Class<?> jaxbContextFactoryClass, final boolean force) {
- if(force
- || System.getProperty(JAXBContext.JAXB_CONTEXT_FACTORY)==null) {
- if(jaxbContextFactoryClass!=null) {
- System.setProperty(JAXBContext.JAXB_CONTEXT_FACTORY, jaxbContextFactoryClass.getName());
- } else {
- System.clearProperty(JAXBContext.JAXB_CONTEXT_FACTORY);
- }
- }
- }
-
- /** uses MOXy */
- public void useMoxy() {
- setDefaultJAXBContextFactory(org.eclipse.persistence.jaxb.JAXBContextFactory.class, true);
- }
+ private final static Class<?> JAXB_CONTEXT_FACTORY = com.sun.xml.bind.v2.ContextFactory.class;
+ private final static Map<String,Object> JAXB_CONTEXT_FACTORY_PROPS = Collections.<String,Object>emptyMap();
+ private final static MethodHandle JAXB_CONTEXT_FACTORY_METHOD_HANDLE = jaxbContextFactoryMethodHandle();
- /** clears the system property override */
- public static void usePlatformDefault() {
- setDefaultJAXBContextFactory(null, true);
+ @SneakyThrows
+ private static MethodHandle jaxbContextFactoryMethodHandle() {
+ return MethodHandles.publicLookup()
+ .findStatic(JAXB_CONTEXT_FACTORY, "createContext",
+ MethodType.methodType(JAXBContext.class, Class[].class, Map.class));
}
@Data @Builder
@@ -249,7 +247,7 @@ public class JaxbUtils {
if(pojo==null) return;
val opts = createOptions(customizers);
try {
- sink.writeAll(os->Try.run(()->opts.marshal(pojo, os)));
+ sink.writeAll(os->opts.marshal(pojo, os));
} catch (Exception cause) {
throw verboseException("marshalling domain object to XML", pojo.getClass(), cause);
}
@@ -264,9 +262,9 @@ public class JaxbUtils {
final @Nullable T pojo,
final JaxbUtils.JaxbCustomizer ... customizers) {
if(pojo==null) return null;
- val sh = _Lists.<String>newArrayList(1);
- write(pojo, DataSink.ofStringUtf8Consumer(sh::add), customizers);
- return sh.stream().findFirst().orElse(null);
+ val sb = new StringBuilder();
+ write(pojo, DataSink.ofStringUtf8Consumer(sb), customizers);
+ return sb.toString();
}
// -- CUSTOMIZERS
@@ -301,7 +299,7 @@ public class JaxbUtils {
@SneakyThrows
private static <T> JAXBContext contextOf(final Class<?> ... classesToBeBound) {
try {
- return JAXBContext.newInstance(classesToBeBound);
+ return (JAXBContext) JAXB_CONTEXT_FACTORY_METHOD_HANDLE.invoke(open(classesToBeBound), JAXB_CONTEXT_FACTORY_PROPS);
} catch (Exception e) {
val msg = String.format("obtaining JAXBContext for classes (to be bound) {%s}", _NullSafe.stream(classesToBeBound)
.map(Class::getName)
@@ -310,6 +308,38 @@ public class JaxbUtils {
}
}
+ /**
+ * Clone of org.glassfish.jaxb.runtime.v2.MUtils.open(Class[]).
+ * @param classes used to resolve module for {@linkplain Module#addOpens(String, Module)}
+ * @throws JAXBException if any of a classes package is not open to our module.
+ */
+ private static Class<?>[] open(final Class<?>[] classes) throws JAXBException {
+ final Module coreModule = JAXB_CONTEXT_FACTORY.getClass().getModule();
+ final Module rtModule = JAXBContextFactory.class.getModule();
+ if (rtModule == coreModule || !rtModule.isNamed()) {
+ //we're either in a bundle or on the classpath
+ return classes;
+ }
+ for (Class<?> cls : classes) {
+ Class<?> jaxbClass = cls.isArray() ? cls.getComponentType() : cls;
+ final Module classModule = jaxbClass.getModule();
+ //no need for unnamed and java.base types
+ if (!classModule.isNamed() || "java.base".equals(classModule.getName())) {
+ continue;
+ }
+ final String packageName = jaxbClass.getPackageName();
+
+ if (classModule.isOpen(packageName, rtModule)) {
+ classModule.addOpens(packageName, coreModule);
+ } else {
+ throw new JAXBException(java.text.MessageFormat.format(
+ "Package {0} with class {1} defined in a module {2} must be open to at least {3} module.",
+ packageName, jaxbClass.getName(), classModule.getName(), rtModule.getName()));
+ }
+ }
+ return classes;
+ }
+
// -- ENHANCE EXCEPTION MESSAGE IF POSSIBLE
private static RuntimeException verboseException(final String doingWhat, @Nullable final Class<?> dtoClass, final Throwable cause) {
@@ -353,7 +383,4 @@ public class JaxbUtils {
/*sonar-ignore-off*/
}
-
-
-
}
diff --git a/commons/src/test/java/org/apache/causeway/commons/internal/resources/XmlRoundTripTest.java b/commons/src/test/java/org/apache/causeway/commons/internal/resources/XmlRoundTripTest.java
index d391b1195f..392aaa1ae0 100644
--- a/commons/src/test/java/org/apache/causeway/commons/internal/resources/XmlRoundTripTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/internal/resources/XmlRoundTripTest.java
@@ -18,17 +18,14 @@
*/
package org.apache.causeway.commons.internal.resources;
-import javax.xml.bind.JAXBContext;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
-import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.apache.causeway.commons.io.JaxbUtils;
@@ -39,33 +36,8 @@ import lombok.val;
class XmlRoundTripTest {
- @AfterEach
- void cleanUp() {
- JaxbUtils.usePlatformDefault();
- }
-
@Test @SneakyThrows
- void testMoxy() {
-
- JaxbUtils.useMoxy();
-
- // test prerequisites
- assertNotNull(JAXBContext.newInstance(SampleDto.class));
-
- val dto = getSample();
- val mapper = JaxbUtils
- .mapperFor(SampleDto.class, opts->opts.allowMissingRootElement(true));
- assertEquals(dto, mapper.clone(dto));
- }
-
- @Test @SneakyThrows
- void testPlatformDefault() {
-
- JaxbUtils.usePlatformDefault();
-
- // test prerequisites
- assertNotNull(JAXBContext.newInstance(SampleDto.class));
-
+ void testFrameworkDefault() {
val dto = getSample();
val mapper = JaxbUtils
.mapperFor(SampleDto.class, opts->opts.allowMissingRootElement(true));
diff --git a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java b/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
index 7243412394..588e4e193b 100644
--- a/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/io/JaxbUtilsTest.java
@@ -28,6 +28,9 @@ import javax.xml.bind.annotation.XmlType;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.causeway.commons.internal.base._Strings;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -76,8 +79,11 @@ class JaxbUtilsTest {
val aXml = JaxbUtils.toStringUtf8(a);
val bXml = JaxbUtils.toStringUtf8(b);
- val aRecovered = JaxbUtils.tryRead(A.class, aXml).ifFailureFail().ifAbsentFail().getValue().get();
- val bRecovered = JaxbUtils.tryRead(B.class, bXml).ifFailureFail().ifAbsentFail().getValue().get();
+ assertTrue(_Strings.isNotEmpty(aXml));
+ assertTrue(_Strings.isNotEmpty(bXml));
+
+ val aRecovered = JaxbUtils.tryRead(A.class, aXml).valueAsNonNullElseFail();
+ val bRecovered = JaxbUtils.tryRead(B.class, bXml).valueAsNonNullElseFail();
// then
assertEquals(a, aRecovered);