Swagger not generating the REST documentation - java

I'm trying to let Swagger autogenerate che documentation of my REST APIs but I only get a partial result.
I'm using Resteasy. I added the Maven Swagger dependency
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jaxrs</artifactId>
<version>1.5.3</version>
</dependency>
Then I configured my Application object
package com.myapp.init;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
#ApplicationPath("/rest")
public class WebappInit extends Application {
public WebappInit() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.0");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("theIP:8080");
beanConfig.setBasePath("/myapp/rest/");
beanConfig.setResourcePackage("the.resource.package");
beanConfig.setScan(true);
beanConfig.setPrettyPrint(true);
}
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
// here I add my REST WSs
s.add(ApiListingResource.class);
s.add(SwaggerSerializers.class);
return s;
}
}
Then I run the web application (on a Wildfly 9 server) and go to the URL http://localhost:8080/myapp/rest/swagger.json. That's what I get
{
swagger: "2.0",
info: {
version: "1.0.0"
},
host: "10.17.36.215:8080",
basePath: "/devops/rest/",
schemes: [
"http"
]
}
It seems that Swagger cannot build the REST documentation, even though my REST endpoints are reachable and are added to the Swagger list of resources.
What can be the problem?
Thank you
Giulio
Update: I checked that in the Swagger init method BeanConfig.classes() my REST classes are correctly discovered.

You need to add an #Api annotation to your resource classes.
For example:
package my.sample;
import io.swagger.annotations.Api;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.core.Response;
#Api
#Path ("/mypath")
public class MyResource
{
#GET
public Response myEndpoint()
{
return Response.ok ();
}
}

I think I got your problem. Your root service extends Application that allows dynamic building of your resources hierarchy. I believe that swagger even cannot support this technique because it generates its metadata (json files) at compile time.
I always use annotation based REST services, i.e. each resource is annotated with appropriate #Path annotation. The framework initializes all resources automatically, so I do not have to extend my root resource from Application and implement getClasses(). In this case swagger can extract all your resources and generate json files at compile time by analyzing of JAXRS annotations like #Path, #PathParam, #GET, #POST etc.

You have to add #Api annotation to your resource class and load the resource package in setResourcePackage method. It should do the magic.

Related

After Update of Springfox from 2.9.2 to 2.10.4 error "Unable to infer base url. ..."

I just updated the Springfox dependency in my Spring Boot application (version 2.3.1.RELEASE) from 2.9.2 to 2.10.4.
<spring-boot.version>2.3.1.RELEASE</spring-boot.version>
<swagger.version>2.10.4</swagger.version>
Due to class changes in the springfox.documentation.* packages, I had to change the annotation in my configuration class from
#EnableSwagger2
to
#EnableSwagger2WebMvc
Also the Predicate import changed from Google Guave to java.util.function. My current Configuration class looks like this
package de.rewe.zlip.config;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
#Configuration
#EnableSwagger2WebMvc
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).globalOperationParameters(globalOperationParameters())
.select()
.apis(sourceScannedForRestApis())
.paths(PathSelectors.any())
.build()
.apiInfo(apiEndPointsInfo())
.genericModelSubstitutes(Optional.class);
}
private List<Parameter> globalOperationParameters() {
List<Parameter> operationParameters = new LinkedList<>();
Parameter authorizationParameter = new ParameterBuilder().name("Authorization")
.description("Your Bearer token ")
.modelRef(new ModelRef("string"))
.parameterType("header")
.build();
operationParameters.add(authorizationParameter);
return operationParameters;
}
private Predicate<RequestHandler> sourceScannedForRestApis() {
return RequestHandlerSelectors.basePackage("de.my.package");
}
private ApiInfo apiEndPointsInfo() {
return new ApiInfoBuilder().title("TEST SERVER REST API")
.description("REST API provided for the TEST web application")
.contact(contactInfo())
.version("v1.0")
.build();
}
private Contact contactInfo() {
return new Contact("Test Team", "https://", "test#test.com");
}
}
When I open http://localhost:8080/swagger-ui.html now, I get the following message:
Unable to infer base url. This is common when using dynamic servlet
registration or when the API is behind an API Gateway. The base url is
the root of where all the swagger resources are served. For e.g. if
the api is available at http://example.org/api/v2/api-docs then the
base url is http://example.org/api/. Please enter the location
manually:
Needless to say, the same configuration (except of the 2 changes mentioned above) worked with 2.9.2. Most of the tips in earlier questions are adding
#EnableSwagger2
but since this annotation has changed in 2.10.X to either #EnableSwagger2Mvc or #EnableSwagger2Flux this won't help.
From springfox issue tracker:
Oh please dont use 2.10... it was an intermediate step so that people who were on 3.0.0-SNAPSHOT can continue using a released version if needed.
I suggest reverting back to 2.9.2 for the time being.
You can try below.
Add this dependency to your pom.xml
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-data-rest</artifactId>
<version>${your.spring.fox.version}</version>
</dependency>
Add this line to either your main class or your swagger config class
#Import(SpringDataRestConfiguration.class)

