Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/javaPlayWithAsynchronousControllers #7705

Merged

Conversation

ignaciomolina
Copy link
Contributor

@ignaciomolina ignaciomolina commented Feb 21, 2018

PR checklist

  • Read the contribution guidelines.
  • Ran the shell script under ./bin/ to update Petstore sample so that CIs can verify the change. (For instance, only need to run ./bin/{LANG}-petstore.sh and ./bin/security/{LANG}-petstore.sh if updating the {LANG} (e.g. php, ruby, python, etc) code generator or {LANG} client's mustache templates). Windows batch files can be found in .\bin\windows\.
  • Filed the PR against the correct branch: 3.0.0 branch for changes related to OpenAPI spec 3.0. Default: master.
  • Copied the technical committee to review the pull request if your PR is targeting a particular programming language.

@bbdouglas @JFCote @sreeshas @jfiala @lukoyanov @cbornet @jeff9finger @tzimisce012

Description of the PR

  • Add property to enable the use of asynchronous classes in java
  • Add support for asynchronous controllers with play framework and java8 CompletionStage interface

@ignaciomolina ignaciomolina reopened this Feb 21, 2018
Copy link
Contributor

@JFCote JFCote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my comments. Did you make sure to generate all the other samples for Play Framework? Since we don't see any change here, it's difficult to say if it's because there is no changes or if you forgot to run them.

Anyway, thanks for this very nice PR!

throw new IllegalArgumentException("'body' parameter is required");
}

// controllerOnly false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I miss to regenerate samples after removing that testing comments 😅

apiKey = null;
}

// controllerOnly false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question

}
}

// controllerOnly false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question

}
}

// controllerOnly false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

etc...

@ignaciomolina
Copy link
Contributor Author

Sorry, I had forgotten to regenerate the samples for the other play framework scenarios, It's done now

Copy link
Contributor

@JFCote JFCote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure your changes don't modify the current samples since it should be 100% independent.

@@ -17,6 +17,7 @@
import swagger.SwaggerUtils;
import com.fasterxml.jackson.core.type.TypeReference;


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of new unnecessary blank line. Can you change the mustache to prevent that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the best case scenario, your changes would not create anything new in the old samples

