Skip to content

Commit 47f9683

Browse files
committed
[Spring] Add apiFirst option
1 parent bd50d36 commit 47f9683

File tree

99 files changed

+2344
-1260
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+2344
-1260
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.swagger.v3.oas.models.Operation;
2424
import io.swagger.v3.oas.models.PathItem;
2525

26+
import org.apache.commons.lang3.tuple.Pair;
2627
import org.openapitools.codegen.CliOption;
2728
import org.openapitools.codegen.CodegenConstants;
2829
import org.openapitools.codegen.CodegenModel;
@@ -41,12 +42,9 @@
4142

4243
import java.io.File;
4344
import java.net.URL;
44-
import java.util.ArrayList;
45-
import java.util.Arrays;
46-
import java.util.HashMap;
47-
import java.util.List;
48-
import java.util.Map;
45+
import java.util.*;
4946
import java.util.regex.Matcher;
47+
import java.util.stream.Collectors;
5048

5149
public class SpringCodegen extends AbstractJavaCodegen
5250
implements BeanValidationFeatures, OptionalFeatures {
@@ -69,6 +67,7 @@ public class SpringCodegen extends AbstractJavaCodegen
6967
public static final String SPRING_CLOUD_LIBRARY = "spring-cloud";
7068
public static final String IMPLICIT_HEADERS = "implicitHeaders";
7169
public static final String OPENAPI_DOCKET_CONFIG = "swaggerDocketConfig";
70+
public static final String API_FIRST = "apiFirst";
7271

7372
protected String title = "OpenAPI Spring";
7473
protected String configPackage = "org.openapitools.configuration";
@@ -85,6 +84,7 @@ public class SpringCodegen extends AbstractJavaCodegen
8584
protected boolean useBeanValidation = true;
8685
protected boolean implicitHeaders = false;
8786
protected boolean openapiDocketConfig = false;
87+
protected boolean apiFirst = false;
8888
protected boolean useOptional = false;
8989

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

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

149+
List<Pair<String,String>> configOptions = additionalProperties.entrySet().stream()
150+
.filter(e -> !Arrays.asList(API_FIRST, "hideGenerationTimestamp").contains(e.getKey()))
151+
.filter(e -> cliOptions.stream().map(CliOption::getOpt).anyMatch(opt -> opt.equals(e.getKey())))
152+
.map(e -> Pair.of(e.getKey(), e.getValue().toString()))
153+
.collect(Collectors.toList());
154+
additionalProperties.put("configOptions", configOptions);
155+
148156
// Process java8 option before common java ones to change the default dateLibrary to java8.
157+
System.out.println("----------------------------------");
149158
if (additionalProperties.containsKey(JAVA_8)) {
159+
System.out.println("has JAVA8");
150160
this.setJava8(Boolean.valueOf(additionalProperties.get(JAVA_8).toString()));
161+
additionalProperties.put(JAVA_8, java8);
151162
}
152163
if (this.java8 && !additionalProperties.containsKey(DATE_LIBRARY)) {
153164
setDateLibrary("java8");
@@ -232,6 +243,10 @@ public void processOpts() {
232243
this.setOpenapiDocketConfig(Boolean.valueOf(additionalProperties.get(OPENAPI_DOCKET_CONFIG).toString()));
233244
}
234245

246+
if (additionalProperties.containsKey(API_FIRST)) {
247+
this.setApiFirst(Boolean.valueOf(additionalProperties.get(API_FIRST).toString()));
248+
}
249+
235250
typeMapping.put("file", "Resource");
236251
importMapping.put("Resource", "org.springframework.core.io.Resource");
237252

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

256271
if (!this.interfaceOnly) {
257272
if (library.equals(SPRING_BOOT)) {
258-
if (!this.reactive) {
259-
supportingFiles.add(new SupportingFile("homeController.mustache",
260-
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "HomeController.java"));
261-
}
262273
supportingFiles.add(new SupportingFile("openapi2SpringBoot.mustache",
263274
(sourceFolder + File.separator + basePackage).replace(".", java.io.File.separator), "OpenAPI2SpringBoot.java"));
264275
supportingFiles.add(new SupportingFile("RFC3339DateFormat.mustache",
265276
(sourceFolder + File.separator + basePackage).replace(".", java.io.File.separator), "RFC3339DateFormat.java"));
266-
supportingFiles.add(new SupportingFile("application.mustache",
267-
("src.main.resources").replace(".", java.io.File.separator), "application.properties"));
268277
}
269278
if (library.equals(SPRING_MVC_LIBRARY)) {
270279
supportingFiles.add(new SupportingFile("webApplication.mustache",
@@ -275,8 +284,6 @@ public void processOpts() {
275284
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "OpenAPIUiConfiguration.java"));
276285
supportingFiles.add(new SupportingFile("RFC3339DateFormat.mustache",
277286
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "RFC3339DateFormat.java"));
278-
supportingFiles.add(new SupportingFile("application.properties",
279-
("src.main.resources").replace(".", java.io.File.separator), "openapi.properties"));
280287
}
281288
if (library.equals(SPRING_CLOUD_LIBRARY)) {
282289
supportingFiles.add(new SupportingFile("apiKeyRequestInterceptor.mustache",
@@ -290,20 +297,19 @@ public void processOpts() {
290297
}
291298
} else {
292299
apiTemplateFiles.put("apiController.mustache", "Controller.java");
293-
supportingFiles.add(new SupportingFile("apiException.mustache",
294-
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiException.java"));
295-
supportingFiles.add(new SupportingFile("apiResponseMessage.mustache",
296-
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiResponseMessage.java"));
297-
supportingFiles.add(new SupportingFile("notFoundException.mustache",
298-
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "NotFoundException.java"));
299-
if (!this.reactive) {
300-
supportingFiles.add(new SupportingFile("apiOriginFilter.mustache",
301-
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiOriginFilter.java"));
300+
supportingFiles.add(new SupportingFile("application.mustache",
301+
("src.main.resources").replace(".", java.io.File.separator), "application.properties"));
302+
supportingFiles.add(new SupportingFile("homeController.mustache",
303+
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "HomeController.java"));
304+
if (!this.reactive && !this.apiFirst) {
302305
supportingFiles.add(new SupportingFile("openapiDocumentationConfig.mustache",
303306
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "OpenAPIDocumentationConfig.java"));
307+
} else {
308+
supportingFiles.add(new SupportingFile("openapi.mustache",
309+
("src/main/resources").replace("/", java.io.File.separator), "openapi.yaml"));
304310
}
305311
}
306-
} else if (this.openapiDocketConfig && !library.equals(SPRING_CLOUD_LIBRARY) && !this.reactive) {
312+
} else if (this.openapiDocketConfig && !library.equals(SPRING_CLOUD_LIBRARY) && !this.reactive && !this.apiFirst) {
307313
supportingFiles.add(new SupportingFile("openapiDocumentationConfig.mustache",
308314
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "OpenAPIDocumentationConfig.java"));
309315
}
@@ -313,6 +319,11 @@ public void processOpts() {
313319
(sourceFolder + File.separator + apiPackage).replace(".", java.io.File.separator), "ApiUtil.java"));
314320
}
315321

322+
if (this.apiFirst) {
323+
apiTemplateFiles.clear();
324+
modelTemplateFiles.clear();
325+
}
326+
316327
if ("threetenbp".equals(dateLibrary)) {
317328
supportingFiles.add(new SupportingFile("customInstantDeserializer.mustache",
318329
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "CustomInstantDeserializer.java"));
@@ -344,6 +355,11 @@ public void processOpts() {
344355
additionalProperties.put(RESPONSE_WRAPPER, "Callable");
345356
}
346357

358+
if(!this.apiFirst && !this.reactive) {
359+
additionalProperties.put("useSpringfox", true);
360+
}
361+
362+
347363
// Some well-known Spring or Spring-Cloud response wrappers
348364
switch (this.responseWrapper) {
349365
case "Future":
@@ -565,6 +581,7 @@ private void removeHeadersFromAllParams(List<CodegenParameter> allParams) {
565581

566582
@Override
567583
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
584+
generateYAMLSpecFile(objs);
568585
if(library.equals(SPRING_CLOUD_LIBRARY)) {
569586
List<CodegenSecurity> authMethods = (List<CodegenSecurity>) objs.get("authMethods");
570587
if (authMethods != null) {
@@ -659,6 +676,10 @@ public void setOpenapiDocketConfig(boolean openapiDocketConfig) {
659676
this.openapiDocketConfig = openapiDocketConfig;
660677
}
661678

679+
public void setApiFirst(boolean apiFirst) {
680+
this.apiFirst = apiFirst;
681+
}
682+
662683
@Override
663684
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
664685
super.postProcessModelProperty(model, property);

modules/openapi-generator/src/main/resources/JavaSpring/customInstantDeserializer.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import com.fasterxml.jackson.core.JsonTokenId;
55
import com.fasterxml.jackson.databind.DeserializationContext;
66
import com.fasterxml.jackson.databind.DeserializationFeature;
77
import com.fasterxml.jackson.databind.JsonDeserializer;
8-
import com.fasterxml.jackson.datatype.threetenbp.DateTimeUtils;
98
import com.fasterxml.jackson.datatype.threetenbp.DecimalUtils;
109
import com.fasterxml.jackson.datatype.threetenbp.deser.ThreeTenDateTimeDeserializerBase;
1110
import com.fasterxml.jackson.datatype.threetenbp.function.BiFunction;
1211
import com.fasterxml.jackson.datatype.threetenbp.function.Function;
1312
import org.threeten.bp.DateTimeException;
13+
import org.threeten.bp.DateTimeUtils;
1414
import org.threeten.bp.Instant;
1515
import org.threeten.bp.OffsetDateTime;
1616
import org.threeten.bp.ZoneId;
@@ -205,7 +205,7 @@ public class CustomInstantDeserializer<T extends Temporal>
205205

206206
private ZoneId getZone(DeserializationContext context) {
207207
// Instants are always in UTC, so don't waste compute cycles
208-
return (_valueClass == Instant.class) ? null : DateTimeUtils.timeZoneToZoneId(context.getTimeZone());
208+
return (_valueClass == Instant.class) ? null : DateTimeUtils.toZoneId(context.getTimeZone());
209209
}
210210
211211
private static class FromIntegerArguments {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package {{configPackage}};
2+
3+
{{^useSpringfox}}
4+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.core.io.Resource;
8+
{{/useSpringfox}}
9+
import org.springframework.stereotype.Controller;
10+
{{^useSpringfox}}
11+
import org.springframework.util.StreamUtils;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
{{/useSpringfox}}
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
{{^useSpringfox}}
16+
import org.springframework.web.bind.annotation.ResponseBody;
17+
{{/useSpringfox}}
18+
{{#reactive}}
19+
import org.springframework.web.reactive.function.server.RouterFunction;
20+
import org.springframework.web.reactive.function.server.ServerResponse;
21+
{{/reactive}}
22+
23+
{{^useSpringfox}}
24+
import java.io.IOException;
25+
import java.io.InputStream;
26+
{{/useSpringfox}}
27+
{{#reactive}}
28+
import java.net.URI;
29+
{{/reactive}}
30+
{{^useSpringfox}}
31+
import java.nio.charset.Charset;
32+
{{/useSpringfox}}
33+
{{#reactive}}
34+
35+
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
36+
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
37+
{{/reactive}}
38+
39+
/**
40+
* Home redirection to OpenAPI api documentation
41+
*/
42+
@Controller
43+
public class HomeController {
44+
45+
{{^useSpringfox}}
46+
private static YAMLMapper yamlMapper = new YAMLMapper();
47+
48+
@Value("classpath:/openapi.yaml")
49+
private Resource openapi;
50+
51+
@Bean
52+
public String openapiContent() throws IOException {
53+
try(InputStream is = openapi.getInputStream()) {
54+
return StreamUtils.copyToString(is, Charset.defaultCharset());
55+
}
56+
}
57+
58+
@GetMapping(value = "/openapi.yaml", produces = "application/vnd.oai.openapi")
59+
@ResponseBody
60+
public String openapiYaml() throws IOException {
61+
return openapiContent();
62+
}
63+
64+
@GetMapping(value = "/openapi.json", produces = "application/json")
65+
@ResponseBody
66+
public Object openapiJson() throws IOException {
67+
return yamlMapper.readValue(openapiContent(), Object.class);
68+
}
69+
70+
{{/useSpringfox}}
71+
{{#reactive}}
72+
@Bean
73+
RouterFunction<ServerResponse> index() {
74+
return route(
75+
GET("/"),
76+
req -> ServerResponse.temporaryRedirect(URI.create("{{#useSpringfox}}swagger-ui.html{{/useSpringfox}}{{^useSpringfox}}swagger-ui/index.html?url=../openapi.json{{/useSpringfox}}")).build()
77+
);
78+
}
79+
{{/reactive}}
80+
{{^reactive}}
81+
@RequestMapping("/")
82+
public String index() {
83+
return "redirect:{{#useSpringfox}}swagger-ui.html{{/useSpringfox}}{{^useSpringfox}}swagger-ui/index.html?url=../openapi.json{{/useSpringfox}}";
84+
}
85+
{{/reactive}}
86+
87+
88+
}

modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-boot/README.mustache

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ This server was generated by the [OpenAPI Generator](https://openapi-generator.t
88
By using the [OpenAPI-Spec](https://openapis.org), you can easily generate a server stub.
99
This is an example of building a OpenAPI-enabled server in Java using the SpringBoot framework.
1010

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

14+
{{/useSpringfox}}
1315
Start your server as an simple java application
1416

1517
{{^reactive}}
1618
You can view the api documentation in swagger-ui by pointing to
17-
http://localhost:8080/
19+
http://localhost:{{serverPort}}/
1820

1921
{{/reactive}}
2022
Change default port value in application.properties{{/interfaceOnly}}{{#interfaceOnly}}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
{{#useSpringfox}}
12
springfox.documentation.swagger.v2.path=/api-docs
3+
{{/useSpringfox}}
24
server.port={{serverPort}}
35
spring.jackson.date-format={{basePackage}}.RFC3339DateFormat
46
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false

0 commit comments

Comments
 (0)