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