Client obj = imp.testSpecialTags(body);
if (configuration.getBoolean("useOutputBeanValidation")) {
SwaggerUtils.validate(obj);
}
JsonNode result = mapper.valueToTree(obj);
return ok(result);
JsonNode result = mapper.valueToTree(obj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here the tabulation is not good anymore

@@ -13,7 +13,7 @@
@Override
public Client testSpecialTags(Client body) throws Exception {
//Do your magic!!!
return new Client();

return new Client();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tabulation not good anymore

@@ -11,6 +11,6 @@

@SuppressWarnings("RedundantThrows")
public interface AnotherFakeApiControllerImpInterface {
Client testSpecialTags(Client body) throws Exception;
Client testSpecialTags(Client body) throws Exception;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing a space

Boolean obj = imp.fakeOuterBooleanSerialize(body);
JsonNode result = mapper.valueToTree(obj);
return ok(result);
JsonNode result = mapper.valueToTree(obj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad tabulation

OuterComposite obj = imp.fakeOuterCompositeSerialize(body);
if (configuration.getBoolean("useOutputBeanValidation")) {
SwaggerUtils.validate(obj);
}
JsonNode result = mapper.valueToTree(obj);
return ok(result);
JsonNode result = mapper.valueToTree(obj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could continue on and on but like I said earlier, just make sure there is no changes in the previous samples and I will approve this PR. If you have trouble using the mustache file (it can be tricky), let me know exactly where you are struggling and I will try to help.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is an easy way to fix the tabulation issue without having to add each one of them manually? 😰

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it is generated, so if you fix the mustache file, it will be fix everywhere. Never edit manually the files in the "sample" folder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I meant in the mustache file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you only need to change it at one place. I don't have time right now to check it out but I'm sure you'll figure it out by trial and error :) If you are completely clueless, I could check it next week.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I fixed the other scenarios, but the async one still lacks tabulations 😕

@@ -15,22 +15,22 @@

@SuppressWarnings("RedundantThrows")
public interface FakeApiControllerImpInterface {
Boolean fakeOuterBooleanSerialize(Boolean body) throws Exception;
Boolean fakeOuterBooleanSerialize(Boolean body) throws Exception;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all missing a space

@@ -749,8 +749,8 @@
"type" : "array",
"items" : {
"type" : "string",
"default" : "$",
"enum" : [ ">", "$" ]
"enum" : [ ">", "$" ],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theses changes could be ignored, they are caused by the core that doesn't do some sorting and because of this, the order is totally random.

@JFCote
Copy link
Contributor

JFCote commented Feb 23, 2018

@ignaciomolina Nice job. I will take some time next week to help you fix the problem with the tabulation in the async sample. But for the rest, this look promising!

@JFCote
Copy link
Contributor

JFCote commented Mar 1, 2018

@ignaciomolina Sorry about the delay, I'm very busy right now. Will try to check it somewhere today or tomorrow but can't be sure. Maybe beginning of next week. Sorry about that :)

@JFCote
Copy link
Contributor

JFCote commented Mar 6, 2018

@ignaciomolina I'm checking this tomorrow morning! I didn't forget you! :)

@JFCote
Copy link
Contributor

JFCote commented Mar 7, 2018

@ignaciomolina
Here is the solution, please copy and replace you code, try generating and let me know!

newApi.mustache

package {{package}};

{{#imports}}import {{import}};
{{/imports}}

import play.mvc.Http;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.FileInputStream;
{{#useBeanValidation}}
import javax.validation.constraints.*;
{{/useBeanValidation}}
{{>generatedAnnotation}}
{{#operations}}
public class {{classname}}ControllerImp {{#useInterfaces}}implements {{classname}}ControllerImpInterface{{/useInterfaces}} {
{{#operation}}
    {{#useInterfaces}}@Override{{/useInterfaces}}
    public {{^returnType}}void{{/returnType}}{{#returnType}}{{#supportAsync}}CompletionStage<{{/supportAsync}}{{>returnTypesNoVoid}}{{#supportAsync}}>{{/supportAsync}}{{/returnType}} {{operationId}}({{#allParams}}{{>pathParams}}{{>queryParams}}{{>bodyParams}}{{>formParams}}{{>headerParams}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) {{#handleExceptions}}throws Exception{{/handleExceptions}} {
        //Do your magic!!!
        {{#returnType}}
        {{#supportAsync}}
        return CompletableFuture.supplyAsync(() -> {
        {{/supportAsync}}
        {{#isResponseFile}}
        return new FileInputStream("replace this");
        {{/isResponseFile}}
        {{^isResponseFile}}
        {{#supportAsync}}   {{/supportAsync}}return new {{>returnTypesNoVoidNoAbstract}}({{#vendorExtensions.missingReturnInfoIfNeeded}}{{vendorExtensions.missingReturnInfoIfNeeded}}{{/vendorExtensions.missingReturnInfoIfNeeded}});
        {{/isResponseFile}}
        {{#supportAsync}}
        });
        {{/supportAsync}}
        {{/returnType}}
    }

{{/operation}}
}
{{/operations}}

newApiController.mustache

package {{package}};

{{#imports}}import {{import}};
{{/imports}}

import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Http;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.inject.Inject;
import java.io.File;
{{^handleExceptions}}
import java.io.IOException;
{{/handleExceptions}}
import swagger.SwaggerUtils;
import com.fasterxml.jackson.core.type.TypeReference;
{{#supportAsync}}

import java.util.concurrent.CompletionStage;
import java.util.concurrent.CompletableFuture;
{{/supportAsync}}

{{#useBeanValidation}}
import javax.validation.constraints.*;
import play.Configuration;
{{/useBeanValidation}}

{{#wrapCalls}}
import swagger.SwaggerUtils.ApiAction;
{{/wrapCalls}}

{{>generatedAnnotation}}
{{#operations}}
public class {{classname}}Controller extends Controller {

    {{^controllerOnly}}
    private final {{classname}}ControllerImp{{#useInterfaces}}Interface{{/useInterfaces}} imp;
    {{/controllerOnly}}
    private final ObjectMapper mapper;
    {{#useBeanValidation}}
    private final Configuration configuration;
    {{/useBeanValidation}}

    @Inject
    private {{classname}}Controller({{#useBeanValidation}}Configuration configuration{{^controllerOnly}}, {{/controllerOnly}}{{/useBeanValidation}}{{^controllerOnly}}{{classname}}ControllerImp{{#useInterfaces}}Interface{{/useInterfaces}} imp{{/controllerOnly}}) {
        {{^controllerOnly}}
        this.imp = imp;
        {{/controllerOnly}}
        mapper = new ObjectMapper();
        {{#useBeanValidation}}
        this.configuration = configuration;
        {{/useBeanValidation}}
    }

{{#operation}}

    {{#wrapCalls}}@ApiAction{{/wrapCalls}}
    public {{#supportAsync}}CompletionStage<{{/supportAsync}}Result{{#supportAsync}}>{{/supportAsync}} {{operationId}}({{#pathParams}}{{>pathParams}}{{#hasMore}},{{/hasMore}}{{/pathParams}}) {{^handleExceptions}}{{#bodyParams}}throws IOException{{/bodyParams}}{{/handleExceptions}}{{#handleExceptions}}throws Exception{{/handleExceptions}} {
        {{#bodyParams}}
        {{^collectionFormat}}
        JsonNode node{{paramName}} = request().body().asJson();
        {{{dataType}}} {{paramName}};
        if (node{{paramName}} != null) {
            {{paramName}} = mapper.readValue(node{{paramName}}.toString(), {{#isContainer}}new TypeReference<{{{dataType}}}>(){}{{/isContainer}}{{^isContainer}}{{{dataType}}}.class{{/isContainer}});
            {{#useBeanValidation}}
            if (configuration.getBoolean("useInputBeanValidation")) {
                {{#isListContainer}}
                for ({{{baseType}}} curItem : {{paramName}}) {
                    SwaggerUtils.validate(curItem);
                }
                {{/isListContainer}}
                {{#isMapContainer}}
                for (Map.Entry<String, {{{baseType}}}> entry : {{paramName}}.entrySet()) {
                    SwaggerUtils.validate(entry.getValue());
                }
                {{/isMapContainer}}
                {{^isContainer}}
                SwaggerUtils.validate({{paramName}});
                {{/isContainer}}
            }
            {{/useBeanValidation}}
        } else {
            {{#required}}
            throw new IllegalArgumentException("'{{baseName}}' parameter is required");
            {{/required}}
            {{^required}}
            {{paramName}} = null;
            {{/required}}
        }
        {{/collectionFormat}}
        {{/bodyParams}}
        {{#queryParams}}
        {{#collectionFormat}}
        String[] {{paramName}}Array = request().queryString().get("{{baseName}}");
        {{#required}}
        if ({{paramName}}Array == null) {
            throw new IllegalArgumentException("'{{baseName}}' parameter is required");
        }
        {{/required}}
        List<String> {{paramName}}List = SwaggerUtils.parametersToList("{{collectionFormat}}", {{paramName}}Array);
        {{{dataType}}} {{paramName}} = new Array{{{dataType}}}();
        for (String curParam : {{paramName}}List) {
            if (!curParam.isEmpty()) {
                //noinspection UseBulkOperation
                {{paramName}}.add({{>itemConversionBegin}}curParam{{>itemConversionEnd}});
            }
        }
        {{/collectionFormat}}
        {{^collectionFormat}}
        String value{{paramName}} = request().getQueryString("{{baseName}}");
        {{{dataType}}} {{paramName}};
        if (value{{paramName}} != null) {
            {{paramName}} = {{>conversionBegin}}value{{paramName}}{{>conversionEnd}};
        } else {
            {{#required}}
            throw new IllegalArgumentException("'{{baseName}}' parameter is required");
            {{/required}}
            {{^required}}
            {{paramName}} = {{>paramDefaultValue}};
            {{/required}}
        }
        {{/collectionFormat}}
        {{/queryParams}}
        {{#formParams}}
        {{^notFile}}
        {{{dataType}}} {{paramName}} = request().body().asMultipartFormData().getFile("{{baseName}}");
        {{#required}}
        if (({{paramName}} == null || ((File) {{paramName}}.getFile()).length() == 0)) {
            throw new IllegalArgumentException("'{{baseName}}' file cannot be empty");
        }
        {{/required}}
        {{/notFile}}
        {{#notFile}}
        {{#collectionFormat}}
        String[] {{paramName}}Array = request().body().asMultipartFormData().asFormUrlEncoded().get("{{baseName}}");
        {{#required}}
        if ({{paramName}}Array == null) {
            throw new IllegalArgumentException("'{{baseName}}' parameter is required");
        }
        {{/required}}
        List<String> {{paramName}}List = SwaggerUtils.parametersToList("{{collectionFormat}}", {{paramName}}Array);
        {{{dataType}}} {{paramName}} = new Array{{{dataType}}}();
        for (String curParam : {{paramName}}List) {
            if (!curParam.isEmpty()) {
                //noinspection UseBulkOperation
                {{paramName}}.add({{>itemConversionBegin}}curParam{{>itemConversionEnd}});
            }
        }
        {{/collectionFormat}}
        {{^collectionFormat}}
        String value{{paramName}} = (request().body().asMultipartFormData().asFormUrlEncoded().get("{{baseName}}"))[0];
        {{{dataType}}} {{paramName}};
        if (value{{paramName}} != null) {
            {{paramName}} = {{>conversionBegin}}value{{paramName}}{{>conversionEnd}};
        } else {
            {{#required}}
            throw new IllegalArgumentException("'{{baseName}}' parameter is required");
            {{/required}}
            {{^required}}
            {{paramName}} = {{>paramDefaultValue}};
            {{/required}}
        }
        {{/collectionFormat}}
        {{/notFile}}
        {{/formParams}}
        {{#headerParams}}
        {{#collectionFormat}}
        String[] {{paramName}}Array = request().headers().get("{{baseName}}");
        {{#required}}
        if ({{paramName}}Array == null) {
            throw new IllegalArgumentException("'{{baseName}}' parameter is required");
        }
        {{/required}}
        List<String> {{paramName}}List = SwaggerUtils.parametersToList("{{collectionFormat}}", {{paramName}}Array);
        {{{dataType}}} {{paramName}} = new Array{{{dataType}}}();
        for (String curParam : {{paramName}}List) {
            if (!curParam.isEmpty()) {
                //noinspection UseBulkOperation
                {{paramName}}.add({{>itemConversionBegin}}curParam{{>itemConversionEnd}});
            }
        }
        {{/collectionFormat}}
        {{^collectionFormat}}
        String value{{paramName}} = request().getHeader("{{baseName}}");
        {{{dataType}}} {{paramName}};
        if (value{{paramName}} != null) {
            {{paramName}} = {{>conversionBegin}}value{{paramName}}{{>conversionEnd}};
        } else {
            {{#required}}
            throw new IllegalArgumentException("'{{baseName}}' parameter is required");
            {{/required}}
            {{^required}}
            {{paramName}} = {{>paramDefaultValue}};
            {{/required}}
        }
        {{/collectionFormat}}
        {{/headerParams}}
        {{^controllerOnly}}
        {{^returnType}}
        {{#supportAsync}}
        return CompletableFuture.supplyAsync(() -> {
        {{/supportAsync}}
        {{/returnType}}
        {{#returnType}}{{#supportAsync}}CompletionStage<{{>returnTypesNoVoid}}> stage = {{/supportAsync}}{{^supportAsync}}{{>returnTypesNoVoid}} obj = {{/supportAsync}}{{/returnType}}{{^returnType}}{{#supportAsync}}    {{/supportAsync}}{{/returnType}}imp.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}{{#supportAsync}}.thenApply(obj -> { {{/supportAsync}}{{/returnType}}{{^supportAsync}};{{/supportAsync}}
        {{#returnType}}
        {{^isResponseFile}}
        {{^returnTypeIsPrimitive}}
        {{#useBeanValidation}}
        {{^supportAsync}}
        if (configuration.getBoolean("useOutputBeanValidation")) {
            {{#isListContainer}}
            for ({{{returnType}}} curItem : obj) {
                SwaggerUtils.validate(curItem);
            }
            {{/isListContainer}}
            {{#isMapContainer}}
            for (Map.Entry<String, {{{returnType}}}> entry : obj.entrySet()) {
                SwaggerUtils.validate(entry.getValue());
            }
            {{/isMapContainer}}
            {{^returnContainer}}
            SwaggerUtils.validate(obj);
            {{/returnContainer}}
        }
        {{/supportAsync}}
        {{#supportAsync}}
            if (configuration.getBoolean("useOutputBeanValidation")) {
                {{#isListContainer}}
                for ({{{returnType}}} curItem : obj) {
                    SwaggerUtils.validate(curItem);
                }
                {{/isListContainer}}
                {{#isMapContainer}}
                for (Map.Entry<String, {{{returnType}}}> entry : obj.entrySet()) {
                    SwaggerUtils.validate(entry.getValue());
                }
                {{/isMapContainer}}
                {{^returnContainer}}
                SwaggerUtils.validate(obj);
                {{/returnContainer}}
            }
        {{/supportAsync}}
        {{/useBeanValidation}}
        {{/returnTypeIsPrimitive}}
        {{/isResponseFile}}
        {{#supportAsync}}
            return obj;
        });
        {{/supportAsync}}
        {{/returnType}}
        {{#returnType}}
        {{#supportAsync}}
        stage.thenApply(obj -> {
        {{/supportAsync}}
        {{^isResponseFile}}
        {{#supportAsync}}    {{/supportAsync}}JsonNode result = mapper.valueToTree(obj);
        {{#supportAsync}}    {{/supportAsync}}return ok(result);
        {{/isResponseFile}}
        {{#isResponseFile}}
        {{#supportAsync}}    {{/supportAsync}}return ok(obj);
        {{/isResponseFile}}
        {{/returnType}}
        {{^returnType}}
        {{#supportAsync}}    {{/supportAsync}}return ok();
        {{/returnType}}
        {{#supportAsync}}
        });
        {{/supportAsync}}
        {{/controllerOnly}}
        {{#controllerOnly}}
        return ok();
        {{/controllerOnly}}
    }
{{/operation}}
}
{{/operations}}

@ignaciomolina
Copy link
Contributor Author

Ok, I already add the changes and regenerate the samples, I am not fun of duplicating the validation block just to add tabulation though

@JFCote
Copy link
Contributor

JFCote commented Mar 7, 2018

I am not fun of duplicating the validation block just to add tabulation though
Yeah me too. If someone else has a better way to do it, I'm listening.
But it does the work!

@@ -215,21 +226,50 @@ public class {{classname}}Controller extends Controller {
SwaggerUtils.validate(obj);
{{/returnContainer}}
}
{{/supportAsync}}
{{#supportAsync}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 I helped @ignaciomolina with his PR by suggesting this solution. To have good tabulation, we need to duplicate stuff in the mustache file (look here for the bean validation).
Any idea to do it more cleanly? Otherwise, this PR looks good to me!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JFCote agreed with you that some files or code (refactored into mustache partial) can be reused between different generators. I've some ideas but would require moving files around (which is not an elegant solution).

Copy link
Contributor

@JFCote JFCote Mar 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 I'm not talking about moving stuff between generators, I'm talking about duplicated stuff in this mustache file. The line that is highlighted. There is the version with supportAsync without tabulation and the version without supportAsync that doesn't have any tabulation. The code is duplicated.

I was wondering if there was a better way to do that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JFCote ah ok. Just went through the mustache file you provided.

What about creating another file newApiControllerAsync.mustache instead of using {{#supportAsync}} .. {{/supportAsync}} in newApiController.mustache? The drawback is that we must remember applying the same enhancement/bug fixes to both files.

Copy link
Contributor Author

@ignaciomolina ignaciomolina Mar 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's even worse than duplicate a chunk of code xD
Maybe the best solution was the first, to just add {{#supportAsync}} {{/supportAsync}} in front each line of the block 😓

@wing328 wing328 added this to the v2.4.0 milestone Mar 8, 2018
@JFCote
Copy link
Contributor

JFCote commented Mar 20, 2018

By the way @wing328 , this is ok for merging I think

@wing328 wing328 merged commit c91ce17 into swagger-api:master Apr 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants