13

What is the best way to plug the server stubs generated by Swagger Codegen into an existing Spring MVC application?

I'm starting off by attempting to use the petstore stubs sample.

My Spring configuration is in Java and looks like this:

public class SpringConfigurationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { ApplicationContext.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebMvcContext.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    // ... onStartup etc.

}

WebMvcConfigurationSupport:

@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@PropertySource({ "classpath:config.properties", "file:${CONFIGDIR}/config.properties" })
@ComponentScan(useDefaultFilters = false, basePackages = { "com.yyy", "com.xxx" }, includeFilters = { @Filter(type = FilterType.ANNOTATION, value = Controller.class) })
public class WebMvcContext extends WebMvcConfigurationSupport {

    // ... beans etc.
}

ApplicationContext:

@Configuration
@EnableAsync
@EnableScheduling
@EnableMBeanExport
@Import({SecurityConfig.class, GeneralDBConfiguration.class})
@ComponentScan(useDefaultFilters = true, basePackages = { "com.yyy", "com.xxx" }, excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = {Controller.class, Configuration.class/*, Aspect.class*/}) })
public class ApplicationContext implements AsyncConfigurer {

    // beans etc.

}

How do I go about including the config classes part of the io.swagger.configuration package into my existing application?

Some more details:

One of the problems I'm having is that if I specify a maven dependency on the petshop stubs (which is installed locally by running mvn install:install-file ... from the spring-mvc-j8-async directory):

    <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-spring-mvc-server</artifactId>
        <version>1.0.0</version>
    </dependency>

Then my spring application finds two AbstractAnnotationConfigDispatcherServletInitializers (one from my app, and the io.swagger.configuration.WebApplication one from swagger-spring-mvc-server) and fails to load up - giving the following exception:

Failed to register servlet with name 'dispatcher'.Check if there is another servlet registered under the same name.

I guess another way to phrase my question would be, how do you use the server stubs generated by swagger-codegen? It looks like I can't just depend on the maven package out of the box...

