Skip to content

Commit

Permalink
Add async native Java Client (#4721)
Browse files Browse the repository at this point in the history
* Add async native Java Client

* UPDATE: Reformat the samples

* test java native async in drone.io

* update test count

Co-authored-by: William Cheng <wing328hk@gmail.com>
  • Loading branch information
UkonnRa and wing328 committed May 3, 2020
1 parent ec0db2f commit f4fa00f
Show file tree
Hide file tree
Showing 190 changed files with 22,661 additions and 427 deletions.
1 change: 1 addition & 0 deletions CI/.drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ steps:
- ./mvnw --quiet verify -Psamples.droneio -Dorg.slf4j.simpleLogger.defaultLogLevel=error
# test java native client
- ./mvnw clean test -f samples/client/petstore/java/native/pom.xml
- ./mvnw clean test -f samples/client/petstore/java/native-async/pom.xml
# test all generators with fake petstore spec (2.0, 3.0)
- /bin/bash bin/utils/test-fake-petstore-for-all.sh
# generate test scripts
Expand Down
35 changes: 35 additions & 0 deletions bin/java-petstore-native-async.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/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 -t modules/openapi-generator/src/main/resources/Java/libraries/native -i modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -g java -c bin/java-petstore-native.json -o samples/client/petstore/java/native-async --additional-properties hideGenerationTimestamp=true,asyncNative=true $@"

echo "Removing files and folders under samples/client/petstore/java/httpclient/src/main"
rm -rf samples/client/petstore/java/native-async/src/main
find samples/client/petstore/java/native-async -maxdepth 1 -type f ! -name "README.md" -exec rm {} +
java $JAVA_OPTS -jar $executable $ags
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class JavaClientCodegen extends AbstractJavaCodegen
public static final String USE_PLAY_WS = "usePlayWS";
public static final String PLAY_VERSION = "playVersion";
public static final String FEIGN_VERSION = "feignVersion";
public static final String ASYNC_NATIVE = "asyncNative";
public static final String PARCELABLE_MODEL = "parcelableModel";
public static final String USE_RUNTIME_EXCEPTION = "useRuntimeException";
public static final String USE_REFLECTION_EQUALS_HASHCODE = "useReflectionEqualsHashCode";
Expand Down Expand Up @@ -95,6 +96,7 @@ public class JavaClientCodegen extends AbstractJavaCodegen
protected boolean usePlayWS = false;
protected String playVersion = PLAY_25;
protected String feignVersion = FEIGN_10;
protected boolean asyncNative = false;
protected boolean parcelableModel = false;
protected boolean useBeanValidation = false;
protected boolean performBeanValidation = false;
Expand Down Expand Up @@ -139,6 +141,7 @@ public JavaClientCodegen() {
cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION, "Perform BeanValidation"));
cliOptions.add(CliOption.newBoolean(USE_GZIP_FEATURE, "Send gzip-encoded requests"));
cliOptions.add(CliOption.newBoolean(USE_RUNTIME_EXCEPTION, "Use RuntimeException instead of Exception"));
cliOptions.add(CliOption.newBoolean(ASYNC_NATIVE, "If true, async handlers will be used, instead of the sync version"));
cliOptions.add(CliOption.newBoolean(FEIGN_VERSION, "Version of OpenFeign: '10.x' (default), '9.x' (deprecated)"));
cliOptions.add(CliOption.newBoolean(USE_REFLECTION_EQUALS_HASHCODE, "Use org.apache.commons.lang3.builder for equals and hashCode in the models. WARNING: This will fail under a security manager, unless the appropriate permissions are set up correctly and also there's potential performance impact."));
cliOptions.add(CliOption.newBoolean(CASE_INSENSITIVE_RESPONSE_HEADERS, "Make API response's headers case-insensitive. Available on " + OKHTTP_GSON + ", " + JERSEY2 + " libraries"));
Expand Down Expand Up @@ -249,6 +252,10 @@ public void processOpts() {
}
additionalProperties.put(FEIGN_VERSION, feignVersion);

if (additionalProperties.containsKey(ASYNC_NATIVE)) {
this.setAsyncNative(convertPropertyToBooleanAndWriteBack(ASYNC_NATIVE));
}

if (additionalProperties.containsKey(PARCELABLE_MODEL)) {
this.setParcelableModel(Boolean.valueOf(additionalProperties.get(PARCELABLE_MODEL).toString()));
}
Expand Down Expand Up @@ -850,6 +857,8 @@ public void setFeignVersion(String feignVersion) {
this.feignVersion = feignVersion;
}

public void setAsyncNative(boolean asyncNative) { this.asyncNative = asyncNative; }

