org.codehaus.modello.plugin.jsonschema.JsonSchemaGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of modello-plugin-jsonschema Show documentation
Show all versions of modello-plugin-jsonschema Show documentation
Modello JSON-Schema Plugin generates a JSON-Schema from the model to be able to validate JSON content.
The newest version!
package org.codehaus.modello.plugin.jsonschema;
/*
* Copyright (c) 2013, Codehaus.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.core.json.JsonWriteFeature;
import org.codehaus.modello.ModelloException;
import org.codehaus.modello.ModelloParameterConstants;
import org.codehaus.modello.model.Model;
import org.codehaus.modello.model.ModelAssociation;
import org.codehaus.modello.model.ModelClass;
import org.codehaus.modello.model.ModelDefault;
import org.codehaus.modello.model.ModelField;
import org.codehaus.modello.plugins.xml.AbstractXmlJavaGenerator;
import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
import org.codehaus.plexus.util.StringUtils;
/**
* @author Simone Tripodi
* @since 1.8
*/
@Named("jsonschema")
public final class JsonSchemaGenerator extends AbstractXmlJavaGenerator {
@Override
public void generate(Model model, Map parameters) throws ModelloException {
initialize(model, parameters);
try {
generateJsonSchema(parameters);
} catch (IOException ioe) {
throw new ModelloException("Exception while generating JSON Schema.", ioe);
}
}
private void generateJsonSchema(Map parameters) throws IOException, ModelloException {
Model objectModel = getModel();
File directory = getOutputDirectory();
if (isPackageWithVersion()) {
directory = new File(directory, getGeneratedVersion().toString());
}
if (!directory.exists()) {
directory.mkdirs();
}
// we assume parameters not null
String schemaFileName = (String) parameters.get(ModelloParameterConstants.OUTPUT_JSONSCHEMA_FILE_NAME);
File schemaFile;
if (schemaFileName != null) {
schemaFile = new File(directory, schemaFileName);
} else {
schemaFile = new File(directory, objectModel.getId() + "-" + getGeneratedVersion() + ".schema.json");
}
JsonGenerator generator = new JsonFactory()
.enable(Feature.AUTO_CLOSE_JSON_CONTENT)
.enable(Feature.AUTO_CLOSE_TARGET)
.enable(Feature.FLUSH_PASSED_TO_STREAM)
.enable(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature())
.enable(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature())
.enable(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature())
.disable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS.mappedFeature())
.createGenerator(newWriter(schemaFile.toPath(), StandardCharsets.UTF_8));
generator.useDefaultPrettyPrinter();
ModelClass root = objectModel.getClass(objectModel.getRoot(getGeneratedVersion()), getGeneratedVersion());
try {
generator.writeStartObject();
generator.writeStringField("$schema", "http://json-schema.org/draft-04/schema#");
writeClassDocumentation(generator, root, true);
generator.writeObjectFieldStart("definitions");
for (ModelClass current : objectModel.getClasses(getGeneratedVersion())) {
if (!root.equals(current)) {
writeClassDocumentation(generator, current, false);
}
}
// end "definitions"
generator.writeEndObject();
// end main object
generator.writeEndObject();
} finally {
generator.close();
}
}
private void writeClassDocumentation(JsonGenerator generator, ModelClass modelClass, boolean isRoot)
throws IOException {
if (!isRoot) {
generator.writeObjectFieldStart(modelClass.getName());
}
generator.writeStringField("id", modelClass.getName() + '#');
writeDescriptionField(generator, modelClass.getDescription());
writeTypeField(generator, "object");
generator.writeObjectFieldStart("properties");
List required = new LinkedList<>();
ModelClass reference = modelClass;
// traverse the whole modelClass hierarchy to create the nested Builder instance
while (reference != null) {
// collect parameters and set them in the instance object
for (ModelField modelField : reference.getFields(getGeneratedVersion())) {
if (modelField.isRequired()) {
required.add(modelField.getName());
}
// each field is represented as object
generator.writeObjectFieldStart(modelField.getName());
writeDescriptionField(generator, modelField.getDescription());
if (modelField instanceof ModelAssociation) {
ModelAssociation modelAssociation = (ModelAssociation) modelField;
if (modelAssociation.isOneMultiplicity()) {
writeTypeField(generator, modelAssociation.getType());
} else {
// MANY_MULTIPLICITY
writeTypeField(generator, "array");
generator.writeObjectFieldStart("items");
String type = modelAssociation.getType();
String toType = modelAssociation.getTo();
if (ModelDefault.LIST.equals(type) || ModelDefault.SET.equals(type)) {
writeTypeField(generator, toType);
} else {
// Map or Properties
writeTypeField(generator, "object");
generator.writeObjectFieldStart("properties");
XmlAssociationMetadata xmlAssociationMetadata = (XmlAssociationMetadata)
modelAssociation.getAssociationMetadata(XmlAssociationMetadata.ID);
if (xmlAssociationMetadata.isMapExplode()) {
// key
generator.writeObjectFieldStart("key");
writeTypeField(generator, "string");
generator.writeEndObject();
// value
generator.writeObjectFieldStart("value");
writeTypeField(generator, toType);
generator.writeEndObject();
// properties
generator.writeEndObject();
// required field
generator.writeArrayFieldStart("required");
generator.writeString("key");
generator.writeString("value");
generator.writeEndArray();
} else {
generator.writeObjectFieldStart("*");
writeTypeField(generator, toType);
generator.writeEndObject();
}
}
// items
generator.writeEndObject();
}
} else {
writeTypeField(generator, modelField.getType());
}
generator.writeEndObject();
}
if (reference.hasSuperClass()) {
reference = reference.getModel().getClass(reference.getSuperClass(), getGeneratedVersion());
} else {
reference = null;
}
}
// end of `properties` element
generator.writeEndObject();
// write `required` sequence
if (!required.isEmpty()) {
generator.writeArrayFieldStart("required");
for (String requiredField : required) {
generator.writeString(requiredField);
}
generator.writeEndArray();
}
// end definition
if (!isRoot) {
generator.writeEndObject();
}
}
private static void writeDescriptionField(JsonGenerator generator, String description) throws IOException {
if (!StringUtils.isEmpty(description)) {
generator.writeStringField("description", description);
}
}
private void writeTypeField(JsonGenerator generator, String type) throws IOException {
if (isClassInModel(type, getModel())) {
generator.writeStringField("$ref", "#/definitions/" + type);
return;
}
// try to make the input type compliant, as much as possible, to JSON Schema primitive types
// see http://json-schema.org/latest/json-schema-core.html#anchor8
if ("boolean".equals(type) || "Boolean".equals(type)) {
type = "boolean";
} else if ("int".equals(type) || "Integer".equals(type)) {
type = "integer";
} else if ("short".equals(type)
|| "Short".equals(type)
|| "long".equals(type)
|| "Long".equals(type)
|| "double".equals(type)
|| "Double".equals(type)
|| "float".equals(type)
|| "Float".equals(type)) {
type = "number";
} else if ("String".equals(type)) {
type = "string";
}
// keep as it is otherwise
generator.writeStringField("type", type);
}
}