From 6b633200ee201cd3adc1c114e292a3b48b5ef5b8 Mon Sep 17 00:00:00 2001 From: Holly Cummins Date: Thu, 30 May 2024 17:58:04 +0100 Subject: [PATCH] Revert "Revert #40601 and disable tests enabled by #40749" This reverts commit fc3988b0e246095f3c6a09e4b750dbe4b09eeae6. It has some additional changes which re-revert part of #40601, to reintroduce removed guards to avoid cloning things like Quarkus runtime classes. Otherwise we get test failures. --- bom/application/pom.xml | 5 + .../extension/it/TestParameterDevModeIT.java | 2 - test-framework/junit5/pom.xml | 6 +- .../test/junit/QuarkusTestExtension.java | 7 +- .../junit/internal/CustomListConverter.java | 63 ---------- .../junit/internal/CustomMapConverter.java | 41 ------- .../internal/CustomMapEntryConverter.java | 55 --------- .../junit/internal/CustomSetConverter.java | 40 ------- .../internal/NewSerializingDeepClone.java | 113 ++++++++++++++++++ .../internal/SerializationDeepClone.java | 46 ------- ...alizationWithXStreamFallbackDeepClone.java | 35 ------ .../junit/{ => internal}/TestInfoImpl.java | 6 +- .../test/junit/internal/XStreamDeepClone.java | 61 ---------- 13 files changed, 128 insertions(+), 352 deletions(-) delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapConverter.java delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapEntryConverter.java delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java rename test-framework/junit5/src/main/java/io/quarkus/test/junit/{ => internal}/TestInfoImpl.java (79%) delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 82635f6f91c33..764d26301515b 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -4848,6 +4848,11 @@ pom + + org.jboss.marshalling + jboss-marshalling + ${jboss-marshalling.version} + org.jboss.threads jboss-threads diff --git a/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java index 6219d4c8ca510..83355e35ca946 100644 --- a/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java +++ b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/it/TestParameterDevModeIT.java @@ -7,7 +7,6 @@ import org.apache.maven.shared.invoker.MavenInvocationException; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; @@ -22,7 +21,6 @@ * mvn install -Dit.test=DevMojoIT#methodName */ @DisabledIfSystemProperty(named = "quarkus.test.native", matches = "true") -@Disabled("Needs https://github.com/junit-team/junit5/pull/3820 and #40601") public class TestParameterDevModeIT extends RunAndCheckMojoTestBase { protected int getPort() { diff --git a/test-framework/junit5/pom.xml b/test-framework/junit5/pom.xml index 132c4db1b6531..449f8fda37df5 100644 --- a/test-framework/junit5/pom.xml +++ b/test-framework/junit5/pom.xml @@ -49,10 +49,8 @@ quarkus-core - com.thoughtworks.xstream - xstream - - 1.4.20 + org.jboss.marshalling + jboss-marshalling diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index da4a434c123b7..ae707cbba745a 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -106,7 +106,8 @@ import io.quarkus.test.junit.callback.QuarkusTestContext; import io.quarkus.test.junit.callback.QuarkusTestMethodContext; import io.quarkus.test.junit.internal.DeepClone; -import io.quarkus.test.junit.internal.SerializationWithXStreamFallbackDeepClone; +import io.quarkus.test.junit.internal.NewSerializingDeepClone; +import io.quarkus.test.junit.internal.TestInfoImpl; public class QuarkusTestExtension extends AbstractJvmQuarkusTestExtension implements BeforeEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, AfterEachCallback, @@ -351,7 +352,7 @@ private void shutdownHangDetection() { } private void populateDeepCloneField(StartupAction startupAction) { - deepClone = new SerializationWithXStreamFallbackDeepClone(startupAction.getClassLoader()); + deepClone = new NewSerializingDeepClone(originalCl, startupAction.getClassLoader()); } private void populateTestMethodInvokers(ClassLoader quarkusClassLoader) { @@ -979,6 +980,7 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation } else if (clonePattern.matcher(theclass.getName()).matches()) { cloneRequired = true; } else { + // Don't clone things which are already loaded by the quarkus application's classloader side of the tree try { cloneRequired = runningQuarkusApplication.getClassLoader() .loadClass(theclass.getName()) != theclass; @@ -1002,6 +1004,7 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation } else { argumentsFromTccl.add(arg); } + } if (testMethodInvokerToUse != null) { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java deleted file mode 100644 index ddb8642d0056c..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.quarkus.test.junit.internal; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; - -import com.thoughtworks.xstream.converters.collections.CollectionConverter; -import com.thoughtworks.xstream.mapper.Mapper; - -/** - * A custom List converter that always uses ArrayList for unmarshalling. - * This is probably not semantically correct 100% of the time, but it's likely fine - * for all the cases where we are using marshalling / unmarshalling. - * - * The reason for doing this is to avoid XStream causing illegal access issues - * for internal JDK lists - */ -public class CustomListConverter extends CollectionConverter { - - // if we wanted to be 100% sure, we'd list all the List.of methods, but I think it's pretty safe to say - // that the JDK won't add custom implementations for the other classes - - private final Predicate supported = new Predicate() { - - private final Set JDK_LIST_CLASS_NAMES = Set.of( - List.of().getClass().getName(), - List.of(Integer.MAX_VALUE).getClass().getName(), - Arrays.asList(Integer.MAX_VALUE).getClass().getName(), - Collections.unmodifiableList(List.of()).getClass().getName(), - Collections.emptyList().getClass().getName(), - List.of(Integer.MIN_VALUE, Integer.MAX_VALUE).subList(0, 1).getClass().getName()); - - @Override - public boolean test(String className) { - return JDK_LIST_CLASS_NAMES.contains(className); - } - }.or(new Predicate<>() { - - private static final String GUAVA_LISTS_PACKAGE = "com.google.common.collect.Lists"; - - @Override - public boolean test(String className) { - return className.startsWith(GUAVA_LISTS_PACKAGE); - } - }); - - public CustomListConverter(Mapper mapper) { - super(mapper); - } - - @Override - public boolean canConvert(Class type) { - return (type != null) && supported.test(type.getName()); - } - - @Override - protected Object createCollection(Class type) { - return new ArrayList<>(); - } -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapConverter.java deleted file mode 100644 index fe93cb8594587..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapConverter.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.quarkus.test.junit.internal; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import com.thoughtworks.xstream.converters.collections.MapConverter; -import com.thoughtworks.xstream.mapper.Mapper; - -/** - * A custom Map converter that always uses HashMap for unmarshalling. - * This is probably not semantically correct 100% of the time, but it's likely fine - * for all the cases where we are using marshalling / unmarshalling. - * - * The reason for doing this is to avoid XStream causing illegal access issues - * for internal JDK maps - */ -public class CustomMapConverter extends MapConverter { - - // if we wanted to be 100% sure, we'd list all the Set.of methods, but I think it's pretty safe to say - // that the JDK won't add custom implementations for the other classes - private final Set SUPPORTED_CLASS_NAMES = Set.of( - Map.of().getClass().getName(), - Map.of(Integer.MAX_VALUE, Integer.MAX_VALUE).getClass().getName(), - Collections.emptyMap().getClass().getName()); - - public CustomMapConverter(Mapper mapper) { - super(mapper); - } - - @Override - public boolean canConvert(Class type) { - return (type != null) && SUPPORTED_CLASS_NAMES.contains(type.getName()); - } - - @Override - protected Object createCollection(Class type) { - return new HashMap<>(); - } -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapEntryConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapEntryConverter.java deleted file mode 100644 index f20a7fe3e3f36..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomMapEntryConverter.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.quarkus.test.junit.internal; - -import java.util.AbstractMap; -import java.util.Map; -import java.util.Set; - -import com.thoughtworks.xstream.converters.MarshallingContext; -import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.converters.collections.MapConverter; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; -import com.thoughtworks.xstream.io.HierarchicalStreamWriter; -import com.thoughtworks.xstream.mapper.Mapper; - -/** - * A custom Map.Entry converter that always uses AbstractMap.SimpleEntry for unmarshalling. - * This is probably not semantically correct 100% of the time, but it's likely fine - * for all the cases where we are using marshalling / unmarshalling. - * - * The reason for doing this is to avoid XStream causing illegal access issues - * for internal JDK types - */ -@SuppressWarnings({ "rawtypes", "unchecked" }) -public class CustomMapEntryConverter extends MapConverter { - - private final Set SUPPORTED_CLASS_NAMES = Set - .of(Map.entry(Integer.MAX_VALUE, Integer.MAX_VALUE).getClass().getName()); - - public CustomMapEntryConverter(Mapper mapper) { - super(mapper); - } - - @Override - public boolean canConvert(Class type) { - return (type != null) && SUPPORTED_CLASS_NAMES.contains(type.getName()); - } - - @Override - public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { - var entryName = mapper().serializedClass(Map.Entry.class); - var entry = (Map.Entry) source; - writer.startNode(entryName); - writeCompleteItem(entry.getKey(), context, writer); - writeCompleteItem(entry.getValue(), context, writer); - writer.endNode(); - } - - @Override - public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - reader.moveDown(); - var key = readCompleteItem(reader, context, null); - var value = readCompleteItem(reader, context, null); - reader.moveUp(); - return new AbstractMap.SimpleEntry(key, value); - } -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java deleted file mode 100644 index 88d434cfaf34a..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.quarkus.test.junit.internal; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import com.thoughtworks.xstream.converters.collections.CollectionConverter; -import com.thoughtworks.xstream.mapper.Mapper; - -/** - * A custom Set converter that always uses HashSet for unmarshalling. - * This is probably not semantically correct 100% of the time, but it's likely fine - * for all the cases where we are using marshalling / unmarshalling. - * - * The reason for doing this is to avoid XStream causing illegal access issues - * for internal JDK sets - */ -public class CustomSetConverter extends CollectionConverter { - - // if we wanted to be 100% sure, we'd list all the Set.of methods, but I think it's pretty safe to say - // that the JDK won't add custom implementations for the other classes - private final Set SUPPORTED_CLASS_NAMES = Set.of( - Set.of().getClass().getName(), - Set.of(Integer.MAX_VALUE).getClass().getName(), - Collections.emptySet().getClass().getName()); - - public CustomSetConverter(Mapper mapper) { - super(mapper); - } - - @Override - public boolean canConvert(Class type) { - return (type != null) && SUPPORTED_CLASS_NAMES.contains(type.getName()); - } - - @Override - protected Object createCollection(Class type) { - return new HashSet<>(); - } -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java new file mode 100644 index 0000000000000..682a196e00c71 --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/NewSerializingDeepClone.java @@ -0,0 +1,113 @@ +package io.quarkus.test.junit.internal; + +import java.io.IOException; +import java.io.Serializable; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.util.Set; +import java.util.function.Supplier; + +import org.jboss.marshalling.cloner.ClassCloner; +import org.jboss.marshalling.cloner.ClonerConfiguration; +import org.jboss.marshalling.cloner.ObjectCloner; +import org.jboss.marshalling.cloner.ObjectCloners; +import org.junit.jupiter.api.TestInfo; + +/** + * A deep-clone implementation using JBoss Marshalling's fast object cloner. + */ +public final class NewSerializingDeepClone implements DeepClone { + private final ObjectCloner cloner; + + public NewSerializingDeepClone(final ClassLoader sourceLoader, final ClassLoader targetLoader) { + ClonerConfiguration cc = new ClonerConfiguration(); + cc.setSerializabilityChecker(clazz -> clazz != Object.class); + cc.setClassCloner(new ClassCloner() { + public Class clone(final Class original) { + if (isUncloneable(original)) { + return original; + } + try { + return targetLoader.loadClass(original.getName()); + } catch (ClassNotFoundException ignored) { + return original; + } + } + + public Class cloneProxy(final Class proxyClass) { + // not really supported + return proxyClass; + } + }); + cc.setCloneTable( + (original, objectCloner, classCloner) -> { + if (EXTRA_IDENTITY_CLASSES.contains(original.getClass())) { + // avoid copying things that do not need to be copied + return original; + } else if (isUncloneable(original.getClass())) { + if (original instanceof Supplier s) { + // sneaky + return (Supplier) () -> clone(s.get()); + } else { + return original; + } + } else if (original instanceof TestInfo info) { + // copy the test info correctly + return new TestInfoImpl(info.getDisplayName(), info.getTags(), + info.getTestClass().map(this::cloneClass), + info.getTestMethod().map(this::cloneMethod)); + } else if (original == sourceLoader) { + return targetLoader; + } + // let the default cloner handle it + return null; + }); + cloner = ObjectCloners.getSerializingObjectClonerFactory().createCloner(cc); + } + + private static boolean isUncloneable(Class clazz) { + return clazz.isHidden() && !Serializable.class.isAssignableFrom(clazz); + } + + private Class cloneClass(Class clazz) { + try { + return (Class) cloner.clone(clazz); + } catch (IOException | ClassNotFoundException e) { + return null; + } + } + + private Method cloneMethod(Method method) { + try { + Class declaring = (Class) cloner.clone(method.getDeclaringClass()); + Class[] argTypes = (Class[]) cloner.clone(method.getParameterTypes()); + return declaring.getDeclaredMethod(method.getName(), argTypes); + } catch (Exception e) { + return null; + } + } + + public Object clone(final Object objectToClone) { + try { + return cloner.clone(objectToClone); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + + /** + * Classes which do not need to be cloned. + */ + private static final Set> EXTRA_IDENTITY_CLASSES = Set.of( + Object.class, + byte[].class, + short[].class, + int[].class, + long[].class, + char[].class, + boolean[].class, + float[].class, + double[].class); +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java deleted file mode 100644 index 3da2c0c16e372..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationDeepClone.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.quarkus.test.junit.internal; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.ObjectStreamClass; - -/** - * Cloning strategy that just serializes and deserializes using plain old java serialization. - */ -class SerializationDeepClone implements DeepClone { - - private final ClassLoader classLoader; - - SerializationDeepClone(ClassLoader classLoader) { - this.classLoader = classLoader; - } - - @Override - public Object clone(Object objectToClone) { - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(512); - try (ObjectOutputStream objOut = new ObjectOutputStream(byteOut)) { - objOut.writeObject(objectToClone); - try (ObjectInputStream objIn = new ClassLoaderAwareObjectInputStream(byteOut)) { - return objIn.readObject(); - } - } catch (IOException | ClassNotFoundException e) { - throw new IllegalStateException("Unable to deep clone object of type '" + objectToClone.getClass().getName() - + "'. Please report the issue on the Quarkus issue tracker.", e); - } - } - - private class ClassLoaderAwareObjectInputStream extends ObjectInputStream { - - public ClassLoaderAwareObjectInputStream(ByteArrayOutputStream byteOut) throws IOException { - super(new ByteArrayInputStream(byteOut.toByteArray())); - } - - @Override - protected Class resolveClass(ObjectStreamClass desc) throws ClassNotFoundException { - return Class.forName(desc.getName(), true, classLoader); - } - } -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java deleted file mode 100644 index 36da89a82e804..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/SerializationWithXStreamFallbackDeepClone.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.quarkus.test.junit.internal; - -import java.io.Serializable; -import java.util.Optional; - -import org.jboss.logging.Logger; - -/** - * Cloning strategy delegating to {@link SerializationDeepClone}, falling back to {@link XStreamDeepClone} in case of error. - */ -public class SerializationWithXStreamFallbackDeepClone implements DeepClone { - - private static final Logger LOG = Logger.getLogger(SerializationWithXStreamFallbackDeepClone.class); - - private final SerializationDeepClone serializationDeepClone; - private final XStreamDeepClone xStreamDeepClone; - - public SerializationWithXStreamFallbackDeepClone(ClassLoader classLoader) { - this.serializationDeepClone = new SerializationDeepClone(classLoader); - this.xStreamDeepClone = new XStreamDeepClone(classLoader); - } - - @Override - public Object clone(Object objectToClone) { - if (objectToClone instanceof Serializable) { - try { - return serializationDeepClone.clone(objectToClone); - } catch (RuntimeException re) { - LOG.debugf("SerializationDeepClone failed (will fall back to XStream): %s", - Optional.ofNullable(re.getCause()).orElse(re)); - } - } - return xStreamDeepClone.clone(objectToClone); - } -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/TestInfoImpl.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/TestInfoImpl.java similarity index 79% rename from test-framework/junit5/src/main/java/io/quarkus/test/junit/TestInfoImpl.java rename to test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/TestInfoImpl.java index 498cc5ff64447..3f7a81b4d3b66 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/TestInfoImpl.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/TestInfoImpl.java @@ -1,4 +1,4 @@ -package io.quarkus.test.junit; +package io.quarkus.test.junit.internal; import java.lang.reflect.Method; import java.util.Optional; @@ -6,14 +6,14 @@ import org.junit.jupiter.api.TestInfo; -class TestInfoImpl implements TestInfo { +public class TestInfoImpl implements TestInfo { private final String displayName; private final Set tags; private final Optional> testClass; private final Optional testMethod; - TestInfoImpl(String displayName, Set tags, Optional> testClass, Optional testMethod) { + public TestInfoImpl(String displayName, Set tags, Optional> testClass, Optional testMethod) { this.displayName = displayName; this.tags = tags; this.testClass = testClass; diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java deleted file mode 100644 index 9951f96734d44..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.quarkus.test.junit.internal; - -import java.util.function.Supplier; - -import com.thoughtworks.xstream.XStream; - -/** - * Super simple cloning strategy that just serializes to XML and deserializes it using xstream - */ -class XStreamDeepClone implements DeepClone { - - private final Supplier xStreamSupplier; - - XStreamDeepClone(ClassLoader classLoader) { - // avoid doing any work eagerly since the cloner is rarely used - xStreamSupplier = () -> { - XStream result = new XStream(); - result.allowTypesByRegExp(new String[] { ".*" }); - result.setClassLoader(classLoader); - result.registerConverter(new CustomListConverter(result.getMapper())); - result.registerConverter(new CustomSetConverter(result.getMapper())); - result.registerConverter(new CustomMapConverter(result.getMapper())); - result.registerConverter(new CustomMapEntryConverter(result.getMapper())); - - return result; - }; - } - - @Override - public Object clone(Object objectToClone) { - if (objectToClone == null) { - return null; - } - - if (objectToClone instanceof Supplier) { - return handleSupplier((Supplier) objectToClone); - } - - return doClone(objectToClone); - } - - private Supplier handleSupplier(final Supplier supplier) { - return new Supplier() { - @Override - public Object get() { - return doClone(supplier.get()); - } - }; - } - - private Object doClone(Object objectToClone) { - XStream xStream = xStreamSupplier.get(); - final String serialized = xStream.toXML(objectToClone); - final Object result = xStream.fromXML(serialized); - if (result == null) { - throw new IllegalStateException("Unable to deep clone object of type '" + objectToClone.getClass().getName() - + "'. Please report the issue on the Quarkus issue tracker."); - } - return result; - } -}