Skip to content

Commit

Permalink
Add support for yaml and yamlschema source types
Browse files Browse the repository at this point in the history
Closes #778
  • Loading branch information
joelittlejohn committed Sep 18, 2017
1 parent eb507b2 commit 9137a6d
Show file tree
Hide file tree
Showing 55 changed files with 1,449 additions and 27 deletions.
4 changes: 4 additions & 0 deletions jsonschema2pojo-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.HashSet;
import java.util.Set;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
Expand All @@ -38,9 +39,18 @@
public class ContentResolver {

private static final Set<String> CLASSPATH_SCHEMES = new HashSet<String>(asList("classpath", "resource", "java"));
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);

private final ObjectMapper objectMapper;

public ContentResolver() {
this(null);
}

public ContentResolver(JsonFactory jsonFactory) {
this.objectMapper = new ObjectMapper(jsonFactory)
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
}

/**
* Resolve a given URI to read its contents and parse the result as JSON.
Expand All @@ -64,7 +74,7 @@ public JsonNode resolve(URI uri) {
}

try {
return OBJECT_MAPPER.readTree(uri.toURL());
return objectMapper.readTree(uri.toURL());
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Error parsing document: " + uri, e);
} catch (MalformedURLException e) {
Expand All @@ -85,7 +95,7 @@ private JsonNode resolveFromClasspath(URI uri) {
}

try {
return OBJECT_MAPPER.readTree(contentAsStream);
return objectMapper.readTree(contentAsStream);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Error parsing document: " + uri, e);
} catch (MalformedURLException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.jsonschema2pojo.util.NameHelper;
import org.jsonschema2pojo.util.URLUtil;

import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JCodeModel;

Expand All @@ -57,8 +58,9 @@ public static void generate(GenerationConfig config) throws IOException {

ruleFactory.setAnnotator(annotator);
ruleFactory.setGenerationConfig(config);
ruleFactory.setSchemaStore(new SchemaStore(createContentResolver(config)));

SchemaMapper mapper = new SchemaMapper(ruleFactory, new SchemaGenerator());
SchemaMapper mapper = new SchemaMapper(ruleFactory, createSchemaGenerator(config));

JCodeModel codeModel = new JCodeModel();

Expand Down Expand Up @@ -90,6 +92,22 @@ public static void generate(GenerationConfig config) throws IOException {
throw new GenerationException("Could not create or access target directory " + config.getTargetDirectory().getAbsolutePath());
}
}

private static ContentResolver createContentResolver(GenerationConfig config) {
if (config.getSourceType() == SourceType.YAMLSCHEMA || config.getSourceType() == SourceType.YAML) {
return new ContentResolver(new YAMLFactory());
} else {
return new ContentResolver();
}
}

private static SchemaGenerator createSchemaGenerator(GenerationConfig config) {
if (config.getSourceType() == SourceType.YAMLSCHEMA || config.getSourceType() == SourceType.YAML) {
return new SchemaGenerator(new YAMLFactory());
} else {
return new SchemaGenerator();
}
}

private static RuleFactory createRuleFactory(GenerationConfig config) {
Class<? extends RuleFactory> clazz = config.getCustomRuleFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import org.jsonschema2pojo.exception.GenerationException;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
Expand All @@ -39,14 +40,22 @@

public class SchemaGenerator {

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
private final ObjectMapper objectMapper;

public SchemaGenerator() {
this(null);
}

public SchemaGenerator(JsonFactory jsonFactory) {
this.objectMapper = new ObjectMapper(jsonFactory)
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
}

public ObjectNode schemaFromExample(URL example) {

try {
JsonNode content = OBJECT_MAPPER.readTree(example);
JsonNode content = this.objectMapper.readTree(example);
return schemaFromExample(content);
} catch (IOException e) {
throw new GenerationException("Could not process JSON in source file", e);
Expand All @@ -68,10 +77,10 @@ public ObjectNode schemaFromExample(JsonNode example) {

private ObjectNode objectSchema(JsonNode exampleObject) {

ObjectNode schema = OBJECT_MAPPER.createObjectNode();
ObjectNode schema = this.objectMapper.createObjectNode();
schema.put("type", "object");

ObjectNode properties = OBJECT_MAPPER.createObjectNode();
ObjectNode properties = this.objectMapper.createObjectNode();
for (Iterator<String> iter = exampleObject.fieldNames(); iter.hasNext();) {
String property = iter.next();
properties.set(property, schemaFromExample(exampleObject.get(property)));
Expand All @@ -82,7 +91,7 @@ private ObjectNode objectSchema(JsonNode exampleObject) {
}

private ObjectNode arraySchema(JsonNode exampleArray) {
ObjectNode schema = OBJECT_MAPPER.createObjectNode();
ObjectNode schema = this.objectMapper.createObjectNode();

schema.put("type", "array");

Expand All @@ -98,7 +107,7 @@ private ObjectNode arraySchema(JsonNode exampleArray) {

private JsonNode mergeArrayItems(JsonNode exampleArray) {

ObjectNode mergedItems = OBJECT_MAPPER.createObjectNode();
ObjectNode mergedItems = this.objectMapper.createObjectNode();

for (JsonNode item : exampleArray) {
if (item.isObject()) {
Expand Down Expand Up @@ -143,11 +152,11 @@ private ObjectNode simpleTypeSchema(JsonNode exampleValue) {

try {

Object valueAsJavaType = OBJECT_MAPPER.treeToValue(exampleValue, Object.class);
Object valueAsJavaType = this.objectMapper.treeToValue(exampleValue, Object.class);

SchemaAware valueSerializer = getValueSerializer(valueAsJavaType);

return (ObjectNode) valueSerializer.getSchema(OBJECT_MAPPER.getSerializerProvider(), null);
return (ObjectNode) valueSerializer.getSchema(this.objectMapper.getSerializerProvider(), null);
} catch (JsonMappingException e) {
throw new GenerationException("Unable to generate a schema for this json example: " + exampleValue, e);
} catch (JsonProcessingException e) {
Expand All @@ -158,7 +167,7 @@ private ObjectNode simpleTypeSchema(JsonNode exampleValue) {

private SchemaAware getValueSerializer(Object valueAsJavaType) throws JsonMappingException {

SerializerProvider serializerProvider = new DefaultSerializerProvider.Impl().createInstance(OBJECT_MAPPER.getSerializationConfig(), BeanSerializerFactory.instance);
SerializerProvider serializerProvider = new DefaultSerializerProvider.Impl().createInstance(this.objectMapper.getSerializationConfig(), BeanSerializerFactory.instance);

if (valueAsJavaType == null) {
return NullSerializer.instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,12 @@ private ObjectNode readSchema(URL schemaUrl) {

switch (ruleFactory.getGenerationConfig().getSourceType()) {
case JSONSCHEMA:
case YAMLSCHEMA:
ObjectNode schemaNode = NODE_FACTORY.objectNode();
schemaNode.put("$ref", schemaUrl.toString());
return schemaNode;
case JSON:
case YAML:
return schemaGenerator.schemaFromExample(schemaUrl);
default:
throw new IllegalArgumentException("Unrecognised source type: " + ruleFactory.getGenerationConfig().getSourceType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@

public class SchemaStore {

protected Map<URI, Schema> schemas = new HashMap<URI, Schema>();
protected final Map<URI, Schema> schemas = new HashMap<URI, Schema>();

protected FragmentResolver fragmentResolver = new FragmentResolver();
protected ContentResolver contentResolver = new ContentResolver();
protected final FragmentResolver fragmentResolver = new FragmentResolver();
protected final ContentResolver contentResolver;

/**
public SchemaStore() {
this.contentResolver = new ContentResolver();
}

public SchemaStore(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
}

/**
* Create or look up a new schema which has the given ID and read the
* contents of the given ID as a URL. If a schema with the given ID is
* already known, then a reference to the original schema will be returned.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,16 @@ public enum SourceType {
* JSON documents, that represent an example of the kind of JSON data that
* the generated Java types will be mapped to.
*/
JSON
JSON,

