How to specify my restful API for swagger-ui in spring-boot application - spring-boot

I am using spring-boot + jersey as restful implementation. I have setup the swagger and I am able to open the swagger ui on the browser. But the swagger-ui doesn't have any API to show, it is an empty page. Below is the code I setup for configuring swagger. How can I let swagger to scan my API definition in jersey?
SwaggerConfiguration.java
#Configuration
#EnableSwagger2
public class SwaggerConfiguration {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/com.hello.*"))
.build().pathMapping("/swagger2");
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("App API")
.description("App API")
.version("1.0.0-SNAPSHOT")
.termsOfServiceUrl("")
.contact("Cooltoo company")
.license("Public")
.licenseUrl("http://hello.com/")
.build();
}
JerseyConfiguration.java
#Configuration
#EnableSwagger2
#EnableAutoConfiguration
#Api(value = "home", description = "Demo API")
#ApplicationPath("/nursego")
public class JerseyConfiguration extends ResourceConfig {
public JerseyConfiguration() {
register(BadgeAPI.class);
register(MultiPartFeature.class);
register(OrderAPI.class);
register(NurseAPI.class);
configureSwagger();
}
private void configureSwagger() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.2");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:8080");
beanConfig.setBasePath("/nursego");
beanConfig.setResourcePackage("com.cooltoo.backend.api");
beanConfig.setPrettyPrint(true);
beanConfig.setScan(true);
register( io.swagger.jaxrs.listing.ApiListingResource.class );
register( io.swagger.jaxrs.listing.SwaggerSerializers.class );
}
}
When I open http://localhost:8080/swagger-ui.html, I see below image but none of them are from my API. I don't know where they are from

I used the BeanConfig class to embed the Swagger into my SpringBoot+Jersey implmentation, the code example is as follows,
#Component
#ApplicationPath( "/api" )
public class JerseyConfig extends ResourceConfig{
public JerseyConfig(){
// method for embedding the Swagger
configSwagger();
// registers the REST resource classes
configEndPoints();
}
private void configEndPoints(){
// here register all the REST resource classes
}
private void configSwagger(){
BeanConfig beanConfig = new BeanConfig();
beanConfig.setSchemes( new String[]{ "http" } );
beanConfig.setHost( "localhost:9001" );
beanConfig.setBasePath( "/api" );
beanConfig.setDescription( "REST API services for accessing the pcg application" );
beanConfig.setTitle( "RESTAPI" );
beanConfig.setVersion( "1.0.1" );
// this will tell Swagger config to scan only these packages
beanConfig.setResourcePackage( "com.aig.rest.web" );
beanConfig.setScan( true );
register( io.swagger.jaxrs.listing.ApiListingResource.class );
register( io.swagger.jaxrs.listing.SwaggerSerializers.class );
}
}

