How to integrate swagger with jersey + spring-boot - java

I am using springboot + jersey for web restful implementation. Now I am going to integrate swagger into our application. I did following.
#Configuration
#EnableSwagger2
public class JerseyConfiguration extends ResourceConfig {
public JerseyConfiguration(){
register(HelloworldAPI.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("/");
beanConfig.setResourcePackage("com.cooltoo.api");
beanConfig.setPrettyPrint(true);
beanConfig.setScan(true);
}
}
I added following dependences on build.gradle:
compile('io.springfox:springfox-swagger2:'+springfoxSwaggerVersion)
compile('io.springfox:springfox-petstore:'+springfoxSwaggerVersion)
compile('io.springfox:springfox-swagger-ui:'+springfoxSwaggerVersion)
compile('io.swagger:swagger-jersey2-jaxrs:1.5.8')
I was able to launch the web application but I wander which url is for swagger? I tried with http://localhost:8080, http://localhost:8080/swagger, and http://localhost:8080/swagger-ui.html. But none of them could be accessed.

I think #EnableSwagger2 annotation and springfox dependencies would work if the endpoints are implemented using Spring MVC instead of a JAX-RS implementation.
I blogged about this a few months ago, Microservices using Spring Boot, Jersey Swagger and Docker
Basically if you need to document your Jersey-implemented endpoints, 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
...

Maybe you use rather old version of springfox, but now (for version 2.4.0) it must be configured very different from your code, see http://springfox.github.io/springfox/docs/current/
For example, I have the following springfox configuration:
#Configuration
#EnableSwagger2
class SwaggerConfig {
#Bean
Docket rsApi() {
new Docket(DocumentationType.SWAGGER_12)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage('com.test.service'))
.paths(PathSelectors.any())
.build()
.pathMapping('/')
.useDefaultResponseMessages(false)
}
private ApiInfo apiInfo() {
new ApiInfoBuilder()
.title('Test API')
.description('Test API')
.version('0.0.10.SNAPSHOT')
.termsOfServiceUrl('')
.contact('Test company')
.license('Public')
.licenseUrl('http://example.com/')
.build()
}
}

Related

How to set urls with port of StubRunner from spring-cloud-contract in spring-cloud-gateway contract tests

I have a spring cloud gateway application which routes requests to another service. Another service defines contracts which are imported as stubs by spring cloud gateway application in tests.
Now I would like to have contract tests in my gateway that will consume the stubs of another service. The problem is that I do not know how to inject the StubRunnerPort as property/environment so it can be picked by my configuration class and configure the routes accordingly :
Api gateway routes configuration
#Configuration
class GatewayConfig {
#Value("${subscriptions.url}")
private String subscriptionsUrl;
#Autowired
private TokenRelayGatewayFilterFactory tokenFilterFactory;
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf(ServerHttpSecurity.CsrfSpec::disable);
return http.build();
}
#Bean
RouteLocator routeLocator(final RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route("subscriptions", subscriptionsRoute())
.build();
}
private Function<PredicateSpec, Buildable<Route>> subscriptionsRoute() {
return spec -> spec
.path("/subscriptions/**")
.filters(s -> s.filter(tokenFilterFactory.apply()).prefixPath("/v1"))
.uri(subscriptionsUrl);
}
}
And the test class :
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {PnApiGatewayApp.class})
#AutoConfigureStubRunner(ids = "io.mkrzywanski:subscription-app:+:stubs", stubsMode = StubRunnerProperties.StubsMode.CLASSPATH)
#ActiveProfiles("test")
class SubscriptionSpec {
private WebTestClient webClient;
#LocalServerPort
private int port;
#StubRunnerPort("io.mkrzywanski:subscription-app")
private int stubRunnerPort;
#Autowired
ConfigurableEnvironment environment;
#BeforeEach
void setup() {
String baseUri = "http://localhost:" + port;
this.webClient = WebTestClient.bindToServer()
.responseTimeout(Duration.ofSeconds(10))
.baseUrl(baseUri).build();
}
#Test
void test() {
String body = "{\"userId\":\"22e90bbd-7399-468a-9b76-cf050ff16c63\",\"itemSet\":[{\"value\":\" Rainbow Six\"}]}";
var response = webClient.post()
.uri("/subscriptions")
.header("Authorization", "Bearer xxx")
.header("Content-type", MediaType.APPLICATION_JSON_VALUE)
.bodyValue(body)
.exchange()
.expectStatus().isCreated()
.expectBody(String.class)
.value(Matchers.equalTo("{\"subscriptionId : \"6d692849-58fd-439b-bb2c-50a5d3669fa9\"\"}"));
}
Ideally I would like to have subscriptions.url property set after stub runner is configured but before my gateway configuration is picked by Spring so that url redirects will work.
I have already tried to use ApplicationContextInitializer but it seems that StubRunnerPort is not configured yet, when instance of initializer is launched.
So the question is - how to get stub runner port and use it to inject it into other services url, so that gateway would route the requests to the stub runner in tests?
Solution 1
This can be achieved by using application-test.yml file with defined url property that makes use of substitution. application-test.yml file. The approach is described here :
subscriptions:
url: http://localhost:${stubrunner.runningstubs.io.mkrzywanski.subscription-app.port}
Here stubrunner.runningstubs.io.mkrzywanski.subscription-app.port will be available as Stub port so it can be substituted. No configuration changes are reuqired.
Solution 2 (requires more code)
I made it work by creating a test configuration which extends configuration that contains url properties and RouteLocator configuration and has a dependency on batchStubRunner bean :
#DependsOn("batchStubRunner")
#EnableAutoConfiguration
#Import(LoggingFilter.class)
class GatewayTestConfig extends GatewayConfig implements InitializingBean {
#Autowired
ConfigurableEnvironment environment;
#Override
public void afterPropertiesSet() {
this.subscriptionsUrl = "http://localhost:" + environment.getProperty("stubrunner.runningstubs.io.mkrzywanski.subscription-app.port");
}
}
The key points here are :
the configuration is run only after batchStubRunner bean is available so the port of StrubRunner can be found in the environment
The configuration implements InitializingBean so I am able to override the subscriptionsUrl which is now protected in parent configuration
After subscriptionsUrl is overriden - it can be used to configure RouteLocator bean from parent configuration.
The test looks like this now :
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {GatewayTestConfig.class})
#AutoConfigureStubRunner(ids = "io.mkrzywanski:subscription-app:+:stubs", stubsMode = StubRunnerProperties.StubsMode.CLASSPATH)
#ActiveProfiles("test")
class SubscriptionSpec {
private WebTestClient webClient;
#LocalServerPort
private int port;
#BeforeEach
void setup() {
String baseUri = "http://localhost:" + port;
this.webClient = WebTestClient.bindToServer()
.responseTimeout(Duration.ofSeconds(10))
.baseUrl(baseUri).build();
}
#Test
void shouldRouteToSubscriptions() {
String body = "{\"userId\":\"22e90bbd-7399-468a-9b76-cf050ff16c63\",\"itemSet\":[{\"value\":\"Rainbow Six\"}]}";
webClient.post()
.uri("/subscriptions")
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.header("Authorization", "Bearer xxx")
.header("Content-type", MediaType.APPLICATION_JSON_VALUE)
.bodyValue(body)
.exchange()
.expectStatus().isCreated()
.expectBody()
.jsonPath("$.subscriptionId").exists()
.jsonPath("$.subscriptionId").value(IsUUID.UUID());
}
}

Change of CORS policy in spring boot version 2.4.0

