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 <[email protected]>
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>