Skip to content

Commit

Permalink
generate jackson serializers
Browse files Browse the repository at this point in the history
fix object mapper generation for all primitive types

implement nested types in generated object mapper

implement SecureField annotation support

avoid generating mapper for pojo with unknown jackson annotations

implement collections serialization

fix all tests in rest-jackson module

wip

wip

refactor and simplification

performance tuning

make reflection-free serializers generation opt-in

add @produces annotation to SimpleJsonResource rest endpoints where appropriate
  • Loading branch information
mariofusco committed Aug 1, 2024
1 parent 92eef40 commit 3b2d10b
Show file tree
Hide file tree
Showing 12 changed files with 684 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.resteasy.reactive.jackson.deployment.processor;

import java.util.function.BooleanSupplier;

import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

/**
* Jackson optimization configuration.
*/
@ConfigMapping(prefix = "quarkus.jackson.optimization")
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public interface JacksonOptimizationConfig {

/**
* Enable build time generation of reflection-free Jackson serializers.
*/
@WithDefault("false")
boolean enableReflectionFreeSerializers();

class IsReflectionFreeSerializersEnabled implements BooleanSupplier {
JacksonOptimizationConfig config;

public boolean getAsBoolean() {
return config.enableReflectionFreeSerializers();
}
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import jakarta.inject.Singleton;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.MediaType;
Expand Down Expand Up @@ -57,6 +58,7 @@
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
Expand Down Expand Up @@ -370,6 +372,35 @@ public void initializeRolesAllowedConfigExp(ResteasyReactiveServerJacksonRecorde
}
}

@BuildStep(onlyIf = JacksonOptimizationConfig.IsReflectionFreeSerializersEnabled.class)
@Record(ExecutionTime.STATIC_INIT)
public void handleEndpointParams(ResteasyReactiveResourceMethodEntriesBuildItem resourceMethodEntries,
JaxRsResourceIndexBuildItem jaxRsIndex, CombinedIndexBuildItem index,
ResteasyReactiveServerJacksonRecorder recorder,
BuildProducer<GeneratedClassBuildItem> generatedClassBuildItemBuildProducer) {

IndexView indexView = jaxRsIndex.getIndexView();

Map<String, ClassInfo> jsonClasses = new HashMap<>();
for (ResteasyReactiveResourceMethodEntriesBuildItem.Entry entry : resourceMethodEntries.getEntries()) {
MethodInfo methodInfo = entry.getMethodInfo();
AnnotationInstance producesAnn = methodInfo.annotation(Produces.class);
if (producesAnn != null && producesAnn.value().toString().contains("json")) {
ClassInfo effectiveReturnClassInfo = getEffectiveReturnClassInfo(methodInfo, indexView);
if (effectiveReturnClassInfo != null) {
jsonClasses.put(effectiveReturnClassInfo.name().toString(), effectiveReturnClassInfo);
}
}
}

if (!jsonClasses.isEmpty()) {
JacksonSerializerFactory factory = new JacksonSerializerFactory(generatedClassBuildItemBuildProducer,
index.getComputingIndex());
factory.create(jsonClasses.values())
.forEach(recorder::recordGeneratedSerializer);
}
}

@BuildStep
public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem resourceMethodEntries,
JaxRsResourceIndexBuildItem index,
Expand Down Expand Up @@ -407,32 +438,7 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
continue;
}

Type returnType = methodInfo.returnType();
if (returnType.kind() == Type.Kind.VOID) {
continue;
}
Type effectiveReturnType = returnType;
if (effectiveReturnType.name().equals(ResteasyReactiveDotNames.REST_RESPONSE) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.UNI) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.COMPLETABLE_FUTURE) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.COMPLETION_STAGE) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.REST_MULTI) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.MULTI)) {
if (effectiveReturnType.kind() != Type.Kind.PARAMETERIZED_TYPE) {
continue;
}

effectiveReturnType = returnType.asParameterizedType().arguments().get(0);
}
if (effectiveReturnType.name().equals(ResteasyReactiveDotNames.SET) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.COLLECTION) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.LIST)) {
effectiveReturnType = effectiveReturnType.asParameterizedType().arguments().get(0);
} else if (effectiveReturnType.name().equals(ResteasyReactiveDotNames.MAP)) {
effectiveReturnType = effectiveReturnType.asParameterizedType().arguments().get(1);
}

ClassInfo effectiveReturnClassInfo = indexView.getClassByName(effectiveReturnType.name());
ClassInfo effectiveReturnClassInfo = getEffectiveReturnClassInfo(methodInfo, indexView);
if (effectiveReturnClassInfo == null) {
continue;
}
Expand Down Expand Up @@ -461,6 +467,39 @@ public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem r
}
}

private static ClassInfo getEffectiveReturnClassInfo(MethodInfo methodInfo, IndexView indexView) {
Type returnType = methodInfo.returnType();
if (returnType.kind() == Type.Kind.VOID) {
return null;
}
Type effectiveReturnType = getEffectiveReturnType(returnType);
return effectiveReturnType == null ? null : indexView.getClassByName(effectiveReturnType.name());
}

private static Type getEffectiveReturnType(Type returnType) {
Type effectiveReturnType = returnType;
if (effectiveReturnType.name().equals(ResteasyReactiveDotNames.REST_RESPONSE) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.UNI) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.COMPLETABLE_FUTURE) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.COMPLETION_STAGE) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.REST_MULTI) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.MULTI)) {
if (effectiveReturnType.kind() != Type.Kind.PARAMETERIZED_TYPE) {
return null;
}

effectiveReturnType = returnType.asParameterizedType().arguments().get(0);
}
if (effectiveReturnType.name().equals(ResteasyReactiveDotNames.SET) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.COLLECTION) ||
effectiveReturnType.name().equals(ResteasyReactiveDotNames.LIST)) {
effectiveReturnType = effectiveReturnType.asParameterizedType().arguments().get(0);
} else if (effectiveReturnType.name().equals(ResteasyReactiveDotNames.MAP)) {
effectiveReturnType = effectiveReturnType.asParameterizedType().arguments().get(1);
}
return effectiveReturnType;
}

private static Map<String, Boolean> getTypesWithSecureField() {
// if any of following types is detected as an endpoint return type or a field of endpoint return type,
// we always need to apply security serialization as any type can be represented with them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

public class Cat extends AbstractNamedPet {

@SecureField(rolesAllowed = "admin")
private int privateAge;

@SecureField(rolesAllowed = "admin")
public int getPrivateAge() {
return privateAge;
}
Expand Down
Loading

0 comments on commit 3b2d10b

Please sign in to comment.