public void setParcelableModel(boolean parcelableModel) {
this.parcelableModel = parcelableModel;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import java.util.List;
import java.util.Map;
{{/fullJavaUtil}}

{{#asyncNative}}
import java.util.concurrent.CompletableFuture;
{{/asyncNative}}

{{>generatedAnnotation}}
{{#operations}}
public class {{classname}} {
Expand Down Expand Up @@ -73,14 +77,19 @@ public class {{classname}} {
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) throws ApiException {
public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture<Void>{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}} ({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) throws ApiException {
{{#allParams}}
{{#required}}
{{#required}}
// verify the required parameter '{{paramName}}' is set
if ({{paramName}} == null) {
throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}");
{{#asyncNative}}
return CompletableFuture.failedFuture(new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"));
{{/asyncNative}}
{{^asyncNative}}
throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}");
{{/asyncNative}}
}
{{/required}}
{{/required}}
{{/allParams}}

HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder();
Expand Down Expand Up @@ -122,7 +131,14 @@ public class {{classname}} {
{{/bodyParam}}
localVarRequestBuilder.header("Accept", "application/json");

{{^asyncNative}}
try {
{{/asyncNative}}
{{#asyncNative}}
{{#bodyParam}}
try {
{{/bodyParam}}
{{/asyncNative}}
{{#bodyParam}}
byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes({{paramName}});
localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofByteArray(localVarPostBody));
Expand All @@ -136,29 +152,63 @@ public class {{classname}} {
if (memberVarInterceptor != null) {
memberVarInterceptor.accept(localVarRequestBuilder);
}

{{^asyncNative}}
HttpResponse<InputStream> localVarResponse = memberVarHttpClient.send(
localVarRequestBuilder.build(),
HttpResponse.BodyHandlers.ofInputStream());
localVarRequestBuilder.build(),
HttpResponse.BodyHandlers.ofInputStream());
if (memberVarResponseInterceptor != null) {
memberVarResponseInterceptor.accept(localVarResponse);
}
if (localVarResponse.statusCode()/ 100 != 2) {
throw new ApiException(localVarResponse.statusCode(),
"{{operationId}} call received non-success response",
localVarResponse.headers(),
localVarResponse.body() == null ? null : new String(localVarResponse.body().readAllBytes()));
throw new ApiException(localVarResponse.statusCode(),
"{{operationId}} call received non-success response",
localVarResponse.headers(),
localVarResponse.body() == null ? null : new String(localVarResponse.body().readAllBytes()));
}

{{#returnType}}
return memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<{{{returnType}}}>() {});
{{/returnType}}
{{/asyncNative}}
{{#asyncNative}}
return memberVarHttpClient.sendAsync(
localVarRequestBuilder.build(),
HttpResponse.BodyHandlers.ofString()).thenComposeAsync(localVarResponse -> {
if (localVarResponse.statusCode()/ 100 != 2) {
return CompletableFuture.failedFuture(new ApiException(localVarResponse.statusCode(),
"{{operationId}} call received non-success response",
localVarResponse.headers(),
localVarResponse.body())
);
} else {
return CompletableFuture.completedFuture(
{{#returnValue}}
memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<{{{returnType}}}>() {})
{{/returnValue}}
{{^returnValue}}
null
{{/returnValue}}
);
}
});
{{/asyncNative}}
{{#asyncNative}}
{{#bodyParam}}
} catch (IOException e) {
return CompletableFuture.failedFuture(new ApiException(e));
}
{{/bodyParam}}
{{/asyncNative}}
{{^asyncNative}}
} catch (IOException e) {
throw new ApiException(e);
} catch (InterruptedException e) {
}
{{/asyncNative}}
{{^asyncNative}}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ApiException(e);
}
{{/asyncNative}}
}
{{/operation}}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,45 @@ public void testJdkHttpClient() throws Exception {
assertTrue(apiClientContent.contains("import java.net.http.HttpRequest;"));
}

@Test
public void testJdkHttpAsyncClient() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put(JavaClientCodegen.JAVA8_MODE, true);
properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
properties.put(JavaClientCodegen.ASYNC_NATIVE, true);

File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("java")
.setLibrary(JavaClientCodegen.NATIVE)
.setAdditionalProperties(properties)
.setInputSpec("src/test/resources/3_0/pingSomeObj.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));

final ClientOptInput clientOptInput = configurator.toClientOptInput();
MockDefaultGenerator generator = new MockDefaultGenerator();
generator.opts(clientOptInput).generate();

Map<String, String> generatedFiles = generator.getFiles();
Assert.assertEquals(generatedFiles.size(), 29);
validateJavaSourceFiles(generatedFiles);
String defaultApiFilename = new File(output, "src/main/java/xyz/abcdef/api/PingApi.java").getAbsolutePath().replace("\\", "/");
String defaultApiContent = generatedFiles.get(defaultApiFilename);
assertTrue(defaultApiContent.contains("public class PingApi"));
assertTrue(defaultApiContent.contains("import java.net.http.HttpClient;"));
assertTrue(defaultApiContent.contains("import java.net.http.HttpRequest;"));
assertTrue(defaultApiContent.contains("import java.net.http.HttpResponse;"));
assertTrue(defaultApiContent.contains("import java.util.concurrent.CompletableFuture;"));

String apiClientFilename = new File(output, "src/main/java/xyz/abcdef/ApiClient.java").getAbsolutePath().replace("\\", "/");
String apiClientContent = generatedFiles.get(apiClientFilename);
assertTrue(apiClientContent.contains("public class ApiClient"));
assertTrue(apiClientContent.contains("import java.net.http.HttpClient;"));
assertTrue(apiClientContent.contains("import java.net.http.HttpRequest;"));
}

@Test
public void testReferencedHeader() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue855.yaml");
Expand Down
21 changes: 21 additions & 0 deletions samples/client/petstore/java/native-async/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# exclude jar for gradle wrapper
!gradle/wrapper/*.jar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# build files
**/target
target
.gradle
build
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4.3.1-SNAPSHOT
16 changes: 16 additions & 0 deletions samples/client/petstore/java/native-async/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# Generated by: https://openapi-generator.tech
#
language: java
jdk:
- oraclejdk11
before_install:
# ensure gradlew has proper permission
- chmod a+x ./gradlew
script:
# test using maven
- mvn test
# uncomment below to test using gradle
# - gradle test
# uncomment below to test using sbt
# - sbt test
Loading

0 comments on commit f4fa00f

Please sign in to comment.