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