Using Spring 2.3.0.RELEASE I had the following CORS confiruration:
#Configuration
#EnableWebSecurity
#ComponentScan("com.softeq.ems.config")
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class EmsJwtSecurityConfig extends BaseSecurityConfig {
#Value("${management.endpoints.web.cors.allowed-origins}")
private String[] allowedOrigins;
#Override
protected void configureHttp(HttpSecurity http) throws Exception {
if (allowedOrigins.length > 0) {
http.cors().configurationSource(corsConfigSource());
}
http.csrf().disable();
}
private CorsConfigurationSource corsConfigSource() {
final CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedHeader(CorsConfiguration.ALL);
corsConfig.addAllowedMethod(CorsConfiguration.ALL);
Stream.of(allowedOrigins).forEach(
origin -> corsConfig.addAllowedOrigin(origin)
);
return request -> corsConfig;
}
Variable management.endpoints.web.cors.allowed-origins = http://localhost:4200, http://127.0.0.1:4200
This configuration worked fine and all the cross-platform requests I needed were authorized.
But after migrating to spring-boot 2.4.0 after the release, when I tried to send a request to the host as usual, I got the classic cors policy error in chrome browser console:
Access to XMLHttpRequest at 'http://localhost:8080/api/v1/me/balance' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status
Spring release notes says that the cors configuration provides a new property allowedOriginPatterns, but I don't understand how to use it:
https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-5.x#general-web-revision
Please help me figure out what my problem is!
Here what I would do to your code:
private CorsConfigurationSource corsConfigSource() {
final CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedHeader(CorsConfiguration.ALL);
corsConfig.addAllowedMethod(CorsConfiguration.ALL);
Stream.of(allowedOrigins).forEach(
//origin -> corsConfig.addAllowedOrigin(origin)
origin -> corsConfig.addAllowedOriginPattern(origin)
);
return request -> corsConfig;
}
I did it like this:
#Configuration
#Profile("!production")
class CorsConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry
.addMapping("/**")
.allowedOriginPatterns("http://localhost:3000")
}
}

Open API 3.0 using openapi-generator-maven-plugin not showing any swagger doc

Facing this issue with Open API 3.0, I am generating the code using openapi-generator-maven-plugin. I am able to generate the code as well. Code is there in the finally generated Jar of Spring boot as well. But Some how I am not able to see the swagger-doc. All I see is this pop-up with this description
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:
I tried setting up the baseUrl property, like this
baseUrl=http://localhost:8080/api
This isn't working, generated Config & controller has following code.
#Controller
public class HomeController {
#RequestMapping("/")
public String index() {
return "redirect:swagger-ui.html";
}
}
Following is configuration class.
#Configuration
#EnableSwagger2
public class OpenAPIDocumentationConfig {
ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("School REST API")
.description("School REST API")
.license("ABC")
.licenseUrl("http://localhost:8080")
.termsOfServiceUrl("")
.version("1.0.0")
.contact(new Contact("","", "xyz#abc.com"))
.build();
}
#Bean
public Docket customImplementation(ServletContext servletContext, #Value("${openapi.schoolRest.base-path:}") String basePath) {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.school.rest.generated.controllers"))
.build()
.pathProvider(new BasePathAwareRelativePathProvider(servletContext, basePath))
.directModelSubstitute(java.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(java.time.OffsetDateTime.class, java.util.Date.class)
.genericModelSubstitutes(Optional.class)
.apiInfo(apiInfo());
}
class BasePathAwareRelativePathProvider extends RelativePathProvider {
private String basePath;
public BasePathAwareRelativePathProvider(ServletContext servletContext, String basePath) {
super(servletContext);
this.basePath = basePath;
}
#Override
protected String applicationPath() {
return Paths.removeAdjacentForwardSlashes(UriComponentsBuilder.fromPath(super.applicationPath()).path(basePath).build().toString());
}
#Override
public String getOperationPath(String operationPath) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromPath("/");
return Paths.removeAdjacentForwardSlashes(
uriComponentsBuilder.path(operationPath.replaceFirst("^" + basePath, "")).build().toString());
}
}
}
I have also removed the springfox & swagger related jars from my project.
Please advice.

Why web service and proxy client not connecting?