I believe #EnableSwagger2 annotation works if the endpoints are implemented using Spring MVC instead of Jersey (or any other JAX-RS impl).
I have detailed how to accomplish this in a blog post I created earlier this year, Microservices using Spring Boot, Jersey Swagger and Docker
Basically if you need to document your Jersey-implemented endpoints via Swagger, you would need to:
1)
Make sure your Spring Boot app scans for components located in specific packages (ie com.asimio.jerseyexample.config) via:
#SpringBootApplication(
scanBasePackages = {
"com.asimio.jerseyexample.config", "com.asimio.jerseyexample.rest"
}
)
2) Jersey configuration class implementation:
package com.asimio.jerseyexample.config;
...
#Component
public class JerseyConfig extends ResourceConfig {
#Value("${spring.jersey.application-path:/}")
private String apiPath;
public JerseyConfig() {
// Register endpoints, providers, ...
this.registerEndpoints();
}
#PostConstruct
public void init() {
// Register components where DI is needed
this.configureSwagger();
}
private void registerEndpoints() {
this.register(HelloResource.class);
// Access through /<Jersey's servlet path>/application.wadl
this.register(WadlResource.class);
}
private void configureSwagger() {
// Available at localhost:port/swagger.json
this.register(ApiListingResource.class);
this.register(SwaggerSerializers.class);
BeanConfig config = new BeanConfig();
config.setConfigId("springboot-jersey-swagger-docker-example");
config.setTitle("Spring Boot + Jersey + Swagger + Docker Example");
config.setVersion("v1");
config.setContact("Orlando L Otero");
config.setSchemes(new String[] { "http", "https" });
config.setBasePath(this.apiPath);
config.setResourcePackage("com.asimio.jerseyexample.rest.v1");
config.setPrettyPrint(true);
config.setScan(true);
}
}
3) Resource implementation using JAX-RS (Jersey) and Swagger annotations:
package com.asimio.jerseyexample.rest.v1;
...
#Component
#Path("/")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Api(value = "Hello resource", produces = "application/json")
public class HelloResource {
private static final Logger LOGGER = LoggerFactory.getLogger(HelloResource.class);
#GET
#Path("v1/hello/{name}")
#ApiOperation(value = "Gets a hello resource. Version 1 - (version in URL)", response = Hello.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Hello resource found"),
#ApiResponse(code = 404, message = "Hello resource not found")
})
public Response getHelloVersionInUrl(#ApiParam #PathParam("name") String name) {
LOGGER.info("getHelloVersionInUrl() v1");
return this.getHello(name, "Version 1 - passed in URL");
}
...
}
4) Make sure your app's Spring Boot configuration file makes a distinction between Spring MVC (for actuator endpoints) and Jersey (for resources) endpoints:
application.yml
...
# Spring MVC dispatcher servlet path. Needs to be different than Jersey's to enable/disable Actuator endpoints access (/info, /health, ...)
server.servlet-path: /
# Jersey dispatcher servlet
spring.jersey.application-path: /api
...

Related

Feign Client Custom Configuration Does Not Take Effect

I use feign in my project. I created a custom configuration but it is overridden by default configuration. Here are the steps:
SpringBootApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableFeignClients
public class MyApplication {}
FeignConfiguration.java
#RequiredArgsConstructor
public class FeignConfiguration{
private final MyConfigurationProperties myConfigProperties;
#Bean
public MetaDataRestClient metaDataRestClient(#Qualifier("metaDataHttpClient") okhttp3.OkHttpClient metaDataHttpClient) {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new OkHttpClient(metaDataHttpClient))
.encoder(new JacksonEncoder(XML_MAPPER))
.decoder(new JacksonDecoder(XML_MAPPER))
.contract(new SpringMvcContract())
.logger(new Slf4jLogger(MetaDataRestClient.class))
.logLevel(Logger.Level.FULL)
.target(MetaDataRestClient.class, myConfigProperties.getMetadata().getEndpoint());
}
#Primary
#Bean(name = "metaDataHttpClient")
public okhttp3.OkHttpClient metaDataHttpClientWithProxy() {
return OkHttpUtil.createNewHttpClientBuilderWithProxy(myConfigProperties.getMetadata().getFeignClient().getConnectTimeout(),
myConfigProperties.getMetadata().getFeignClient().getReadTimeout()).build();
}
MetaDataRestClient.java
#FeignClient(name = "metaDataRestClient", url = "https://myurl.net", configuration = FeignConfiguration.class)
public interface MetaDataRestClient {
#Headers("Content-Type: text/xml")
#GetMapping("/metadata")
public EntityDescriptor getMetadaData();
}
I see that the metaDataRestClient bean is triggered on startup, but when I dig into feign library code, I see that this method is triggered twice: first time with my custom OkHttpClient, and second time with somehing called org.springframework.cloud.sleuth.instrument.web.client.feign.LazyClient. So my custom OkHttpClient is overridden by this lazy client.
Here is the related Feign library code that is being triggered twice:
FeignBuilder.java
public Builder client(Client client) {
this.client = client;
return this;
}
I do not have any feign configurationn in my application.yaml file. What could be the reason for that?
Thanks.

Multiple rest templates in spring boot

I want to configure multiple rest template clients to access different API's. Both are having different authorization headers. I already configured one, Same way configured other rest template too, but that throws error bean 'restTemplate' defined in class path resource .class could not be registered..
#Configuration
public class RestTemplateConfig {
#Autowired
private HeaderRequestInterceptor headerRequestInterceptor;
//constructor
public RestClientConfig() {}
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(headerRequestInterceptor));
return restTemplate;
}
}
HeaderRequestInterceptor has base64 encoded authorization, so could not post that code here.
Another RestTemplate:
#Configuration
public class AnotherRestClientConfig {
#Autowired
private AnotherHeaderRequestInterceptor anotherHeaderRequestInterceptor;
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(anotherHeaderRequestInterceptor));
return restTemplate;
}
}
Could someone let me know how to configure multiple rest templates in an application.
you could use #Qualifier as mentioned by #VirtualTroll. Or create a specific client bean per api and hold the restemplate instance there.
#Component
public class ApiClient1 {
private final RestTemplate customRestTemplate;
public ApiClient1() {
this.customRestTemplate = ...
}
public void useApi() {
}
}

