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

[Spring] Add apiFirst option #184

Merged
merged 6 commits into from
Jun 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bin/ensure-up-to-date
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ sleep 5
if [ -n "$(git status --porcelain)" ]; then
echo "UNCOMMITTED CHANGES ERROR"
echo "There are uncommitted changes in working tree after execution of 'bin/ensure-up-to-date'"
echo "Perform git diff"
git --no-pager diff
echo "Perform git status"
git status
echo "Please run 'bin/ensure-up-to-date' locally and commit changes"
echo "Please run 'bin/ensure-up-to-date' locally and commit changes (UNCOMMITTED CHANGES ERROR)"
exit 1
else
echo "Git working tree is clean"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;

import org.apache.commons.lang3.tuple.Pair;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenModel;
Expand All @@ -41,12 +42,9 @@

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

public class SpringCodegen extends AbstractJavaCodegen
implements BeanValidationFeatures, OptionalFeatures {
Expand All @@ -69,6 +67,7 @@ public class SpringCodegen extends AbstractJavaCodegen
public static final String SPRING_CLOUD_LIBRARY = "spring-cloud";
public static final String IMPLICIT_HEADERS = "implicitHeaders";
public static final String OPENAPI_DOCKET_CONFIG = "swaggerDocketConfig";
public static final String API_FIRST = "apiFirst";

protected String title = "OpenAPI Spring";
protected String configPackage = "org.openapitools.configuration";
Expand All @@ -85,6 +84,7 @@ public class SpringCodegen extends AbstractJavaCodegen
protected boolean useBeanValidation = true;
protected boolean implicitHeaders = false;
protected boolean openapiDocketConfig = false;
protected boolean apiFirst = false;
protected boolean useOptional = false;

public SpringCodegen() {
Expand All @@ -103,18 +103,19 @@ public SpringCodegen() {
cliOptions.add(new CliOption(TITLE, "server title name or client service name"));
cliOptions.add(new CliOption(CONFIG_PACKAGE, "configuration package for generated code"));
cliOptions.add(new CliOption(BASE_PACKAGE, "base package (invokerPackage) for generated code"));
cliOptions.add(CliOption.newBoolean(INTERFACE_ONLY, "Whether to generate only API interface stubs without the server files.",interfaceOnly));
cliOptions.add(CliOption.newBoolean(DELEGATE_PATTERN, "Whether to generate the server files using the delegate pattern",delegatePattern));
cliOptions.add(CliOption.newBoolean(SINGLE_CONTENT_TYPES, "Whether to select only one produces/consumes content-type by operation.",singleContentTypes));
cliOptions.add(CliOption.newBoolean(JAVA_8, "use java8 default interface",java8));
cliOptions.add(CliOption.newBoolean(ASYNC, "use async Callable controllers",async));
cliOptions.add(CliOption.newBoolean(REACTIVE, "wrap responses in Mono/Flux Reactor types (spring-boot only)",reactive));
cliOptions.add(CliOption.newBoolean(INTERFACE_ONLY, "Whether to generate only API interface stubs without the server files.", interfaceOnly));
cliOptions.add(CliOption.newBoolean(DELEGATE_PATTERN, "Whether to generate the server files using the delegate pattern", delegatePattern));
cliOptions.add(CliOption.newBoolean(SINGLE_CONTENT_TYPES, "Whether to select only one produces/consumes content-type by operation.", singleContentTypes));
cliOptions.add(CliOption.newBoolean(JAVA_8, "use java8 default interface", java8));
cliOptions.add(CliOption.newBoolean(ASYNC, "use async Callable controllers", async));
cliOptions.add(CliOption.newBoolean(REACTIVE, "wrap responses in Mono/Flux Reactor types (spring-boot only)", reactive));
cliOptions.add(new CliOption(RESPONSE_WRAPPER, "wrap the responses in given type (Future,Callable,CompletableFuture,ListenableFuture,DeferredResult,HystrixCommand,RxObservable,RxSingle or fully qualified type)"));
cliOptions.add(CliOption.newBoolean(USE_TAGS, "use tags for creating interface and controller classnames",useTags));
cliOptions.add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations",useBeanValidation));
cliOptions.add(CliOption.newBoolean(IMPLICIT_HEADERS, "Use of @ApiImplicitParams for headers.",implicitHeaders));
cliOptions.add(CliOption.newBoolean(OPENAPI_DOCKET_CONFIG, "Generate Spring OpenAPI Docket configuration class.",openapiDocketConfig));
cliOptions.add(CliOption.newBoolean(USE_OPTIONAL,"Use Optional container for optional parameters",useOptional));
cliOptions.add(CliOption.newBoolean(USE_TAGS, "use tags for creating interface and controller classnames", useTags));
cliOptions.add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations", useBeanValidation));
cliOptions.add(CliOption.newBoolean(IMPLICIT_HEADERS, "Use of @ApiImplicitParams for headers.", implicitHeaders));
cliOptions.add(CliOption.newBoolean(OPENAPI_DOCKET_CONFIG, "Generate Spring OpenAPI Docket configuration class.", openapiDocketConfig));
cliOptions.add(CliOption.newBoolean(API_FIRST, "Generate the API from the OAI spec at server compile time (API first approach)", apiFirst));
cliOptions.add(CliOption.newBoolean(USE_OPTIONAL,"Use Optional container for optional parameters", useOptional));

supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application using the SpringFox integration.");
supportedLibraries.put(SPRING_MVC_LIBRARY, "Spring-MVC Server application using the SpringFox integration.");
Expand Down Expand Up @@ -145,9 +146,19 @@ public String getHelp() {
@Override
public void processOpts() {

List<Pair<String,String>> configOptions = additionalProperties.entrySet().stream()
.filter(e -> !Arrays.asList(API_FIRST, "hideGenerationTimestamp").contains(e.getKey()))
.filter(e -> cliOptions.stream().map(CliOption::getOpt).anyMatch(opt -> opt.equals(e.getKey())))
.map(e -> Pair.of(e.getKey(), e.getValue().toString()))
.collect(Collectors.toList());
additionalProperties.put("configOptions", configOptions);

// Process java8 option before common java ones to change the default dateLibrary to java8.
System.out.println("----------------------------------");
if (additionalProperties.containsKey(JAVA_8)) {
System.out.println("has JAVA8");
this.setJava8(Boolean.valueOf(additionalProperties.get(JAVA_8).toString()));
additionalProperties.put(JAVA_8, java8);
}
if (this.java8 && !additionalProperties.containsKey(DATE_LIBRARY)) {
setDateLibrary("java8");
Expand Down Expand Up @@ -232,6 +243,10 @@ public void processOpts() {
this.setOpenapiDocketConfig(Boolean.valueOf(additionalProperties.get(OPENAPI_DOCKET_CONFIG).toString()));
}

if (additionalProperties.containsKey(API_FIRST)) {
this.setApiFirst(Boolean.valueOf(additionalProperties.get(API_FIRST).toString()));
}

typeMapping.put("file", "Resource");
importMapping.put("Resource", "org.springframework.core.io.Resource");

Expand All @@ -255,16 +270,10 @@ public void processOpts() {

if (!this.interfaceOnly) {
if (library.equals(SPRING_BOOT)) {
if (!this.reactive) {
supportingFiles.add(new SupportingFile("homeController.mustache",
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "HomeController.java"));
}
supportingFiles.add(new SupportingFile("openapi2SpringBoot.mustache",
(sourceFolder + File.separator + basePackage).replace(".", java.io.File.separator), "OpenAPI2SpringBoot.java"));
supportingFiles.add(new SupportingFile("RFC3339DateFormat.mustache",
(sourceFolder + File.separator + basePackage).replace(".", java.io.File.separator), "RFC3339DateFormat.java"));
supportingFiles.add(new SupportingFile("application.mustache",
("src.main.resources").replace(".", java.io.File.separator), "application.properties"));
}
if (library.equals(SPRING_MVC_LIBRARY)) {
supportingFiles.add(new SupportingFile("webApplication.mustache",
Expand All @@ -275,8 +284,6 @@ public void processOpts() {
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "OpenAPIUiConfiguration.java"));
supportingFiles.add(new SupportingFile("RFC3339DateFormat.mustache",
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "RFC3339DateFormat.java"));
supportingFiles.add(new SupportingFile("application.properties",
("src.main.resources").replace(".", java.io.File.separator), "openapi.properties"));
}
if (library.equals(SPRING_CLOUD_LIBRARY)) {
supportingFiles.add(new SupportingFile("apiKeyRequestInterceptor.mustache",
Expand All @@ -290,20 +297,19 @@ public void processOpts() {
}
} else {
apiTemplateFiles.put("apiController.mustache", "Controller.java");
supportingFiles.add(new SupportingFile("apiException.mustache",
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiException.java"));
supportingFiles.add(new SupportingFile("apiResponseMessage.mustache",
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiResponseMessage.java"));
supportingFiles.add(new SupportingFile("notFoundException.mustache",
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "NotFoundException.java"));
if (!this.reactive) {
supportingFiles.add(new SupportingFile("apiOriginFilter.mustache",
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiOriginFilter.java"));
supportingFiles.add(new SupportingFile("application.mustache",
("src.main.resources").replace(".", java.io.File.separator), "application.properties"));
supportingFiles.add(new SupportingFile("homeController.mustache",
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "HomeController.java"));
if (!this.reactive && !this.apiFirst) {
supportingFiles.add(new SupportingFile("openapiDocumentationConfig.mustache",
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "OpenAPIDocumentationConfig.java"));
} else {
supportingFiles.add(new SupportingFile("openapi.mustache",
("src/main/resources").replace("/", java.io.File.separator), "openapi.yaml"));
}
}
} else if (this.openapiDocketConfig && !library.equals(SPRING_CLOUD_LIBRARY) && !this.reactive) {
} else if (this.openapiDocketConfig && !library.equals(SPRING_CLOUD_LIBRARY) && !this.reactive && !this.apiFirst) {
supportingFiles.add(new SupportingFile("openapiDocumentationConfig.mustache",
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "OpenAPIDocumentationConfig.java"));
}
Expand All @@ -313,6 +319,11 @@ public void processOpts() {
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiUtil.java"));
}

if (this.apiFirst) {
apiTemplateFiles.clear();
modelTemplateFiles.clear();
}

if ("threetenbp".equals(dateLibrary)) {
supportingFiles.add(new SupportingFile("customInstantDeserializer.mustache",
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "CustomInstantDeserializer.java"));
Expand Down Expand Up @@ -344,6 +355,11 @@ public void processOpts() {
additionalProperties.put(RESPONSE_WRAPPER, "Callable");
}

if(!this.apiFirst && !this.reactive) {
additionalProperties.put("useSpringfox", true);
}


// Some well-known Spring or Spring-Cloud response wrappers
switch (this.responseWrapper) {
case "Future":
Expand Down Expand Up @@ -565,6 +581,7 @@ private void removeHeadersFromAllParams(List<CodegenParameter> allParams) {

@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
generateYAMLSpecFile(objs);
if(library.equals(SPRING_CLOUD_LIBRARY)) {
List<CodegenSecurity> authMethods = (List<CodegenSecurity>) objs.get("authMethods");
if (authMethods != null) {
Expand Down Expand Up @@ -659,6 +676,10 @@ public void setOpenapiDocketConfig(boolean openapiDocketConfig) {
this.openapiDocketConfig = openapiDocketConfig;
}

public void setApiFirst(boolean apiFirst) {
this.apiFirst = apiFirst;
}

@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
super.postProcessModelProperty(model, property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.datatype.threetenbp.DateTimeUtils;
import com.fasterxml.jackson.datatype.threetenbp.DecimalUtils;
import com.fasterxml.jackson.datatype.threetenbp.deser.ThreeTenDateTimeDeserializerBase;
import com.fasterxml.jackson.datatype.threetenbp.function.BiFunction;
import com.fasterxml.jackson.datatype.threetenbp.function.Function;
import org.threeten.bp.DateTimeException;
import org.threeten.bp.DateTimeUtils;
import org.threeten.bp.Instant;
import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.ZoneId;
Expand Down Expand Up @@ -205,7 +205,7 @@ public class CustomInstantDeserializer<T extends Temporal>

private ZoneId getZone(DeserializationContext context) {
// Instants are always in UTC, so don't waste compute cycles
return (_valueClass == Instant.class) ? null : DateTimeUtils.timeZoneToZoneId(context.getTimeZone());
return (_valueClass == Instant.class) ? null : DateTimeUtils.toZoneId(context.getTimeZone());
}

private static class FromIntegerArguments {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package {{configPackage}};

{{^useSpringfox}}
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
{{/useSpringfox}}
import org.springframework.stereotype.Controller;
{{^useSpringfox}}
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.GetMapping;
{{/useSpringfox}}
import org.springframework.web.bind.annotation.RequestMapping;
{{^useSpringfox}}
import org.springframework.web.bind.annotation.ResponseBody;
{{/useSpringfox}}
{{#reactive}}
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
{{/reactive}}

{{^useSpringfox}}
import java.io.IOException;
import java.io.InputStream;
{{/useSpringfox}}
{{#reactive}}
import java.net.URI;
{{/reactive}}
{{^useSpringfox}}
import java.nio.charset.Charset;
{{/useSpringfox}}
{{#reactive}}

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
{{/reactive}}

/**
* Home redirection to OpenAPI api documentation
*/
@Controller
public class HomeController {

{{^useSpringfox}}
private static YAMLMapper yamlMapper = new YAMLMapper();

@Value("classpath:/openapi.yaml")
private Resource openapi;

@Bean
public String openapiContent() throws IOException {
try(InputStream is = openapi.getInputStream()) {
return StreamUtils.copyToString(is, Charset.defaultCharset());
}
}

@GetMapping(value = "/openapi.yaml", produces = "application/vnd.oai.openapi")
@ResponseBody
public String openapiYaml() throws IOException {
return openapiContent();
}

@GetMapping(value = "/openapi.json", produces = "application/json")
@ResponseBody
public Object openapiJson() throws IOException {
return yamlMapper.readValue(openapiContent(), Object.class);
}

{{/useSpringfox}}
{{#reactive}}
@Bean
RouterFunction<ServerResponse> index() {
return route(
GET("/"),
req -> ServerResponse.temporaryRedirect(URI.create("{{#useSpringfox}}swagger-ui.html{{/useSpringfox}}{{^useSpringfox}}swagger-ui/index.html?url=../openapi.json{{/useSpringfox}}")).build()
);
}
{{/reactive}}
{{^reactive}}
@RequestMapping("/")
public String index() {
return "redirect:{{#useSpringfox}}swagger-ui.html{{/useSpringfox}}{{^useSpringfox}}swagger-ui/index.html?url=../openapi.json{{/useSpringfox}}";
}
{{/reactive}}


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ This server was generated by the [OpenAPI Generator](https://openapi-generator.t
By using the [OpenAPI-Spec](https://openapis.org), you can easily generate a server stub.
This is an example of building a OpenAPI-enabled server in Java using the SpringBoot framework.

{{#useSpringfox}}
The underlying library integrating OpenAPI to SpringBoot is [springfox](https://github.com/springfox/springfox)

{{/useSpringfox}}
Start your server as an simple java application

{{^reactive}}
You can view the api documentation in swagger-ui by pointing to
http://localhost:8080/
http://localhost:{{serverPort}}/

{{/reactive}}
Change default port value in application.properties{{/interfaceOnly}}{{#interfaceOnly}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{{#useSpringfox}}
springfox.documentation.swagger.v2.path=/api-docs
{{/useSpringfox}}
server.port={{serverPort}}
spring.jackson.date-format={{basePackage}}.RFC3339DateFormat
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
Loading