jlb
  • 19,090
  • 8
  • 34
  • 65
  • 1
    Seems like you're using the Spring mvc stack and not the max-Es stack. I'd recommend using the [spring-mvc](https://github.com/swagger-api/swagger-codegen/tree/master/samples/server/petstore/spring-mvc) sample instead. – Dilip Krishnan Apr 21 '16 at 00:50
  • @DilipKrishnan what do you mean by max-Es? The petstore stub im trying to integrate with is the [`spring-mvc-j8-async`](https://github.com/swagger-api/swagger-codegen/tree/master/samples/server/petstore/spring-mvc-j8-async) stub -- and in fact the `io.swagger.configuration` package is identical to one in the "plain" [`spring-mvc`](https://github.com/swagger-api/swagger-codegen/tree/master/samples/server/petstore/spring-mvc) stub. – jlb Apr 21 '16 at 09:17
  • That was a typo I meant JAX-RS. Not sure about other sample (I wrote the spring mvc version). For sure it's missing the @enableSwagger2 annotation. – Dilip Krishnan Apr 21 '16 at 11:50
  • @DilipKrishnan thanks - much appreciated. I think im struggling with a bigger issue though -- please see the additional section added at the end of the question. – jlb Apr 21 '16 at 13:13
  • why dose not use springfox – ali akbar azizkhani Mar 23 '17 at 14:36
  • @jlb did you ever find a solution to this error: "Caused by: java.lang.ClassNotFoundException: org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer"? – Bass Mar 15 '20 at 00:56
  • @Bass it was a long time ago - unfortunately I can't remember :( – jlb Mar 16 '20 at 08:46

1 Answers1

7

I can see this is quite an old one, but I struggled with this a lot and the info I collected might be useful to others until generator docs get improved.

This description is for generating and use spring-mvc server stub from OpenApi 3.0.0 spec using openapi-generator-maven-plugin.

  1. Don't use swagger-codegen, use openapi generator instead: https://github.com/OpenAPITools/openapi-generator (reasoning of the fork is here: https://github.com/OpenAPITools/openapi-generator/blob/master/docs/qna.md)

  2. Create a separate project/module for server stuff and configure maven plugin.

<build>
    <plugins>
        <plugin>
            <groupId>org.openapitools</groupId>
            <artifactId>openapi-generator-maven-plugin</artifactId>
            <version>3.3.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    <configuration>
                        <skipIfSpecIsUnchanged>true</skipIfSpecIsUnchanged>
                        <inputSpec>${engine-openapi-spec.location}</inputSpec>
                        <output>${project.build.directory}/generated-sources/openapi</output>
                        <generatorName>spring</generatorName>
                        <library>spring-mvc</library>
                        <apiPackage>eu.dorsum.swift.engine.service.api</apiPackage>
                        <modelPackage>eu.dorsum.swift.engine.service.model</modelPackage>
                        <generateApis>true</generateApis>
                        <generateApiDocumentation>false</generateApiDocumentation>
                        <generateApiTests>false</generateApiTests>
                        <generateModels>true</generateModels>
                        <generateModelDocumentation>false</generateModelDocumentation>
                        <generateModelTests>false</generateModelTests>
                        <generateSupportingFiles>true</generateSupportingFiles>
                        <configOptions>
                            <sourceFolder>src/main/java</sourceFolder>
                            <java8>true</java8>
                            <dateLibrary>java8</dateLibrary>
                            <useTags>true</useTags>
                            <configPackage>eu.dorsum.swift.engine.appconfig</configPackage>
                            <interfaceOnly>false</interfaceOnly>
                            <delegatePattern>true</delegatePattern>
                        </configOptions>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

There are two pieces of configuration which are kind of tricky to figure out.

2.a: Set configOptions/configPackage to a package where your application's root config resides. This option will add your config package as additional basePackage to component scan in OpenAPIUiConfiguration.java: @ComponentScan(basePackages = {"eu.dorsum.swift.engine.service.api", "eu.dorsum.swift.engine.appconfig"}). This is an inverted approach you might think of initially by having the generated mvc config start your existing stuff, but this is the only way I've found requiring no modification in generated code.

2.b: Set configOptions/delegatePattern to true. This one I like a lot! This will generate an additional delegation interface which your server controller can implement. Generated ApiController will delegate all service calls to this interface, so you can plug in your implementation very elegantly. In my setup I have this chain: MessageApi (generated interface) -> MessageApiController implements MessageApi (generated mvc controller) -> MessageApiDelegate (generated interface) -> MessageService implements MessageApiDelegate (my implementation of service methods).

Having these two pieces of config there is no need to modify generated sources. If api spec changes delegation interface changes and you have to implement those changes in MessageService.

Below is an update to my original post to explain in more detail how the service implementation is bound to the delegate interface.

The sources generated by openapi-gen are in a separate jar module containing only swagger.yaml and pom.xml as checked-in 'sources'. The module using the generated code is a war which has a direct dependency on the generated jar. Service implementation is in the war and implements MessageApiDelegate interface which is in the generated source. Spring context is pulled up from the generated code and the packages you define as apiPackage and configPackage in openapi-gen config will be added as basePackages for @ComponentScan in the generated OpenAPIUiConfiguration.java, so your service implementation must be placed in or under apiPackages.

Here is a simplified structure to put the pieces together.

parent/
  swift-engin-service/ (generated jar module)
    swagger.yaml
    pom.xml
    target/generated-sources/openapi/src/main/java/eu/dorsum/swift/engine/
      appconfig/ (generated spring webmvc config)
        OpenAPIUiConfiguration.java
        WebApplication.java (brings up spring context by extending AbstractAnnotationConfigDispatcherServletInitializer)
      service/
        MessageApiController.java (@Controller, @RequestMapping etc.)
        MessageApiDelegate.java (implement this to get your service implementation plugged in the controller)
  swift-engine/ (war module, your code)
    pom.xml
    src/main/java/eu/dorsum/swift/engine/
      appconfig/
        EngineConfig.java (my spring config)
      service/api/ (must be the same as apiPackages property)
        MessageService.java (service implementation)

Relevant part of swift-engine/pom.xml

<project>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>eu.dorsum.core.java.swift-engine</groupId>
            <artifactId>swift-engine-service</artifactId>
        </dependency>
    </dependencies>
</project>

Relevant part of eu.dorsum.swift.engine.service.api.MessageService

@Service
public class MessageService implements MessageApiDelegate {
    @Override
    public ResponseEntity<List<SwiftMessage>> listMessages(List<String> sorting, Integer pageStart, Integer pageSize, String filter) {
        // implementation
    }

    @Override
    public ResponseEntity<Void> updateMessage(SwiftMessage message) {
        // implementation
    }
}

Relevant part of eu.dorsum.swift.engine.appconfig.OpenAPIUiConfiguration (generated)

@Configuration
@ComponentScan(basePackages = {"eu.dorsum.swift.engine.service.api", "eu.dorsum.swift.engine.appconfig"})
@EnableWebMvc
public class OpenAPIUiConfiguration extends WebMvcConfigurerAdapter {
    ...
}
Peter
  • 343
  • 4
  • 10
  • I can't seem to get the implemented methods getting called. Instead, the default methods in the interface are still getting called although my annotated service class implements the interface methods (default). Something missing on my end? – Bass Mar 07 '20 at 01:58
  • Most probably yes :) Do you use spring-mvc also? – Peter Mar 08 '20 at 14:23
  • I do Peter, along with Spring 5.1.1. I have attempted different setup combinations, one with openapi with/without springboot, another on the same module, as my springmvc application, all with weblogic as the app server. Do you have a separate war for the openapi module? How does the openapi module "see" the MessageService, all in the same module, I suppose? Could you also post your pom.xml for the openapi module? I am using open-api-generator-plugin 4.2.3 – Bass Mar 09 '20 at 16:49
  • The `delegatePattern`is for sure the most important one. I was already thinking to only generate the interfaces until I saw your post. Many thanks! – LionH Mar 09 '20 at 21:10
  • Bass, sorry for the late reply, I couldn't find time earlier to answer. I edited my answer with details, hope it helps. – Peter Mar 15 '20 at 19:13