How to split Swagger auto-generated openapi.json into multiple parts by ResourceConfig extends Application (Jersey)

Viewed 333

My current project with Jersey has several applications.

(org.glassfish.jersey.server.ResourceConfig extends javax.ws.rs.core.Application)

I have added Swagger documentation to the project, but I would like to have different urls with different resource methods for different applications. Something like:

https://our.company.name:8080/project/**${APPLICATION_NAME}**/openapi.json

At the moment, all resource methods in all applications are added to a single openapi.json that is displayed on any of the urls. How can I split it?

pom:

    <dependency>
       <groupId>io.swagger.core.v3</groupId>
       <artifactId>swagger-jaxrs2</artifactId>
       <version>2.1.5</version>        
    </dependency>

    <dependency>
       <groupId>io.swagger.core.v3</groupId>
       <artifactId>swagger-core</artifactId>
       <version>2.1.5</version>
    </dependency>

One of the Applications example:

public SomeApplication(@Context ServletConfig servletConfig) {
    final String[] packages = {"com.projectname.rest.SOMEAPPLICATION.api","io.swagger.v3.jaxrs2.integration.resources"};
    packages(packages);

    property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
            
    register(CorsResponseFilter.class);
    register(UnknownParameterRequestFilter.class);
    register(BTSMarshallingFeature.class);
            
    SwaggerManager swaggerManager = new SwaggerManager();
    OpenAPI oas = new OpenAPI();
    Info info = new Info();
    info.setTitle("SOMEAPPLICATION Public API Documentation");
    info.setVersion("v1");
    info.setDescription("description...");
    oas.info(info);
            
    SecurityScheme securitySchemeApiKey = new SecurityScheme();
    securitySchemeApiKey.setName("Token");
    securitySchemeApiKey.setType(io.swagger.v3.oas.models.security.SecurityScheme.Type.APIKEY);
    securitySchemeApiKey.setIn(io.swagger.v3.oas.models.security.SecurityScheme.In.HEADER);
            
    oas.schemaRequirement(securitySchemeApiKey.getName(), securitySchemeApiKey);
    oas.servers(Collections.singletonList(new Server().url("/s1")));
    SwaggerConfiguration oasConfig = new SwaggerConfiguration().openAPI(oas).prettyPrint(true).readAllResources(false).resourcePackages(Stream.of("com.projectname.rest.SOMEAPPLICATION.api").collect(Collectors.toSet()));
                
    try {
        new JaxrsOpenApiContextBuilder<>().servletConfig(servletConfig).application(config).openApiConfiguration(oasConfig).buildContext(true);
    } catch (OpenApiConfigurationException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

html page:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Swagger UI</title>
        <!--<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">-->
        <link href="../swagger/swagger-ui.css" rel="stylesheet" type="text/css">
        <!--<link rel="stylesheet" type="text/css" href=".../swagger/css/swagger.css" >-->
        <link href="../swagger/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/>
        <link href="../swagger/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/>
        <style>
            html {
                box-sizing: border-box;
                overflow: -moz-scrollbars-vertical;
                overflow-y: scroll;
            }

            *,
            *:before,
            *:after {
                box-sizing: inherit;
            }

            body {
                margin: 0;
                background: #fafafa;
            }
        </style>
    </head>

    <body>
    <div id="swagger-ui"></div>

    <script src="../swagger/swagger-ui-bundle.js"></script>
    <script src="../swagger/swagger-ui-standalone-preset.js"></script>
    <script src="../script/swagger_APPLICATION_NAME_api.js"></script>
    </body>
    </html>

swagger_APPLICATION_NAME_api.js:

var versionNumber = "v2";
function getUrlParameter(name) {
    name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
    var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
    var results = regex.exec(location.search);
    return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
window.onload = function () {
    var versionParam = getUrlParameter("version");
    if (versionParam) {
        versionNumber = versionParam;
    }
    if (!window.location.origin) {
        window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
    }
    var swaggerUrl = window.location.origin + "/s1/APPLICATION_NAME/openapi.json";
    const ui = SwaggerUIBundle({
    url: swaggerUrl,
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
    plugins: [SwaggerUIBundle.plugins.DownloadUrl],
    layout: "StandaloneLayout",
    operationsSorter: (a, b) => {
        var result = a.get("path").localeCompare(b.get("path"));
        if (result === 0) {
            var methodsOrder = ["get", "post", "put", "delete", "patch", "options", "trace"];
            result = methodsOrder.indexOf(a.get("method")) - methodsOrder.indexOf(b.get("method"));
        }
        return result;
    },
    tagsSorter: "alpha"
});
window.ui = ui;
};

Resource method example:

@Operation(
    tags = {"Dummy"}, method = "GET", summary = "gets example list",
    description = "Returns a List of Strings",
    responses = {
        @ApiResponse(responseCode = "200", description = "OK",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(implementation = String.class)
            )
        ),
        @ApiResponse(responseCode = "401", description = "Unauthorized"),
        @ApiResponse(responseCode = "422", description = "Validation error"),
        @ApiResponse(responseCode = "500", description = "Internal server error", headers={
            @Header(name = "X-P8-Information", description = "Request has errors.")
        })
    }
)
public Response getDummy() {
    return respondGet(List.of("APPLICATION_MAME", "Dummy", "Response"));
}

I think I have this result because we have a Singleton here:

package io.swagger.v3.oas.integration;
public class GenericOpenApiContextBuilder<T extends GenericOpenApiContextBuilder> implements 
OpenApiContextBuilder {

    @Override
    public OpenApiContext buildContext(boolean init) throws OpenApiConfigurationException {
        if (StringUtils.isBlank(ctxId)) {
            ctxId = OpenApiContext.OPENAPI_CONTEXT_ID_DEFAULT;
        }
        OpenApiContext ctx = OpenApiContextLocator.getInstance().getOpenApiContext(ctxId);
    }    
    ...
}

I have tried to set unique ctxId for each Application. The results are:

  1. Same behavior for methods.
  2. Info (title, version... ) was missing.
1 Answers

Just add correct scannerClass (JaxrsApplicationScanner):

SwaggerConfiguration oasConfig = new SwaggerConfiguration()
  .openAPI(oas)
  .prettyPrint(true)
  .readAllResources(false)
  .scannerClass(JaxrsApplicationScanner.class.getName())
  .resourcePackages(Stream.of(
        "com.projectname.rest.SOMEAPPLICATION.api")
  .collect(Collectors.toSet()));
          
Related