I have an application where I try to combine Spring MVC and Apache CFX(soap) web services. When I run just the app, everything seems fine, I see generated WSDL by this link(http://localhost:8080/services/customer?wsdl). But when I run tests, it throws WebServiceException: Could not send Message... Connection refused.
I've opened all ports for public, private and domain area through Windows Firewall Defender. Maybe I've missed something.
In a desperate attempt to investigate it, I've checked the link with this command (wsimport -keep -verbose http://localhost:8080/services/customer?wsdl). As a result, it gave this:
[ERROR] Server returned HTTP response code: 403 for URL: http://localhost:8080/services/customer?wsdl
Failed to read the WSDL document: http://localhost:8080/services/customer?wsdl, because 1) could not find the document; /2) the document could not be read; 3) the root element of the document is not <wsdl:definitions>.
[ERROR] Could not find wsdl:service in the provided WSDL(s):
At least one WSDL with at least one service definition needs to be provided.
Now I do not know which way to dig.
WebServiceDispatcherServletInitializer
public class WebServiceDispatcherServletInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(WebServiceConfig.class);
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new CXFServlet());
dispatcher.addMapping("/services/*");
}
}
WebServiceConfig
#Configuration
public class WebServiceConfig {
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), new CustomerWebServiceImpl() );
endpoint.publish("http://localhost:8080/services/customer");
return endpoint;
}
}
ClientConfig
#Configuration
public class ClientConfig {
#Bean(name = "client")
public Object generateProxy() {
return proxyFactoryBean().create();
}
#Bean
public JaxWsProxyFactoryBean proxyFactoryBean() {
JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
proxyFactory.setServiceClass(CustomerWebService.class);
proxyFactory.setAddress("http://localhost:8080/services/customer");
return proxyFactory;
}
}
CustomerWebServiceImplTest
#ActiveProfiles(profiles = "test")
#ContextConfiguration(classes = {
PersistenceConfig.class,
RootConfig.class,
WebServiceConfig.class,
ClientConfig.class
})
#WebAppConfiguration
public class CustomerWebServiceImplTest {
private ApplicationContext context = new AnnotationConfigApplicationContext(ClientConfig.class);
private CustomerWebService customerWsProxy = (CustomerWebService) context.getBean("client");
#Test
public void addCustomer() {
CustomerDto customer = new CustomerDto();
customer.setName("John");
assertEquals("Hello " + customer.getName(), customerWsProxy.addCustomer(customer));
}
}
Could you give a hint where the error might be?
UPD: I checked this setup on PC where I and my applications have full access rights and it still throws the Exception.
A solution was quite simple - just need to add #RunWith(SpringRunner.class). Because this annotation is run spring beans, not #WebAppConfiguration with #ContextConfiguration.
This is how it will look like
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {
RootConfig.class,
WebServiceConfig.class,
ClientConfig.class
})
public class CustomerWebServiceImplTest {
...
}

Spring Boot wsdl first - change url to wsdl

I'm using Spring Boot 1.3.* to build a contract first web service. I looked at the answer for the question at How to use WSDL with spring-boot? . This worked fine
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
//http://localhost:8080/ws/services.wsdl --bean name is set to 'services'
#Bean(name = "services")
public Wsdl11Definition defaultWsdl11Definition() {
SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();
wsdl11Definition.setWsdl(new ClassPathResource("/schema/MyWsdl.wsdl")); //your wsdl location
return wsdl11Definition;
}
}
My wsdl is now located at http://localhost:8080/ws/services.wsdl . Problem is that the application that will be the consumer of this web service requires the wsdl url to be written as
http://localhost:8080/ws/services?wsdl
How can i achieve this?
To configure urlrewrite as a bean check this tuckey-url-rewrite-filter-java-class-configuration
This will forward in backend and user will not be notified. Ad this rule in your urlrewrite.xml
<rule>
<from>/ws/services?wsdl</from>
<to>/ws/services.wsdl</to>
</rule>

Categories