[Java][Jersey2] various improvements (#6518)

* [java][jersey2] Add support for discriminator, fix nullable typo and nullable deserialization (#6495)

* Mustache template should use invokerPackage tag to generate import

* fix typo, fix script issue, add log statement for troubleshooting

* Add java jersey2 samples with OpenAPI doc that has HTTP signature security scheme

* Add sample for Java jersey2 and HTTP signature scheme

* Add unit test for oneOf schema deserialization

* Add unit test for oneOf schema deserialization

* Add log statements

* Add profile for jersey2

* Temporarily disable unit test

* Temporarily disable unit test

* support for discriminator in jersey2

* fix typo in pom.xml

* disable unit test because jersey2 deserialization is broken

* disable unit test because jersey2 deserialization is broken

* fix duplicate jersey2 samples

* fix duplicate jersey2 samples

* Add code comments

* fix duplicate artifact id

* fix duplicate jersey2 samples

* run samples scripts

* resolve merge conflicts

* Add unit tests

* fix unit tests

* continue implementation of discriminator lookup

* throw deserialization exception when value is null and schema does not allow null value

* continue implementation of compose schema

* continue implementation of compose schema

* continue implementation of compose schema

* Add more unit tests

* Add unit tests for anyOf

* Add unit tests

Co-authored-by: Vikrant Balyan (vvb) <vvb@cisco.com>

* update samples

* add tests to oas3 java jersey2 petstore

* comment out jersey2 ensure uptodate

* Jersey2 supports additional properties with composed schema (#6523)

* Mustache template should use invokerPackage tag to generate import

* fix typo, fix script issue, add log statement for troubleshooting

* Add java jersey2 samples with OpenAPI doc that has HTTP signature security scheme

* Add sample for Java jersey2 and HTTP signature scheme

* Add unit test for oneOf schema deserialization

* Add unit test for oneOf schema deserialization

* Add log statements

* Add profile for jersey2

* Temporarily disable unit test

* Temporarily disable unit test

* support for discriminator in jersey2

* fix typo in pom.xml

* disable unit test because jersey2 deserialization is broken

* disable unit test because jersey2 deserialization is broken

* fix duplicate jersey2 samples

* fix duplicate jersey2 samples

* Add code comments

* fix duplicate artifact id

* fix duplicate jersey2 samples

* run samples scripts

* resolve merge conflicts

* Add unit tests

* fix unit tests

* continue implementation of discriminator lookup

* throw deserialization exception when value is null and schema does not allow null value

* continue implementation of compose schema

* continue implementation of compose schema

* continue implementation of compose schema

* Add more unit tests

* Add unit tests for anyOf

* Add unit tests

* Set supportsAdditionalPropertiesWithComposedSchema to true for Java jersey2

* Support additional properties as nested field

* Support additional properties as nested field

* add code comments

* add customer deserializer

* Fix 'method too big' error with generated code

* resolve merge conflicts

Co-authored-by: Vikrant Balyan (vvb) <vvb@cisco.com>

* [Jersey2] Fix code generation of 'registerDiscriminator' method for large models (#6535)

* Mustache template should use invokerPackage tag to generate import

* fix typo, fix script issue, add log statement for troubleshooting

* Add java jersey2 samples with OpenAPI doc that has HTTP signature security scheme

* Add sample for Java jersey2 and HTTP signature scheme

* Add unit test for oneOf schema deserialization

* Add unit test for oneOf schema deserialization

* Add log statements

* Add profile for jersey2

* Temporarily disable unit test

* Temporarily disable unit test

* support for discriminator in jersey2

* fix typo in pom.xml

* disable unit test because jersey2 deserialization is broken

* disable unit test because jersey2 deserialization is broken

* fix duplicate jersey2 samples

* fix duplicate jersey2 samples

* Add code comments

* fix duplicate artifact id

* fix duplicate jersey2 samples

* run samples scripts

* resolve merge conflicts

* Add unit tests

* fix unit tests

* continue implementation of discriminator lookup

* throw deserialization exception when value is null and schema does not allow null value

* continue implementation of compose schema

* continue implementation of compose schema

* continue implementation of compose schema

* Add more unit tests

* Add unit tests for anyOf

* Add unit tests

* Fix 'method too big' error with generated code

* resolve merge conflicts

Co-authored-by: Vikrant Balyan (vvb) <vvb@cisco.com>

* update samples

* comment out tests

* support additional properties in serialize and deserialize

* add discriminator lookup

* remove oneof/anyof logic in apilcient

* add serializer to mammal.java

* add serialize to oneOf model

* add serializer to anyof model

* comment out test cases that are subject to further discussion

* add back files

* update configs, samples

Co-authored-by: Sebastien Rosset <serosset@cisco.com>
Co-authored-by: Vikrant Balyan (vvb) <vvb@cisco.com>
This commit is contained in:
William Cheng
2020-06-10 00:38:11 +08:00
committed by GitHub
parent 60ceded171
commit 233087c5aa
164 changed files with 3171 additions and 874 deletions

View File

@@ -1649,10 +1649,24 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code
@Override
protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
super.addAdditionPropertiesToCodeGenModel(codegenModel, schema);
if (!supportsAdditionalPropertiesWithComposedSchema) {
// The additional (undeclared) propertiees are modeled in Java as a HashMap.
//
// 1. supportsAdditionalPropertiesWithComposedSchema is set to false:
// The generated model class extends from the HashMap. That does not work
// with composed schemas that also use a discriminator because the model class
// is supposed to extend from the generated parent model class.
// 2. supportsAdditionalPropertiesWithComposedSchema is set to true:
// The HashMap is a field.
super.addAdditionPropertiesToCodeGenModel(codegenModel, schema);
}
// See https://github.com/OpenAPITools/openapi-generator/pull/1729#issuecomment-449937728
codegenModel.additionalPropertiesType = getSchemaType(getAdditionalProperties(schema));
addImport(codegenModel, codegenModel.additionalPropertiesType);
Schema s = getAdditionalProperties(schema);
// 's' may be null if 'additionalProperties: false' in the OpenAPI schema.
if (s != null) {
codegenModel.additionalPropertiesType = getSchemaType(s);
addImport(codegenModel, codegenModel.additionalPropertiesType);
}
}
}

View File

@@ -176,6 +176,7 @@ public class JavaClientCodegen extends AbstractJavaCodegen
// inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values,
// and the discriminator mapping schemas in the OAS document.
this.setLegacyDiscriminatorBehavior(false);
}
@Override
@@ -371,6 +372,14 @@ public class JavaClientCodegen extends AbstractJavaCodegen
}
supportingFiles.add(new SupportingFile("AbstractOpenApiSchema.mustache", (sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar)).replace('/', File.separatorChar), "AbstractOpenApiSchema.java"));
forceSerializationLibrary(SERIALIZATION_LIBRARY_JACKSON);
// Composed schemas can have the 'additionalProperties' keyword, as specified in JSON schema.
// In principle, this should be enabled by default for all code generators. However due to limitations
// in other code generators, support needs to be enabled on a case-by-case basis.
// The flag below should be set for all Java libraries, but the templates need to be ported
// one by one for each library.
supportsAdditionalPropertiesWithComposedSchema = true;
} else if (NATIVE.equals(getLibrary())) {
setJava8Mode(true);
additionalProperties.put("java8", "true");
@@ -587,38 +596,6 @@ public class JavaClientCodegen extends AbstractJavaCodegen
objs = AbstractJavaJAXRSServerCodegen.jaxrsPostProcessOperations(objs);
}
if (JERSEY2.equals(getLibrary())) {
// index the model
HashMap<String, CodegenModel> modelMaps = new HashMap<String, CodegenModel>();
for (Object o : allModels) {
HashMap<String, Object> h = (HashMap<String, Object>) o;
CodegenModel m = (CodegenModel) h.get("model");
modelMaps.put(m.classname, m);
}
// check if return type is oneOf/anyeOf model
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");
for (CodegenOperation op : operationList) {
if (op.returnType != null) {
// look up the model to see if it's anyOf/oneOf
if (modelMaps.containsKey(op.returnType) && modelMaps.get(op.returnType) != null) {
CodegenModel cm = modelMaps.get(op.returnType);
if (cm.oneOf != null && !cm.oneOf.isEmpty()) {
op.vendorExtensions.put("x-java-return-type-one-of", true);
}
if (cm.anyOf != null && !cm.anyOf.isEmpty()) {
op.vendorExtensions.put("x-java-return-type-any-of", true);
}
} else {
//LOGGER.error("cannot lookup model " + op.returnType);
}
}
}
}
return objs;
}

