diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java index f177f63199d55..262751b00c3aa 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java @@ -19,6 +19,7 @@ public final class ReflectiveClassBuildItem extends MultiBuildItem { private final boolean constructors; private final boolean finalFieldsWritable; private final boolean weak; + private final boolean serialization; public ReflectiveClassBuildItem(boolean methods, boolean fields, Class... className) { this(true, methods, fields, className); @@ -30,6 +31,11 @@ public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean f private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable, boolean weak, Class... className) { + this(constructors, methods, fields, false, false, false, className); + } + + private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable, + boolean weak, boolean serialization, Class... className) { List names = new ArrayList<>(); for (Class i : className) { if (i == null) { @@ -43,6 +49,7 @@ private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean this.constructors = constructors; this.finalFieldsWritable = finalFieldsWritable; this.weak = weak; + this.serialization = serialization; } public ReflectiveClassBuildItem(boolean methods, boolean fields, String... className) { @@ -53,12 +60,26 @@ public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean f this(constructors, methods, fields, false, false, className); } + public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean serialization, + String... className) { + this(constructors, methods, fields, false, false, serialization, className); + } + public static ReflectiveClassBuildItem weakClass(String... className) { return new ReflectiveClassBuildItem(true, true, true, false, true, className); } + public static ReflectiveClassBuildItem serializationClass(String... className) { + return new ReflectiveClassBuildItem(false, false, false, false, false, true, className); + } + private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable, boolean weak, String... className) { + this(constructors, methods, fields, finalFieldsWritable, weak, false, className); + } + + private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable, + boolean weak, boolean serialization, String... className) { for (String i : className) { if (i == null) { throw new NullPointerException(); @@ -70,6 +91,7 @@ private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean this.constructors = constructors; this.finalFieldsWritable = finalFieldsWritable; this.weak = weak; + this.serialization = serialization; } public List getClassNames() { @@ -96,6 +118,10 @@ public boolean isWeak() { return weak; } + public boolean isSerialization() { + return serialization; + } + public static Builder builder(Class... className) { String[] classNameStrings = stream(className) .map(aClass -> { @@ -122,6 +148,7 @@ public static class Builder { private boolean fields; private boolean finalFieldsWritable; private boolean weak; + private boolean serialization; private Builder() { } @@ -156,6 +183,11 @@ public Builder weak(boolean weak) { return this; } + public Builder serialization(boolean serialize) { + this.serialization = serialization; + return this; + } + public ReflectiveClassBuildItem build() { return new ReflectiveClassBuildItem(constructors, methods, fields, finalFieldsWritable, weak, className); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveHierarchyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveHierarchyBuildItem.java index fa5740182743a..dcca87d743c7b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveHierarchyBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveHierarchyBuildItem.java @@ -41,6 +41,7 @@ public final class ReflectiveHierarchyBuildItem extends MultiBuildItem { private final Predicate ignoreFieldPredicate; private final Predicate ignoreMethodPredicate; private final String source; + private final boolean serialization; /** * @deprecated Use the Builder instead. @@ -104,17 +105,19 @@ public ReflectiveHierarchyBuildItem(Type type, Predicate ignoreTypePred @Deprecated public ReflectiveHierarchyBuildItem(Type type, IndexView index, Predicate ignoreTypePredicate, String source) { this(type, index, ignoreTypePredicate, DefaultIgnoreFieldPredicate.INSTANCE, DefaultIgnoreMethodPredicate.INSTANCE, - source); + source, false); } private ReflectiveHierarchyBuildItem(Type type, IndexView index, Predicate ignoreTypePredicate, - Predicate ignoreFieldPredicate, Predicate ignoreMethodPredicate, String source) { + Predicate ignoreFieldPredicate, Predicate ignoreMethodPredicate, String source, + boolean serialization) { this.type = type; this.index = index; this.ignoreTypePredicate = ignoreTypePredicate; this.ignoreFieldPredicate = ignoreFieldPredicate; this.ignoreMethodPredicate = ignoreMethodPredicate; this.source = source; + this.serialization = serialization; } public Type getType() { @@ -141,6 +144,10 @@ public boolean hasSource() { return source != null; } + public boolean isSerialization() { + return serialization; + } + public String getSource() { return source; } @@ -153,6 +160,7 @@ public static class Builder { private Predicate ignoreFieldPredicate = DefaultIgnoreFieldPredicate.INSTANCE; private Predicate ignoreMethodPredicate = DefaultIgnoreMethodPredicate.INSTANCE; private String source = UNKNOWN_SOURCE; + private boolean serialization; public Builder type(Type type) { this.type = type; @@ -184,9 +192,14 @@ public Builder source(String source) { return this; } + public Builder serialization(boolean serialization) { + this.serialization = serialization; + return this; + } + public ReflectiveHierarchyBuildItem build() { return new ReflectiveHierarchyBuildItem(type, index, ignoreTypePredicate, ignoreFieldPredicate, - ignoreMethodPredicate, source); + ignoreMethodPredicate, source, serialization); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java index d0dd44c9052d2..8f83110185321 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java @@ -2,6 +2,7 @@ import static io.quarkus.gizmo.MethodDescriptor.ofMethod; +import java.io.ObjectStreamClass; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; @@ -37,6 +38,8 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.builditem.nativeimage.UnsafeAccessedFieldBuildItem; import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; @@ -269,6 +272,7 @@ public void write(String s, byte[] bytes) { addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, i.isConstructors(), i.isMethods(), i.isFields(), i.areFinalFieldsWritable(), i.isWeak(), + i.isSerialization(), i.getClassNames().toArray(new String[0])); } for (ReflectiveFieldBuildItem i : reflectiveFields) { @@ -279,12 +283,13 @@ public void write(String s, byte[] bytes) { } for (ServiceProviderBuildItem i : serviceProviderBuildItems) { - addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, true, false, false, false, false, + addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, true, false, false, false, false, false, i.providers().toArray(new String[] {})); } - for (Map.Entry entry : reflectiveClasses.entrySet()) { + MethodDescriptor registerSerializationMethod = null; + for (Map.Entry entry : reflectiveClasses.entrySet()) { MethodCreator mv = file.getMethodCreator("registerClass" + count++, "V"); mv.setModifiers(Modifier.PRIVATE | Modifier.STATIC); overallCatch.invokeStaticMethod(mv.getMethodDescriptor()); @@ -357,8 +362,8 @@ public void write(String s, byte[] bytes) { if (entry.getValue().fields) { tc.invokeStaticMethod( ofMethod(RUNTIME_REFLECTION, "register", void.class, - boolean.class, Field[].class), - tc.load(entry.getValue().finalFieldsWritable), fields); + boolean.class, boolean.class, Field[].class), + tc.load(entry.getValue().finalFieldsWritable), tc.load(entry.getValue().serialization), fields); } else if (!entry.getValue().fieldSet.isEmpty()) { ResultHandle farray = tc.newArray(Field.class, tc.load(1)); for (String field : entry.getValue().fieldSet) { @@ -370,6 +375,15 @@ public void write(String s, byte[] bytes) { farray); } } + + if (entry.getValue().serialization) { + if (registerSerializationMethod == null) { + registerSerializationMethod = createRegisterSerializationForClassMethod(file); + } + + tc.invokeStaticMethod(registerSerializationMethod, clazz); + } + CatchBlockCreator cc = tc.addCatch(Throwable.class); //cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); mv.returnValue(null); @@ -439,11 +453,143 @@ public void write(String s, byte[] bytes) { file.close(); } + private MethodDescriptor createRegisterSerializationForClassMethod(ClassCreator file) { + //register serialization feature as requested + MethodCreator requiredFeatures = file.getMethodCreator("getRequiredFeatures", "java.util.List"); + TryBlock requiredCatch = requiredFeatures.tryBlock(); + + ResultHandle serializationFeatureClass = requiredCatch + .loadClass("com.oracle.svm.reflect.serialize.hosted.SerializationFeature"); + ResultHandle requiredFeaturesList = requiredCatch.invokeStaticMethod( + ofMethod("java.util.Collections", "singletonList", List.class, Object.class), + serializationFeatureClass); + + requiredCatch.returnValue(requiredFeaturesList); + + // method to register class for registration + MethodCreator addSerializationForClass = file.getMethodCreator("registerSerializationForClass", "V", Class.class); + addSerializationForClass.setModifiers(Modifier.PRIVATE | Modifier.STATIC); + ResultHandle clazz = addSerializationForClass.getMethodParam(0); + + TryBlock tc = addSerializationForClass.tryBlock(); + + ResultHandle currentThread = tc + .invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class)); + ResultHandle tccl = tc.invokeVirtualMethod( + ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), + currentThread); + + ResultHandle objectClass = tc.invokeStaticMethod( + ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), + tc.load("java.lang.Object"), tc.load(false), tccl); + + ResultHandle serializationSupport = tc.invokeStaticMethod( + IMAGE_SINGLETONS_LOOKUP, + tc.loadClass("com.oracle.svm.core.jdk.serialize.SerializationRegistry")); + + ResultHandle reflectionFactory = tc.invokeStaticMethod( + ofMethod("sun.reflect.ReflectionFactory", "getReflectionFactory", "sun.reflect.ReflectionFactory")); + + AssignableResultHandle newSerializationConstructor = tc.createVariable(Constructor.class); + + ResultHandle externalizableClass = tc.invokeStaticMethod( + ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), + tc.load("java.io.Externalizable"), tc.load(false), tccl); + + BranchResult isExternalizable = tc + .ifTrue(tc.invokeVirtualMethod(ofMethod(Class.class, "isAssignableFrom", boolean.class, Class.class), + externalizableClass, clazz)); + BytecodeCreator ifIsExternalizable = isExternalizable.trueBranch(); + + ResultHandle array1 = ifIsExternalizable.newArray(Class.class, tc.load(1)); + ResultHandle classClass = ifIsExternalizable.invokeStaticMethod( + ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), + ifIsExternalizable.load("java.lang.Class"), ifIsExternalizable.load(false), tccl); + ifIsExternalizable.writeArrayValue(array1, 0, classClass); + + ResultHandle externalizableLookupMethod = ifIsExternalizable.invokeStaticMethod( + ofMethod("com.oracle.svm.util.ReflectionUtil", "lookupMethod", Method.class, Class.class, String.class, + Class[].class), + ifIsExternalizable.loadClass(ObjectStreamClass.class), ifIsExternalizable.load("getExternalizableConstructor"), + array1); + + ResultHandle array2 = ifIsExternalizable.newArray(Object.class, tc.load(1)); + ifIsExternalizable.writeArrayValue(array2, 0, clazz); + + ResultHandle externalizableConstructor = ifIsExternalizable.invokeVirtualMethod( + ofMethod(Method.class, "invoke", Object.class, Object.class, + Object[].class), + externalizableLookupMethod, ifIsExternalizable.loadNull(), array2); + + ResultHandle externalizableConstructorClass = ifIsExternalizable.invokeVirtualMethod( + ofMethod(Constructor.class, "getDeclaringClass", Class.class), + externalizableConstructor); + + ifIsExternalizable.invokeStaticMethod( + ofMethod("com.oracle.svm.reflect.serialize.hosted.SerializationFeature", "addReflections", void.class, + Class.class, Class.class), + clazz, externalizableConstructorClass); + + ifIsExternalizable.returnValue(null); + + ResultHandle clazzModifiers = tc + .invokeVirtualMethod(ofMethod(Class.class, "getModifiers", int.class), clazz); + BranchResult isAbstract = tc.ifTrue(tc + .invokeStaticMethod(ofMethod(Modifier.class, "isAbstract", boolean.class, int.class), clazzModifiers)); + + BytecodeCreator ifIsAbstract = isAbstract.trueBranch(); + BytecodeCreator ifNotAbstract = isAbstract.falseBranch(); + + //abstract classes uses SerializationSupport$StubForAbstractClass for constructor + ResultHandle stubConstructor = ifIsAbstract.invokeVirtualMethod( + ofMethod("sun.reflect.ReflectionFactory", "newConstructorForSerialization", Constructor.class, + Class.class), + reflectionFactory, + tc + .loadClass("com.oracle.svm.reflect.serialize.SerializationSupport$StubForAbstractClass")); + ifIsAbstract.assign(newSerializationConstructor, stubConstructor); + + ResultHandle classConstructor = ifNotAbstract.invokeVirtualMethod( + ofMethod("sun.reflect.ReflectionFactory", "newConstructorForSerialization", Constructor.class, + Class.class), + reflectionFactory, clazz); + ifNotAbstract.assign(newSerializationConstructor, classConstructor); + + ResultHandle newSerializationConstructorClass = tc.invokeVirtualMethod( + ofMethod(Constructor.class, "getDeclaringClass", Class.class), + newSerializationConstructor); + + ResultHandle lookupMethod = tc.invokeStaticMethod( + ofMethod("com.oracle.svm.util.ReflectionUtil", "lookupMethod", Method.class, Class.class, String.class, + Class[].class), + tc.loadClass(Constructor.class), tc.load("getConstructorAccessor"), + tc.newArray(Class.class, tc.load(0))); + + ResultHandle accessor = tc.invokeVirtualMethod( + ofMethod(Method.class, "invoke", Object.class, Object.class, + Object[].class), + lookupMethod, newSerializationConstructor, + tc.newArray(Object.class, tc.load(0))); + + tc.invokeVirtualMethod( + ofMethod("com.oracle.svm.reflect.serialize.SerializationSupport", "addConstructorAccessor", + Object.class, Class.class, Class.class, Object.class), + serializationSupport, clazz, newSerializationConstructorClass, accessor); + tc.invokeStaticMethod( + ofMethod("com.oracle.svm.reflect.serialize.hosted.SerializationFeature", "addReflections", void.class, + Class.class, Class.class), + clazz, objectClass); + + addSerializationForClass.returnValue(null); + + return addSerializationForClass.getMethodDescriptor(); + } + public void addReflectiveMethod(Map reflectiveClasses, ReflectiveMethodBuildItem methodInfo) { String cl = methodInfo.getDeclaringClass(); ReflectionInfo existing = reflectiveClasses.get(cl); if (existing == null) { - reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false, false, false)); + reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false, false, false, false)); } if (methodInfo.getName().equals("")) { existing.ctorSet.add(methodInfo); @@ -454,13 +600,13 @@ public void addReflectiveMethod(Map reflectiveClasses, R public void addReflectiveClass(Map reflectiveClasses, Set forcedNonWeakClasses, boolean constructors, boolean method, - boolean fields, boolean finalFieldsWritable, boolean weak, + boolean fields, boolean finalFieldsWritable, boolean weak, boolean serialization, String... className) { for (String cl : className) { ReflectionInfo existing = reflectiveClasses.get(cl); if (existing == null) { reflectiveClasses.put(cl, new ReflectionInfo(constructors, method, fields, finalFieldsWritable, - !forcedNonWeakClasses.contains(cl) && weak)); + !forcedNonWeakClasses.contains(cl) && weak, serialization)); } else { if (constructors) { existing.constructors = true; @@ -471,6 +617,9 @@ public void addReflectiveClass(Map reflectiveClasses, Se if (fields) { existing.fields = true; } + if (serialization) { + existing.serialization = true; + } } } } @@ -479,7 +628,7 @@ public void addReflectiveField(Map reflectiveClasses, Re String cl = fieldInfo.getDeclaringClass(); ReflectionInfo existing = reflectiveClasses.get(cl); if (existing == null) { - reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false, false, false)); + reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false, false, false, false)); } existing.fieldSet.add(fieldInfo.getName()); } @@ -490,17 +639,20 @@ static final class ReflectionInfo { boolean fields; boolean finalFieldsWritable; boolean weak; + boolean serialization; Set fieldSet = new HashSet<>(); Set methodSet = new HashSet<>(); Set ctorSet = new HashSet<>(); private ReflectionInfo(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable, - boolean weak) { + boolean weak, boolean serialization) { this.methods = methods; this.fields = fields; this.constructors = constructors; this.finalFieldsWritable = finalFieldsWritable; this.weak = weak; + this.serialization = serialization; } } + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java index 0ddd07023e62f..34d44c937496f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java @@ -195,6 +195,7 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem .methods(true) .fields(true) .finalFieldsWritable(doFinalFieldsNeedToBeWritable(info, finalFieldsWritable)) + .serialization(reflectiveHierarchyBuildItem.isSerialization()) .build()); processedReflectiveHierarchies.add(name); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java index 891e6f4d1b5de..7ce6f56eea31d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/RegisterForReflectionBuildStep.java @@ -30,6 +30,7 @@ public void build(BuildProducer reflectiveClass) { boolean methods = getBooleanValue(i, "methods"); boolean fields = getBooleanValue(i, "fields"); boolean ignoreNested = getBooleanValue(i, "ignoreNested"); + boolean serialization = i.value("serialization") != null && i.value("serialization").asBoolean(); AnnotationValue targetsValue = i.value("targets"); AnnotationValue classNamesValue = i.value("classNames"); @@ -37,21 +38,23 @@ public void build(BuildProducer reflectiveClass) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (targetsValue == null && classNamesValue == null) { ClassInfo classInfo = i.target().asClass(); - registerClass(classLoader, classInfo.name().toString(), methods, fields, ignoreNested, reflectiveClass); + registerClass(classLoader, classInfo.name().toString(), methods, fields, ignoreNested, serialization, + reflectiveClass); continue; } if (targetsValue != null) { Type[] targets = targetsValue.asClassArray(); for (Type type : targets) { - registerClass(classLoader, type.name().toString(), methods, fields, ignoreNested, reflectiveClass); + registerClass(classLoader, type.name().toString(), methods, fields, ignoreNested, serialization, + reflectiveClass); } } if (classNamesValue != null) { String[] classNames = classNamesValue.asStringArray(); for (String className : classNames) { - registerClass(classLoader, className, methods, fields, ignoreNested, reflectiveClass); + registerClass(classLoader, className, methods, fields, ignoreNested, serialization, reflectiveClass); } } } @@ -61,8 +64,9 @@ public void build(BuildProducer reflectiveClass) { * BFS Recursive Method to register a class and it's inner classes for Reflection. */ private void registerClass(ClassLoader classLoader, String className, boolean methods, boolean fields, - boolean ignoreNested, final BuildProducer reflectiveClass) { - reflectiveClass.produce(new ReflectiveClassBuildItem(methods, fields, className)); + boolean ignoreNested, boolean serialization, final BuildProducer reflectiveClass) { + reflectiveClass.produce(serialization ? ReflectiveClassBuildItem.serializationClass(className) + : new ReflectiveClassBuildItem(methods, fields, className)); if (ignoreNested) { return; @@ -71,7 +75,7 @@ private void registerClass(ClassLoader classLoader, String className, boolean me try { Class[] declaredClasses = classLoader.loadClass(className).getDeclaredClasses(); for (Class clazz : declaredClasses) { - registerClass(classLoader, clazz.getName(), methods, fields, false, reflectiveClass); + registerClass(classLoader, clazz.getName(), methods, fields, false, serialization, reflectiveClass); } } catch (ClassNotFoundException e) { log.warnf(e, "Failed to load Class %s", className); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/RegisterForReflection.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/RegisterForReflection.java index 66a9bc563609d..afe4ae9f11a64 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/annotations/RegisterForReflection.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/RegisterForReflection.java @@ -43,4 +43,6 @@ * register private classes for Reflection. */ String[] classNames() default {}; + + boolean serialization() default false; } diff --git a/integration-tests/main/src/main/java/io/quarkus/it/corestuff/SerializationTestEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/SerializationTestEndpoint.java new file mode 100644 index 0000000000000..f0b095f07e60a --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/SerializationTestEndpoint.java @@ -0,0 +1,70 @@ +package io.quarkus.it.corestuff; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintWriter; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.quarkus.it.corestuff.serialization.ExternalizablePerson; +import io.quarkus.it.corestuff.serialization.Person; +import io.quarkus.it.corestuff.serialization.SomeSerializationObject; + +/** + * Some core serialization functionality tests + */ +@WebServlet(name = "CoreSerializationTestEndpoint", urlPatterns = "/core/serialization") +public class SerializationTestEndpoint extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + reflectiveSetterInvoke(resp); + } + + private void reflectiveSetterInvoke(HttpServletResponse resp) throws IOException { + try { + SomeSerializationObject instance = new SomeSerializationObject(); + instance.setPerson(new Person("Sheldon")); + ExternalizablePerson ep = new ExternalizablePerson(); + ep.setName("Sheldon 2.0"); + instance.setExternalizablePerson(ep); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(out); + os.writeObject(instance); + ByteArrayInputStream bais = new ByteArrayInputStream(out.toByteArray()); + ObjectInputStream is = new ObjectInputStream(bais); + SomeSerializationObject result = (SomeSerializationObject) is.readObject(); + if (result.getPerson().getName().equals("Sheldon") + && result.getExternalizablePerson().getName().equals("Sheldon 2.0")) { + resp.getWriter().write("OK"); + } else { + reportException("Serialized output differs from input", null, resp); + } + } catch (Exception e) { + reportException(e, resp); + } + } + + private void reportException(final Exception e, final HttpServletResponse resp) throws IOException { + reportException(null, e, resp); + } + + private void reportException(String errorMessage, final Exception e, final HttpServletResponse resp) throws IOException { + final PrintWriter writer = resp.getWriter(); + if (errorMessage != null) { + writer.write(errorMessage); + writer.write(" "); + } + writer.write(e.toString()); + writer.append("\n\t"); + e.printStackTrace(writer); + writer.append("\n\t"); + } + +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/AbstractParent.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/AbstractParent.java new file mode 100644 index 0000000000000..92aa0a55c8bcb --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/AbstractParent.java @@ -0,0 +1,19 @@ +package io.quarkus.it.corestuff.serialization; + +import java.io.Serializable; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection(serialization = true) +abstract class AbstractPerson implements Serializable { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/ExternalizablePerson.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/ExternalizablePerson.java new file mode 100644 index 0000000000000..f9f287487e6d7 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/ExternalizablePerson.java @@ -0,0 +1,37 @@ +package io.quarkus.it.corestuff.serialization; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection(serialization = true) +public class ExternalizablePerson implements Externalizable { + + private String name; + + public ExternalizablePerson() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + out.writeUTF(name); + } + + @Override + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException { + this.name = in.readUTF(); + } + +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/Person.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/Person.java new file mode 100644 index 0000000000000..a2760fa69689f --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/Person.java @@ -0,0 +1,13 @@ +package io.quarkus.it.corestuff.serialization; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection(serialization = true) +public class Person extends AbstractPerson { + public Person() { + } + + public Person(String name) { + setName(name); + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/SomeSerializationObject.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/SomeSerializationObject.java new file mode 100644 index 0000000000000..a5d8b3f32c412 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/serialization/SomeSerializationObject.java @@ -0,0 +1,28 @@ +package io.quarkus.it.corestuff.serialization; + +import java.io.Serializable; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection(serialization = true) +public class SomeSerializationObject implements Serializable { + + private Person person; + private ExternalizablePerson externalizablePerson; + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + + public ExternalizablePerson getExternalizablePerson() { + return externalizablePerson; + } + + public void setExternalizablePerson(ExternalizablePerson externalizablePerson) { + this.externalizablePerson = externalizablePerson; + } +} diff --git a/integration-tests/main/src/main/resources/application.properties b/integration-tests/main/src/main/resources/application.properties index 62b34bb36b257..35e1b85913c9a 100644 --- a/integration-tests/main/src/main/resources/application.properties +++ b/integration-tests/main/src/main/resources/application.properties @@ -52,4 +52,4 @@ quarkus.native.resources.excludes = **/unwanted.* quarkus.log.metrics.enabled=true -quarkus.native.additional-build-args =-H:ReflectionConfigurationFiles=reflection-config.json +quarkus.native.additional-build-args =-H:ReflectionConfigurationFiles=reflection-config.json,-H:SerializationConfigurationResources=serialization-config.json diff --git a/integration-tests/main/src/main/resources/serialization-config.json b/integration-tests/main/src/main/resources/serialization-config.json new file mode 100644 index 0000000000000..6b04afb00f7f7 --- /dev/null +++ b/integration-tests/main/src/main/resources/serialization-config.json @@ -0,0 +1,5 @@ +[ + { + "name" : "java.lang.String" + } +] \ No newline at end of file diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/CoreSerializationInGraalITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/CoreSerializationInGraalITCase.java new file mode 100644 index 0000000000000..2dda1189ee166 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/CoreSerializationInGraalITCase.java @@ -0,0 +1,19 @@ +package io.quarkus.it.main; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.NativeImageTest; +import io.restassured.RestAssured; + +@NativeImageTest +public class CoreSerializationInGraalITCase { + + @Test + public void testEntitySerializationFromServlet() throws Exception { + RestAssured.when().get("/core/serialization").then() + .body(is("OK")); + } + +}