From c1580cf4a714db1930ebc65e3494bfd28c6c923e Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 10 Apr 2024 15:53:46 -0400 Subject: [PATCH] fix: rely upon jackson json schema for java types closes #5866 Signed-off-by: Steve Hawkins --- CHANGELOG.md | 1 + crd-generator/api/pom.xml | 5 ++ .../crd/generator/AbstractJsonSchema.java | 55 ++++++++++++++++++- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ef15ee320..8feb1c90966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Fix #5847: Missing `Log4j2Plugins.dat` descriptor in Kubernetes Lookup * Fix #5853: [java-generator] Gracefully handle colliding enum definitions * Fix #5860: Corrections to java-generator gradle plugin extension +* Fix #5866: Addressed cycle in crd generation with Java 19+ and ZonedDateTime * Fix #5817: NPE on EKS OIDC cluster when token needs to be refreshed #### Improvements diff --git a/crd-generator/api/pom.xml b/crd-generator/api/pom.xml index ee4996ccdf7..92cb8edfbc1 100644 --- a/crd-generator/api/pom.xml +++ b/crd-generator/api/pom.xml @@ -35,6 +35,11 @@ kubernetes-client-api compile + + + com.fasterxml.jackson.module + jackson-module-jsonSchema + io.fabric8 diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java index 68e266807db..4f2bb577444 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java @@ -16,7 +16,10 @@ package io.fabric8.crd.generator; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.module.jsonSchema.JsonSchema; +import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator; import io.fabric8.crd.generator.InternalSchemaSwaps.SwapResult; import io.fabric8.crd.generator.annotation.SchemaSwap; import io.fabric8.crd.generator.utils.Types; @@ -24,6 +27,7 @@ import io.fabric8.kubernetes.api.model.Duration; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; import io.sundr.builder.internal.functions.TypeAs; import io.sundr.model.AnnotationRef; import io.sundr.model.ClassRef; @@ -43,6 +47,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -122,6 +127,9 @@ public abstract class AbstractJsonSchema { public static final String JSON_NODE_TYPE = "com.fasterxml.jackson.databind.JsonNode"; public static final String ANY_TYPE = "io.fabric8.kubernetes.api.model.AnyType"; + private static final JsonSchemaGenerator GENERATOR; + private static final Set COMPLEX_JAVA_TYPES = new HashSet<>(); + static { COMMON_MAPPINGS.put(STRING_REF, STRING_MARKER); COMMON_MAPPINGS.put(DATE_REF, STRING_MARKER); @@ -138,6 +146,10 @@ public abstract class AbstractJsonSchema { COMMON_MAPPINGS.put(QUANTITY_REF, INT_OR_STRING_MARKER); COMMON_MAPPINGS.put(INT_OR_STRING_REF, INT_OR_STRING_MARKER); COMMON_MAPPINGS.put(DURATION_REF, STRING_MARKER); + ObjectMapper mapper = new ObjectMapper(); + // initialize with client defaults + new KubernetesSerialization(mapper, false); + GENERATOR = new JsonSchemaGenerator(mapper); } public static String getSchemaTypeFor(TypeRef typeRef) { @@ -853,18 +865,55 @@ private T internalFromImpl(String name, TypeRef typeRef, LinkedHashMap visited, InternalSchemaSwaps schemaSwaps) { - if (visited.put(def.getFullyQualifiedName(), name) != null) { + String fullyQualifiedName = def.getFullyQualifiedName(); + String mapping = resolveJavaClass(fullyQualifiedName); + if (mapping != null) { + return singleProperty(mapping); + } + if (visited.put(fullyQualifiedName, name) != null) { throw new IllegalArgumentException( - "Found a cyclic reference involving the field of type " + def.getFullyQualifiedName() + " starting a field " + "Found a cyclic reference involving the field of type " + fullyQualifiedName + " starting a field " + visited.entrySet().stream().map(e -> e.getValue() + " >>\n" + e.getKey()).collect(Collectors.joining(".")) + "." + name); } T res = internalFromImpl(def, visited, schemaSwaps); - visited.remove(def.getFullyQualifiedName()); + visited.remove(fullyQualifiedName); return res; } + private String resolveJavaClass(String fullyQualifiedName) { + if ((!fullyQualifiedName.startsWith("java.") && !fullyQualifiedName.startsWith("javax.")) + || COMPLEX_JAVA_TYPES.contains(fullyQualifiedName)) { + return null; + } + String mapping = null; + try { + Class clazz = Class.forName(fullyQualifiedName); + JsonSchema schema = GENERATOR.generateSchema(clazz); + if (schema.isIntegerSchema()) { + mapping = INTEGER_MARKER; + } else if (schema.isNumberSchema()) { + mapping = NUMBER_MARKER; + } else if (schema.isBooleanSchema()) { + mapping = BOOLEAN_MARKER; + } else if (schema.isStringSchema()) { + mapping = STRING_MARKER; + } + } catch (Exception e) { + LOGGER.debug( + "Something went wrong with detecting java type schema for {}, will use full introspection instead", + fullyQualifiedName, e); + } + // cache the result for subsequent calls + if (mapping != null) { + COMMON_MAPPINGS.put(TypeDef.forName(fullyQualifiedName).toReference(), mapping); + } else { + COMPLEX_JAVA_TYPES.add(fullyQualifiedName); + } + return mapping; + } + /** * Builds the schema for specifically handled property types (e.g. intOrString properties) *