View File

@@ -30,14 +30,14 @@ public abstract class AbstractOpenApiSchema {
this.isNullable = isNullable;
}
/***
* Get the list of schemas allowed to be stored in this object
/**
* Get the list of oneOf/anyOf composed schemas allowed to be stored in this object
*
* @return an instance of the actual schema/object
*/
public abstract Map<String, GenericType> getSchemas();
/***
/**
* Get the actual instance
*
* @return an instance of the actual schema/object
@@ -45,14 +45,14 @@ public abstract class AbstractOpenApiSchema {
@JsonValue
public Object getActualInstance() {return instance;}
/***
/**
* Set the actual instance
*
* @param instance the actual instance of the schema/object
*/
public void setActualInstance(Object instance) {this.instance = instance;}
/***
/**
* Get the schema type (e.g. anyOf, oneOf)
*
* @return the schema type
@@ -101,7 +101,7 @@ public abstract class AbstractOpenApiSchema {
return Objects.hash(instance, isNullable, schemaType);
}
/***
/**
* Is nullalble
*
* @return true if it's nullable
@@ -113,4 +113,7 @@ public abstract class AbstractOpenApiSchema {
return Boolean.FALSE;
}
}
{{>libraries/jersey2/additional_properties}}
}

View File

@@ -68,7 +68,6 @@ import {{invokerPackage}}.auth.ApiKeyAuth;
{{#hasOAuthMethods}}
import {{invokerPackage}}.auth.OAuth;
{{/hasOAuthMethods}}
import {{invokerPackage}}.model.AbstractOpenApiSchema;
{{>generatedAnnotation}}
public class ApiClient {
@@ -894,67 +893,6 @@ public class ApiClient {
}
}
public AbstractOpenApiSchema deserializeSchemas(Response response, AbstractOpenApiSchema schema) throws ApiException{
Object result = null;
int matchCounter = 0;
ArrayList<String> matchSchemas = new ArrayList<>();
if (schema.isNullable()) {
response.bufferEntity();
if ("{}".equals(String.valueOf(response.readEntity(String.class))) ||
"".equals(String.valueOf(response.readEntity(String.class)))) {
// schema is nullable and the response body is {} or empty string
return schema;
}
}
for (Map.Entry<String, GenericType> entry : schema.getSchemas().entrySet()) {
String schemaName = entry.getKey();
GenericType schemaType = entry.getValue();
if (schemaType instanceof GenericType) { // model
try {
Object deserializedObject = deserialize(response, schemaType);
if (deserializedObject != null) {
result = deserializedObject;
matchCounter++;
if ("anyOf".equals(schema.getSchemaType())) {
break;
} else if ("oneOf".equals(schema.getSchemaType())) {
matchSchemas.add(schemaName);
} else {
throw new ApiException("Unknowe type found while expecting anyOf/oneOf:" + schema.getSchemaType());
}
} else {
// failed to deserialize the response in the schema provided, proceed to the next one if any
}
} catch (Exception ex) {
// failed to deserialize, do nothing and try next one (schema)
// Logging the error may be useful to troubleshoot why a payload fails to match
// the schema.
log.log(Level.FINE, "Input data does not match schema '" + schemaName + "'", ex);
}
} else {// unknown type
throw new ApiException(schemaType.getClass() + " is not a GenericType and cannot be handled properly in deserialization.");
}
}
if (matchCounter > 1 && "oneOf".equals(schema.getSchemaType())) {// more than 1 match for oneOf
throw new ApiException("Response body is invalid as it matches more than one schema (" + StringUtil.join(matchSchemas, ", ") + ") defined in the oneOf model: " + schema.getClass().getName());
} else if (matchCounter == 0) { // fail to match any in oneOf/anyOf schemas
throw new ApiException("Response body is invalid as it does not match any schemas (" + StringUtil.join(schema.getSchemas().keySet(), ", ") + ") defined in the oneOf/anyOf model: " + schema.getClass().getName());
} else { // only one matched
schema.setActualInstance(result);
return schema;
}
}
/**
* Deserialize response body to Java object according to the Content-Type.
* @param <T> Type
@@ -1062,7 +1000,6 @@ public class ApiClient {
* @param contentType The request's Content-Type header
* @param authNames The authentications to apply
* @param returnType The return type into which to deserialize the response
* @param schema An instance of the response that uses oneOf/anyOf
* @return The response body in type of string
* @throws ApiException API exception
*/
@@ -1078,8 +1015,7 @@ public class ApiClient {
String accept,
String contentType,
String[] authNames,
GenericType<T> returnType,
AbstractOpenApiSchema schema)
GenericType<T> returnType)
throws ApiException {
// Not using `.target(targetURL).path(path)` below,
@@ -1178,12 +1114,10 @@ public class ApiClient {
if (response.getStatusInfo() == Status.NO_CONTENT) {
return new ApiResponse<T>(statusCode, responseHeaders);
} else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) {
if (returnType == null) return new ApiResponse<T>(statusCode, responseHeaders);
else if (schema == null) {
if (returnType == null) {
return new ApiResponse<T>(statusCode, responseHeaders);
} else {
return new ApiResponse<T>(statusCode, responseHeaders, deserialize(response, returnType));
} else { // oneOf/anyOf
return new ApiResponse<T>(
statusCode, responseHeaders, (T) deserializeSchemas(response, schema));
}
} else {
String message = "error";
@@ -1229,8 +1163,8 @@ public class ApiClient {
* @deprecated Add qualified name of the operation as a first parameter.
*/
@Deprecated
public <T> ApiResponse<T> invokeAPI(String path, String method, List<Pair> queryParams, Object body, Map<String, String> headerParams, Map<String, String> cookieParams, Map<String, Object> formParams, String accept, String contentType, String[] authNames, GenericType<T> returnType, AbstractOpenApiSchema schema) throws ApiException {
return invokeAPI(null, path, method, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames, returnType, schema);
public <T> ApiResponse<T> invokeAPI(String path, String method, List<Pair> queryParams, Object body, Map<String, String> headerParams, Map<String, String> cookieParams, Map<String, Object> formParams, String accept, String contentType, String[] authNames, GenericType<T> returnType) throws ApiException {
return invokeAPI(null, path, method, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames, returnType);
}
/**

View File

@@ -15,9 +15,16 @@ import com.fasterxml.jackson.datatype.joda.JodaModule;
{{#threetenbp}}
import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule;
{{/threetenbp}}
{{#models.0}}
import {{modelPackage}}.*;
{{/models.0}}
import java.text.DateFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.ext.ContextResolver;
{{>generatedAnnotation}}
@@ -69,4 +76,197 @@ public class JSON implements ContextResolver<ObjectMapper> {
* @return object mapper
*/
public ObjectMapper getMapper() { return mapper; }
/**
* Returns the target model class that should be used to deserialize the input data.
* The discriminator mappings are used to determine the target model class.
*
* @param node The input data.
* @param modelClass The class that contains the discriminator mappings.
*/
public static Class getClassForElement(JsonNode node, Class modelClass) {
ClassDiscriminatorMapping cdm = modelDiscriminators.get(modelClass);
if (cdm != null) {
return cdm.getClassForElement(node, new HashSet<Class>());
}
return null;
}
/**
* Helper class to register the discriminator mappings.
*/
private static class ClassDiscriminatorMapping {
// The model class name.
Class modelClass;
// The name of the discriminator property.
String discriminatorName;
// The discriminator mappings for a model class.
Map<String, Class> discriminatorMappings;
// Constructs a new class discriminator.
ClassDiscriminatorMapping(Class cls, String propertyName, Map<String, Class> mappings) {
modelClass = cls;
discriminatorName = propertyName;
discriminatorMappings = new HashMap<String, Class>();
if (mappings != null) {
discriminatorMappings.putAll(mappings);
}
}
// Return the name of the discriminator property for this model class.
String getDiscriminatorPropertyName() {
return discriminatorName;
}
// Return the discriminator value or null if the discriminator is not
// present in the payload.
String getDiscriminatorValue(JsonNode node) {
// Determine the value of the discriminator property in the input data.
if (discriminatorName != null) {
// Get the value of the discriminator property, if present in the input payload.
node = node.get(discriminatorName);
if (node != null && node.isValueNode()) {
String discrValue = node.asText();
if (discrValue != null) {
return discrValue;
}
}
}
return null;
}
/**
* Returns the target model class that should be used to deserialize the input data.
* This function can be invoked for anyOf/oneOf composed models with discriminator mappings.
* The discriminator mappings are used to determine the target model class.
*
* @param node The input data.
* @param visitedClasses The set of classes that have already been visited.
*/
Class getClassForElement(JsonNode node, Set<Class> visitedClasses) {
if (visitedClasses.contains(modelClass)) {
// Class has already been visited.
return null;
}
// Determine the value of the discriminator property in the input data.
String discrValue = getDiscriminatorValue(node);
if (discrValue == null) {
return null;
}
Class cls = discriminatorMappings.get(discrValue);
// It may not be sufficient to return this cls directly because that target class
// may itself be a composed schema, possibly with its own discriminator.
visitedClasses.add(modelClass);
for (Class childClass : discriminatorMappings.values()) {
ClassDiscriminatorMapping childCdm = modelDiscriminators.get(childClass);
if (childCdm == null) {
continue;
}
if (!discriminatorName.equals(childCdm.discriminatorName)) {
discrValue = getDiscriminatorValue(node);
if (discrValue == null) {
continue;
}
}
if (childCdm != null) {
// Recursively traverse the discriminator mappings.
Class childDiscr = childCdm.getClassForElement(node, visitedClasses);
if (childDiscr != null) {
return childDiscr;
}
}
}
return cls;
}
}
/**
* Returns true if inst is an instance of modelClass in the OpenAPI model hierarchy.
*
* The Java class hierarchy is not implemented the same way as the OpenAPI model hierarchy,
* so it's not possible to use the instanceof keyword.
*
* @param modelClass A OpenAPI model class.
* @param inst The instance object.
*/
public static boolean isInstanceOf(Class modelClass, Object inst, Set<Class> visitedClasses) {
if (modelClass.isInstance(inst)) {
// This handles the 'allOf' use case with single parent inheritance.
return true;
}
if (visitedClasses.contains(modelClass)) {
// This is to prevent infinite recursion when the composed schemas have
// a circular dependency.
return false;
}
visitedClasses.add(modelClass);
// Traverse the oneOf/anyOf composed schemas.
Map<String, GenericType> descendants = modelDescendants.get(modelClass);
if (descendants != null) {
for (GenericType childType : descendants.values()) {
if (isInstanceOf(childType.getRawType(), inst, visitedClasses)) {
return true;
}
}
}
return false;
}
/**
* A map of discriminators for all model classes.
*/
private static Map<Class, ClassDiscriminatorMapping> modelDiscriminators = new HashMap<Class, ClassDiscriminatorMapping>();
/**
* A map of oneOf/anyOf descendants for each model class.
*/
private static Map<Class, Map<String, GenericType>> modelDescendants = new HashMap<Class, Map<String, GenericType>>();
/**
* Register a model class discriminator.
*
* @param modelClass the model class
* @param discriminatorPropertyName the name of the discriminator property
* @param mappings a map with the discriminator mappings.
*/
public static void registerDiscriminator(Class modelClass, String discriminatorPropertyName, Map<String, Class> mappings) {
ClassDiscriminatorMapping m = new ClassDiscriminatorMapping(modelClass, discriminatorPropertyName, mappings);
modelDiscriminators.put(modelClass, m);
}
/**
* Register the oneOf/anyOf descendants of the modelClass.
*
* @param modelClass the model class
* @param descendants a map of oneOf/anyOf descendants.
*/
public static void registerDescendants(Class modelClass, Map<String, GenericType> descendants) {
modelDescendants.put(modelClass, descendants);
}
private static JSON json;
static
{
json = new JSON();
}
/**
* Get the default JSON instance.
*
* @return the default JSON instance
*/
public static JSON getDefault() {
return json;
}
/**
* Set the default JSON instance.
*
* @param json JSON instance to be used
*/
public static void setDefault(JSON json) {
JSON.json = json;
}
}

View File

@@ -0,0 +1,39 @@
{{#additionalPropertiesType}}
/**
* A container for additional, undeclared properties.
* This is a holder for any undeclared properties as specified with
* the 'additionalProperties' keyword in the OAS document.
*/
private Map<String, {{{.}}}> additionalProperties;
/**
* Set the additional (undeclared) property with the specified name and value.
* If the property does not already exist, create it otherwise replace it.
*/
@JsonAnySetter
public {{classname}} putAdditionalProperty(String key, {{{.}}} value) {
if (this.additionalProperties == null) {
this.additionalProperties = new HashMap<String, {{{.}}}>();
}
this.additionalProperties.put(key, value);
return this;
}
/**
* Return the additional (undeclared) property.
*/
@JsonAnyGetter
public Map<String, {{{.}}}> getAdditionalProperties() {
return additionalProperties;
}
/**
* Return the additional (undeclared) property with the specified name.
*/
public {{{.}}} getAdditionalProperty(String key) {
if (this.additionalProperties == null) {
return null;
}
return this.additionalProperties.get(key);
}
{{/additionalPropertiesType}}

View File

@@ -4,21 +4,44 @@ import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;
import java.util.HashSet;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.openapitools.client.JSON;
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}}
@JsonDeserialize(using={{classname}}.{{classname}}Deserializer.class)
@JsonSerialize(using = {{classname}}.{{classname}}Serializer.class)
public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}}, {{{.}}}{{/vendorExtensions.x-implements}} {
private static final Logger log = Logger.getLogger({{classname}}.class.getName());
public static class {{classname}}Serializer extends StdSerializer<{{classname}}> {
public {{classname}}Serializer(Class<{{classname}}> t) {
super(t);
}
public {{classname}}Serializer() {
this(null);
}
@Override
public void serialize({{classname}} value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeObject(value.getActualInstance());
}
}
public static class {{classname}}Deserializer extends StdDeserializer<{{classname}}> {
public {{classname}}Deserializer() {
this({{classname}}.class);
@@ -33,6 +56,18 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
JsonNode tree = jp.readValueAsTree();
Object deserialized = null;
{{#discriminator}}
Class cls = JSON.getClassForElement(tree, {{classname}}.class);
if (cls != null) {
// When the OAS schema includes a discriminator, use the discriminator value to
// discriminate the anyOf schemas.
// Get the discriminator mapping value to get the class.
deserialized = tree.traverse(jp.getCodec()).readValueAs(cls);
{{classname}} ret = new {{classname}}();
ret.setActualInstance(deserialized);
return ret;
}
{{/discriminator}}
{{#anyOf}}
// deserialzie {{{.}}}
try {
@@ -48,6 +83,41 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
{{/anyOf}}
throw new IOException(String.format("Failed deserialization for {{classname}}: no match found"));
}
{{#additionalPropertiesType}}
/**
* Method called to deal with a property that did not map to a known Bean property.
* Method can deal with the problem as it sees fit (ignore, throw exception); but if it does return,
* it has to skip the matching Json content parser has.
*
* @param p - Parser that points to value of the unknown property
* @param ctxt - Context for deserialization; allows access to the parser, error reporting functionality
* @param instanceOrClass - Instance that is being populated by this deserializer, or if not known, Class that would be instantiated. If null, will assume type is what getValueClass() returns.
* @param propName - Name of the property that cannot be mapped
*/
@Override
protected void handleUnknownProperty(JsonParser p,
DeserializationContext ctxt,
Object instanceOrClass,
String propName) throws IOException {
System.out.println("Deserializing unknown property " + propName);
{{{.}}} deserialized = p.readValueAs({{{.}}}.class);
additionalProperties.put(propName, deserialized);
}
{{/additionalPropertiesType}}
/**
* Handle deserialization of the 'null' value.
*/
@Override
public {{classname}} getNullValue(DeserializationContext ctxt) throws JsonMappingException {
{{#isNullable}}
return null;
{{/isNullable}}
{{^isNullable}}
throw new JsonMappingException("{{classname}} cannot be null");
{{/isNullable}}
}
}
// store a list of schema names defined in anyOf
@@ -56,7 +126,18 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
public {{classname}}() {
super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}});
}
{{> libraries/jersey2/additional_properties }}
{{#additionalPropertiesType}}
@Override
public boolean equals(Object o) {
return super.equals(o) && Objects.equals(this.additionalProperties, o.additionalProperties)
}
@Override
public int hashCode() {
return Objects.hash(instance, isNullable, schemaType, additionalProperties);
}
{{/additionalPropertiesType}}
{{#anyOf}}
public {{classname}}({{{.}}} o) {
super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}});
@@ -69,6 +150,16 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
schemas.put("{{{.}}}", new GenericType<{{{.}}}>() {
});
{{/anyOf}}
JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas));
{{#discriminator}}
// Initialize and register the discriminator mappings.
Map<String, Class> mappings = new HashMap<String, Class>();
{{#mappedModels}}
mappings.put("{{mappingName}}", {{modelName}}.class);
{{/mappedModels}}
mappings.put("{{name}}", {{classname}}.class);
JSON.registerDiscriminator({{classname}}.class, "{{propertyBaseName}}", mappings);
{{/discriminator}}
}
@Override
@@ -78,15 +169,15 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
@Override
public void setActualInstance(Object instance) {
{{#isNulalble}}
{{#isNullable}}
if (instance == null) {
super.setActualInstance(instance);
return;
}
{{/isNulalble}}
{{/isNullable}}
{{#anyOf}}
if (instance instanceof {{{.}}}) {
if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet<Class>())) {
super.setActualInstance(instance);
return;
}

View File

@@ -167,7 +167,7 @@ public class {{classname}} {
{{/returnType}}
return apiClient.invokeAPI("{{classname}}.{{operationId}}", localVarPath, "{{httpMethod}}", localVarQueryParams, localVarPostBody,
localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType,
localVarAuthNames, {{#returnType}}localVarReturnType{{/returnType}}{{^returnType}}null{{/returnType}}, {{#vendorExtensions.x-java-return-type-one-of}}new {{{returnType}}}(){{/vendorExtensions.x-java-return-type-one-of}}{{^vendorExtensions.x-java-return-type-one-of}}{{#vendorExtensions.x-java-return-type-any-of}}new {{{returnType}}}(){{/vendorExtensions.x-java-return-type-any-of}}{{^vendorExtensions.x-java-return-type-any-of}}null{{/vendorExtensions.x-java-return-type-any-of}}{{/vendorExtensions.x-java-return-type-one-of}});
localVarAuthNames, {{#returnType}}localVarReturnType{{/returnType}}{{^returnType}}null{{/returnType}});
}
{{#vendorExtensions.x-group-parameters}}

View File

@@ -6,9 +6,10 @@ import {{invokerPackage}}.*;
import {{invokerPackage}}.auth.*;
{{#imports}}import {{import}};
{{/imports}}
import org.junit.Test;
import org.junit.Ignore;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
{{^fullJavaUtil}}
import java.util.ArrayList;

View File

@@ -6,9 +6,21 @@ package {{package}};
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
{{/useReflectionEqualsHashCode}}
{{#models}}
{{#model}}
{{#additionalPropertiesType}}
import java.util.Map;
import java.util.HashMap;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
{{/additionalPropertiesType}}
{{/model}}
{{/models}}
{{^supportJava6}}
import java.util.Objects;
import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;
{{/supportJava6}}
{{#supportJava6}}
import org.apache.commons.lang3.ObjectUtils;
@@ -39,9 +51,16 @@ import javax.validation.Valid;
{{#performBeanValidation}}
import org.hibernate.validator.constraints.*;
{{/performBeanValidation}}
import {{invokerPackage}}.JSON;
{{#models}}
{{#model}}
{{#oneOf}}
{{#-first}}
import com.fasterxml.jackson.core.type.TypeReference;
{{/-first}}
{{/oneOf}}
{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#oneOf}}{{#-first}}{{>oneof_model}}{{/-first}}{{/oneOf}}{{^oneOf}}{{#anyOf}}{{#-first}}{{>anyof_model}}{{/-first}}{{/anyOf}}{{^anyOf}}{{>pojo}}{{/anyOf}}{{/oneOf}}{{/isEnum}}
{{/model}}
{{/models}}

View File

@@ -4,21 +4,44 @@ import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;
import java.util.HashSet;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.openapitools.client.JSON;
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}}
@JsonDeserialize(using={{classname}}.{{classname}}Deserializer.class)
@JsonDeserialize(using = {{classname}}.{{classname}}Deserializer.class)
@JsonSerialize(using = {{classname}}.{{classname}}Serializer.class)
public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}}, {{{.}}}{{/vendorExtensions.x-implements}} {
private static final Logger log = Logger.getLogger({{classname}}.class.getName());
public static class {{classname}}Serializer extends StdSerializer<{{classname}}> {
public {{classname}}Serializer(Class<{{classname}}> t) {
super(t);
}
public {{classname}}Serializer() {
this(null);
}
@Override
public void serialize({{classname}} value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeObject(value.getActualInstance());
}
}
public static class {{classname}}Deserializer extends StdDeserializer<{{classname}}> {
public {{classname}}Deserializer() {
this({{classname}}.class);
@@ -31,13 +54,33 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
@Override
public {{classname}} deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode tree = jp.readValueAsTree();
int match = 0;
Object deserialized = null;
{{#useOneOfDiscriminatorLookup}}
{{#discriminator}}
{{classname}} new{{classname}} = new {{classname}}();
Map<String,Object> result2 = tree.traverse(jp.getCodec()).readValueAs(new TypeReference<Map<String, Object>>() {});
String discriminatorValue = (String)result2.get("{{{propertyBaseName}}}");
switch (discriminatorValue) {
{{#mappedModels}}
case "{{{mappingName}}}":
deserialized = tree.traverse(jp.getCodec()).readValueAs({{{modelName}}}.class);
new{{classname}}.setActualInstance(deserialized);
return new{{classname}};
{{/mappedModels}}
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue));
}
{{/discriminator}}
{{/useOneOfDiscriminatorLookup}}
int match = 0;
{{#oneOf}}
// deserialize {{{.}}}
try {
deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class);
// TODO: there is no validation against JSON schema constraints
// (min, max, enum, pattern...), this does not perform a strict JSON
// validation, which means the 'match' count may be higher than it should be.
match++;
log.log(Level.FINER, "Input data matches schema '{{{.}}}'");
} catch (Exception e) {
@@ -53,6 +96,41 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
}
throw new IOException(String.format("Failed deserialization for {{classname}}: %d classes match result, expected 1", match));
}
{{#additionalPropertiesType}}
/**
* Method called to deal with a property that did not map to a known Bean property.
* Method can deal with the problem as it sees fit (ignore, throw exception); but if it does return,
* it has to skip the matching Json content parser has.
*
* @param p - Parser that points to value of the unknown property
* @param ctxt - Context for deserialization; allows access to the parser, error reporting functionality
* @param instanceOrClass - Instance that is being populated by this deserializer, or if not known, Class that would be instantiated. If null, will assume type is what getValueClass() returns.
* @param propName - Name of the property that cannot be mapped
*/
@Override
protected void handleUnknownProperty(JsonParser p,
DeserializationContext ctxt,
Object instanceOrClass,
String propName) throws IOException {
System.out.println("Deserializing unknown property " + propName);
{{{.}}} deserialized = p.readValueAs({{{.}}}.class);
additionalProperties.put(propName, deserialized);
}
{{/additionalPropertiesType}}
/**
* Handle deserialization of the 'null' value.
*/
@Override
public {{classname}} getNullValue(DeserializationContext ctxt) throws JsonMappingException {
{{#isNullable}}
return null;
{{/isNullable}}
{{^isNullable}}
throw new JsonMappingException("{{classname}} cannot be null");
{{/isNullable}}
}
}
// store a list of schema names defined in oneOf
@@ -61,7 +139,18 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
public {{classname}}() {
super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}});
}
{{> libraries/jersey2/additional_properties }}
{{#additionalPropertiesType}}
@Override
public boolean equals(Object o) {
return super.equals(o) && Objects.equals(this.additionalProperties, o.additionalProperties)
}
@Override
public int hashCode() {
return Objects.hash(instance, isNullable, schemaType, additionalProperties);
}
{{/additionalPropertiesType}}
{{#oneOf}}
public {{classname}}({{{.}}} o) {
super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}});
@@ -74,6 +163,16 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
schemas.put("{{{.}}}", new GenericType<{{{.}}}>() {
});
{{/oneOf}}
JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas));
{{#discriminator}}
// Initialize and register the discriminator mappings.
Map<String, Class> mappings = new HashMap<String, Class>();
{{#mappedModels}}
mappings.put("{{mappingName}}", {{modelName}}.class);
{{/mappedModels}}
mappings.put("{{name}}", {{classname}}.class);
JSON.registerDiscriminator({{classname}}.class, "{{propertyBaseName}}", mappings);
{{/discriminator}}
}
@Override
@@ -81,17 +180,24 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
return {{classname}}.schemas;
}
/**
* Set the instance that matches the oneOf child schema, check
* the instance parameter is valid against the oneOf child schemas.
*
* It could be an instance of the 'oneOf' schemas.
* The oneOf child schemas may themselves be a composed schema (allOf, anyOf, oneOf).
*/
@Override
public void setActualInstance(Object instance) {
{{#isNulalble}}
{{#isNullable}}
if (instance == null) {
super.setActualInstance(instance);
return;
}
{{/isNulalble}}
{{/isNullable}}
{{#oneOf}}
if (instance instanceof {{{.}}}) {
if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet<Class>())) {
super.setActualInstance(instance);
return;
}

View File

@@ -99,8 +99,12 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
{{^isReadOnly}}
public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) {
{{#vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});{{/vendorExtensions.x-is-jackson-optional-nullable}}
{{^vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = {{name}};{{/vendorExtensions.x-is-jackson-optional-nullable}}
{{#vendorExtensions.x-is-jackson-optional-nullable}}
this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});
{{/vendorExtensions.x-is-jackson-optional-nullable}}
{{^vendorExtensions.x-is-jackson-optional-nullable}}
this.{{name}} = {{name}};
{{/vendorExtensions.x-is-jackson-optional-nullable}}
return this;
}
{{#isListContainer}}
@@ -226,7 +230,7 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
{{/isReadOnly}}
{{/vars}}
{{>libraries/jersey2/additional_properties}}
{{^supportJava6}}
@Override
public boolean equals(java.lang.Object o) {
@@ -242,7 +246,8 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
}{{#hasVars}}
{{classname}} {{classVarName}} = ({{classname}}) o;
return {{#vars}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{#hasMore}} &&
{{/hasMore}}{{/vars}}{{#parent}} &&
{{/hasMore}}{{/vars}}{{#additionalPropertiesType}}&&
Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/additionalPropertiesType}}{{#parent}} &&
super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}}
return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}}
{{/useReflectionEqualsHashCode}}
@@ -254,7 +259,7 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
return HashCodeBuilder.reflectionHashCode(this);
{{/useReflectionEqualsHashCode}}
{{^useReflectionEqualsHashCode}}
return Objects.hash({{#vars}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{#hasMore}}, {{/hasMore}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}});
return Objects.hash({{#vars}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{#hasMore}}, {{/hasMore}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#additionalPropertiesType}}, additionalProperties{{/additionalPropertiesType}});
{{/useReflectionEqualsHashCode}}
}
@@ -292,6 +297,9 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
{{#vars}}
sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n");
{{/vars}}
{{#additionalPropertiesType}}
sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n");
{{/additionalPropertiesType}}
sb.append("}");
return sb.toString();
}
@@ -366,4 +374,15 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
}
};
{{/parcelableModel}}
{{#discriminator}}
static {
// Initialize and register the discriminator mappings.
Map<String, Class> mappings = new HashMap<String, Class>();
{{#mappedModels}}
mappings.put("{{mappingName}}", {{modelName}}.class);
{{/mappedModels}}
mappings.put("{{name}}", {{classname}}.class);
JSON.registerDiscriminator({{classname}}.class, "{{propertyBaseName}}", mappings);
}
{{/discriminator}}
}

View File

@@ -74,6 +74,7 @@
<argLine>-Xms512m -Xmx1500m</argLine>
<parallel>methods</parallel>
<threadCount>10</threadCount>
<trimStackTrace>false</trimStackTrace>
</configuration>
</plugin>
<plugin>