- Generating stubs with Swagger Codegen and Gradle
- Using a custom template for Swagger Codegen with Gradle
- Generating Feign clients with Swagger Codegen and Gradle
- Accessing custom attributes in Swagger Codegen
- Extending Swagger Codegen with new mustache template files using a new language
We’ve already seen a lot of things in the previous parts of the series. Although, it’s very useful if you know how to extend the code generation process for the custom requirements. In order to gain full control over the process, the best way to tackle this is by using a custom language for the generation. Imagine the situation when it’s required to have a custom generated class which will wrap all the Feign calls and do some logic, for example logs something, check the privileges of the currently logged in user, checks locale settings and so on. In this article, we’ll check how to implement this extension.
The new language in Swagger Codegen
In the previous articles, we’ve used spring as a base language for the generation. It was also customized a bit to have spring-cloud as a library and the mustache templates were changed a little bit. For the purpose of extending the list of generated files, it’s required to have a custom language.
First of all, let’s create a new module custom-swagger-codegen which will be the custom artifact including the new language we are putting together. It will be a simple Gradle project with the following build.gradle
buildscript { repositories { mavenCentral() } } apply plugin: 'java' apply plugin: 'maven' group = 'com.arnoldgalovics.blog' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile "io.swagger:swagger-codegen:2.3.1" }
Basic stuff, the only needed dependency is the code generator from Swagger which will be extended. Only a single Java file is needed, call it CustomCodegen for now, it will extend the SpringCodegen class from swagger-codegen which is basically the spring language descriptor which we used originally. If you check SpringCodegen , there are tons of methods available where you can customize the whole generation.
As a first step, let’s define the name for the new language. This is done by overriding the getName method and providing the name there, call the language custom . Overriding the processOpts method will give us a possibility to extend the list of template files which needs to be used. Here, I’ll add a configuration that apiClientWrapper.mustache have to be generated to *ApiClientWrapper.java .
import io.swagger.codegen.languages.SpringCodegen; public class CustomCodegen extends SpringCodegen { @Override public void processOpts() { super.processOpts(); if (SPRING_CLOUD_LIBRARY.equals(getLibrary())) { apiTemplateFiles.put("apiClientWrapper.mustache", "ClientWrapper.java"); } } @Override public String getName() { return "custom"; } }
Additionally, the code is checking whether the currently selected library is spring-cloud as the wrapper only makes sense in case of Feign clients.
That’s all the implementation we needed for the new language. Now we have to make this language discoverable for Swagger Codegen. A new file must be created in the src/main/resources/META-INF/services folder called io.swagger.codegen.CodegenConfig . This file will be used by Swagger for detecting all the available languages. The only thing to put into the file is a fully-qualified name of the class which we created, in my case com.arnoldgalovics.blog.codegen.CustomCodegen .
Now the artifact creation, just executing ./gradlew clean build install will put the customized code generator into Maven local which will be used by the already existing contract project.
Using the new language
Open up the user-service-contract project which we have created in the previous parts of the series. In the customized generator, the Feign client generation was extended with the new mustache file so we only need to change the client build.
First, we have to tell Gradle to use Maven local for artifact resolution. After that, use the customized generator in the buildscript.
buildscript { repositories { mavenLocal() mavenCentral() } dependencies { classpath("com.arnoldgalovics.blog:custom-swagger-codegen:0.0.1-SNAPSHOT") } }
Find the Feign client generation in the buildscript and within the configuration of the code generator, change up the language to the new one which was created. The name of the new language is the value from the getName method in the CustomCodegen class.
config.setLang("custom")
This is how the full generation looks like for the client side:
project("${rootProject.appName}-feign-client") { // user-service-feign-client // Dependencies for the generated sources dependencies { compile('org.springframework.boot:spring-boot-starter-web:1.5.13.RELEASE') compile('org.springframework.cloud:spring-cloud-starter-feign:1.4.0.RELEASE') compile('io.springfox:springfox-swagger2:2.7.0') } // Actual task for generating the Feign client task generateClient { doLast { def config = new CodegenConfigurator() config.setLang("custom") config.setLibrary('spring-cloud') config.setApiPackage(rootProject.apiPackage) // Package to be used for the API interfaces config.setModelPackage(rootProject.modelPackage) // Package to be used for the API models config.setInputSpec(rootProject.swaggerFile.toString()) // The swagger API file config.setOutputDir(project.buildDir.toString()) // The output directory, user-service-contract/build/user-service-feign-client/ config.setTemplateDir(rootProject.templateDir) // The directory where the templates are located config.setAdditionalProperties([ 'dateLibrary' : 'java8', 'title' : rootProject.appName, 'useTags' : 'true' ]) new DefaultGenerator().opts(config.toClientOptInput()).generate() } } // Task for deleting the unnecessary configuration task deleteNonClientRelatedClasses(dependsOn: 'generateClient') { doLast { delete "${project.buildDir}/src/main/java/io" } } compileJava.dependsOn('deleteNonClientRelatedClasses') }
Now the only missing step is to create a new template for the Feign client wrapping. In the custom generator, we named the new mustache file as apiClientWrapper.mustache so this is what’s needed. As the spring-cloud library is used and it’s templates are in the spring-cloud subdirectory, the new file is also needed to be put there.
The goal of this new class is to wrap the already generated Feign client and do some logic before calling the actual client, in the current case it will be just a simple log message but it could be anything, checking privileges, sending locale information through the client, error handling, etc.
Let’s finally see the contents of the template/libraries/spring-cloud/apiClientWrapper.mustache
package {{package}}; {{#imports}}import {{import}}; {{/imports}} import io.swagger.annotations.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import javax.validation.constraints.*; import java.io.IOException; import java.util.List; import java.util.Optional; @Component {{#operations}} public class {{classname}}ClientWrapper implements {{classname}} { private static final Logger logger = LoggerFactory.getLogger({{classname}}ClientWrapper.class); private final {{classname}}Client feignClient; @Autowired public {{classname}}ClientWrapper({{classname}}Client feignClient) { this.feignClient = feignClient; } {{#operation}} public {{#vendorExtensions.x-responseEntity}}ResponseEntity<{{>returnTypes}}>{{/vendorExtensions.x-responseEntity}}{{^vendorExtensions.x-responseEntity}}{{>returnTypes}}{{/vendorExtensions.x-responseEntity}} {{operationId}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{#hasMore}},{{/hasMore}}{{/allParams}}) { logger.info("Calling {{operationId}} with Feign"); return feignClient.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}); } {{/operation}} } {{/operations}}
And the generated code is, omitting the imports:
@Component public class UserApiClientWrapper implements UserApi { private static final Logger logger = LoggerFactory.getLogger(UserApiClientWrapper.class); private final UserApiClient feignClient; @Autowired public UserApiClientWrapper(UserApiClient feignClient) { this.feignClient = feignClient; } public ResponseEntity<Void> createUser(@ApiParam(value = "The user data" ,required=true ) @Valid @RequestBody UserCreateRequest user) { logger.info("Calling createUser with Feign"); return feignClient.createUser(user); } public ListUserResponse getUsers() { logger.info("Calling getUsers with Feign"); return feignClient.getUsers(); } }
Testing time
From the previous series, bring up the payment-service module. As this was a client only modification, there is no need to change anything on the user-service side.
As the new class is annotated as a @Component , we have to make sure that Spring will find it. In the Application class, modify the component scan packages:
@SpringBootApplication(scanBasePackageClasses = { PaymentServiceApplication.class, UserApiClientWrapper.class })
Now change the pure Feign client in the Controller to use the new wrapper implementation.
@RestController("/payment") class PaymentController { private UserApiClientWrapper userApiClientWrapper; public PaymentController(UserApiClientWrapper userApiClientWrapper) { this.userApiClientWrapper = userApiClientWrapper; } @GetMapping public ListPayingUserResponse getPayingUsers() { return new ListPayingUserResponse(userApiClientWrapper.getUsers().stream().map(UserResponse::getName).collect(Collectors.toList())); } }
By starting up the discovery-service, payment-service and user-service, invoking http://localhost:8001/payment will result in the following log message:
2018-09-29 17:05:50.251 INFO 4012 --- [nio-8001-exec-5] c.a.b.u.api.UserApiClientWrapper : Calling getUsers with Feign
Summary
We’ve seen how to customize the code generation process even more like adding a new template file with a custom language. We’ve achieved that for all the Feign clients, there will be a wrapper class generated as well where a custom logic can be executed before calling the actual client.
The full code can be found on GitHub. Also, I’d like to thank Balazs Mracsko for making this series possible.
If you liked the article, spread the word, share it and make sure you follow me on Twitter for more interesting stuff.
Hmm it appears like your site ate my first comment (it was extremely long)
so I guess I’ll just sum it up what I submitted and say, I’m thoroughly enjoying your blog.
I too am an aspiring blog blogger but I’m still new to everything.
Do you have any points for first-time blog
writers? I’d certainly appreciate it.
If I want to generate an exception class to the generated project which it does not generate currently, what should I do sir? I’m using open api generator maven plugin instead of swagger codegen cradle plugin.