Using Multiple JAX-RS Application Classes with Swagger
Asked Answered
F

2

8

I'm trying to implement Swagger on a Java application that has two Application classes due to the fact that one deals with "public" web services and the other deals with "admin" web services. I'm trying to generate two separate swagger.json files, one for each Application class. However, only one of them is being generated for both urls. Here's some code:

Public Application class:

@WebServlet
@ApplicationPath("/public") 
public class PublicApplication extends Application {

    public PublicApplication() {

        BeanConfig beanConfig = new BeanConfig();
        beanConfig.setVersion("1.0");
        beanConfig.setTitle("A Fine Title");
        beanConfig.setDescription("A Fine Description.");
        beanConfig.setSchemes(new String[]{"http"});
        beanConfig.setBasePath("/api"); 
        beanConfig.setResourcePackage("com.test.rest.resource.external");
        beanConfig.setPrettyPrint(true);
        beanConfig.setScan(true);
    }
}

Private Application class:

@WebServlet
@ApplicationPath("/admin") 
public class AdminApplication extends Application {

    public AdminApplication() {

        BeanConfig beanConfig = new BeanConfig();
        beanConfig.setVersion("1.0");
        beanConfig.setTitle("Another Fine Title");
        beanConfig.setDescription("Another Fine Description.");
        beanConfig.setSchemes(new String[]{"http"});
        beanConfig.setBasePath("/apiTwo"); 
        beanConfig.setResourcePackage("com.test.rest.resource.internal");
        beanConfig.setPrettyPrint(true);
        beanConfig.setScan(true);
    }
}

Now if I hit either of these urls I get the same "public" swagger json file:

What am I doing wrong?

Thanks to all who read!

Foolery answered 31/10, 2016 at 20:38 Comment(2)
I am having the same issue. Did you find a solution?Isolecithal
The requirements changed on my project and this setup was no longer necessary...so no. I didn't need to configure this way in the end. However, I likely will in the future so I'm still interested in an answer.Foolery
S
7

By default, Swagger does initialization of scanner and configuration once. If you have multiple applications or configs, you need to set a configId, scannerId & contextId to each of your application via BeanConfig and this should match with the values in your servlet config. And this settings work only with latest versions of swagger I think. I tried with swagger-1.5.13. An example is shown below.