Working of routing functions (reactive-webflux) with spring cloud contract

I am building a spring webflux project in which I have implemented Routing function as controllers with spring customer driven contract.
I am facing issue while the tests cases are getting executed and I haven't found any solution regarding this online anywhere.
My requirement is to execute the generated tests and loading the application context for n no. of tests.Below is the sample code:
==========Base class===========
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class GetBase extends SampleParentBase{
protected MockMvc mvc ;
#MockBean
private PersonService service;
#MockBean
private ServerRequest request;
#Before
public void setup() throws Exception {
mvc = MockMvcBuilders
.webAppContextSetup(context).build();
}
}
============groovy file==================
Contract.make {
description "."
request {
method GET()
urlPath('/person/1')
headers {
contentType(applicationJson())
header('''Accept''', applicationJson())
}
}
response {
headers {
contentType(applicationJson())
}
status 200
bodyMatchers {
jsonPath('$.name', byType())
jsonPath('$.age', byType())
jsonPath('$.pId', byType())
}
body ('''{
"name":"string",
"age":20,
"pId":"string"
}''')
}
}
=======================Router Configuration====================
#Configuration
public class RoutesConfiguration {
#Autowired
PersonRespository personRespository;
#Bean
RouterFunction<?> routes() {
return nest(path("/person"),
route(RequestPredicates.GET("/{id}"),
request -> ok().body(personRespository.findById(request.pathVariable("id")), Person.class))
.andRoute(method(HttpMethod.GET),
request -> ok().body(personRespository.findAll(), Person.class))
);
}
}
We've updated the snapshot documentation to contain information on how to work with Web Flux. You can check it out here https://cloud.spring.io/spring-cloud-contract/2.0.x/single/spring-cloud-contract.html#_working_with_web_flux and here you have a sample with Web Flux https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/master/producer_webflux . Let me copy the part of the documentation for your convenience
Spring Cloud Contract requires the usage of EXPLICIT mode in your generated tests to work with Web Flux.
Maven.
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<testMode>EXPLICIT</testMode>
</configuration>
</plugin>
Gradle.
contracts {
testMode = 'EXPLICIT'
}
The following example shows how to set up a base class and Rest Assured for Web Flux:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = BeerRestBase.Config.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = "server.port=0")
public abstract class BeerRestBase {
// your tests go here
// in this config class you define all controllers and mocked services
#Configuration
#EnableAutoConfiguration
static class Config {
#Bean
PersonCheckingService personCheckingService() {
return personToCheck -> personToCheck.age >= 20;
}
#Bean
ProducerController producerController() {
return new ProducerController(personCheckingService());
}
}
}

How do I use Swagger with Jersey in Spring?

