12

I use swagger for documenting endpoints of a resteasy API, and I serve the swagger.json description using a servlet with a method like this:

public void init(ServletConfig config) throws ServletException
{
    super.init(config);
    BeanConfig beanConfig = new BeanConfig();
    beanConfig.setHost("localhost:8080");       
    beanConfig.setBasePath("/api");
    beanConfig.setResourcePackage("my.rest.resources");
    beanConfig.setScan(true);       
}

and I can access the swagger.json at localhost:8080/api/swagger.json. However, my collaborators would like to avoid extra servlets other than the resteasy servlet, and I wonder if I can serve the swagger generated json from a method from a resource class, something like this:

@GET
@Path("/myswagger")
@Produces("application/json")
public String myswagger(@Context UriInfo uriInfo) 
{
    Swagger swagger = new Swagger();
    // Do something to retrieve the Swagger Json as a string
    // ... 
    return(swaggerJsonString);
}

and then access the swagger generated json via localhost:8080/api/myswagger. Is this possible?

egorlitvinenko
  • 2,736
  • 2
  • 16
  • 36
user1981275
  • 13,002
  • 8
  • 72
  • 101

3 Answers3

5

Possible and pretty simple

import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.*;
import io.swagger.jaxrs.Reader;
import io.swagger.models.Swagger;
import io.swagger.util.Json;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.net.HttpURLConnection;
import java.util.HashSet;
import java.util.Set;


@SwaggerDefinition(
        info = @Info(
                title = "title",
                version = "0.2",
                description = "description",
                termsOfService = "termsOfService",
                contact = @Contact(
                        name = "contact",
                        url = "http://contact.org",
                        email = "info@contact.org"
                ),
                license = @License(
                        name = "Apache2",
                        url = "http://license.org/license"
                )
        ),
        host = "host.org",
        basePath = "",
        schemes = SwaggerDefinition.Scheme.HTTPS
)
public class SwaggerMain {

    @Path("/a")
    @Api(value = "/a", description = "aaa")
    public class A {

        @GET
        @Path("/getA")
        @Produces(MediaType.APPLICATION_JSON)
        @ApiOperation(value = "Method for A.")
        @ApiResponses(value = {
                @ApiResponse(code = HttpURLConnection.HTTP_OK, message = "OK"),
                @ApiResponse(code = HttpURLConnection.HTTP_UNAUTHORIZED, message = "Unauthorized"),
                @ApiResponse(code = HttpURLConnection.HTTP_NOT_FOUND, message = "Not found"),
                @ApiResponse(code = HttpURLConnection.HTTP_INTERNAL_ERROR, message = "Internal server problems")
        })
        public String getA() {
            return "Hello, A";
        }

    }

    @Path("/b")
    @Api(value = "/b", description = "bbb")
    public class B {
        @GET
        @Path("/getA")
        @Produces(MediaType.APPLICATION_JSON)
        @ApiOperation(value = "Method for B.")
        @ApiResponses(value = {
                @ApiResponse(code = HttpURLConnection.HTTP_OK, message = "OK"),
                @ApiResponse(code = HttpURLConnection.HTTP_UNAUTHORIZED, message = "Unauthorized"),
                @ApiResponse(code = HttpURLConnection.HTTP_NOT_FOUND, message = "Not found"),
                @ApiResponse(code = HttpURLConnection.HTTP_INTERNAL_ERROR, message = "Internal server problems")
        })
        public String getA() {
            return "Hello, B";
        }
    }

    public static void main(String[] args) {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(SwaggerMain.class);
        classes.add(A.class);
        classes.add(B.class);
        Swagger swagger = new Reader(new Swagger()).read(classes);
        try {
            System.out.println(Json.mapper().writeValueAsString(swagger));;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

}

Gives json:

{
  "swagger": "2.0",
  "info": {
    "description": "description",
    "version": "0.2",
    "title": "title",
    "termsOfService": "termsOfService",
    "contact": {
      "name": "contact",
      "url": "http://contact.org",
      "email": "info@contact.org"
    },
    "license": {
      "name": "Apache2",
      "url": "http://license.org/license"
    }
  },
  "host": "host.org",
  "tags": [
    {
      "name": "a"
    },
    {
      "name": "b"
    }
  ],
  "schemes": [
    "https"
  ],
  "paths": {
    "/a/getA": {
      "get": {
        "tags": [
          "a"
        ],
        "summary": "Method for A.",
        "description": "",
        "operationId": "getA",
        "produces": [
          "application/json"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server problems"
          }
        }
      }
    },
    "/b/getA": {
      "get": {
        "tags": [
          "b"
        ],
        "summary": "Method for B.",
        "description": "",
        "operationId": "getA",
        "produces": [
          "application/json"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server problems"
          }
        }
      }
    }
  }
}
egorlitvinenko
  • 2,736
  • 2
  • 16
  • 36
  • This works very well, thank you! I managed to serve the swagger json from an endpoint by just returning the `Swagger` object! – user1981275 Sep 01 '17 at 12:55
  • I liked both answers of Hugues M. and egorlitvinenko and both solutions work fine. I'll accept egorlitvinenko's solution because his solution is actually what I asked for. – user1981275 Sep 01 '17 at 15:26
  • This solution its ok for RestEasy. Now, I need that works to Spring, but "paths" in JSON is blank. Have any idea? – devinvestidor Jul 17 '18 at 19:21
  • I'd love to see a v3 swagger solution to this. – ndtreviv Aug 28 '20 at 11:31