/**
* JSON-schema documents, represented as YAML
*/
YAMLSCHEMA,

/**
* YAML documents, that represent an example of the kind of YAML (or JSON) data that
* the generated Java types will be mapped to.
*/
YAML
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public void arrayItemsAreNotRecursivelyMerged() throws Exception {
assertEquals(Integer.class, genType.getMethod("getScalar").getReturnType());

thrown.expect(InvalidFormatException.class);
thrown.expectMessage("Can not construct instance of java.lang.Integer from String value (\"what\")");
thrown.expectMessage(startsWith("Cannot deserialize value of type `java.lang.Integer` from String \"what\": not a valid Integer value"));
OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/json/simplePropertiesInArrayItem.json"), Array.newInstance(genType, 0).getClass());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Copyright © 2010-2017 Nokia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jsonschema2pojo.integration.yaml;

import static org.hamcrest.Matchers.*;
import static org.jsonschema2pojo.integration.util.CodeGenerationHelper.*;
import static org.junit.Assert.*;

import java.util.List;

import org.jsonschema2pojo.integration.util.Jsonschema2PojoRule;
import org.junit.Rule;
import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

public class PlainYamlRealExamplesIT {

@Rule public Jsonschema2PojoRule schemaRule = new Jsonschema2PojoRule();

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new YAMLFactory());

@Test
public void getUserDataProducesValidTypes() throws Exception {

ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/yaml/examples/GetUserData.yaml", "com.example",
config("sourceType", "yaml",
"useLongIntegers", true));

Class<?> userDataType = resultsClassLoader.loadClass("com.example.GetUserData");

Object userData = OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/yaml/examples/GetUserData.yaml"), userDataType);
Object result = userDataType.getMethod("getResult").invoke(userData);
Object data = result.getClass().getMethod("getData").invoke(result);
Object userUIPref = data.getClass().getMethod("getUserUIPref").invoke(data);

assertThat(userUIPref.getClass().getMethod("getPimColor").invoke(userUIPref).toString(), is("blue"));

Object externalAccounts = data.getClass().getMethod("getExternalAccounts").invoke(data);
Object extAccount = externalAccounts.getClass().getMethod("getExtAccount").invoke(externalAccounts);
Object extAccount0 = ((List<?>) extAccount).get(0);
assertThat(extAccount0.getClass().getMethod("getFolder").invoke(extAccount0).toString(), is("Inbox"));

}

@Test
public void torrentProducesValidTypes() throws Exception {

ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/yaml/examples/torrent.yaml", "com.example",
config("sourceType", "yaml",
"propertyWordDelimiters", "_"));

Class<?> torrentType = resultsClassLoader.loadClass("com.example.Torrent");

Object torrent = OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/yaml/examples/torrent.yaml"), torrentType);

Object props = torrentType.getMethod("getProps").invoke(torrent);
Object prop0 = ((List<?>) props).get(0);
assertThat((Integer) prop0.getClass().getMethod("getSeedRatio").invoke(prop0), is(1500));

}
}
Loading

0 comments on commit 9137a6d

Please sign in to comment.