swagger-api / swagger-core

Examples and server integrations for generating the Swagger API Specification, which enables easy access to your REST API
http://swagger.io
Apache License 2.0
7.37k stars 2.17k forks source link

Using swagger with multiple API applications, swagger.json documents get mixed up #2099

Open kriegerkl opened 7 years ago

kriegerkl commented 7 years ago

We are running two separate Jersey APIs in the same server context, il.e. two separate classes extending com.sun.jersey.api.core.PackagesResourceConfig are configured in Tomcat 7's web.xml to be two accessible under two separate base paths. Simplified it's:

Api1 com.sun.jersey.spi.container.servlet.ServletContainer javax.ws.rs.Application api1.server.config.Api1ServerConfig Api1 /api1/* Api2 com.sun.jersey.spi.container.servlet.ServletContainer javax.ws.rs.Application api2.server.config.Api2ServerConfig Api2 /api2/*

The two APIs do not share any model classes or other code and are completely separate in every aspect incl security, filter chains etc. There are two separate application.wadl files (and their corresponding XSD schema) generated by Jersey, each describing only the resources from their respective API.

We are using swagger in the first API, configuring it using BeanConfig:

BeanConfig bc = new BeanConfig(); bc.setVersion("1.0"); bc.setTitle(" API1 Documentation"); bc.setSchemes(new String[]{"https"}); bc.setBasePath("/appRoot/api1"); bc.setResourcePackage("api1.resource"); bc.setScan(true);

Now we try to add Swagger to the second API as well, by declaring this in our Api2ServerConfig:

BeanConfig bc = new BeanConfig(); bc.setVersion("1.0"); bc.setTitle(" API2 Documentation"); bc.setSchemes(new String[]{"https"}); bc.setBasePath("/appRoot/api2"); bc.setResourcePackage("api2.resource"); bc.setScan(true);

Unfortunately while this results in two separate swagger.json documents under /appRot/api1/swagger.json and /appRoot/api2/swagger.json, the two APIs are not treated separately as we had hoped.

The contents of whichever swagger document we access first after starting the server are fine. If we access /appRoot/api1/swagger.json, it contains all the paths and types of API1 and nothing from API2's realm. If however we next access /appRoot/api2/swagger.json, it lists both its own resources AND every tag, path and type definition from API1. Vice versa, if /appRoot/api2/swagger.json is accessed first and then appRoot/api1/swagger.json, API2's document is correct, but itself is now leaking into API1's swagger.json. The title and basePath are correct in both documents.

It is the same problem like in #1359. It is not possible to have 2 BeanConfigs with 2 different BasePath.

andi-livn commented 7 years ago

Thanks @kriegerkl for re-opening this ticket. The suggested solution in the original thread appears to be for a different problem, namely how to add multiple resource packages to the same swagger API, rather than how to keep two APIs separated.

kirankonerukewill commented 7 years ago

Any solution for this, even I am facing the same problem.

andi-livn commented 7 years ago

Many many thanks to Dherik, who just emailed me this solution to the problem:

Did you try to set the ConfigId and ScannerId in the BeanConfig? I have the same issue, but I was using web.xml and add this parameters in my Application:

For api1: `

swagger.scanner.id
<param-value>api1</param-value>

swagger.config.id api1 swagger.use.path.based.config true ` For api2: ` swagger.scanner.id api2 swagger.config.id api2 swagger.use.path.based.config true ` Add the above init parameters to the two separate APIs' web.xml servlets. Then use setScannerId and setConfigId in your BeanConfig to set these IDs to api1 and api2. Using that approach works! Again, thank you very much for this.
dherik commented 7 years ago

@andi-livn , I'm glad that work for you! I posted the solution, but after that I thought that your problem is different and I deleted my answer. But you had already received the e-mail from Github :)

I think I posted the solution in another thread. I'm trying to find where now... lol.

All the secret for this works is in BaseApiListingResource (code). By default, the swagger load only one time (see the static initialized variable) and this is the problem that I have: show always the swagger documention from the first endpoint loaded. I can't explain only looking the code your case, in which the second (last) endpoint loaded is mandatory.

About setScannerId and setConfigId, when you set these variables, Swagger use another mechanism to control what endpoint was already loaded, using the static ConcurrentMap and controlling using a kind of "key" for that ConcurrentMap: the scannerId and configId values.

xmeng1 commented 6 years ago

@andi-livn Are you use the Guice with the servlet? if I use the Guice like the official demo https://github.com/swagger-api/swagger-samples/blob/master/java/java-jersey2-guice/src/main/webapp/WEB-INF/web.xml , how to config it?

xmeng1 commented 6 years ago

according to my test, which servlet module is configured firstly, the corresponding beanConfig will be enabled.

andi-livn commented 6 years ago

@xmeng1 We aren't using Guice and in fact not even Jersey 2, but the old 1.19, so unfortunately I cannot answer your question, sorry.

strangelookingnerd commented 5 years ago

@andi-livn @dherik Do you have any idea on how this would translate to Swagger 2.0?

andi-livn commented 5 years ago

Sorry @strangelookingnerd we have never made the switch to Swagger 2.x in our application, still on the 1.5.x branch.

strangelookingnerd commented 5 years ago

For anyone looking for a Swagger 2.x solution, here is what I did:

public abstract class AbstractApplication extends Application {

    @Context
    private ServletConfig _servletConfig;

    public AbstractApplication() {
        try {
            SwaggerConfiguration oasConfig = new SwaggerConfiguration().id(UUID.randomUUID().toString())
                    .prettyPrint(true)
                    .resourceClasses(getServiceApis().stream().map(Class::toString).collect(Collectors.toSet()));
            new JaxrsOpenApiContextBuilder<>().servletConfig(_servletConfig).openApiConfiguration(oasConfig)
                    .buildContext(true);
        } catch (OpenApiConfigurationException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }

    public abstract Set<Class<?>> getServiceApis();

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> resources = new HashSet<>(getServiceApis());
        // Add Swagger
        resources.add(OpenApiResource.class);
        return resources ;
    }
public class MyApplication extends AbstractApplication {

    @Override
    public Set<Class<?>> getServiceApis() {
        return Stream.of(MyServiceApi.class).collect(Collectors.toSet());
    }
}

This way I didn't have to tinker with the web(-fragment).xml and all of my Applications only had to extend the abstract and provide their Service APIs.

The only thing that is important here is that each and every Application has their own (unique) SwaggerConfiguration ID in order to be split into multiple swagger.jason properly.

frantuma commented 5 years ago

Generally in Swagger Core 2.0, you can achieve this using contexts and context ids, please check wiki Context section, specifically the last example.