This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push: new 5ca42f7f [JOHNZON-387] basic jsonschema to pojo generation logic 5ca42f7f is described below commit 5ca42f7f9013b5aaf31ec66803929be35f98152d Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Mon Aug 8 11:56:06 2022 +0200 [JOHNZON-387] basic jsonschema to pojo generation logic --- johnzon-jsonschema/pom.xml | 2 +- .../jsonschema/generator/PojoGenerator.java | 633 ++++++++++++++++++++ .../jsonschema/generator/PojoGeneratorTest.java | 624 ++++++++++++++++++++ .../src/test/resources/ConfigMap.json | 193 +++++++ johnzon-jsonschema/src/test/resources/Node.json | 634 +++++++++++++++++++++ .../johnzon/maven/plugin/JsonSchemaToPojoMojo.java | 129 +++++ pom.xml | 4 +- 7 files changed, 2216 insertions(+), 3 deletions(-) diff --git a/johnzon-jsonschema/pom.xml b/johnzon-jsonschema/pom.xml index a03ba174..38464977 100644 --- a/johnzon-jsonschema/pom.xml +++ b/johnzon-jsonschema/pom.xml @@ -32,7 +32,7 @@ <dependency> <!-- only when nashorn will be dropped --> <groupId>org.jruby.joni</groupId> <artifactId>joni</artifactId> - <version>2.1.16</version> + <version>2.1.41</version> <scope>provided</scope> <optional>true</optional> </dependency> diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/generator/PojoGenerator.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/generator/PojoGenerator.java new file mode 100644 index 00000000..b410e848 --- /dev/null +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/generator/PojoGenerator.java @@ -0,0 +1,633 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.johnzon.jsonschema.generator; + +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; +import javax.json.bind.annotation.JsonbProperty; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static java.util.Collections.singletonMap; +import static java.util.Comparator.comparing; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +// todo: support ref resolution for schema and avoid to generate as much classes as attributes +public class PojoGenerator { + private final PojoConfiguration configuration; + + protected final Set<String> imports = new TreeSet<>(String::compareTo); + protected final List<Attribute> attributes = new ArrayList<>(); + protected final Map<String, String> nested = new TreeMap<>(String::compareTo); + + public PojoGenerator(final PojoConfiguration configuration) { + this.configuration = configuration; + } + + public PojoGenerator setNested(final Map<String, String> nested) { + this.nested.putAll(nested); + return this; + } + + public Map<String, String> generate() { + final String name = configuration.getPackageName() + '.' + configuration.getClassName(); + final String path = name.replace('.', '/') + ".java"; + attributes.sort(comparing(a -> a.javaName)); + if (!attributes.isEmpty()) { + imports.add(Objects.class.getName()); + } + final String content = "" + + "package " + configuration.getPackageName() + ";\n" + + "\n" + + (imports.isEmpty() ? "" : imports.stream() + .sorted() + .map(it -> "import " + it + ";") + .collect(joining("\n", "", "\n\n"))) + + "public class " + configuration.getClassName() + afterClassName() + " {\n" + + (attributes.isEmpty() ? + ("" + + " @Override\n" + + " public int hashCode() {\n" + + " return 0;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(final Object other) {\n" + + " return other instanceof " + configuration.getClassName() + ";\n" + + " }\n" + + "}\n") : + (attributes.stream() + .map(a -> "" + + (configuration.isAddJsonbProperty() && !Objects.equals(a.javaName, a.jsonName) ? + " @JsonbProperty(\"" + a.jsonName.replace("\"", "\\\"") + "\")\n" : + "") + + " private " + a.type + " " + a.javaName + ";") + .collect(joining("\n", "", "\n\n")) + + (configuration.isAddAllArgsConstructor() ? + " public " + configuration.getClassName() + "() {\n" + + " // no-op\n" + + " }\n" + + "\n" + + " public " + configuration.getClassName() + "(" + + attributes.stream() + .map(a -> "final " + a.type + " " + a.javaName) + .collect(joining( + ",\n" + IntStream.range( + 0, + " public (".length() + + configuration.getClassName().length()) + .mapToObj(i -> " ") + .collect(joining()), + "", + ") {\n" + + " // no-op\n" + + " }\n\n")) : + "") + + attributes.stream() + .map(a -> { + final String marker = Character.toUpperCase(a.javaName.charAt(0)) + a.javaName.substring(1); + return "" + + " public " + a.type + " get" + Character.toUpperCase(a.javaName.charAt(0)) + a.javaName.substring(1) + "() {\n" + + " return " + a.javaName + ";\n" + + " }\n" + + "\n" + + " public " + + (configuration.isFluentSetters() ? a.type : "void") + + " set" + marker + "(final " + a.type + " " + a.javaName + ") {\n" + + " this." + a.javaName + " = " + a.javaName + ";\n" + + (configuration.isFluentSetters() ? " return this;\n" : "") + + " }\n" + + ""; + }) + .collect(joining("\n", "", "\n")) + + " @Override\n" + + " public int hashCode() {\n" + + " return Objects.hash(\n" + + attributes.stream() + .map(a -> a.javaName) + .collect(joining(",\n ", " ", ");\n")) + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(final Object __other) {\n" + + " if (!(__other instanceof " + configuration.getClassName() + ")) {\n" + + " return false;\n" + + " }\n" + + " final " + configuration.getClassName() + " __otherCasted = (" + configuration.getClassName() + ") __other;\n" + + " return " + attributes.stream() + .map(a -> a.javaName) + .map(it -> "Objects.equals(" + it + ", __otherCasted." + it + ")") + .collect(joining(" &&\n ")) + ";\n" + + " }\n" + )) + + beforeClassEnd() + + "}\n"; + if (nested.isEmpty()) { + return singletonMap(path, content); + } + return Stream.concat( + nested.entrySet().stream(), + Stream.of(new AbstractMap.SimpleImmutableEntry<>(path, content))) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, () -> new TreeMap<>(String::compareTo))); + } + + public PojoGenerator visitSchema(final JsonObject schema) { + if (!schema.containsKey("properties")) { + throw new IllegalArgumentException("Unsupported schema since it does not contain any properties: " + schema); + } + + final JsonObject properties = getValueAs(schema, "properties", JsonObject.class); + final JsonValue required = schema.get("required"); + final List<String> requiredAttrs = required != null && required.getValueType() == JsonValue.ValueType.ARRAY ? + required.asJsonArray().stream().map(JsonString.class::cast).map(JsonString::getString).collect(toList()) : + null; + attributes.addAll(properties.entrySet().stream() + .map(e -> { + final String javaName = toJavaName(e.getKey()); + return new Attribute( + javaName, e.getKey(), + asType(javaName, e.getValue().asJsonObject(), requiredAttrs != null && requiredAttrs.contains(e.getKey()))); + }) + .collect(toList())); + + return this; + } + + /** + * @param ref the reference to resolve. + * @return the reference class name if resolved else null. + */ + protected String onRef(final String ref) { + return null; // todo: check if already in nested for ex + } + + protected String beforeClassEnd() { + return ""; + } + + protected String afterClassName() { + return ""; + } + + protected String asType(final String javaName, final JsonObject schema, final boolean required) { + final JsonValue ref = schema.get("ref"); + if (ref != null && ref.getValueType() == JsonValue.ValueType.STRING) { + final String name = onRef(JsonString.class.cast(ref).getString()); + if (name != null) { + return name; + } + } + + final JsonValue value = schema.get("type"); + String type; + if (value == null) { + if (schema.containsKey("properties") || schema.containsKey("additionalProperties")) { + type = "object"; + } else if (schema.containsKey("items")) { + type = "array"; + } else { // unknown, don't fail for wrongly written schema + imports.add(JsonValue.class.getName()); + return JsonValue.class.getSimpleName(); + } + } else { + type = JsonString.class.cast(value).getString(); + } + final JsonValue formatValue = schema.get("date-time"); + if (formatValue != null && formatValue.getValueType() == JsonValue.ValueType.STRING) { + type = JsonString.class.cast(formatValue).getString(); + } + + switch (type) { + case "array": + final JsonObject items = getValueAs(schema, "items", JsonObject.class); + final String itemType = onItemSchema(javaName, items); + imports.add(List.class.getName()); + return List.class.getSimpleName() + "<" + itemType + ">"; + case "object": + return onObjectAttribute(javaName, schema); + case "null": + imports.add(JsonValue.class.getName()); + return JsonValue.class.getSimpleName(); + case "boolean": + return required ? "boolean" : "Boolean"; + case "string": + final JsonValue enumList = schema.get("enum"); + if (enumList != null && enumList.getValueType() == JsonValue.ValueType.ARRAY) { + return onEnum(javaName, enumList); + } + return "String"; + case "number": + case "double": // openapi + return required ? "double" : "Double"; + // openapi types + case "int": + case "integer": + return required ? "int" : "Integer"; + case "long": + return required ? "long" : "Long"; + case "float": + return required ? "float" : "Float"; + case "date": + imports.add(LocalDate.class.getName()); + return LocalDate.class.getSimpleName(); + case "duration": + imports.add(Duration.class.getName()); + return Duration.class.getSimpleName(); + case "date-time": + case "dateTime": + imports.add(OffsetDateTime.class.getName()); + return OffsetDateTime.class.getSimpleName(); + case "time": + imports.add(LocalTime.class.getName()); + return LocalTime.class.getSimpleName(); + case "byte": + return "byte[]"; + case "uuid": + case "hostname": + case "idn-hostname": + case "email": + case "idn-email": + case "ipv4": + case "ipv6": + case "uri": + case "uri-reference": + case "iri": + case "iri-reference": + case "uri-template": + case "json-pointer": + case "relative-json-pointer": + case "regex": + case "binary": // base64 + case "password": + return "String"; + default: + throw new IllegalArgumentException("Unsupported type: " + type); + } + } + + protected String onEnum(final String javaName, final JsonValue enumList) { + final String className = configuration.getClassName() + Character.toUpperCase(javaName.charAt(0)) + javaName.substring(1); + final Map<String, String> values = enumList.asJsonArray().stream() + .map(JsonString.class::cast) + .map(JsonString::getString) + .collect(toMap(identity(), this::toJavaName)); + final boolean injectValues = !values.keySet().equals(new HashSet<>(values.values())); // java != json + nested.put( + configuration.getPackageName().replace('.', '/') + '/' + className + ".java", "" + + "package " + configuration.getPackageName() + ";\n" + + "\n" + + "public enum " + className + " {\n" + + values.entrySet().stream() + .map(it -> "" + + (injectValues && configuration.isAddJsonbProperty() ? + " @JsonbProperty(\"" + it.getKey().replace("\"", "\\\"") + "\")\n" : + "") + + " " + it.getValue() + + (injectValues ? "(\"" + it.getKey().replace("\"", "\\\"") + "\")" : "")) + .collect(joining(",\n", "", injectValues ? ";\n\n" : "\n")) + + (injectValues ? + "" + + " private String value;\n" + + "\n" + + " " + className + "(final String value) {\n" + + " this.value = value;\n" + + " }\n" + + "\n" + + " public String toString() {\n" + + " return value;\n" + + " }\n" + + "" : + "") + + "}\n"); + return className; + } + + protected String onObjectAttribute(final String javaName, final JsonObject schema) { + final JsonValue additionalProperties = schema.get("additionalProperties"); + final JsonValue properties = schema.get("properties"); + final boolean hasProperties = properties != null && properties.getValueType() == JsonValue.ValueType.OBJECT; + if (!hasProperties && + additionalProperties != null && + additionalProperties.getValueType() == JsonValue.ValueType.OBJECT) { + final JsonObject propSchema = additionalProperties.asJsonObject(); + final JsonValue propTypeValue = propSchema.get("type"); + if (propTypeValue != null && propTypeValue.getValueType() == JsonValue.ValueType.STRING) { + String propType = JsonString.class.cast(propTypeValue).getString(); + final JsonValue formatValue = schema.get("date-time"); + if (formatValue != null && formatValue.getValueType() == JsonValue.ValueType.STRING) { + propType = JsonString.class.cast(formatValue).getString(); + } + switch (propType) { + case "uuid": + case "hostname": + case "idn-hostname": + case "email": + case "idn-email": + case "ipv4": + case "ipv6": + case "uri": + case "uri-reference": + case "iri": + case "iri-reference": + case "uri-template": + case "json-pointer": + case "relative-json-pointer": + case "regex": + case "string": + case "binary": + case "password": + imports.add(Map.class.getName()); + return "Map<String, String>"; + case "boolean": + imports.add(Map.class.getName()); + return "Map<String, Boolean>"; + case "number": + case "double": + imports.add(Map.class.getName()); + return "Map<String, Double>"; + case "int": + case "integer": + imports.add(Map.class.getName()); + return "Map<String, Integer>"; + case "long": + imports.add(Map.class.getName()); + return "Map<String, Long>"; + case "float": + imports.add(Map.class.getName()); + return "Map<String, Float>"; + case "date": + imports.add(Map.class.getName()); + imports.add(LocalDate.class.getName()); + return "Map<String, LocalDate>"; + case "dateTime": + case "date-time": + imports.add(Map.class.getName()); + imports.add(OffsetDateTime.class.getName()); + return "Map<String, OffsetDateTime>"; + case "duration": + imports.add(Map.class.getName()); + imports.add(Duration.class.getName()); + return "Map<String, Duration>"; + case "time": + imports.add(Map.class.getName()); + imports.add(LocalTime.class.getName()); + return "Map<String, LocalTime>"; + default: + // todo: case array, object + } + } + } else if (hasProperties) { + final String className = configuration.getClassName() + Character.toUpperCase(javaName.charAt(0)) + javaName.substring(1); + nested.putAll(new PojoGenerator(new PojoConfiguration() + .setPackageName(configuration.getPackageName()) + .setClassName(className) + .setAddJsonbProperty(configuration.isAddJsonbProperty()) + .setAddAllArgsConstructor(configuration.isAddAllArgsConstructor())) + .visitSchema(schema) + .generate()); + return className; + } + + imports.add(JsonObject.class.getName()); + return JsonObject.class.getSimpleName(); + } + + protected String onItemSchema(final String javaName, final JsonObject schema) { + final JsonValue ref = schema.get("ref"); + if (ref != null && ref.getValueType() == JsonValue.ValueType.STRING) { + final String name = onRef(JsonString.class.cast(ref).getString()); + if (name != null) { + return name; + } + } + + final JsonValue propTypeValue = schema.get("type"); + if (propTypeValue != null && propTypeValue.getValueType() == JsonValue.ValueType.STRING) { + String type = JsonString.class.cast(propTypeValue).getString(); + final JsonValue formatValue = schema.get("date-time"); + if (formatValue != null && formatValue.getValueType() == JsonValue.ValueType.STRING) { + type = JsonString.class.cast(formatValue).getString(); + } + switch (type) { + case "array": + throw new IllegalStateException("Array of array unsupported"); + case "object": + final String className = configuration.getClassName() + Character.toUpperCase(javaName.charAt(0)) + javaName.substring(1); + nested.putAll(new PojoGenerator(new PojoConfiguration() + .setPackageName(configuration.getPackageName()) + .setClassName(className) + .setAddJsonbProperty(configuration.isAddJsonbProperty()) + .setAddAllArgsConstructor(configuration.isAddAllArgsConstructor())) + .visitSchema(schema) + .generate()); + return className; + case "null": + imports.add(JsonValue.class.getName()); + return JsonValue.class.getSimpleName(); + case "boolean": + return "Boolean"; + case "uuid": + case "hostname": + case "idn-hostname": + case "email": + case "idn-email": + case "ipv4": + case "ipv6": + case "uri": + case "uri-reference": + case "iri": + case "iri-reference": + case "uri-template": + case "json-pointer": + case "relative-json-pointer": + case "regex": + case "string": + case "binary": + case "password": + return "String"; + case "number": + case "double": + return "Double"; + case "int": + case "integer": + return "Integer"; + case "long": + return "Long"; + case "float": + return "Float"; + case "date": + imports.add(LocalDate.class.getName()); + return LocalDate.class.getSimpleName(); + case "dateTime": + imports.add(OffsetDateTime.class.getName()); + return OffsetDateTime.class.getSimpleName(); + case "duration": + imports.add(Duration.class.getName()); + return Duration.class.getSimpleName(); + case "time": + imports.add(LocalTime.class.getName()); + return LocalTime.class.getSimpleName(); + case "byte": + return "byte[]"; + default: + throw new IllegalArgumentException("Unsupported type: " + type); + } + } + + imports.add(JsonValue.class.getName()); + return JsonValue.class.getSimpleName(); + } + + private String toJavaName(final String key) { + String name = key.chars() + .mapToObj(i -> Character.toString(!Character.isJavaIdentifierPart(i) ? '_' : (char) i)) + .collect(joining()); + if (Character.isDigit(name.charAt(0))) { + name = "a" + name; + } + while (name.startsWith("_")) { + name = name.substring(1); + } + if (name.isEmpty()) { + throw new IllegalArgumentException("Can't find a name for '" + key + "'"); + } + + if (isReserved(name)) { + name += "Value"; + } + + if (!Objects.equals(key, name) && configuration.isAddJsonbProperty()) { + imports.add(JsonbProperty.class.getName()); + } + return name; + } + + protected boolean isReserved(final String name) { + return "continue".equals(name) || "break".equals(name) || + "do".equals(name) || "while".equals(name) || + "for".equals(name) || + "if".equals(name) || "else".equals(name) || + "int".equals(name) || + "long".equals(name) || + "float".equals(name) || + "double".equals(name) || + "boolean".equals(name) || + "byte".equals(name) || + "char".equals(name) || + "short".equals(name) || + "String".equals(name); + } + + private static <T> T getValueAs(final JsonObject schema, final String attribute, final Class<T> type) { + final JsonValue value = schema.get(attribute); + if (value == null) { + throw new IllegalArgumentException("No \"" + attribute + "\" value in " + schema); + } + return valueAs(schema, type, value); + } + + private static <T> T valueAs(final JsonObject schema, final Class<T> type, final JsonValue value) { + if (!type.isInstance(value)) { + throw new IllegalArgumentException("\"items\" not an object: " + schema); + } + return type.cast(value); + } + + public static class PojoConfiguration { + private String packageName = "org.apache.johnzon.generated.pojo"; + private String className; + private boolean addJsonbProperty = true; + private boolean addAllArgsConstructor = true; + private boolean fluentSetters = false; + + public boolean isFluentSetters() { + return fluentSetters; + } + + public PojoConfiguration setFluentSetters(final boolean fluentSetters) { + this.fluentSetters = fluentSetters; + return this; + } + + public boolean isAddAllArgsConstructor() { + return addAllArgsConstructor; + } + + public PojoConfiguration setAddAllArgsConstructor(final boolean addAllArgsConstructor) { + this.addAllArgsConstructor = addAllArgsConstructor; + return this; + } + + public boolean isAddJsonbProperty() { + return addJsonbProperty; + } + + public PojoConfiguration setAddJsonbProperty(final boolean addJsonbProperty) { + this.addJsonbProperty = addJsonbProperty; + return this; + } + + public String getClassName() { + return className; + } + + public PojoConfiguration setClassName(final String className) { + this.className = className; + return this; + } + + public String getPackageName() { + return packageName; + } + + public PojoConfiguration setPackageName(final String packageName) { + this.packageName = packageName; + return this; + } + } + + protected static class Attribute { + private final String javaName; + private final String jsonName; + private final String type; + + protected Attribute(final String javaName, final String jsonName, final String type) { + this.javaName = javaName; + this.jsonName = jsonName; + this.type = type; + } + } +} diff --git a/johnzon-jsonschema/src/test/java/org/apache/johnzon/jsonschema/generator/PojoGeneratorTest.java b/johnzon-jsonschema/src/test/java/org/apache/johnzon/jsonschema/generator/PojoGeneratorTest.java new file mode 100644 index 00000000..8115839e --- /dev/null +++ b/johnzon-jsonschema/src/test/java/org/apache/johnzon/jsonschema/generator/PojoGeneratorTest.java @@ -0,0 +1,624 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.johnzon.jsonschema.generator; + +import org.junit.Test; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertEquals; + +public class PojoGeneratorTest { + private final PojoGenerator.PojoConfiguration configuration = new PojoGenerator.PojoConfiguration() + .setAddAllArgsConstructor(false) + .setClassName("TheClass") + .setPackageName("org.test"); + + @Test + public void generate() { + final Map<String, String> expected = new HashMap<>(); + expected.put("org/test/TheClass.java", root()); + expected.put("org/test/TheClassMetadata.java", metadata()); + expected.put("org/test/TheClassMetadataManagedFields.java", managedFields()); + expected.put("org/test/TheClassMetadataOwnerReferences.java", ownerRefs()); + final Map<String, String> generated = new PojoGenerator(configuration) + .visitSchema(load("ConfigMap.json")) + .generate(); + + // actual assertion but since we want a better error message we split it in 2 + // assertEquals(expected, generated); + + assertEquals(expected.keySet(), generated.keySet()); + expected.forEach((k, v) -> assertEquals(v, generated.get(k))); + } + + @Test + public void generateEnums() { + final Map<String, String> generated = new PojoGenerator(configuration) + .visitSchema(load("Node.json")) + .generate(); + assertEquals("" + + "package org.test;\n" + + "\n" + + "public enum TheClassSpecTaintsEffect {\n" + + " PreferNoSchedule,\n" + + " NoSchedule,\n" + + " NoExecute\n" + + "}\n", + generated.get("org/test/TheClassSpecTaintsEffect.java")); + assertEquals("" + + "package org.test;\n" + + "\n" + + "import java.util.Objects;\n" + + "\n" + + "public class TheClassSpecTaints {\n" + + " private TheClassSpecTaintsEffect effect;\n" + + " private String key;\n" + + " private String timeAdded;\n" + + " private String value;\n" + + "\n" + + " public TheClassSpecTaintsEffect getEffect() {\n" + + " return effect;\n" + + " }\n" + + "\n" + + " public void setEffect(final TheClassSpecTaintsEffect effect) {\n" + + " this.effect = effect;\n" + + " }\n" + + "\n" + + " public String getKey() {\n" + + " return key;\n" + + " }\n" + + "\n" + + " public void setKey(final String key) {\n" + + " this.key = key;\n" + + " }\n" + + "\n" + + " public String getTimeAdded() {\n" + + " return timeAdded;\n" + + " }\n" + + "\n" + + " public void setTimeAdded(final String timeAdded) {\n" + + " this.timeAdded = timeAdded;\n" + + " }\n" + + "\n" + + " public String getValue() {\n" + + " return value;\n" + + " }\n" + + "\n" + + " public void setValue(final String value) {\n" + + " this.value = value;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public int hashCode() {\n" + + " return Objects.hash(\n" + + " effect,\n" + + " key,\n" + + " timeAdded,\n" + + " value);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(final Object __other) {\n" + + " if (!(__other instanceof TheClassSpecTaints)) {\n" + + " return false;\n" + + " }\n" + + " final TheClassSpecTaints __otherCasted = (TheClassSpecTaints) __other;\n" + + " return Objects.equals(effect, __otherCasted.effect) &&\n" + + " Objects.equals(key, __otherCasted.key) &&\n" + + " Objects.equals(timeAdded, __otherCasted.timeAdded) &&\n" + + " Objects.equals(value, __otherCasted.value);\n" + + " }\n" + + "}\n", + generated.get("org/test/TheClassSpecTaints.java")); + } + + private static String root() { + return "" + + "package org.test;\n" + + "\n" + + "import java.util.Map;\n" + + "import java.util.Objects;\n" + + "\n" + + "public class TheClass {\n" + + " private String apiVersion;\n" + + " private Map<String, String> binaryData;\n" + + " private Map<String, String> data;\n" + + " private Boolean immutable;\n" + + " private String kind;\n" + + " private TheClassMetadata metadata;\n" + + "\n" + + " public String getApiVersion() {\n" + + " return apiVersion;\n" + + " }\n" + + "\n" + + " public void setApiVersion(final String apiVersion) {\n" + + " this.apiVersion = apiVersion;\n" + + " }\n" + + "\n" + + " public Map<String, String> getBinaryData() {\n" + + " return binaryData;\n" + + " }\n" + + "\n" + + " public void setBinaryData(final Map<String, String> binaryData) {\n" + + " this.binaryData = binaryData;\n" + + " }\n" + + "\n" + + " public Map<String, String> getData() {\n" + + " return data;\n" + + " }\n" + + "\n" + + " public void setData(final Map<String, String> data) {\n" + + " this.data = data;\n" + + " }\n" + + "\n" + + " public Boolean getImmutable() {\n" + + " return immutable;\n" + + " }\n" + + "\n" + + " public void setImmutable(final Boolean immutable) {\n" + + " this.immutable = immutable;\n" + + " }\n" + + "\n" + + " public String getKind() {\n" + + " return kind;\n" + + " }\n" + + "\n" + + " public void setKind(final String kind) {\n" + + " this.kind = kind;\n" + + " }\n" + + "\n" + + " public TheClassMetadata getMetadata() {\n" + + " return metadata;\n" + + " }\n" + + "\n" + + " public void setMetadata(final TheClassMetadata metadata) {\n" + + " this.metadata = metadata;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public int hashCode() {\n" + + " return Objects.hash(\n" + + " apiVersion,\n" + + " binaryData,\n" + + " data,\n" + + " immutable,\n" + + " kind,\n" + + " metadata);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(final Object __other) {\n" + + " if (!(__other instanceof TheClass)) {\n" + + " return false;\n" + + " }\n" + + " final TheClass __otherCasted = (TheClass) __other;\n" + + " return Objects.equals(apiVersion, __otherCasted.apiVersion) &&\n" + + " Objects.equals(binaryData, __otherCasted.binaryData) &&\n" + + " Objects.equals(data, __otherCasted.data) &&\n" + + " Objects.equals(immutable, __otherCasted.immutable) &&\n" + + " Objects.equals(kind, __otherCasted.kind) &&\n" + + " Objects.equals(metadata, __otherCasted.metadata);\n" + + " }\n" + + "}\n"; + } + + private static String metadata() { + return "" + + "package org.test;\n" + + "\n" + + "import java.util.List;\n" + + "import java.util.Map;\n" + + "import java.util.Objects;\n" + + "\n" + + "public class TheClassMetadata {\n" + + " private Map<String, String> annotations;\n" + + " private String clusterName;\n" + + " private String creationTimestamp;\n" + + " private Integer deletionGracePeriodSeconds;\n" + + " private String deletionTimestamp;\n" + + " private List<String> finalizers;\n" + + " private String generateName;\n" + + " private Integer generation;\n" + + " private Map<String, String> labels;\n" + + " private List<TheClassMetadataManagedFields> managedFields;\n" + + " private String name;\n" + + " private String namespace;\n" + + " private List<TheClassMetadataOwnerReferences> ownerReferences;\n" + + " private String resourceVersion;\n" + + " private String selfLink;\n" + + " private String uid;\n" + + "\n" + + " public Map<String, String> getAnnotations() {\n" + + " return annotations;\n" + + " }\n" + + "\n" + + " public void setAnnotations(final Map<String, String> annotations) {\n" + + " this.annotations = annotations;\n" + + " }\n" + + "\n" + + " public String getClusterName() {\n" + + " return clusterName;\n" + + " }\n" + + "\n" + + " public void setClusterName(final String clusterName) {\n" + + " this.clusterName = clusterName;\n" + + " }\n" + + "\n" + + " public String getCreationTimestamp() {\n" + + " return creationTimestamp;\n" + + " }\n" + + "\n" + + " public void setCreationTimestamp(final String creationTimestamp) {\n" + + " this.creationTimestamp = creationTimestamp;\n" + + " }\n" + + "\n" + + " public Integer getDeletionGracePeriodSeconds() {\n" + + " return deletionGracePeriodSeconds;\n" + + " }\n" + + "\n" + + " public void setDeletionGracePeriodSeconds(final Integer deletionGracePeriodSeconds) {\n" + + " this.deletionGracePeriodSeconds = deletionGracePeriodSeconds;\n" + + " }\n" + + "\n" + + " public String getDeletionTimestamp() {\n" + + " return deletionTimestamp;\n" + + " }\n" + + "\n" + + " public void setDeletionTimestamp(final String deletionTimestamp) {\n" + + " this.deletionTimestamp = deletionTimestamp;\n" + + " }\n" + + "\n" + + " public List<String> getFinalizers() {\n" + + " return finalizers;\n" + + " }\n" + + "\n" + + " public void setFinalizers(final List<String> finalizers) {\n" + + " this.finalizers = finalizers;\n" + + " }\n" + + "\n" + + " public String getGenerateName() {\n" + + " return generateName;\n" + + " }\n" + + "\n" + + " public void setGenerateName(final String generateName) {\n" + + " this.generateName = generateName;\n" + + " }\n" + + "\n" + + " public Integer getGeneration() {\n" + + " return generation;\n" + + " }\n" + + "\n" + + " public void setGeneration(final Integer generation) {\n" + + " this.generation = generation;\n" + + " }\n" + + "\n" + + " public Map<String, String> getLabels() {\n" + + " return labels;\n" + + " }\n" + + "\n" + + " public void setLabels(final Map<String, String> labels) {\n" + + " this.labels = labels;\n" + + " }\n" + + "\n" + + " public List<TheClassMetadataManagedFields> getManagedFields() {\n" + + " return managedFields;\n" + + " }\n" + + "\n" + + " public void setManagedFields(final List<TheClassMetadataManagedFields> managedFields) {\n" + + " this.managedFields = managedFields;\n" + + " }\n" + + "\n" + + " public String getName() {\n" + + " return name;\n" + + " }\n" + + "\n" + + " public void setName(final String name) {\n" + + " this.name = name;\n" + + " }\n" + + "\n" + + " public String getNamespace() {\n" + + " return namespace;\n" + + " }\n" + + "\n" + + " public void setNamespace(final String namespace) {\n" + + " this.namespace = namespace;\n" + + " }\n" + + "\n" + + " public List<TheClassMetadataOwnerReferences> getOwnerReferences() {\n" + + " return ownerReferences;\n" + + " }\n" + + "\n" + + " public void setOwnerReferences(final List<TheClassMetadataOwnerReferences> ownerReferences) {\n" + + " this.ownerReferences = ownerReferences;\n" + + " }\n" + + "\n" + + " public String getResourceVersion() {\n" + + " return resourceVersion;\n" + + " }\n" + + "\n" + + " public void setResourceVersion(final String resourceVersion) {\n" + + " this.resourceVersion = resourceVersion;\n" + + " }\n" + + "\n" + + " public String getSelfLink() {\n" + + " return selfLink;\n" + + " }\n" + + "\n" + + " public void setSelfLink(final String selfLink) {\n" + + " this.selfLink = selfLink;\n" + + " }\n" + + "\n" + + " public String getUid() {\n" + + " return uid;\n" + + " }\n" + + "\n" + + " public void setUid(final String uid) {\n" + + " this.uid = uid;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public int hashCode() {\n" + + " return Objects.hash(\n" + + " annotations,\n" + + " clusterName,\n" + + " creationTimestamp,\n" + + " deletionGracePeriodSeconds,\n" + + " deletionTimestamp,\n" + + " finalizers,\n" + + " generateName,\n" + + " generation,\n" + + " labels,\n" + + " managedFields,\n" + + " name,\n" + + " namespace,\n" + + " ownerReferences,\n" + + " resourceVersion,\n" + + " selfLink,\n" + + " uid);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(final Object __other) {\n" + + " if (!(__other instanceof TheClassMetadata)) {\n" + + " return false;\n" + + " }\n" + + " final TheClassMetadata __otherCasted = (TheClassMetadata) __other;\n" + + " return Objects.equals(annotations, __otherCasted.annotations) &&\n" + + " Objects.equals(clusterName, __otherCasted.clusterName) &&\n" + + " Objects.equals(creationTimestamp, __otherCasted.creationTimestamp) &&\n" + + " Objects.equals(deletionGracePeriodSeconds, __otherCasted.deletionGracePeriodSeconds) &&\n" + + " Objects.equals(deletionTimestamp, __otherCasted.deletionTimestamp) &&\n" + + " Objects.equals(finalizers, __otherCasted.finalizers) &&\n" + + " Objects.equals(generateName, __otherCasted.generateName) &&\n" + + " Objects.equals(generation, __otherCasted.generation) &&\n" + + " Objects.equals(labels, __otherCasted.labels) &&\n" + + " Objects.equals(managedFields, __otherCasted.managedFields) &&\n" + + " Objects.equals(name, __otherCasted.name) &&\n" + + " Objects.equals(namespace, __otherCasted.namespace) &&\n" + + " Objects.equals(ownerReferences, __otherCasted.ownerReferences) &&\n" + + " Objects.equals(resourceVersion, __otherCasted.resourceVersion) &&\n" + + " Objects.equals(selfLink, __otherCasted.selfLink) &&\n" + + " Objects.equals(uid, __otherCasted.uid);\n" + + " }\n" + + "}\n"; + } + + private static String managedFields() { + return "" + + "package org.test;\n" + + "\n" + + "import java.util.Objects;\n" + + "import javax.json.JsonObject;\n" + + "\n" + + "public class TheClassMetadataManagedFields {\n" + + " private String apiVersion;\n" + + " private String fieldsType;\n" + + " private JsonObject fieldsV1;\n" + + " private String manager;\n" + + " private String operation;\n" + + " private String subresource;\n" + + " private String time;\n" + + "\n" + + " public String getApiVersion() {\n" + + " return apiVersion;\n" + + " }\n" + + "\n" + + " public void setApiVersion(final String apiVersion) {\n" + + " this.apiVersion = apiVersion;\n" + + " }\n" + + "\n" + + " public String getFieldsType() {\n" + + " return fieldsType;\n" + + " }\n" + + "\n" + + " public void setFieldsType(final String fieldsType) {\n" + + " this.fieldsType = fieldsType;\n" + + " }\n" + + "\n" + + " public JsonObject getFieldsV1() {\n" + + " return fieldsV1;\n" + + " }\n" + + "\n" + + " public void setFieldsV1(final JsonObject fieldsV1) {\n" + + " this.fieldsV1 = fieldsV1;\n" + + " }\n" + + "\n" + + " public String getManager() {\n" + + " return manager;\n" + + " }\n" + + "\n" + + " public void setManager(final String manager) {\n" + + " this.manager = manager;\n" + + " }\n" + + "\n" + + " public String getOperation() {\n" + + " return operation;\n" + + " }\n" + + "\n" + + " public void setOperation(final String operation) {\n" + + " this.operation = operation;\n" + + " }\n" + + "\n" + + " public String getSubresource() {\n" + + " return subresource;\n" + + " }\n" + + "\n" + + " public void setSubresource(final String subresource) {\n" + + " this.subresource = subresource;\n" + + " }\n" + + "\n" + + " public String getTime() {\n" + + " return time;\n" + + " }\n" + + "\n" + + " public void setTime(final String time) {\n" + + " this.time = time;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public int hashCode() {\n" + + " return Objects.hash(\n" + + " apiVersion,\n" + + " fieldsType,\n" + + " fieldsV1,\n" + + " manager,\n" + + " operation,\n" + + " subresource,\n" + + " time);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(final Object __other) {\n" + + " if (!(__other instanceof TheClassMetadataManagedFields)) {\n" + + " return false;\n" + + " }\n" + + " final TheClassMetadataManagedFields __otherCasted = (TheClassMetadataManagedFields) __other;\n" + + " return Objects.equals(apiVersion, __otherCasted.apiVersion) &&\n" + + " Objects.equals(fieldsType, __otherCasted.fieldsType) &&\n" + + " Objects.equals(fieldsV1, __otherCasted.fieldsV1) &&\n" + + " Objects.equals(manager, __otherCasted.manager) &&\n" + + " Objects.equals(operation, __otherCasted.operation) &&\n" + + " Objects.equals(subresource, __otherCasted.subresource) &&\n" + + " Objects.equals(time, __otherCasted.time);\n" + + " }\n" + + "}\n"; + } + + private static String ownerRefs() { + return "" + + "package org.test;\n" + + "\n" + + "import java.util.Objects;\n" + + "\n" + + "public class TheClassMetadataOwnerReferences {\n" + + " private String apiVersion;\n" + + " private Boolean blockOwnerDeletion;\n" + + " private Boolean controller;\n" + + " private String kind;\n" + + " private String name;\n" + + " private String uid;\n" + + "\n" + + " public String getApiVersion() {\n" + + " return apiVersion;\n" + + " }\n" + + "\n" + + " public void setApiVersion(final String apiVersion) {\n" + + " this.apiVersion = apiVersion;\n" + + " }\n" + + "\n" + + " public Boolean getBlockOwnerDeletion() {\n" + + " return blockOwnerDeletion;\n" + + " }\n" + + "\n" + + " public void setBlockOwnerDeletion(final Boolean blockOwnerDeletion) {\n" + + " this.blockOwnerDeletion = blockOwnerDeletion;\n" + + " }\n" + + "\n" + + " public Boolean getController() {\n" + + " return controller;\n" + + " }\n" + + "\n" + + " public void setController(final Boolean controller) {\n" + + " this.controller = controller;\n" + + " }\n" + + "\n" + + " public String getKind() {\n" + + " return kind;\n" + + " }\n" + + "\n" + + " public void setKind(final String kind) {\n" + + " this.kind = kind;\n" + + " }\n" + + "\n" + + " public String getName() {\n" + + " return name;\n" + + " }\n" + + "\n" + + " public void setName(final String name) {\n" + + " this.name = name;\n" + + " }\n" + + "\n" + + " public String getUid() {\n" + + " return uid;\n" + + " }\n" + + "\n" + + " public void setUid(final String uid) {\n" + + " this.uid = uid;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public int hashCode() {\n" + + " return Objects.hash(\n" + + " apiVersion,\n" + + " blockOwnerDeletion,\n" + + " controller,\n" + + " kind,\n" + + " name,\n" + + " uid);\n" + + " }\n" + + "\n" + + " @Override\n" + + " public boolean equals(final Object __other) {\n" + + " if (!(__other instanceof TheClassMetadataOwnerReferences)) {\n" + + " return false;\n" + + " }\n" + + " final TheClassMetadataOwnerReferences __otherCasted = (TheClassMetadataOwnerReferences) __other;\n" + + " return Objects.equals(apiVersion, __otherCasted.apiVersion) &&\n" + + " Objects.equals(blockOwnerDeletion, __otherCasted.blockOwnerDeletion) &&\n" + + " Objects.equals(controller, __otherCasted.controller) &&\n" + + " Objects.equals(kind, __otherCasted.kind) &&\n" + + " Objects.equals(name, __otherCasted.name) &&\n" + + " Objects.equals(uid, __otherCasted.uid);\n" + + " }\n" + + "}\n"; + } + + private JsonObject load(final String resource) { + try (final JsonReader reader = Json.createReader(requireNonNull(Thread.currentThread().getContextClassLoader() + .getResourceAsStream(resource)))) { + return reader.readObject(); + } + } +} diff --git a/johnzon-jsonschema/src/test/resources/ConfigMap.json b/johnzon-jsonschema/src/test/resources/ConfigMap.json new file mode 100644 index 00000000..55385fd1 --- /dev/null +++ b/johnzon-jsonschema/src/test/resources/ConfigMap.json @@ -0,0 +1,193 @@ +{ + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "binaryData": { + "additionalProperties": { + "format": "byte", + "type": "string" + }, + "description": "BinaryData contains the binary data. Each key must consist of alphanumeric characters, '-', '_' or '.'. BinaryData can contain byte sequences that are not in the UTF-8 range. The keys stored in BinaryData must not overlap with the ones in the Data field, this is enforced during validation process. Using this field will require 1.10+ apiserver and kubelet.", + "type": "object" + }, + "data": { + "additionalProperties": { + "type": "string" + }, + "description": "Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.", + "type": "object" + }, + "immutable": { + "description": "Immutable, if set to true, ensures that data stored in the ConfigMap cannot be updated (only object metadata can be modified). If not set to true, the field can be modified at any time. Defaulted to nil.", + "type": "boolean" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations", + "type": "object" + }, + "clusterName": { + "description": "Deprecated: ClusterName is a legacy field that was always cleared by the system and never used; it will be removed completely in 1.25.\n\nThe name in the go struct is changed to help clients detect accidental use.", + "type": "string" + }, + "creationTimestamp": { + "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "format": "date-time", + "type": "string" + }, + "deletionGracePeriodSeconds": { + "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + "format": "int64", + "type": "integer" + }, + "deletionTimestamp": { + "description": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionT [...] + "format": "date-time", + "type": "string" + }, + "finalizers": { + "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If [...] + "items": { + "type": "string" + }, + "type": "array" + }, + "generateName": { + "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is sp [...] + "type": "string" + }, + "generation": { + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + "format": "int64", + "type": "integer" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels", + "type": "object" + }, + "managedFields": { + "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + "items": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" + }, + "fieldsType": { + "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type": "string" + }, + "fieldsV1": { + "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", + "type": "object" + }, + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" + }, + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" + }, + "subresource": { + "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main [...] + "type": "string" + }, + "time": { + "description": "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over.", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "name": { + "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names", + "type": "string" + }, + "namespace": { + "description": "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces", + "type": "string" + }, + "ownerReferences": { + "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + "items": { + "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + "properties": { + "apiVersion": { + "description": "API version of the referent.", + "type": "string" + }, + "blockOwnerDeletion": { + "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessabl [...] + "type": "boolean" + }, + "controller": { + "description": "If true, this reference points to the managing controller.", + "type": "boolean" + }, + "kind": { + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "description": "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", + "type": "string" + }, + "uid": { + "description": "UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids", + "type": "string" + } + }, + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "type": "object" + }, + "type": "array" + }, + "resourceVersion": { + "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treate [...] + "type": "string" + }, + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" + }, + "uid": { + "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "ConfigMap", + "version": "v1" + } + ] +} \ No newline at end of file diff --git a/johnzon-jsonschema/src/test/resources/Node.json b/johnzon-jsonschema/src/test/resources/Node.json new file mode 100644 index 00000000..ddc5cc39 --- /dev/null +++ b/johnzon-jsonschema/src/test/resources/Node.json @@ -0,0 +1,634 @@ +{ + "description":"Node is a worker node in Kubernetes. Each node will have a unique identifier in the cache (i.e. in etcd).", + "properties":{ + "apiVersion":{ + "description":"APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type":"string" + }, + "kind":{ + "description":"Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type":"string" + }, + "metadata":{ + "description":"Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "properties":{ + "annotations":{ + "additionalProperties":{ + "type":"string" + }, + "description":"Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations", + "type":"object" + }, + "clusterName":{ + "description":"The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.", + "type":"string" + }, + "creationTimestamp":{ + "description":"CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "format":"date-time", + "type":"string" + }, + "deletionGracePeriodSeconds":{ + "description":"Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + "format":"int64", + "type":"integer" + }, + "deletionTimestamp":{ + "description":"DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTi [...] + "format":"date-time", + "type":"string" + }, + "finalizers":{ + "description":"Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If [...] + "items":{ + "type":"string" + }, + "type":"array" + }, + "generateName":{ + "description":"GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is spe [...] + "type":"string" + }, + "generation":{ + "description":"A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + "format":"int64", + "type":"integer" + }, + "labels":{ + "additionalProperties":{ + "type":"string" + }, + "description":"Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels", + "type":"object" + }, + "managedFields":{ + "description":"ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + "items":{ + "description":"ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "properties":{ + "apiVersion":{ + "description":"APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type":"string" + }, + "fieldsType":{ + "description":"FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type":"string" + }, + "fieldsV1":{ + "description":"FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", + "type":"object" + }, + "manager":{ + "description":"Manager is an identifier of the workflow managing these fields.", + "type":"string" + }, + "operation":{ + "description":"Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type":"string" + }, + "subresource":{ + "description":"Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main r [...] + "type":"string" + }, + "time":{ + "description":"Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply'", + "format":"date-time", + "type":"string" + } + }, + "type":"object" + }, + "type":"array" + }, + "name":{ + "description":"Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names", + "type":"string" + }, + "namespace":{ + "description":"Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces", + "type":"string" + }, + "ownerReferences":{ + "description":"List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + "items":{ + "description":"OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + "properties":{ + "apiVersion":{ + "description":"API version of the referent.", + "type":"string" + }, + "blockOwnerDeletion":{ + "description":"If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + "type":"boolean" + }, + "controller":{ + "description":"If true, this reference points to the managing controller.", + "type":"boolean" + }, + "kind":{ + "description":"Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type":"string" + }, + "name":{ + "description":"Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", + "type":"string" + }, + "uid":{ + "description":"UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids", + "type":"string" + } + }, + "required":[ + "apiVersion", + "kind", + "name", + "uid" + ], + "type":"object" + }, + "type":"array" + }, + "resourceVersion":{ + "description":"An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated [...] + "type":"string" + }, + "selfLink":{ + "description":"SelfLink is a URL representing this object. Populated by the system. Read-only.\n\nDEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release.", + "type":"string" + }, + "uid":{ + "description":"UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids", + "type":"string" + } + }, + "type":"object" + }, + "spec":{ + "description":"Spec defines the behavior of a node. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "properties":{ + "configSource":{ + "description":"Deprecated. If specified, the source of the node's configuration. The DynamicKubeletConfig feature gate must be enabled for the Kubelet to use this field. This field is deprecated as of 1.22: https://git.k8s.io/enhancements/keps/sig-node/281-dynamic-kubelet-configuration", + "properties":{ + "configMap":{ + "description":"ConfigMap is a reference to a Node's ConfigMap", + "properties":{ + "kubeletConfigKey":{ + "description":"KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.", + "type":"string" + }, + "name":{ + "description":"Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "namespace":{ + "description":"Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "resourceVersion":{ + "description":"ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + }, + "uid":{ + "description":"UID is the metadata.UID of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + } + }, + "required":[ + "namespace", + "name", + "kubeletConfigKey" + ], + "type":"object" + } + }, + "type":"object" + }, + "externalID":{ + "description":"Deprecated. Not all kubelets will set this field. Remove field after 1.13. see: https://issues.k8s.io/61966", + "type":"string" + }, + "podCIDR":{ + "description":"PodCIDR represents the pod IP range assigned to the node.", + "type":"string" + }, + "podCIDRs":{ + "description":"podCIDRs represents the IP ranges assigned to the node for usage by Pods on that node. If this field is specified, the 0th entry must match the podCIDR field. It may contain at most 1 value for each of IPv4 and IPv6.", + "items":{ + "type":"string" + }, + "type":"array" + }, + "providerID":{ + "description":"ID of the node assigned by the cloud provider in the format: <ProviderName>://<ProviderSpecificNodeID>", + "type":"string" + }, + "taints":{ + "description":"If specified, the node's taints.", + "items":{ + "description":"The node this Taint is attached to has the \"effect\" on any pod that does not tolerate the Taint.", + "properties":{ + "effect":{ + "description":"Required. The effect of the taint on pods that do not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule and NoExecute.\n\nPossible enum values:\n - `\"NoExecute\"` Evict any already-running pods that do not tolerate the taint. Currently enforced by NodeController.\n - `\"NoSchedule\"` Do not allow new pods to schedule onto the node unless they tolerate the taint, but allow all pods submitted to Kubelet without going through the scheduler to [...] + "enum":[ + "NoExecute", + "NoSchedule", + "PreferNoSchedule" + ], + "type":"string" + }, + "key":{ + "description":"Required. The taint key to be applied to a node.", + "type":"string" + }, + "timeAdded":{ + "description":"TimeAdded represents the time at which the taint was added. It is only written for NoExecute taints.", + "format":"date-time", + "type":"string" + }, + "value":{ + "description":"The taint value corresponding to the taint key.", + "type":"string" + } + }, + "required":[ + "key", + "effect" + ], + "type":"object" + }, + "type":"array" + }, + "unschedulable":{ + "description":"Unschedulable controls node schedulability of new pods. By default, node is schedulable. More info: https://kubernetes.io/docs/concepts/nodes/node/#manual-node-administration", + "type":"boolean" + } + }, + "type":"object" + }, + "status":{ + "description":"Most recently observed status of the node. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "properties":{ + "addresses":{ + "description":"List of addresses reachable to the node. Queried from cloud provider, if available. More info: https://kubernetes.io/docs/concepts/nodes/node/#addresses Note: This field is declared as mergeable, but the merge key is not sufficiently unique, which can cause data corruption when it is merged. Callers should instead use a full-replacement patch. See http://pr.k8s.io/79391 for an example.", + "items":{ + "description":"NodeAddress contains information for the node's address.", + "properties":{ + "address":{ + "description":"The node address.", + "type":"string" + }, + "type":{ + "description":"Node address type, one of Hostname, ExternalIP or InternalIP.\n\nPossible enum values:\n - `\"ExternalDNS\"` identifies a DNS name which resolves to an IP address which has the characteristics of a NodeExternalIP. The IP it resolves to may or may not be a listed NodeExternalIP address.\n - `\"ExternalIP\"` identifies an IP address which is, in some way, intended to be more usable from outside the cluster then an internal IP, though no specific semantics are [...] + "enum":[ + "ExternalDNS", + "ExternalIP", + "Hostname", + "InternalDNS", + "InternalIP" + ], + "type":"string" + } + }, + "required":[ + "type", + "address" + ], + "type":"object" + }, + "type":"array" + }, + "allocatable":{ + "additionalProperties":{ + "description":"Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n<quantity> ::= <signedNumber><suffix>\n (Note that <suffix> may be empty, from the \"\" case in <decimalSI>.)\n<digit> ::= 0 | 1 | ... | 9 <digits> ::= <digit> | <digit><digits> <number> ::= <digits> | <digits>.<digits> | <dig [...] + "type":"string" + }, + "description":"Allocatable represents the resources of a node that are available for scheduling. Defaults to Capacity.", + "type":"object" + }, + "capacity":{ + "additionalProperties":{ + "description":"Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n<quantity> ::= <signedNumber><suffix>\n (Note that <suffix> may be empty, from the \"\" case in <decimalSI>.)\n<digit> ::= 0 | 1 | ... | 9 <digits> ::= <digit> | <digit><digits> <number> ::= <digits> | <digits>.<digits> | <dig [...] + "type":"string" + }, + "description":"Capacity represents the total resources of a node. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#capacity", + "type":"object" + }, + "conditions":{ + "description":"Conditions is an array of current observed node conditions. More info: https://kubernetes.io/docs/concepts/nodes/node/#condition", + "items":{ + "description":"NodeCondition contains condition information for a node.", + "properties":{ + "lastHeartbeatTime":{ + "description":"Last time we got an update on a given condition.", + "format":"date-time", + "type":"string" + }, + "lastTransitionTime":{ + "description":"Last time the condition transit from one status to another.", + "format":"date-time", + "type":"string" + }, + "message":{ + "description":"Human readable message indicating details about last transition.", + "type":"string" + }, + "reason":{ + "description":"(brief) reason for the condition's last transition.", + "type":"string" + }, + "status":{ + "description":"Status of the condition, one of True, False, Unknown.", + "type":"string" + }, + "type":{ + "description":"Type of node condition.\n\nPossible enum values:\n - `\"DiskPressure\"` means the kubelet is under pressure due to insufficient available disk.\n - `\"MemoryPressure\"` means the kubelet is under pressure due to insufficient available memory.\n - `\"NetworkUnavailable\"` means that network for the node is not correctly configured.\n - `\"PIDPressure\"` means the kubelet is under pressure due to insufficient available PID.\n - `\"Ready\"` means kubelet is he [...] + "enum":[ + "DiskPressure", + "MemoryPressure", + "NetworkUnavailable", + "PIDPressure", + "Ready" + ], + "type":"string" + } + }, + "required":[ + "type", + "status" + ], + "type":"object" + }, + "type":"array" + }, + "config":{ + "description":"Status of the config assigned to the node via the dynamic Kubelet config feature.", + "properties":{ + "active":{ + "description":"Active reports the checkpointed config the node is actively using. Active will represent either the current version of the Assigned config, or the current LastKnownGood config, depending on whether attempting to use the Assigned config results in an error.", + "properties":{ + "configMap":{ + "description":"ConfigMap is a reference to a Node's ConfigMap", + "properties":{ + "kubeletConfigKey":{ + "description":"KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.", + "type":"string" + }, + "name":{ + "description":"Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "namespace":{ + "description":"Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "resourceVersion":{ + "description":"ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + }, + "uid":{ + "description":"UID is the metadata.UID of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + } + }, + "required":[ + "namespace", + "name", + "kubeletConfigKey" + ], + "type":"object" + } + }, + "type":"object" + }, + "assigned":{ + "description":"Assigned reports the checkpointed config the node will try to use. When Node.Spec.ConfigSource is updated, the node checkpoints the associated config payload to local disk, along with a record indicating intended config. The node refers to this record to choose its config checkpoint, and reports this record in Assigned. Assigned only updates in the status after the record has been checkpointed to disk. When the Kubelet is restarted, it tries to make the Assig [...] + "properties":{ + "configMap":{ + "description":"ConfigMap is a reference to a Node's ConfigMap", + "properties":{ + "kubeletConfigKey":{ + "description":"KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.", + "type":"string" + }, + "name":{ + "description":"Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "namespace":{ + "description":"Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "resourceVersion":{ + "description":"ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + }, + "uid":{ + "description":"UID is the metadata.UID of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + } + }, + "required":[ + "namespace", + "name", + "kubeletConfigKey" + ], + "type":"object" + } + }, + "type":"object" + }, + "error":{ + "description":"Error describes any problems reconciling the Spec.ConfigSource to the Active config. Errors may occur, for example, attempting to checkpoint Spec.ConfigSource to the local Assigned record, attempting to checkpoint the payload associated with Spec.ConfigSource, attempting to load or validate the Assigned config, etc. Errors may occur at different points while syncing config. Earlier errors (e.g. download or checkpointing errors) will not result in a rollback t [...] + "type":"string" + }, + "lastKnownGood":{ + "description":"LastKnownGood reports the checkpointed config the node will fall back to when it encounters an error attempting to use the Assigned config. The Assigned config becomes the LastKnownGood config when the node determines that the Assigned config is stable and correct. This is currently implemented as a 10-minute soak period starting when the local record of Assigned config is updated. If the Assigned config is Active at the end of this period, it becomes the Las [...] + "properties":{ + "configMap":{ + "description":"ConfigMap is a reference to a Node's ConfigMap", + "properties":{ + "kubeletConfigKey":{ + "description":"KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.", + "type":"string" + }, + "name":{ + "description":"Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "namespace":{ + "description":"Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.", + "type":"string" + }, + "resourceVersion":{ + "description":"ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + }, + "uid":{ + "description":"UID is the metadata.UID of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type":"string" + } + }, + "required":[ + "namespace", + "name", + "kubeletConfigKey" + ], + "type":"object" + } + }, + "type":"object" + } + }, + "type":"object" + }, + "daemonEndpoints":{ + "description":"Endpoints of daemons running on the Node.", + "properties":{ + "kubeletEndpoint":{ + "description":"Endpoint on which Kubelet is listening.", + "properties":{ + "Port":{ + "description":"Port number of the given endpoint.", + "format":"int32", + "type":"integer" + } + }, + "required":[ + "Port" + ], + "type":"object" + } + }, + "type":"object" + }, + "images":{ + "description":"List of container images on this node", + "items":{ + "description":"Describe a container image", + "properties":{ + "names":{ + "description":"Names by which this image is known. e.g. [\"k8s.gcr.io/hyperkube:v1.0.7\", \"dockerhub.io/google_containers/hyperkube:v1.0.7\"]", + "items":{ + "type":"string" + }, + "type":"array" + }, + "sizeBytes":{ + "description":"The size of the image in bytes.", + "format":"int64", + "type":"integer" + } + }, + "type":"object" + }, + "type":"array" + }, + "nodeInfo":{ + "description":"Set of ids/uuids to uniquely identify the node. More info: https://kubernetes.io/docs/concepts/nodes/node/#info", + "properties":{ + "architecture":{ + "description":"The Architecture reported by the node", + "type":"string" + }, + "bootID":{ + "description":"Boot ID reported by the node.", + "type":"string" + }, + "containerRuntimeVersion":{ + "description":"ContainerRuntime Version reported by the node through runtime remote API (e.g. docker://1.5.0).", + "type":"string" + }, + "kernelVersion":{ + "description":"Kernel Version reported by the node from 'uname -r' (e.g. 3.16.0-0.bpo.4-amd64).", + "type":"string" + }, + "kubeProxyVersion":{ + "description":"KubeProxy Version reported by the node.", + "type":"string" + }, + "kubeletVersion":{ + "description":"Kubelet Version reported by the node.", + "type":"string" + }, + "machineID":{ + "description":"MachineID reported by the node. For unique machine identification in the cluster this field is preferred. Learn more from man(5) machine-id: http://man7.org/linux/man-pages/man5/machine-id.5.html", + "type":"string" + }, + "operatingSystem":{ + "description":"The Operating System reported by the node", + "type":"string" + }, + "osImage":{ + "description":"OS Image reported by the node from /etc/os-release (e.g. Debian GNU/Linux 7 (wheezy)).", + "type":"string" + }, + "systemUUID":{ + "description":"SystemUUID reported by the node. For unique machine identification MachineID is preferred. This field is specific to Red Hat hosts https://access.redhat.com/documentation/en-us/red_hat_subscription_management/1/html/rhsm/uuid", + "type":"string" + } + }, + "required":[ + "machineID", + "systemUUID", + "bootID", + "kernelVersion", + "osImage", + "containerRuntimeVersion", + "kubeletVersion", + "kubeProxyVersion", + "operatingSystem", + "architecture" + ], + "type":"object" + }, + "phase":{ + "description":"NodePhase is the recently observed lifecycle phase of the node. More info: https://kubernetes.io/docs/concepts/nodes/node/#phase The field is never populated, and now is deprecated.\n\nPossible enum values:\n - `\"Pending\"` means the node has been created/added by the system, but not configured.\n - `\"Running\"` means the node has been configured and has Kubernetes components running.\n - `\"Terminated\"` means the node has been removed from the cluster.", + "enum":[ + "Pending", + "Running", + "Terminated" + ], + "type":"string" + }, + "volumesAttached":{ + "description":"List of volumes that are attached to the node.", + "items":{ + "description":"AttachedVolume describes a volume attached to a node", + "properties":{ + "devicePath":{ + "description":"DevicePath represents the device path where the volume should be available", + "type":"string" + }, + "name":{ + "description":"Name of the attached volume", + "type":"string" + } + }, + "required":[ + "name", + "devicePath" + ], + "type":"object" + }, + "type":"array" + }, + "volumesInUse":{ + "description":"List of attachable volumes in use (mounted) by the node.", + "items":{ + "type":"string" + }, + "type":"array" + } + }, + "type":"object" + } + }, + "type":"object", + "x-kubernetes-group-version-kind":[ + { + "group":"", + "kind":"Node", + "version":"v1" + } + ] +} \ No newline at end of file diff --git a/johnzon-maven-plugin/src/main/java/org/apache/johnzon/maven/plugin/JsonSchemaToPojoMojo.java b/johnzon-maven-plugin/src/main/java/org/apache/johnzon/maven/plugin/JsonSchemaToPojoMojo.java new file mode 100644 index 00000000..074f7f18 --- /dev/null +++ b/johnzon-maven-plugin/src/main/java/org/apache/johnzon/maven/plugin/JsonSchemaToPojoMojo.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.johnzon.maven.plugin; + +import org.apache.johnzon.jsonschema.generator.PojoGenerator; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonReaderFactory; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyMap; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; +import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES; + +/** + * Generates mojo bindings from json schema specification(s). + */ +@Mojo(name = "jsonschema2pojo", defaultPhase = GENERATE_SOURCES) +public class JsonSchemaToPojoMojo extends AbstractMojo { + /** + * Generation configuration. + * Note that if source is a directory, class name is ignored and auto set from schema name. + */ + @Parameter + private PojoGenerator.PojoConfiguration generator; + + /** + * Extensions to consider if source is a directory. + */ + @Parameter(property = "johnzon.jsonschema.extensions", defaultValue = ".jsonschema.json") + private List<String> jsonSchemaExtensions; + + /** + * Source jsonschema or directory containing json schemas. + */ + @Parameter(property = "johnzon.source", defaultValue = "${project.basedir}/src/main/johnzon/jsonschema") + private File source; + + /** + * Where to dump generated classes. + */ + @Parameter(property = "johnzon.target", defaultValue = "${project.build.directory}/generated-sources/johnzon-pojo") + private File target; + + @Override + public void execute() { + final JsonReaderFactory readerFactory = Json.createReaderFactory(emptyMap()); + if (source.isDirectory()) { + try { + Files.walkFileTree(source.toPath(), new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final String name = file.getFileName().toString(); + final List<String> matchingExt = jsonSchemaExtensions.stream() + .filter(name::endsWith) + .sorted(comparing(String::length).reversed()) + .collect(toList()); + if (matchingExt.size() >= 1) { + final PojoGenerator.PojoConfiguration conf = generator == null ? new PojoGenerator.PojoConfiguration() : generator; + conf.setClassName(name.substring(0, name.length() - matchingExt.get(0).length())); + dump(new PojoGenerator(conf) + .visitSchema(read(readerFactory, source.toPath())) + .generate()); + } + return super.visitFile(file, attrs); + } + }); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + } else { + dump(new PojoGenerator(generator == null ? new PojoGenerator.PojoConfiguration() : generator) + .visitSchema(read(readerFactory, source.toPath())) + .generate()); + } + } + + private JsonObject read(final JsonReaderFactory readerFactory, final Path path) { + try (final JsonReader reader = readerFactory.createReader(Files.newBufferedReader(path))) { + return reader.readObject(); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + } + + private void dump(final Map<String, String> generate) { + final Path root = target.toPath(); + try { + for (final Map.Entry<String, String> entry : generate.entrySet()) { + final Path out = root.resolve(entry.getKey()); + Files.createDirectories(out.getParent()); + Files.write(out, entry.getValue().getBytes(StandardCharsets.UTF_8)); + } + } catch (final IOException ioe) { + throw new IllegalStateException(ioe); + } + } +} diff --git a/pom.xml b/pom.xml index 19e9f038..c5ef5793 100644 --- a/pom.xml +++ b/pom.xml @@ -36,8 +36,8 @@ <url>http://johnzon.apache.org</url> <properties> - <geronimo-jsonp.version>1.3</geronimo-jsonp.version> - <geronimo-jsonb.version>1.2</geronimo-jsonb.version> + <geronimo-jsonp.version>1.5</geronimo-jsonp.version> + <geronimo-jsonb.version>1.4</geronimo-jsonb.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <johnzon.site.url>https://svn.apache.org/repos/asf/johnzon/site/publish/</johnzon.site.url> <pubsub.url>scm:svn:${johnzon.site.url}</pubsub.url>