2

So you tried hooking up swagger to your resteasy application using automatic scanning and registration.

When using automatic scanning, swagger-core is unable to detect the resources automatically. To resolve that, you must tell swagger-core which packages to scan. The suggested solution is to use the BeanConfig method (most likely as a Servlet).

So you did that, but now you want the same without needing a separate servlet.

You should probably not try manually hooking swagger into each resource & provider of your application. You should just annotate them with @Api (I'm assuming you already do that), and then, since you use RESTEasy, you can move your BeanConfig to your existing resteasy Application, or to a custom one, in any case that will be taken care of by your existing resteasy servlet. See using a custom Application subclass.

import io.swagger.jaxrs.config.BeanConfig;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

public class MyApplication extends Application {

    public MyApplication() {
        BeanConfig beanConfig = new BeanConfig();
        beanConfig.setVersion("1.0");
        beanConfig.setSchemes(new String[] { "http" });
        beanConfig.setTitle("My API"); // <- mandatory
        beanConfig.setHost("localhost:8080");       
        beanConfig.setBasePath("/api");
        beanConfig.setResourcePackage("my.rest.resources");
        beanConfig.setScan(true);
    }

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> set = new HashSet<Class<?>>();
        set.add(MyRestResourceFoo.class); // Add your own application's resources and providers
        set.add(io.swagger.jaxrs.listing.ApiListingResource.class);
        set.add(io.swagger.jaxrs.listing.SwaggerSerializers.class);
        return set;
    }
}

Your resources & providers should stay clean of Swagger code except for annotations. For example, here is a simple echo service:

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;

@Api
@Path("/echo")
public class EchoRestService {

    @ApiOperation(value = "Echoes message back")
    @GET
    @Path("/{param}")
    public Response printMessage(@PathParam("param") String msg) {
        String result = "Echoing: " + msg;
        return Response.status(200).entity(result).build();
    }
}

Then visit http://localhost:8080/api/swagger.json to get the JSON string (same with .yaml).

I've pushed an example to GitHub, it's very simple and depending on your existing application you'll probably need a bit more details, but it could help you get started.

Hugues M.
  • 19,846
  • 6
  • 37
  • 65
  • Thanks for your answer, your github example and the hint to keep only annotations in the resource classes! I added the swagger `beanconfig` to my `Application` subclass and this seems to work fine! If you don't mind, I have two more questions (next comments) – user1981275 Aug 28 '17 at 14:46
  • In the `getClasses` `Application` subclass, you explicitly specify the resource classes. Is this even necessary? It works fine for me if I just `return null` ... – user1981275 Aug 28 '17 at 14:50
  • My second question is a bit of a noob question (new to Java and the web stuff)... I'd like to play around with the SwagEasy example, but I don't know how to deploy (I am using wildfly). When I copy the `swageasy.war` into wildfly's `standalone/deployments/` directory, I cannot find the resource at `localhost:8080/swageasy`. I also tried `wildfly-maven-plugin`, and no success. What am I missing? – user1981275 Aug 28 '17 at 14:59
  • Hmm sorry I'm not sure about your first question right now, will have limited time today to verify (I'll see what I can do in a few hours). For your second question, after deployment, there is nothing directly under `localhost:8080/swageasy` (this is normal, I have put nothing under webapp root), but if you try `localhost:8080/swageasy/echo/something` the service should respond `Echoing: something`, and if you try `localhost:8080/swageasy/swagger.json` you should get the swagger json. – Hugues M. Aug 28 '17 at 17:44
  • If it works for you by returning null, you probably have some other way enabled somewhere to get RestEASY to load the resources. In that case you should perhaps not override `getClasses()` at all (or if you really want to, maybe return `Collections.emptySet()` instead of `null`). – Hugues M. Aug 28 '17 at 18:03
1

Assuming you have access to the json file from your java application, you should just be able to read in the json file and return that as the String return value of your method.

As a super simple example:

String swaggerJsonString = new String(Files.readAllBytes(Paths.get("swagger.json")));

You will have to figure out how to find the file's path in your application.

Scott Newson
  • 2,745
  • 2
  • 24
  • 26