1
2
3
4
5
6 package gov.nist.secauto.metaschema.schemagen.json;
7
8 import com.fasterxml.jackson.core.JsonFactory;
9 import com.fasterxml.jackson.core.JsonGenerator;
10 import com.fasterxml.jackson.core.JsonGenerator.Feature;
11 import com.fasterxml.jackson.databind.ObjectMapper;
12 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
13 import com.fasterxml.jackson.databind.node.ObjectNode;
14
15 import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
16 import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
17 import gov.nist.secauto.metaschema.core.model.IModule;
18 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
19 import gov.nist.secauto.metaschema.schemagen.AbstractSchemaGenerator;
20 import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException;
21 import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature;
22 import gov.nist.secauto.metaschema.schemagen.json.IDefineableJsonSchema.IKey;
23 import gov.nist.secauto.metaschema.schemagen.json.impl.JsonDatatypeManager;
24 import gov.nist.secauto.metaschema.schemagen.json.impl.JsonGenerationState;
25
26 import java.io.IOException;
27 import java.io.Writer;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.stream.Collectors;
32
33 import edu.umd.cs.findbugs.annotations.NonNull;
34
35 public class JsonSchemaGenerator
36 extends AbstractSchemaGenerator<JsonGenerator, JsonDatatypeManager, JsonGenerationState> {
37 @NonNull
38 private final JsonFactory jsonFactory;
39
40 public JsonSchemaGenerator() {
41 this(new JsonFactory());
42 }
43
44 public JsonSchemaGenerator(@NonNull JsonFactory jsonFactory) {
45 this.jsonFactory = jsonFactory;
46 }
47
48 @NonNull
49 public JsonFactory getJsonFactory() {
50 return jsonFactory;
51 }
52
53 @SuppressWarnings("resource")
54 @Override
55 protected JsonGenerator newWriter(Writer out) {
56 try {
57 return ObjectUtils.notNull(getJsonFactory().createGenerator(out)
58 .setCodec(new ObjectMapper())
59 .useDefaultPrettyPrinter()
60 .disable(Feature.AUTO_CLOSE_TARGET));
61 } catch (IOException ex) {
62 throw new SchemaGenerationException(ex);
63 }
64 }
65
66 @Override
67 protected JsonGenerationState newGenerationState(
68 IModule module,
69 JsonGenerator schemaWriter,
70 IConfiguration<SchemaGenerationFeature<?>> configuration) {
71 return new JsonGenerationState(module, schemaWriter, configuration);
72 }
73
74 @Override
75 protected void generateSchema(JsonGenerationState state) {
76 IModule module = state.getModule();
77 try {
78 state.writeStartObject();
79
80 state.writeField("$schema", "http://json-schema.org/draft-07/schema#");
81 state.writeField("$id",
82 String.format("%s/%s-%s-schema.json",
83 module.getXmlNamespace(),
84 module.getShortName(),
85 module.getVersion()));
86 state.writeField("$comment", module.getName().toMarkdown());
87 state.writeField("type", "object");
88
89 ObjectNode definitionsObject = state.generateDefinitions();
90 if (!definitionsObject.isEmpty()) {
91 state.writeField("definitions", definitionsObject);
92 }
93
94 List<IAssemblyDefinition> rootAssemblyDefinitions = state.getMetaschemaIndex().getDefinitions().stream()
95 .map(entry -> entry.getDefinition())
96 .filter(
97 definition -> definition instanceof IAssemblyDefinition && ((IAssemblyDefinition) definition).isRoot())
98 .map(definition -> (IAssemblyDefinition) definition)
99 .collect(Collectors.toUnmodifiableList());
100
101 if (rootAssemblyDefinitions.isEmpty()) {
102 throw new SchemaGenerationException("No root definitions found");
103 }
104
105
106 List<RootPropertyEntry> rootEntries = rootAssemblyDefinitions.stream()
107 .map(root -> {
108 assert root != null;
109 return new RootPropertyEntry(root, state);
110 })
111 .collect(Collectors.toUnmodifiableList());
112
113 @SuppressWarnings("resource") JsonGenerator writer = state.getWriter();
114
115 if (rootEntries.size() == 1) {
116 rootEntries.iterator().next().write(writer);
117 } else {
118 writer.writeFieldName("oneOf");
119 writer.writeStartArray();
120
121 for (RootPropertyEntry root : rootEntries) {
122 assert root != null;
123 writer.writeStartObject();
124 root.write(writer);
125 writer.writeEndObject();
126 }
127
128 writer.writeEndArray();
129 }
130
131 state.writeEndObject();
132 } catch (IOException ex) {
133 throw new SchemaGenerationException(ex);
134 }
135 }
136
137 @NonNull
138 private static Map<String, ObjectNode> generateRootProperties(
139 @NonNull IAssemblyDefinition definition,
140 @NonNull JsonGenerationState state) {
141 Map<String, ObjectNode> properties = new LinkedHashMap<>();
142
143 properties.put("$schema", JsonNodeFactory.instance.objectNode()
144 .put("type", "string")
145 .put("format", "uri-reference"));
146
147 ObjectNode rootObj = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
148 IDefinitionJsonSchema<IAssemblyDefinition> schema = state.getSchema(IKey.of(definition));
149 schema.generateSchemaOrRef(rootObj, state);
150
151 properties.put(definition.getRootJsonName(), rootObj);
152 return properties;
153 }
154
155 private static class RootPropertyEntry {
156 @NonNull
157 private final IAssemblyDefinition definition;
158 @NonNull
159 private final Map<String, ObjectNode> properties;
160
161 public RootPropertyEntry(
162 @NonNull IAssemblyDefinition definition,
163 @NonNull JsonGenerationState state) {
164 this.definition = definition;
165 this.properties = generateRootProperties(definition, state);
166 }
167
168 @NonNull
169 protected IAssemblyDefinition getDefinition() {
170 return definition;
171 }
172
173 @NonNull
174 protected Map<String, ObjectNode> getProperties() {
175 return properties;
176 }
177
178 public void write(JsonGenerator writer) throws IOException {
179 writer.writeFieldName("properties");
180 writer.writeStartObject();
181
182 for (Map.Entry<String, ObjectNode> entry : getProperties().entrySet()) {
183 writer.writeFieldName(entry.getKey());
184 writer.writeTree(entry.getValue());
185 }
186
187 writer.writeEndObject();
188
189 writer.writeFieldName("required");
190 writer.writeStartArray();
191 writer.writeString(getDefinition().getRootJsonName());
192 writer.writeEndArray();
193
194 writer.writeBooleanField("additionalProperties", false);
195 }
196 }
197 }