Skip to content

Commit

Permalink
Add jersey2-experimental to Java client generator (OpenAPITools#6024)
Browse files Browse the repository at this point in the history
* add jersey2 experimental

* add new files

* add abstract one of class

* read the stream multiple times

* rename to getActualInstance

* update petstore

* fix jackon check

* test new java petstore in ci

* fix broken tests

* remove todo

* better exception message, primitive type handling

* add anyof support

* update samples

* add new files

* update all java client samples

* update doc, fix pom

* better null check for allOf

* add primitive types support in oneof, anyof

* better validation

* update python exp samples

* remove primitive type support in allOf
  • Loading branch information
wing328 authored and michaelpro1 committed May 7, 2020
1 parent 123957f commit 3f79428
Show file tree
Hide file tree
Showing 219 changed files with 27,324 additions and 30 deletions.
4 changes: 4 additions & 0 deletions bin/java-petstore-jersey2-experimental.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"library": "jersey2-experimental",
"artifactId": "petstore-jersey2-exp"
}
48 changes: 48 additions & 0 deletions bin/java-petstore-jersey2-experimental.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/bin/sh

SCRIPT="$0"
echo "# START SCRIPT: $SCRIPT"

while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done

if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi

executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"

if [ ! -f "$executable" ]
then
mvn -B clean package
fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -i modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -g java -c bin/java-petstore-jersey2-experimental.json -o samples/client/petstore/java/jersey2-experimental -t modules/openapi-generator/src/main/resources/Java --additional-properties hideGenerationTimestamp=true $@"

echo "Removing files and folders under samples/client/petstore/java/jersey2-experimental/src/main"
rm -rf samples/client/petstore/java/jersey2-experimental/src/main
find samples/client/petstore/java/jersey2-experimental -maxdepth 1 -type f ! -name "README.md" -exec rm {} +
java $JAVA_OPTS -jar $executable $ags

# copy additional manually written unit-tests
#mkdir samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client
#mkdir samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/auth
#mkdir samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/model