I am trying to use Swagger (2.6) with Jersey (1.5) in SpringBoot (1.5.8)
My calls to my public API work fine http://localhost:57116/rest/customer/list
When I load up the http://localhost:57116/swagger-ui.html site, it displays Swagger with a lot of default APIs, but it does not display my API.
I tried following this Config Swagger-ui with Jersey but it still does not come back.
This is my JerseyConfig
#Configuration
#ApplicationPath("rest")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig()
{
register(CustomerImpl.class);
configureSwagger();
}
public void configureSwagger()
{
this.register(ApiListingResource.class);
this.register(SwaggerSerializers.class);
BeanConfig beanConfig = new BeanConfig();
beanConfig.setTitle("Swagger sample app");
beanConfig.setVersion("1.0.0");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:57116");
beanConfig.setBasePath("/rest");
beanConfig.setResourcePackage("com.mypackage");
beanConfig.setScan(true);
}
}
This is my application class
#SpringBootApplication
#EnableDiscoveryClient
#EnableSwagger2
public class CustomerApplication {...}
This is my endpoint
#Component
#Path("customer")
#Api(value = "Customer API")
public class CustomerImpl {
#Path("list")
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(value = "list")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Success", response = List.class),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 403, message = "Forbidden"),
#ApiResponse(code = 404, message = "Not Found"),
#ApiResponse(code = 500, message = "Failure")})
public String[] getList()
{
return new String[]{"IBM", "Microsoft", "Victor"};
}
}
When I try a similar config using Spring RestController, it works fine, but with Jersey I don't see my public API. It seems to be ignoring my Swagger configuration
Are you using org.springframework.boot:spring-boot-starter-jersey dependency or org.springframework.boot:spring-boot-starter-web in your pom.xml?
It also seems you are using the Swagger bean generated by SpringFox and not by your BeanConfig.
In order for Swagger to work, you should rely on spring-boot-starter-jersey and remove all SpringFox dependencies including #EnableSwagger2 annotation.
The swagger.json in a Jersey application is generated by the io.swagger:swagger-jersey2-jaxrs library. As for the Swagger UI, you can use vanilla Swagger UI static resources.
You may also need to change the resource package in your BeanConfig to your application base package. Currently, you have:
beanConfig.setResourcePackage("io.swagger.resources");
Here is a working example with Spring Boot, Jersey, and Swagger.
In this example, the resource package is set to the application base package:
beanConfig.setResourcePackage("com.basaki");

Springfox swagger - no api-docs with spring boot jersey and gradle