Simple Spring Java #Configuration to #Autowire without #Bean

I want to make use of spring #Autowired in a java rest project. For the last days, I am trying to set up a simple spring java project with java configuration without explicit bean configuration to check that functionality out. But I can't get it to work. I may be missing something fundamental.
None of the approaches I found in the web and on this site solved my problem so far. I couldn't find a sample for exactly what I'm trying to achieve too. This is mainly due to the amount of different spring versions and approaches spread over the web.
Here is an as easy as I could come up with Java Spring rest sample. I added a few comments with how I interpret spring annotations, as I may err here too:
App base class
package restoverflow;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/")
public class App extends Application {
}
Config class
package restoverflow;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration //this is a configuration class and also found by spring scan
#ComponentScan //this package and its subpackages are being checked for components and its subtypes
public class AppConfig {
}
Some Pojo
package restoverflow;
public class Pojo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
A service
package restoverflow;
import org.springframework.stereotype.Service;
#Service //this is a subtype of component and found by the componentscan
public class PojoService {
public Pojo getPojo(){
Pojo pojo = new Pojo();
pojo.setName("pojoName");
return pojo;
}
}
And finally a resource where the autowiring of the service should be done
package restoverflow;
import javax.ws.rs.GET;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/resource")
#Controller //this is a subtype of component and found by the componentscan
public class Resource {
#Autowired //this tells to automatically instantiate PojoService with a default contructor instance of PojoService
private PojoService pojoService;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Pojo getPojo() {
return pojoService.getPojo();
}
}
Pom:
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
...
I want the pojoService to be instantiated. But I get a NullPointerException.
It looks like you are using Field level injection.
Please go through below link to understand all types of injections:
https://www.vojtechruzicka.com/field-dependency-injection-considered-harmful/
Can't see any clear reason why pojoService is coming null.
Please check whether pojoService bean is being initialized properly. It might be due to pojoService bean has not been initialized and you are getting null in your controller.
A nullpointer instead of a NoSuchBeanDefinitionException is more of an indication that the Spring context is not loaded at all, rather than loaded improperly.
If you're using Spring boot, modify your main class to initialize Spring:
#SpringBootApplication
#ApplicationPath("/")
public class App extends Application {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Or else (as the pom.xml snippet did not mention Spring boot), initialize Spring manually by initializing a ClassPathXmlApplicationContext and adding <context:component-scan base-package="restoverflow" /> in your applicationContext.xml.

How can I serve static html from spring boot?

I ran the spring-boot-sample-web-static project from here, made this alteration to the pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
And added this class to serve a duplicate page index2.html from the same static folder location:
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class Rester {
#RequestMapping(value = "/rand", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
private RandomObj jsonEndpoint() {
return new RandomObj();
}
#RequestMapping(value = "/tw")
public String somePg() {
return "index2";
}
}
The json url works fine, but when I try to access localhost:8080/tw I get a blank page, and this error in the console:
2017-02-22 15:37:22.076 ERROR 21494 --- [nio-8080-exec-9] o.s.boot.web.support.ErrorPageFilter : Cannot forward to error page for request [/tw] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false
Am I doing something wrong?
Static files should be served from resources, not from a controller.
Spring Boot will automatically add static web resources located within
any of the following directories:
/META-INF/resources/
/resources/
/static/
/public/
refs:
https://spring.io/blog/2013/12/19/serving-static-web-content-with-spring-boot
https://spring.io/guides/gs/serving-web-content/
In Spring boot, /META-INF/resources/, /resources/, static/ and public/ directories are available to serve static contents.
So you can create a static/ or public/ directory under resources/ directory and put your static contents there. And they will be accessible by: http://localhost:8080/your-file.ext. (assuming the server.port is 8080)
You can customize these directories using spring.resources.static-locations in the application.properties.
For example:
spring.resources.static-locations=classpath:/custom/
Now you can use custom/ folder under resources/ to serve static files.
This is also possible using Java config in Spring Boot 2:
#Configuration
public class StaticConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/custom/");
}
}
This confugration maps contents of custom directory to the http://localhost:8080/static/** url.
I am using :: Spring Boot :: (v2.0.4.RELEASE) with Spring Framework 5
Spring Boot 2.0 requires Java 8 as a minimum version. Many existing APIs have been updated to take advantage of Java 8 features such as: default methods on interfaces, functional callbacks, and new APIs such as javax.time.
Static Content
By default, Spring Boot serves static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext. It uses the ResourceHttpRequestHandler from Spring MVC so that you can modify that behavior by adding your own WebMvcConfigurer and overriding the addResourceHandlers method.
By default, resources are mapped on /** and located on /static directory.
But you can customize the static loactions programmatically inside our web context configuration class.
#Configuration #EnableWebMvc
public class Static_ResourceHandler implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// When overriding default behavior, you need to add default(/) as well as added static paths(/webapp).
// src/main/resources/static/...
registry
//.addResourceHandler("/**") // « /css/myStatic.css
.addResourceHandler("/static/**") // « /static/css/myStatic.css
.addResourceLocations("classpath:/static/") // Default Static Loaction
.setCachePeriod( 3600 )
.resourceChain(true) // 4.1
.addResolver(new GzipResourceResolver()) // 4.1
.addResolver(new PathResourceResolver()); //4.1
// src/main/resources/templates/static/...
registry
.addResourceHandler("/templates/**") // « /templates/style.css
.addResourceLocations("classpath:/templates/static/");
// Do not use the src/main/webapp/... directory if your application is packaged as a jar.
registry
.addResourceHandler("/webapp/**") // « /webapp/css/style.css
.addResourceLocations("/");
// File located on disk
registry
.addResourceHandler("/system/files/**")
.addResourceLocations("file:///D:/");
}
}
http://localhost:8080/handlerPath/resource-path+name
/static /css/myStatic.css
/webapp /css/style.css
/templates /style.css
In Spring every request will go through the DispatcherServlet. To avoid Static file request through DispatcherServlet(Front contoller) we configure MVC Static content.
As #STEEL said static resources should not go through Controller. Thymleaf is a ViewResolver which takes the view name form controller and adds prefix and suffix to View Layer.
As it is written before, some folders (/META-INF/resources/, /resources/, /static/, /public/) serve static content by default, conroller misconfiguration can break this behaviour.
It is a common pitfall that people define the base url of a controller in the #RestController annotation, instead of the #RequestMapping annotation on the top of the controllers.
This is wrong:
#RestController("/api/base")
public class MyController {
#PostMapping
public String myPostMethod( ...) {
The above example will prevent you from opening the index.html. The Spring expects a POST method at the root, because the myPostMethod is mapped to the "/" path.
You have to use this instead:
#RestController
#RequestMapping("/api/base")
public class MyController {
#PostMapping
public String myPostMethod( ...) {
I had to add thymeleaf dependency to pom.xml. Without this dependency Spring boot didn't find static resources.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
You can quickly serve static content in JAVA Spring-boot App via thymeleaf (ref: source)
I assume you have already added Spring Boot plugin apply plugin: 'org.springframework.boot' and the necessary buildscript
Then go ahead and ADD thymeleaf to your build.gradle ==>
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Lets assume you have added home.html at src/main/resources
To serve this file, you will need to create a controller.
package com.ajinkya.th.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class HomePageController {
#RequestMapping("/")
public String homePage() {
return "home";
}
}
Thats it ! Now restart your gradle server. ./gradlew bootRun

How to annotate JAX-RS on an interface while using Jersey

This question had been asked a few times before, however the answers doesn't seem to work and/or Jersey has moved on with more changes.
I'm exposing some REST APIs using JAX-RS and Jersey (version 2.24). And I wish to annotate the interface with JAX-RS and a concrete implementation (without any annotations). However, since this patch Jersey stopped supporting this possibility. As far as I understand the spec, it doesn't strictly prohibit doing that.
If a subclass or implementation method has any JAX-RS annotations then all of the annotations on the superclass or interface method are ignored.
implying that it is totally okay to do that. In many cases it is good to use an interface, and have a server and client each have their respective implementations.
There are plenty of solutions out there,
Use a ResourceConfig and do a registerClasses(MyImplementation.class) . However, this doesn't work.
Disable the package scanning configuration in web.xml, create a custom javax.ws.rs.Application and do a register of your implementation from there. Doesn't work.
use a ResourceConfig and define a custom AbstractBinder and do a bind so that Jersey's dependency injection can find the concrete implementations. Doesn't work.
Use RESTEasy. RESTEasy doesn't seem to impose the interface restrictions as in Jersey. Never tried it myself.
I would appreciate if someone can share their experience with this. Any help on how to get Jersey working would be great too. As for the option (4) is it really necessary to switch ? A sample code below.
MyResource
package com.foo;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
#Path("/hello")
public interface MyResource {
#GET
public String sayHello();
}
MyResourceImpl
package com.bar;
public class MyResourceImpl implements MyResource {
#Override
public String sayHello() {
return "Hello Jersey";
}
}
Also have a web.xml that has the package scanning enabled to scan com.foo
If you want to separate Resource interface from implementation (allowing you to use the interface with some REST client like resteasy client) you can use #RequestScoped on the implementation. Thus, this bean could use injected resources like EJB, EntityManager, ...
Using your sample :
import javax.ws.rs.GET;
import javax.ws.rs.Path;
#Path("/hello")
public interface MyResource {
#GET
public String sayHello();
}
MyResourceImpl
package com.bar;
#RequestScoped
public class MyResourceImpl implements MyResource {
#Override
public String sayHello() {
return "Hello Jersey";
}
}
Nevertheless, you have to take into consideration that as soon as you use specific JAX-RS classes in your implementation code (like UriInfo, Response object, ...) you will create a coupling between your implementation and the JAX-RS API.
In Jersey,We should Put the class level #Path on the implementation instead of the interface.
package com.foo;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
#Path("/hello")
public interface MyResource {
#GET
public String sayHello();
}
MyResourceImpl
package com.bar;
#Path("/hello")
public class MyResourceImpl implements MyResource {
#Override
public String sayHello() {
return "Hello Jersey";
}
}

How to make a JAX-RS annotated singleton class EAGERLY instantiated

I am using Jax-RS 2.0 with Jersey 2.22.1 and Java SE 8, deployed in Tomcat 8.0.30.
I have the POJO annotated with the appropriate JAX-RS annotations, and its working as expected. I have also annotated the POJO with #Singleton. The class gets lazily instantiated as a singleton, which is the expected behavior of #Singleton. But I would like to instantiated the class eagerly, at application startup. Is there a way to do that? I have looked at the #Startup annotation but unfortunately, that is part of the EJB package and I am not using EJB's (nor would I like to import the EJB jar file).
I am also using the Springframework 4.2.4 which would by default eagerly instantiate singletons when using the #Service or #Component annotations but unfortunately, I cannot use those annotations on a JAX-RS POJO (this is why the class extends from SpringBeanAutowiringSupport.
I have attached the java code but since its working as expected I'm not sure that would add anything useful.
import javax.inject.Singleton;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.core.MediaType;
import org.xxx.profile.util.GeneralUtil;
import org.xxx.profile.util.WebServiceLogger;
import org.xxx.profile.util.datatransfer.AccountDTO;
import org.xxx.profile.web.exception.RestException;
import org.xxx.profile.web.exception.FailureResponse;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
#Singleton
#Path( "account" )
public class AccountWebService extends SpringBeanAutowiringSupport{
#Autowired
private AccountLogic accountLogic;
#Autowired
protected SimpleParser simplerParser;
#GET
#Path("get/{id: [a-zA-Z][a-zA-Z_0-9]*}")
#Produces( MediaType.APPLICATION_JSON )
#Consumes( MediaType.APPLICATION_JSON )
public AccountDTO retrieveAccount(#PathParam("id") String customerId) throws RestException {
try {
return accountLogic.retrieveAccount(customerId);
}
catch(Exception e){
String transactionID = GeneralUtil.getUniqueID();
WebServiceLogger.severe( transactionID, "Unable to retrieve an account for the customerId: " + customerId, e, this.getClass() );
throw new RestException( new FailureResponse( FailureResponse.Status.FAIL, "add user friendly message here", transactionID ), e );
}
}
}
See Jersey 2.22.1 User Guide:
If you want to use Jersey Spring DI support you will need to add the jersey-spring3 module into the list of your dependencies:
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.22.1</version>
</dependency>
It seems to work for Spring 4, too. See Question 21443088

Categories