diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index 46054e40424..317c6b7baf2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -22,6 +22,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.stream.Stream; @@ -68,6 +70,6 @@ protected void handlePrinterColumns(AbstractJsonSchema resolver, PrinterCo }); } - public abstract Stream finish(); + public abstract Stream>> finish(); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index f65fce97ac3..256e5dcc9ed 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.node.JsonNodeFactory; @@ -92,6 +91,7 @@ public abstract class AbstractJsonSchema dependentClasses = new HashSet<>(); public static class AnnotationMetadata { public final Annotation annotation; @@ -118,6 +118,10 @@ public T getSchema() { return root; } + public Set getDependentClasses() { + return dependentClasses; + } + public Optional getSinglePath(Class clazz) { return ofNullable(pathMetadata.get(clazz)).flatMap(m -> m.keySet().stream().findFirst()); } @@ -136,7 +140,7 @@ public Map getAllPaths(Class clazz) { */ private T resolveRoot(Class definition) { InternalSchemaSwaps schemaSwaps = new InternalSchemaSwaps(); - JsonSchema schema = toJsonSchema(definition); + JsonSchema schema = resolvingContext.toJsonSchema(definition); if (schema instanceof GeneratorObjectSchema) { return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata"); } @@ -285,8 +289,11 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null; } - consumeRepeatingAnnotation(gos.javaType.getRawClass(), SchemaSwap.class, ss -> { - swaps.registerSwap(gos.javaType.getRawClass(), + Class rawClass = gos.javaType.getRawClass(); + collectDependentClasses(rawClass); + + consumeRepeatingAnnotation(rawClass, SchemaSwap.class, ss -> { + swaps.registerSwap(rawClass, ss.originalType(), ss.fieldName(), ss.targetType(), ss.depth()); @@ -300,7 +307,7 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa continue; } schemaSwaps = schemaSwaps.branchDepths(); - SwapResult swapResult = schemaSwaps.lookupAndMark(gos.javaType.getRawClass(), name); + SwapResult swapResult = schemaSwaps.lookupAndMark(rawClass, name); LinkedHashMap savedVisited = visited; if (swapResult.onGoing) { visited = new LinkedHashMap<>(); @@ -331,7 +338,7 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa // fully omit - this is a little inconsistent with the NullSchema handling continue; } - propertySchema = toJsonSchema(propertyMetadata.schemaFrom); + propertySchema = resolvingContext.toJsonSchema(propertyMetadata.schemaFrom); type = resolvingContext.objectMapper.getSerializationConfig().constructType(propertyMetadata.schemaFrom); } @@ -359,12 +366,19 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa objectSchema.setXKubernetesPreserveUnknownFields(true); } List validationRules = new ArrayList<>(); - consumeRepeatingAnnotation(gos.javaType.getRawClass(), ValidationRule.class, + consumeRepeatingAnnotation(rawClass, ValidationRule.class, v -> validationRules.add(from(v))); addToValidationRules(objectSchema, validationRules); return objectSchema; } + private void collectDependentClasses(Class rawClass) { + if (rawClass != null && !rawClass.getName().startsWith("java.") && dependentClasses.add(rawClass.getName())) { + Stream.of(rawClass.getInterfaces()).forEach(this::collectDependentClasses); + collectDependentClasses(rawClass.getSuperclass()); + } + } + static String toFQN(LinkedHashMap visited, String name) { if (visited.isEmpty()) { return "." + name; @@ -494,14 +508,6 @@ private static String mapNotEmpty(String s) { return Utils.isNullOrEmpty(s) ? null : s; } - private JsonSchema toJsonSchema(Class clazz) { - try { - return resolvingContext.generator.generateSchema(clazz); - } catch (JsonMappingException e) { - throw new RuntimeException(e); - } - } - protected abstract V newKubernetesValidationRule(); /** diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java index ea85f2ab830..e216af4aebb 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java @@ -19,6 +19,7 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class CRDGenerationInfo { @@ -33,9 +34,9 @@ public Map> getCRDDetailsPerNameAndVersion() { return crdNameToVersionToCRDInfoMap; } - void add(String crdName, String version, URI fileURI) { + void add(String crdName, String version, URI fileURI, Set dependentClassNames) { crdNameToVersionToCRDInfoMap.computeIfAbsent(crdName, k -> new HashMap<>()) - .put(version, new CRDInfo(crdName, version, new File(fileURI).getAbsolutePath())); + .put(version, new CRDInfo(crdName, version, new File(fileURI).getAbsolutePath(), dependentClassNames)); } public int numberOfGeneratedCRDs() { diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index 0df30063270..6b287fe7847 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -28,6 +28,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -173,29 +174,28 @@ public CRDGenerationInfo detailedGenerate() { handlers.values().stream().map(h -> ForkJoinPool.commonPool().submit(() -> h.handle(info, context.forkContext()))) .collect(Collectors.toList()).stream().forEach(ForkJoinTask::join); } else { - handlers.values().stream().forEach(h -> h.handle(info, context)); + handlers.values().stream().forEach(h -> h.handle(info, context.forkContext())); } } } final CRDGenerationInfo crdGenerationInfo = new CRDGenerationInfo(); - handlers.values().stream().flatMap(AbstractCustomResourceHandler::finish).forEach(crd -> emitCrd(crd, crdGenerationInfo)); + handlers.values().stream().flatMap(AbstractCustomResourceHandler::finish) + .forEach(crd -> emitCrd(crd.getKey(), crd.getValue(), crdGenerationInfo)); return crdGenerationInfo; } - public void emitCrd(HasMetadata crd, CRDGenerationInfo crdGenerationInfo) { + public void emitCrd(HasMetadata crd, Set dependentClassNames, CRDGenerationInfo crdGenerationInfo) { final String version = ApiVersionUtil.trimVersion(crd.getApiVersion()); final String crdName = crd.getMetadata().getName(); try { final String outputName = getOutputName(crdName, version); - try (final OutputStream outputStream = output.outputFor(outputName)) { - outputStream.write( - "# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n" - .getBytes()); + try (final OutputStreamWriter writer = new OutputStreamWriter(output.outputFor(outputName), StandardCharsets.UTF_8)) { + writer.write("# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n"); String yaml = kubernetesSerialization.asYaml(crd); - outputStream.write(yaml.getBytes(StandardCharsets.UTF_8)); + writer.write(yaml); final URI fileURI = output.crdURI(outputName); - crdGenerationInfo.add(crdName, version, fileURI); + crdGenerationInfo.add(crdName, version, fileURI, dependentClassNames); } } catch (IOException e) { throw new RuntimeException(e); diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java index ac5281b639d..df26d8135f2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java @@ -15,15 +15,19 @@ */ package io.fabric8.crdv2.generator; +import java.util.Set; + public class CRDInfo { private final String crdName; private final String crdSpecVersion; private final String filePath; + private final Set dependentClassNames; - public CRDInfo(String crdName, String crdSpecVersion, String filePath) { + public CRDInfo(String crdName, String crdSpecVersion, String filePath, Set dependentClassNames) { this.crdName = crdName; this.crdSpecVersion = crdSpecVersion; this.filePath = filePath; + this.dependentClassNames = dependentClassNames; } public String getCrdName() { @@ -38,4 +42,8 @@ public String getFilePath() { return filePath; } + public Set getDependentClassNames() { + return dependentClassNames; + } + } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java index 82cb28a9f4b..b3a5b6e7850 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; @@ -143,4 +144,12 @@ public SchemaFactoryWrapper getWrapper(SerializerProvider provider, VisitorConte }); } + JsonSchema toJsonSchema(Class clazz) { + try { + return generator.generateSchema(clazz); + } catch (JsonMappingException e) { + throw new RuntimeException(e); + } + } + } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java index 66efd563412..7088c8f0292 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -31,16 +31,20 @@ import io.fabric8.kubernetes.model.annotation.SpecReplicas; import io.fabric8.kubernetes.model.annotation.StatusReplicas; +import java.util.AbstractMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Queue; +import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import java.util.stream.Stream; public class CustomResourceHandler extends AbstractCustomResourceHandler { - private Queue crds = new ConcurrentLinkedQueue<>(); + private Queue>> crds = new ConcurrentLinkedQueue<>(); public static final String VERSION = "v1"; @@ -111,24 +115,28 @@ public void addPrinterColumn(String path, String column, String format, int prio .endSpec() .build(); - crds.add(crd); + crds.add(new AbstractMap.SimpleEntry<>(crd, resolver.getDependentClasses())); } @Override - public Stream finish() { - return crds.stream().collect(Collectors.groupingBy(crd -> crd.getMetadata().getName())).values().stream() + public Stream>> finish() { + return crds.stream().collect(Collectors.groupingBy(crd -> crd.getKey().getMetadata().getName())).values().stream() .map(this::combine); } - private CustomResourceDefinition combine(List definitions) { - CustomResourceDefinition primary = definitions.get(0); + private Map.Entry> combine( + List>> definitions) { + Map.Entry> primary = definitions.get(0); if (definitions.size() == 1) { return primary; } - List versions = definitions.stream().flatMap(crd -> crd.getSpec().getVersions().stream()) + List versions = definitions.stream() + .flatMap(crd -> crd.getKey().getSpec().getVersions().stream()) .collect(Collectors.toList()); + Set allDependentClasses = definitions.stream().flatMap(crd -> crd.getValue().stream()).collect(Collectors.toSet()); + List storageVersions = versions.stream() .filter(v -> Optional.ofNullable(v.getStorage()).orElse(true)) .map(CustomResourceDefinitionVersion::getName) @@ -137,13 +145,15 @@ private CustomResourceDefinition combine(List definiti if (storageVersions.size() > 1) { throw new IllegalStateException(String.format( "'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.", - primary.getMetadata().getName(), storageVersions)); + primary.getKey().getMetadata().getName(), storageVersions)); } versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName); //TODO: we could double check that the top-level metadata is consistent across all versions - return new CustomResourceDefinitionBuilder(primary).editSpec().withVersions(versions).endSpec().build(); + return new AbstractMap.SimpleEntry<>( + new CustomResourceDefinitionBuilder(primary.getKey()).editSpec().withVersions(versions).endSpec().build(), + allDependentClasses); } }