public class PublicApplication extends Application {

public PublicApplication() {

    BeanConfig beanConfig = new BeanConfig();
    beanConfig.setVersion("1.0");
    beanConfig.setTitle("A Fine Title");
    beanConfig.setDescription("A Fine Description.");
    beanConfig.setSchemes(new String[]{"http"});
    beanConfig.setBasePath("/api"); 
    beanConfig.setResourcePackage("com.test.rest.resource.external");
    beanConfig.setPrettyPrint(true);

    // Set configId,contextId & scannerId
    beanConfig.setConfigId("public");  
    beanConfig.setContextId("public");
    beanConfig.setScannerId("public");
    beanConfig.setScan(true);

}

Your AdminApplication class

public AdminApplication() {

    BeanConfig beanConfig = new BeanConfig();
    beanConfig.setVersion("1.0");
    beanConfig.setTitle("Another Fine Title");
    beanConfig.setDescription("Another Fine Description.");
    beanConfig.setSchemes(new String[]{"http"});
    beanConfig.setBasePath("/apiTwo"); 
    beanConfig.setResourcePackage("com.test.rest.resource.internal");
    beanConfig.setPrettyPrint(true);

    // Set configId,contextId & scannerId
    beanConfig.setConfigId("admin");  
    beanConfig.setContextId("admin");
    beanConfig.setScannerId("admin");
    beanConfig.setScan(true);
    beanConfig.setScan(true);
}

You should also specify the configId, contextId & scannerId in your servlet config as init parameter as shown below.

<servlet>
    <servlet-name>jersey-rest-public</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>com.test.rest.resource.PublicApplication</param-value>
    </init-param>
    <init-param>
        <param-name>swagger.scanner.id</param-name>
        <param-value>public</param-value>
    </init-param>
    <init-param>
        <param-name>swagger.context.id</param-name>
        <param-value>public</param-value>
    </init-param>
    <init-param>
        <param-name>swagger.config.id</param-name>
        <param-value>public</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
    <servlet-name>jersey-rest-admin</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>com.test.rest.resource.AdminApplication</param-value>
    </init-param>
    <init-param>
        <param-name>swagger.context.id</param-name>
        <param-value>admin</param-value>
    </init-param>
    <init-param>
        <param-name>swagger.scanner.id</param-name>
        <param-value>admin</param-value>
    </init-param>
    <init-param>
        <param-name>swagger.config.id</param-name>
        <param-value>admin</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
Stipe answered 21/3, 2017 at 16:28 Comment(16)
When I set configId, contextId & scannerId, this section of the generated swagger disappears. Any idea why? "info": { "description": "API v1.0 definition", "version": "1.0", "title": "API v1.0" }, "basePath": "/api/1.0",Isolecithal
Did you set the same info in both servlet config and BeanConfig?Stipe
@Isolecithal Make sure you are setting contextId, configId & scannerId in BeanConfig.Stipe
I am using resourceConfig instead Map<String, Object> initparams = new HashMap<String, Object>(); initparams.put("swagger.scanner.id", "v2"); initparams.put("swagger.config.id", "v2"); initparams.put("swagger.context.id", "v2"); addProperties(initparams);Isolecithal
I am setting it in BeanConfig as well but I am seeing the behavior I mentioned above. The info section is omitted from the generated swagger.json fileIsolecithal
I think it happens because you are setting properties in ResourceConfig. Swagger specifically looks for ServletConfig parameters. Can you try setting those parameters as part of servlet config?Stipe
Yep, configuring it in web.xml works but that's annoying. There is no way to set that programmatically rather than web.xml?Isolecithal
Let us continue this discussion in chat.Stipe
If you two come to a working solution please post back or share and I'll mark this as the accepted answer. I'm still curious on how we can get this working. I, too, would rather not use a web.xml unless absolutely necessary.Foolery
As per my understanding, Swagger expects these properties as part of Jersey's ServletConfig. You can set it via web.xml or @WebServlet annotation or may be even programmatically as ServletConfig.getServletContext().setInitParameter("swagger.scanner.id", "public")(I haven't tried this option). @Isolecithal was trying to set it programmatically when we had discussion yesterday. Not sure whether he succeeded.Stipe
Unfortunately, I could not find a way. I tried the first two answers of this other question but both did not work for me. The only approach worked is the web.xml one. #19450702Isolecithal
@Red, but you're saying that the way that Justin Jose answered the question with the code above actually does work? If it works with a web.xml, but simply isn't the most (arguably) elegant, it's still a working solution that will be helpful. And also I'll mark the question as answered.Foolery
One of my colleagues tried this out as well and the webl.xml solution works. Thanks @JustinJose for the solution; I've marked your solution as the accepted answer. It's still a little confusing as to why setting the BeanConfig properties doesn't work, but at least a solution is available!Foolery
Has anyone tried to do this with the new swagger2 config style?Burnett
can anyone please enlighten me about two different URLs at which swagger.json is loaded ?Principal
@Justin Dose Dont we the below config in web.xml <servlet-name>Jersey2Config</servlet-name> <servlet-class>io.swagger.jersey.config.JerseyJaxrsConfig</servlet-class>Percutaneous
C
0

I run into the same issue but with with swagger v3. It took me quiet some effort to solve it so I want to share it.

import io.swagger.v3.jaxrs2.Reader
import io.swagger.v3.jaxrs2.integration.JaxrsApplicationScanner
import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContext
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.servers.Server
import javax.enterprise.inject.Instance
import javax.inject.Inject
import javax.servlet.ServletConfig
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.Application
import javax.ws.rs.core.Context
import javax.ws.rs.core.MediaType

@Path("/openapi.json")
open class OpenApiPath {
    @field:Inject
    private lateinit var applications: Instance<Application>

    @field:Context
    private lateinit var servletConfig: ServletConfig

    private val openApi by lazy { initOpenApi() }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Operation(hidden = true)
    open fun getSchema() = openApi

    private fun initOpenApi(): OpenAPI {
        val initial = OpenAPI().apply {
            info(Info().title("Application title"))
            servers(
                listOf(
                    Server().url(servletConfig.servletContext.contextPath)
                )
            )
        }
        val applications = applications.toList()

        val openApi = applications.fold(initial) { initial, app ->
            val context = JaxrsOpenApiContext<JaxrsOpenApiContext<*>>()
            context.app(app)
            context.setOpenApiReader(Reader(initial).apply { setApplication(app) })
            context.setOpenApiScanner(JaxrsApplicationScanner().application(app))
            context.read()
        }

        return openApi
    }
}

This works if you have registered all your jaxrs resources in the Application class.

Don't forget to register io.swagger.v3.jaxrs2.SwaggerSerializers.

Camlet answered 15/12, 2023 at 4:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.