1
2
3
4
5
6 package gov.nist.secauto.metaschema.schemagen.json.impl;
7
8 import com.fasterxml.jackson.databind.node.ArrayNode;
9 import com.fasterxml.jackson.databind.node.ObjectNode;
10
11 import gov.nist.secauto.metaschema.core.model.IModule;
12 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
13 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
14 import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException;
15
16 import java.util.List;
17 import java.util.Set;
18 import java.util.stream.Collectors;
19 import java.util.stream.Stream;
20
21 import edu.umd.cs.findbugs.annotations.NonNull;
22
23
24
25
26 public class JsonSchemaModule
27 implements IJsonSchema {
28 private final IModule module;
29 private final List<IJsonSchemaDefinitionAssembly> roots;
30
31
32
33
34
35
36
37
38
39 public JsonSchemaModule(
40 @NonNull IModule module,
41 @NonNull IJsonGenerationState state) {
42 this.module = module;
43 this.roots = module.getExportedRootAssemblyDefinitions().stream()
44 .map(root -> state.getAssemblyDefinition(ObjectUtils.notNull(root), null))
45 .collect(Collectors.toUnmodifiableList());
46 }
47
48 @Override
49 public boolean isInline(IJsonGenerationState state) {
50
51 return true;
52 }
53
54
55
56
57
58
59
60
61
62 @NonNull
63 public Stream<IJsonSchemaDefinable> collectDefinitions(@NonNull IJsonGenerationState state) {
64 return ObjectUtils.notNull(roots.stream()
65 .flatMap(root -> root.collectDefinitions(CollectionUtil.emptySet(), state)));
66 }
67
68 @Override
69 public void generateInlineJsonSchema(ObjectNode node, IJsonGenerationState state) {
70 node.put("$schema", "http://json-schema.org/draft-07/schema#");
71 node.put("$id",
72 String.format("%s/%s-%s-schema.json",
73 module.getXmlNamespace(),
74 module.getShortName(),
75 module.getVersion()));
76 node.put("$comment", module.getName().toMarkdown());
77 node.put("type", "object");
78
79 if (roots.isEmpty()) {
80 throw new SchemaGenerationException("No root definitions found");
81 }
82
83 node.set("definitions", generateDefinitions(state));
84
85 if (roots.size() == 1) {
86 generateRoot(node, ObjectUtils.notNull(roots.iterator().next()), state);
87 } else {
88 ArrayNode oneOfNode = node.putArray("oneOf");
89 roots.forEach(root -> {
90 assert root != null;
91 ObjectNode rootNode = ObjectUtils.notNull(oneOfNode.addObject());
92 assert rootNode != null;
93
94 generateRoot(rootNode, root, state);
95 });
96 }
97 }
98
99
100
101
102
103
104
105
106 private ObjectNode generateDefinitions(@NonNull IJsonGenerationState state) {
107
108
109 Set<IJsonSchemaDefinable> usedDefinitions = ObjectUtils.notNull(collectDefinitions(state)
110 .collect(Collectors.toUnmodifiableSet()));
111
112 ObjectNode definitionsNode = ObjectUtils.notNull(state.getJsonNodeFactory().objectNode());
113
114 usedDefinitions.stream()
115 .filter(definition -> !definition.isInline(state))
116 .distinct()
117 .sorted(JsonSchemaHelper.DEFINABLE_NAME_COMPARATOR)
118 .forEach(definition -> {
119 ObjectNode definitionNode = definitionsNode.putObject(definition.getDefinitionName());
120 assert definitionNode != null;
121 definition.generateDefinitionJsonSchema(definitionNode, state);
122 });
123
124 state.generateDataTypeDefinitions(definitionsNode);
125
126 return definitionsNode;
127 }
128
129 private static void generateRoot(
130 @NonNull ObjectNode node,
131 @NonNull IJsonSchemaDefinitionAssembly schema,
132 @NonNull IJsonGenerationState state) {
133 ObjectNode propertiesObj = node.putObject("properties");
134
135 propertiesObj.putObject("$schema")
136 .put("type", "string")
137 .put("format", "uri-reference");
138
139 String name = schema.getDefinition().getRootJsonName();
140
141 schema.generateJsonSchemaOrDefinitionRef(ObjectUtils.notNull(propertiesObj.putObject(name)), state);
142
143 node.putArray("required")
144 .add(name);
145 node.put("additionalProperties", false);
146 }
147 }