#cp CI/samples.ci/client/petstore/java/test-manual/common/StringUtilTest.java samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/StringUtilTest.java
#cp CI/samples.ci/client/petstore/java/test-manual/jersey2/ApiClientTest.java samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/ApiClientTest.java
#cp CI/samples.ci/client/petstore/java/test-manual/common/ConfigurationTest.java samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/ConfigurationTest.java
#cp CI/samples.ci/client/petstore/java/test-manual/jersey2/auth/ApiKeyAuthTest.java samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/auth/ApiKeyAuthTest.java
#cp CI/samples.ci/client/petstore/java/test-manual/jersey2/auth/HttpBasicAuthTest.java samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/auth/HttpBasicAuthTest.java
#cp CI/samples.ci/client/petstore/java/test-manual/jersey2/model/EnumValueTest.java samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/model/EnumValueTest.java
#cp CI/samples.ci/client/petstore/java/test-manual/jersey2/JSONTest.java samples/client/petstore/java/jersey2/src/test/java/org/openapitools/client/JSONTest.java
2 changes: 1 addition & 1 deletion docs/generators/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ sidebar_label: java
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |false|
|invokerPackage|root package for generated code| |org.openapitools.client|
|java8|Option. Use Java8 classes instead of third party equivalents|<dl><dt>**true**</dt><dd>Use Java 8 classes such as Base64</dd><dt>**false**</dt><dd>Various third party libraries as needed</dd></dl>|false|
|library|library template (sub-template) to use|<dl><dt>**jersey1**</dt><dd>HTTP client: Jersey client 1.19.x. JSON processing: Jackson 2.9.x. Enable Java6 support using '-DsupportJava6=true'. Enable gzip request encoding using '-DuseGzipFeature=true'. IMPORTANT NOTE: jersey 1.x is no longer actively maintained so please upgrade to 'jersey2' or other HTTP libaries instead.</dd><dt>**jersey2**</dt><dd>HTTP client: Jersey client 2.25.1. JSON processing: Jackson 2.9.x</dd><dt>**feign**</dt><dd>HTTP client: OpenFeign 9.x (deprecated) or 10.x (default). JSON processing: Jackson 2.9.x.</dd><dt>**okhttp-gson**</dt><dd>[DEFAULT] HTTP client: OkHttp 3.x. JSON processing: Gson 2.8.x. Enable Parcelable models on Android using '-DparcelableModel=true'. Enable gzip request encoding using '-DuseGzipFeature=true'.</dd><dt>**retrofit**</dt><dd>HTTP client: OkHttp 2.x. JSON processing: Gson 2.x (Retrofit 1.9.0). IMPORTANT NOTE: retrofit1.x is no longer actively maintained so please upgrade to 'retrofit2' instead.</dd><dt>**retrofit2**</dt><dd>HTTP client: OkHttp 3.x. JSON processing: Gson 2.x (Retrofit 2.3.0). Enable the RxJava adapter using '-DuseRxJava[2]=true'. (RxJava 1.x or 2.x)</dd><dt>**resttemplate**</dt><dd>HTTP client: Spring RestTemplate 4.x. JSON processing: Jackson 2.9.x</dd><dt>**webclient**</dt><dd>HTTP client: Spring WebClient 5.x. JSON processing: Jackson 2.9.x</dd><dt>**resteasy**</dt><dd>HTTP client: Resteasy client 3.x. JSON processing: Jackson 2.9.x</dd><dt>**vertx**</dt><dd>HTTP client: VertX client 3.x. JSON processing: Jackson 2.9.x</dd><dt>**google-api-client**</dt><dd>HTTP client: Google API client 1.x. JSON processing: Jackson 2.9.x</dd><dt>**rest-assured**</dt><dd>HTTP client: rest-assured : 4.x. JSON processing: Gson 2.x or Jackson 2.10.x. Only for Java 8</dd><dt>**native**</dt><dd>HTTP client: Java native HttpClient. JSON processing: Jackson 2.9.x. Only for Java11+</dd><dt>**microprofile**</dt><dd>HTTP client: Microprofile client X.x. JSON processing: Jackson 2.9.x</dd></dl>|okhttp-gson|
|library|library template (sub-template) to use|<dl><dt>**jersey1**</dt><dd>HTTP client: Jersey client 1.19.x. JSON processing: Jackson 2.9.x. Enable Java6 support using '-DsupportJava6=true'. Enable gzip request encoding using '-DuseGzipFeature=true'. IMPORTANT NOTE: jersey 1.x is no longer actively maintained so please upgrade to 'jersey2' or other HTTP libaries instead.</dd><dt>**jersey2**</dt><dd>HTTP client: Jersey client 2.25.1. JSON processing: Jackson 2.9.x</dd><dt>**jersey2-experimental**</dt><dd>HTTP client: Jersey client 2.25.1. JSON processing: Jackson 2.9.x</dd><dt>**feign**</dt><dd>HTTP client: OpenFeign 9.x (deprecated) or 10.x (default). JSON processing: Jackson 2.9.x.</dd><dt>**okhttp-gson**</dt><dd>[DEFAULT] HTTP client: OkHttp 3.x. JSON processing: Gson 2.8.x. Enable Parcelable models on Android using '-DparcelableModel=true'. Enable gzip request encoding using '-DuseGzipFeature=true'.</dd><dt>**retrofit**</dt><dd>HTTP client: OkHttp 2.x. JSON processing: Gson 2.x (Retrofit 1.9.0). IMPORTANT NOTE: retrofit1.x is no longer actively maintained so please upgrade to 'retrofit2' instead.</dd><dt>**retrofit2**</dt><dd>HTTP client: OkHttp 3.x. JSON processing: Gson 2.x (Retrofit 2.3.0). Enable the RxJava adapter using '-DuseRxJava[2]=true'. (RxJava 1.x or 2.x)</dd><dt>**resttemplate**</dt><dd>HTTP client: Spring RestTemplate 4.x. JSON processing: Jackson 2.9.x</dd><dt>**webclient**</dt><dd>HTTP client: Spring WebClient 5.x. JSON processing: Jackson 2.9.x</dd><dt>**resteasy**</dt><dd>HTTP client: Resteasy client 3.x. JSON processing: Jackson 2.9.x</dd><dt>**vertx**</dt><dd>HTTP client: VertX client 3.x. JSON processing: Jackson 2.9.x</dd><dt>**google-api-client**</dt><dd>HTTP client: Google API client 1.x. JSON processing: Jackson 2.9.x</dd><dt>**rest-assured**</dt><dd>HTTP client: rest-assured : 4.x. JSON processing: Gson 2.x or Jackson 2.10.x. Only for Java 8</dd><dt>**native**</dt><dd>HTTP client: Java native HttpClient. JSON processing: Jackson 2.9.x. Only for Java11+</dd><dt>**microprofile**</dt><dd>HTTP client: Microprofile client X.x. JSON processing: Jackson 2.9.x</dd></dl>|okhttp-gson|
|licenseName|The name of the license| |Unlicense|
|licenseUrl|The URL of the license| |http://unlicense.org|
|modelPackage|package for generated models| |org.openapitools.client.model|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1846,8 +1846,8 @@ public String toAnyOfName(List<String> names, ComposedSchema composedSchema) {
@SuppressWarnings("static-method")
public String toOneOfName(List<String> names, ComposedSchema composedSchema) {
Map<String, Object> exts = composedSchema.getExtensions();
if (exts != null && exts.containsKey("x-oneOf-name")) {
return (String) exts.get("x-oneOf-name");
if (exts != null && exts.containsKey("x-one-of-name")) {
return (String) exts.get("x-one-of-name");
}
return "oneOf<" + String.join(",", names) + ">";
}
Expand Down Expand Up @@ -2190,9 +2190,33 @@ public CodegenModel fromModel(String name, Schema schema) {
m.interfaces = new ArrayList<String>();

for (Schema interfaceSchema : interfaces) {
interfaceSchema = ModelUtils.unaliasSchema(this.openAPI, interfaceSchema, importMapping);

if (StringUtils.isBlank(interfaceSchema.get$ref())) {
// primitive type
String languageType = getTypeDeclaration(interfaceSchema);

if (composed.getAnyOf() != null) {
if (m.anyOf.contains(languageType)) {
LOGGER.warn("{} (anyOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
} else {
m.anyOf.add(languageType);
}
} else if (composed.getOneOf() != null) {
if (m.oneOf.contains(languageType)) {
LOGGER.warn("{} (oneOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
} else {
m.oneOf.add(languageType);
}
} else if (composed.getAllOf() != null) {
// no need to add primitive type to allOf, which should comprise of schemas (models) only
} else {
LOGGER.error("Composed schema has incorrect anyOf, allOf, oneOf defined: {}", composed);
}
continue;
}

// the rest of the section is for model
Schema refSchema = null;
String ref = ModelUtils.getSimpleRef(interfaceSchema.get$ref());
if (allDefinitions != null) {
Expand Down Expand Up @@ -2393,8 +2417,10 @@ protected void addProperties(Map<String, Schema> properties, List<String> requir
if (schema instanceof ComposedSchema) {
ComposedSchema composedSchema = (ComposedSchema) schema;

for (Schema component : composedSchema.getAllOf()) {
addProperties(properties, required, component);
if (composedSchema.getAllOf() != null) {
for (Schema component : composedSchema.getAllOf()) {
addProperties(properties, required, component);
}
}

if (schema.getRequired() != null) {
Expand Down Expand Up @@ -5788,14 +5814,14 @@ public void setRemoveEnumValuePrefix(final boolean removeEnumValuePrefix) {
//// Following methods are related to the "useOneOfInterfaces" feature

/**
* Add "x-oneOf-name" extension to a given oneOf schema (assuming it has at least 1 oneOf elements)
* Add "x-one-of-name" extension to a given oneOf schema (assuming it has at least 1 oneOf elements)
*
* @param s schema to add the extension to
* @param name name of the parent oneOf schema
*/
public void addOneOfNameExtension(ComposedSchema s, String name) {
if (s.getOneOf() != null && s.getOneOf().size() > 0) {
s.addExtension("x-oneOf-name", name);
s.addExtension("x-one-of-name", name);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public class JavaClientCodegen extends AbstractJavaCodegen
public static final String GOOGLE_API_CLIENT = "google-api-client";
public static final String JERSEY1 = "jersey1";
public static final String JERSEY2 = "jersey2";
public static final String JERSEY2_EXPERIMENTAL = "jersey2-experimental";
public static final String NATIVE = "native";
public static final String OKHTTP_GSON = "okhttp-gson";
public static final String RESTEASY = "resteasy";
Expand Down Expand Up @@ -144,6 +145,7 @@ public JavaClientCodegen() {

supportedLibraries.put(JERSEY1, "HTTP client: Jersey client 1.19.x. JSON processing: Jackson 2.9.x. Enable Java6 support using '-DsupportJava6=true'. Enable gzip request encoding using '-DuseGzipFeature=true'. IMPORTANT NOTE: jersey 1.x is no longer actively maintained so please upgrade to 'jersey2' or other HTTP libaries instead.");
supportedLibraries.put(JERSEY2, "HTTP client: Jersey client 2.25.1. JSON processing: Jackson 2.9.x");
supportedLibraries.put(JERSEY2_EXPERIMENTAL, "HTTP client: Jersey client 2.25.1. JSON processing: Jackson 2.9.x");
supportedLibraries.put(FEIGN, "HTTP client: OpenFeign 9.x (deprecated) or 10.x (default). JSON processing: Jackson 2.9.x.");
supportedLibraries.put(OKHTTP_GSON, "[DEFAULT] HTTP client: OkHttp 3.x. JSON processing: Gson 2.8.x. Enable Parcelable models on Android using '-DparcelableModel=true'. Enable gzip request encoding using '-DuseGzipFeature=true'.");
supportedLibraries.put(RETROFIT_1, "HTTP client: OkHttp 2.x. JSON processing: Gson 2.x (Retrofit 1.9.0). IMPORTANT NOTE: retrofit1.x is no longer actively maintained so please upgrade to 'retrofit2' instead.");
Expand Down Expand Up @@ -367,9 +369,12 @@ public void processOpts() {
if ("retrofit2".equals(getLibrary()) && !usePlayWS) {
supportingFiles.add(new SupportingFile("JSON.mustache", invokerFolder, "JSON.java"));
}
} else if (JERSEY2.equals(getLibrary())) {
} else if (JERSEY2.equals(getLibrary()) || JERSEY2_EXPERIMENTAL.equals(getLibrary())) {
supportingFiles.add(new SupportingFile("JSON.mustache", invokerFolder, "JSON.java"));
supportingFiles.add(new SupportingFile("ApiResponse.mustache", invokerFolder, "ApiResponse.java"));
if (JERSEY2_EXPERIMENTAL.equals(getLibrary())) {
supportingFiles.add(new SupportingFile("AbstractOpenApiSchema.mustache", (sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar)).replace('/', File.separatorChar), "AbstractOpenApiSchema.java"));
}
forceSerializationLibrary(SERIALIZATION_LIBRARY_JACKSON);
} else if (NATIVE.equals(getLibrary())) {
setJava8Mode(true);
Expand Down Expand Up @@ -499,7 +504,7 @@ public void processOpts() {
additionalProperties.remove(SERIALIZATION_LIBRARY_GSON);
}

if (additionalProperties.containsKey(SERIALIZATION_LIBRARY_JACKSON)) {
if (additionalProperties.containsKey(SERIALIZATION_LIBRARY_JACKSON) && !JERSEY2_EXPERIMENTAL.equals(getLibrary())) {
useOneOfInterfaces = true;
addOneOfInterfaceImports = true;
}
Expand Down Expand Up @@ -595,6 +600,39 @@ public int compare(CodegenParameter one, CodegenParameter another) {
objs = AbstractJavaJAXRSServerCodegen.jaxrsPostProcessOperations(objs);
}

if (JERSEY2_EXPERIMENTAL.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;
}

Expand Down Expand Up @@ -769,12 +807,16 @@ public Map<String, Object> postProcessModels(Map<String, Object> objs) {
CodegenModel cm = (CodegenModel) mo.get("model");
cm.getVendorExtensions().putIfAbsent("implements", new ArrayList<String>()); // TODO: 5.0 Remove
cm.getVendorExtensions().putIfAbsent("x-implements", cm.getVendorExtensions().get("implements"));
List<String> impl = (List<String>) cm.getVendorExtensions().get("implements");
//List<String> impl = (List<String>) cm.getVendorExtensions().get("x-implements");
if (JERSEY2_EXPERIMENTAL.equals(getLibrary())) {
cm.getVendorExtensions().put("x-implements", new ArrayList<String>());
}

if (this.parcelableModel) {
impl.add("Parcelable");
((ArrayList<String>) cm.getVendorExtensions().get("x-implements")).add("Parcelable");
}
if (this.serializableModel) {
impl.add("Serializable");
((ArrayList<String>) cm.getVendorExtensions().get("x-implements")).add("Serializable");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,45 @@ import {{invokerPackage}}.ApiException;
{{/imports}}
import org.junit.Test;
import org.junit.Ignore;
import org.junit.Assert;

{{^fullJavaUtil}}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
{{/fullJavaUtil}}

{{/fullJavaUtil}}
/**
* API tests for {{classname}}
*/
@Ignore
public class {{classname}}Test {
private final {{classname}} api = new {{classname}}();

{{#operations}}{{#operation}}
{{#operations}}
{{#operation}}
/**
{{#summary}}
* {{summary}}
*
{{/summary}}
{{#notes}}
* {{notes}}
*
{{/notes}}
* @throws ApiException
* if the Api call fails
*/
@Test
public void {{operationId}}Test() throws ApiException {
{{#allParams}}
{{{dataType}}} {{paramName}} = null;
{{/allParams}}
{{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
//{{#allParams}}
//{{{dataType}}} {{paramName}} = null;
//{{/allParams}}
//{{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});

// TODO: test validations
}
{{/operation}}{{/operations}}
{{/operation}}
{{/operations}}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{{>licenseInfo}}

package {{invokerPackage}}.model;

import org.openapitools.client.ApiException;
import java.lang.reflect.Type;
import java.util.Map;
import javax.ws.rs.core.GenericType;

{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}
public abstract class AbstractOpenApiSchema {
private Object instance;
public final String schemaType;
public AbstractOpenApiSchema(String schemaType) {
this.schemaType = schemaType;
}

public abstract Map<String, GenericType> getSchemas();

public Object getActualInstance() {return instance;}

public void setActualInstance(Object instance) {this.instance = instance;}

public String getSchemaType() {
return schemaType;
}
}
Loading

0 comments on commit 3f79428

Please sign in to comment.