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.

2 Replies to “Extending Swagger Codegen with new mustache template files using a new language”

  1. teramune says:
  2. Asis says:

Leave a Reply

Your email address will not be published. Required fields are marked *