I have a spring boot application with jersey and gradle, and I am trying to automatically generate the API documentation using springfox.
I have followed the steps here: http://springfox.github.io/springfox/docs/current/
Here is what I did:
build.gradle:
dependencies {
.........
//Swagger
compile "io.springfox:springfox-swagger2:2.4.0"
compile "io.springfox:springfox-bean-validators:2.4.0"
compile 'io.springfox:springfox-swagger-ui:2.4.0'
}
Spring boot Application:
#SpringBootApplication
#EnableSwagger2
public class AnalyzerServiceApplication{
public static void main(String[] args) {
SpringApplication.run(AnalyzerServiceApplication.class, args);
}
#Bean
public Docket analyzerApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.pathMapping("/")
.directModelSubstitute(LocalDate.class, String.class)
.genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(
newRule(typeResolver.resolve(DeferredResult.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class)))
.useDefaultResponseMessages(false)
.globalResponseMessage(RequestMethod.GET,
newArrayList(new ResponseMessageBuilder()
.code(500)
.message("500 message")
.responseModel(new ModelRef("Error"))
.build()))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.enableUrlTemplating(true)
.globalOperationParameters(
newArrayList(new ParameterBuilder()
.name("someGlobalParameter")
.description("Description of someGlobalParameter")
.modelRef(new ModelRef("string"))
.parameterType("query")
.required(true)
.build()))
.tags(new Tag("Pet Service", "All apis relating to pets"))
;
}
#Autowired
private TypeResolver typeResolver;
private ApiKey apiKey() {
return new ApiKey("mykey", "api_key", "header");
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("/anyPath.*"))
.build();
}
List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope
= new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return newArrayList(
new SecurityReference("mykey", authorizationScopes));
}
#Bean
SecurityConfiguration security() {
return new SecurityConfiguration(
"test-app-client-id",
"test-app-client-secret",
"test-app-realm",
"test-app",
"apiKey",
ApiKeyVehicle.HEADER,
"api_key",
"," /*scope separator*/);
}
#Bean
UiConfiguration uiConfig() {
return new UiConfiguration("validatorUrl");
}
Now the controller (Jersey)
#Api(value = "/widget")
#Path("/widget")
#Component
public class WidgetController extends BaseController {
#Autowired
private WidgetService widgetService;
#GET
#Path("/secHealth")
#ApiOperation(value = "Find pet by ID", notes = "Returns a pet when ID < 10. ID > 10 or nonintegers will simulate API error conditions", response = Pet.class)
#ApiResponses(value = { #ApiResponse(code = 400, message = "Invalid ID supplied"),
#ApiResponse(code = 404, message = "Pet not found") })
public Response getPet() {
//Do something
}
When I start the server and navigate to http://localhost:8080/swagger-ui.html, I can see the "green" UI screen with only the basic-error-controller listed there. My own controller is not there.
What did I do wrong?
Thanks
Guy
As of version 2.5.0 springfox only supports spring-mvc controllers. Jax-rs implementations like jersey aren't supported.
The current alternative to using springfox is to use the swagger-core library for jax-rs/jersey based services.
It does have the hooks needed to implement support for jersey in 2.6+. Here is an excerpt of a way to implement it in this issue
Currently ResourceConfig has a method called "getClasses" which will
list everything registerted. like Resources, Filters,etc... Maybe this
could help. But be aware that the returning classes could also be
filters or any other stuff you could register with jersey2.
To be able to see Jersey methods from Springfox swagger UI:
Configure your Swagger with Jersey following https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-2.X-Project-Setup-1.5
Configure Springfox Swagger following http://springfox.github.io/springfox/docs/current/
Add in you SpringBoot application configuration class (annotated with #Configuration):
#Value("${springfox.documentation.swagger.v2.path}")
private String swagger2Endpoint;
In application.properties add reference to your Jersey swagger.json:
springfox.documentation.swagger.v2.path=/{change it to your Jersey api path}/swagger.json
Now you should be able to see Jersey Swagger generated api from Springfox Swagger UI page.
Thanks #Dilip-Krishnan for the springfox update and #Guy-Hudara for the question, I came up with the following solution to get swagger support in my springboot jersey powered app :
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* As of version 2.5.0 springfox only supports spring-mvc controllers. Jax-rs implementations like jersey aren't supported.
*
* Fortunately io.swagger::swagger-jersey2-jaxrs::1.5.3 have the hooks needed to implement support for jersey in 2.6+.
*
* some pointers I used to get this swagger config done and swagger-core, springboot and jersey integrated:
* http://stackoverflow.com/questions/37640863/springfox-swagger-no-api-docs-with-spring-boot-jersey-and-gardle
* https://www.insaneprogramming.be/blog/2015/09/04/spring-jaxrs/
* https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-2.X-Project-Setup-1.5#adding-the-dependencies-to-your-application
*
*/
#Configuration
public class SwaggerConfiguration {
#Autowired
ResourceConfig resourceConfig;
#PostConstruct
public void configure() {
resourceConfig.register(ApiListingResource.class);
resourceConfig.register(SwaggerSerializers.class);
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.2");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:8888");
beanConfig.setBasePath("/api");
beanConfig.setResourcePackage("com.my.resource");
beanConfig.setPrettyPrint(true);
beanConfig.setScan(true);
}
}
That worked out great for me

Resources