1 package org.codehaus.modello.plugin.jsonschema;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import javax.inject.Named;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.nio.charset.StandardCharsets;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Map;
33
34 import com.fasterxml.jackson.core.JsonFactory;
35 import com.fasterxml.jackson.core.JsonGenerator;
36 import com.fasterxml.jackson.core.JsonGenerator.Feature;
37 import com.fasterxml.jackson.core.json.JsonWriteFeature;
38 import org.codehaus.modello.ModelloException;
39 import org.codehaus.modello.ModelloParameterConstants;
40 import org.codehaus.modello.model.Model;
41 import org.codehaus.modello.model.ModelAssociation;
42 import org.codehaus.modello.model.ModelClass;
43 import org.codehaus.modello.model.ModelDefault;
44 import org.codehaus.modello.model.ModelField;
45 import org.codehaus.modello.plugins.xml.AbstractXmlJavaGenerator;
46 import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
47 import org.codehaus.plexus.util.StringUtils;
48
49
50
51
52
53 @Named("jsonschema")
54 public final class JsonSchemaGenerator extends AbstractXmlJavaGenerator {
55
56 @Override
57 public void generate(Model model, Map<String, Object> parameters) throws ModelloException {
58 initialize(model, parameters);
59
60 try {
61 generateJsonSchema(parameters);
62 } catch (IOException ioe) {
63 throw new ModelloException("Exception while generating JSON Schema.", ioe);
64 }
65 }
66
67 private void generateJsonSchema(Map<String, Object> parameters) throws IOException, ModelloException {
68 Model objectModel = getModel();
69
70 File directory = getOutputDirectory();
71
72 if (isPackageWithVersion()) {
73 directory = new File(directory, getGeneratedVersion().toString());
74 }
75
76 if (!directory.exists()) {
77 directory.mkdirs();
78 }
79
80
81 String schemaFileName = (String) parameters.get(ModelloParameterConstants.OUTPUT_JSONSCHEMA_FILE_NAME);
82
83 File schemaFile;
84
85 if (schemaFileName != null) {
86 schemaFile = new File(directory, schemaFileName);
87 } else {
88 schemaFile = new File(directory, objectModel.getId() + "-" + getGeneratedVersion() + ".schema.json");
89 }
90
91 JsonGenerator generator = new JsonFactory()
92 .enable(Feature.AUTO_CLOSE_JSON_CONTENT)
93 .enable(Feature.AUTO_CLOSE_TARGET)
94 .enable(Feature.FLUSH_PASSED_TO_STREAM)
95 .enable(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature())
96 .enable(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature())
97 .enable(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature())
98 .disable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS.mappedFeature())
99 .createGenerator(newWriter(schemaFile.toPath(), StandardCharsets.UTF_8));
100
101 generator.useDefaultPrettyPrinter();
102
103 ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
104
105 try {
106 generator.writeStartObject();
107 generator.writeStringField("$schema", "http://json-schema.org/draft-04/schema#");
108
109 writeClassDocumentation(generator, root, true);
110
111 generator.writeObjectFieldStart("definitions");
112
113 for (ModelClass current : objectModel.getClasses(getGeneratedVersion())) {
114 if (!root.equals(current)) {
115 writeClassDocumentation(generator, current, false);
116 }
117 }
118
119
120 generator.writeEndObject();
121
122
123 generator.writeEndObject();
124 } finally {
125 generator.close();
126 }
127 }
128
129 private void writeClassDocumentation(JsonGenerator generator, ModelClass modelClass, boolean isRoot)
130 throws IOException {
131 if (!isRoot) {
132 generator.writeObjectFieldStart(modelClass.getName());
133 }
134
135 generator.writeStringField("id", modelClass.getName() + '#');
136 writeDescriptionField(generator, modelClass.getDescription());
137 writeTypeField(generator, "object");
138
139 generator.writeObjectFieldStart("properties");
140
141 List<String> required = new LinkedList<>();
142
143 ModelClass reference = modelClass;
144
145 while (reference != null) {
146
147 for (ModelField modelField : reference.getFields(getGeneratedVersion())) {
148 if (modelField.isRequired()) {
149 required.add(modelField.getName());
150 }
151
152
153 generator.writeObjectFieldStart(modelField.getName());
154
155 writeDescriptionField(generator, modelField.getDescription());
156
157 if (modelField instanceof ModelAssociation) {
158 ModelAssociation modelAssociation = (ModelAssociation) modelField;
159
160 if (modelAssociation.isOneMultiplicity()) {
161 writeTypeField(generator, modelAssociation.getType());
162 } else {
163
164 writeTypeField(generator, "array");
165
166 generator.writeObjectFieldStart("items");
167
168 String type = modelAssociation.getType();
169 String toType = modelAssociation.getTo();
170
171 if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
172 writeTypeField(generator, toType);
173 } else {
174
175
176 writeTypeField(generator, "object");
177
178 generator.writeObjectFieldStart("properties");
179
180 XmlAssociationMetadata xmlAssociationMetadata = (XmlAssociationMetadata)
181 modelAssociation.getAssociationMetadata(XmlAssociationMetadata.ID);
182
183 if (xmlAssociationMetadata.isMapExplode()) {
184
185 generator.writeObjectFieldStart("key");
186 writeTypeField(generator, "string");
187 generator.writeEndObject();
188
189
190 generator.writeObjectFieldStart("value");
191 writeTypeField(generator, toType);
192 generator.writeEndObject();
193
194
195 generator.writeEndObject();
196
197
198 generator.writeArrayFieldStart("required");
199 generator.writeString("key");
200 generator.writeString("value");
201 generator.writeEndArray();
202 } else {
203 generator.writeObjectFieldStart("*");
204 writeTypeField(generator, toType);
205 generator.writeEndObject();
206 }
207 }
208
209
210 generator.writeEndObject();
211 }
212 } else {
213 writeTypeField(generator, modelField.getType());
214 }
215
216 generator.writeEndObject();
217 }
218
219 if (reference.hasSuperClass()) {
220 reference = reference.getModel().getClass(reference.getSuperClass(), getGeneratedVersion());
221 } else {
222 reference = null;
223 }
224 }
225
226
227 generator.writeEndObject();
228
229
230 if (!required.isEmpty()) {
231 generator.writeArrayFieldStart("required");
232
233 for (String requiredField : required) {
234 generator.writeString(requiredField);
235 }
236
237 generator.writeEndArray();
238 }
239
240
241 if (!isRoot) {
242 generator.writeEndObject();
243 }
244 }
245
246 private static void writeDescriptionField(JsonGenerator generator, String description) throws IOException {
247 if (!StringUtils.isEmpty(description)) {
248 generator.writeStringField("description", description);
249 }
250 }
251
252 private void writeTypeField(JsonGenerator generator, String type) throws IOException {
253 if (isClassInModel(type, getModel())) {
254 generator.writeStringField("$ref", "#/definitions/" + type);
255 return;
256 }
257
258
259
260 if ("boolean".equals(type) || "Boolean".equals(type)) {
261 type = "boolean";
262 } else if ("int".equals(type) || "Integer".equals(type)) {
263 type = "integer";
264 } else if ("short".equals(type)
265 || "Short".equals(type)
266 || "long".equals(type)
267 || "Long".equals(type)
268 || "double".equals(type)
269 || "Double".equals(type)
270 || "float".equals(type)
271 || "Float".equals(type)) {
272 type = "number";
273 } else if ("String".equals(type)) {
274 type = "string";
275 }
276
277
278
279 generator.writeStringField("type", type);
280 }
281 }