openapi-generator-maven-plugin: change rest client base path during runtime - spring-boot

I have a Spring Boot Application and I use openapi-generator-maven-plugin for generating rest client. I want to have a option to change url during runtime.
The url of the rest server is now hardcoded in the following snippet of OpenAPI definition:
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: 'http://localhost:8080'
description: Generated server url
Configuration of the maven plugin:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>4.3.1</version>
<execution>
<id>vydejClient</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>
${project.basedir}/src/main/resources/manualni_kodovani_vydej.yaml
</inputSpec>
<generatorName>java</generatorName>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
<configOptions>
<dateLibrary>java8</dateLibrary>
</configOptions>
<library>resttemplate</library>
<typeMappings>
<typeMapping>File=org.springframework.core.io.Resource</typeMapping>
</typeMappings>
<apiPackage>client</apiPackage>
<modelPackage>client.model</modelPackage>
</configuration>
</execution>
</executions>
</plugin>
This code is generated
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-11-23T14:40:42.232315+01:00[Europe/Prague]")
#Component("ApiClient")
public class ApiClient {
...
private String basePath = "http://localhost:8080";
...
/**
* Set the base path, which should include the host
* #param basePath the base path
* #return ApiClient this client
*/
public ApiClient setBasePath(String basePath) {
this.basePath = basePath;
return this;
}
}
I need to have this attribute configurable. Any idea how to do it?

You can create a bean of your generated api client class and an ApiClient bean configured with your desired endpoint to that class.
#Configuration
public class YourGeneratedApiConfig {
#Value("${rest.clients.yourGeneratedApi.endpoint}")
private String endpoint;
#Bean public YourGeneratedApi yourGeneratedApi() {
return new YourGeneratedApi(apiClient);
}
#Bean public ApiClient apiclient(RestTemplate restTemplate) {
ApiClient apiclient = new ApiClient(restTemplate);
apiclient.setBasePath(endpoint);
return apiclient;
}
#Bean public RestTemplate restTemplate (RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
}

Related

Setting dynamic version for swagger API version

I'm trying to make version dynamic in below annotation.
#SpringBootApplication
#OpenAPIDefinition(info = #Info(title = "Test APIs", version = "${project.version}", description = "Testing APIs"))
public class DemoApplication {
}
Here I want to make version dynamic so it will take value from pom.xml file.
Do I need to have any configuration for that?
TIA.
You can define the OpenAPIDefinition programatically and use the BuildProperties to set OpenAPIDefinition.info data like this:
#SpringBootApplication
public class ApiApplication {
#Autowired
private BuildProperties buildProperties;
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
#Bean
OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components())
.info(new Info()
.title(buildProperties.getArtifact() + " API")
.version(buildProperties.getVersion())
.description(buildProperties.getArtifact() + " - API Swagger documentation")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}
}
You'll need add the 'build-info' goal in the 'spring-boot-maven-plugin' into pom.xml file in order to use de BuildProperties component in your code:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Spring boot CORS config with ConfigurationProperties

I have a global CORS configuration which is working well:
#Component
public class CorsConfiguration implements WebMvcConfigurer {
String allowedOrigins = "*";
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigins)
}
public String getAllowedOrigins() {
return allowedOrigins;
}
public void setAllowedOrigins(String allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
}
Now I want to make the allowsOriginsfield dynamic for each application through application.yaml. I tried to add #ConfigurationPropierties(prefix="cors") to the CorsConfiguration Class. And added cors.allowedOrigins=test to application.yaml. But this does nothing. I have read many posts and tutorials, but I don't get why this isn't working. It should be as easy as that. What am I missing?
EDIT I also tried #Value annotation like this
#Value("${cors.allowedOrigins:*}")
String allowedOrigins;
Application.yaml
cors:
allowedOrigins=http://localhost:4200
Result is that I only get the default value *, nothing is read from application.yaml.
Thanks
Try using #Value annotation to get your property value from application.yml like this :
application.yml
origin:
allowedOrigins: [HOST_HERE]
And then use it in your CorsConfiguration class :
#Component
public class CorsConfiguration implements WebMvcConfigurer {
#Value("${origin.allowedOrigins:DEFAULT_VALUE_HERE}")
private String allowedOrigins;
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigins)
}
// code here
}
I think you should add the following annotation on CorsConfiguration:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = { "<packge name>" })
the last one is not essential but sometimes you need it
Manual way
I recommended for you making Configuration Class, like this
#lombok.Data
#ConfigurationProperties(prefix = "cors")
public class PathCorsConfiguration {
#Nullable
private String[] allowedOrigins;
}
application.yml
cors:
allowedOrigins:
- "https://www.someurl_1.com"
- "https://www.someurl_2.com"
and for highlighting new properties in config file you may add annotation processor:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
Automatically way
Few years ago I asked to self the same question like you and recently I released self library for make it easily:
add dependency
<dependency>
<groupId>io.github.iruzhnikov</groupId>
<artifactId>spring-webmvc-cors-properties-autoconfigure</artifactId>
<version>VERSION</version>
</dependency>
and add configuration
spring:
web:
cors:
enabled: true
mappings:
baseOrigins:
paths: /**
allowed-origins: "http://localhost:4200"
allowed-origin-patterns: .*

How to run controller test classes in order with spring integration test

Running controller test classes in order.
I have this test classes below.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void findAll() throws Exception {
MvcResult result = mockMvc
.perform(get("/api/user").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andReturn();
MockHttpServletResponse response = result.getResponse();
RestResponse restResponse = mapper.readValue(response.getContentAsString(), RestResponse.class);
Assert.assertEquals(restResponse.getHttpStatus().name(), HttpStatus.OK.name() );
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class ProductControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void findAll() throws Exception {
MvcResult result = mockMvc
.perform(get("/api/product").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andReturn();
MockHttpServletResponse response = result.getResponse();
RestResponse restResponse = mapper.readValue(response.getContentAsString(), RestResponse.class);
Assert.assertEquals(restResponse.getHttpStatus().name(), HttpStatus.OK.name() );
}
}
I want to run this controller test classes in order. For example, first UserControllerTest runs after that ProductControllerTest.
How can i do this?
Thank you.
If you have Junit 5 as dependency you can control complete control of the order of the methods but within the test class itself by using #TestMethodOrder.
Regarding the order of the test classes themselves there is not much control available. Maven Failsafe docs say about the <runOrder> configuration:
Supported values are "alphabetical", "reversealphabetical", "random",
"hourly" (alphabetical on even hours, reverse alphabetical on odd
hours), "failedfirst", "balanced" and "filesystem".
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<runOrder>alphabetical</runOrder>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
</executions>
</plugin>

Spring Cloud Contract + Pact (Broker): json string can not be null or empty

I've been experimenting with Contract Testing using Spring Cloud Contract (SCC) and am now trying to use Pact in combination with SCC to serve as an intermediate step before going pure Pact.
On my consumer project I've specified a simple contract:
#RunWith(SpringRunner.class)
#SpringBootTest
public class AccountServicePactTest {
#Autowired
TransactionService transactionService;
#Rule
public PactProviderRuleMk2 mockProvider = new PactProviderRuleMk2("account-service", "localhost", 8081, this);
#Pact(consumer = "transaction-service")
public RequestResponsePact createPact(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json;charset=UTF-8");
return builder
.given("An account with UUID 8ea0f76b-b7a6-49eb-b25c-073b664d2de3 exists")
.uponReceiving("Request for an account by UUID")
.path("/api/accounts/8ea0f76b-b7a6-49eb-b25c-073b664d2de3")
.method("GET")
.willRespondWith()
.headers(headers)
.status(200)
.body("{\n" +
" \"accountUUID\": \"8ea0f76b-b7a6-49eb-b25c-073b664d2de3\",\n" +
" \"customerId\": 1,\n" +
" \"balance\": 0.00,\n" +
"}")
.toPact();
}
#Test
#PactVerification
public void runTest() {
AccountRetrievalRequest request = new AccountRetrievalRequest(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
AccountDTO accountDTO = transactionService.retrieveAccount(request);
assertThat(accountDTO.getAccountUUID()).isEqualTo(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
}
}
(I know that hardcoding the UUID, customerId and balance makes the test brittle, but this is just a simple test)
Using the pact-jvm-provider-maven_2.12 plugin (provider plugin on consumer side, confusing I know) the Pact file gets pushed to a Pact Broker:
<plugin>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.12</artifactId>
<version>3.5.22</version>
<configuration>
<pactBrokerUrl>http://localhost:8888</pactBrokerUrl>
<pactBrokerUsername></pactBrokerUsername>
<pactBrokerPassword></pactBrokerPassword>
<trimSnapshot>true</trimSnapshot>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>publish</goal>
</goals>
</execution>
</executions>
</plugin>
So far so good.
On the provider project I run into issues trying to verify the the contract. Again, I'm using SCC and Pact together. pom.xml plugins snippet:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<contractsRepositoryUrl>pact://http://localhost:8888</contractsRepositoryUrl>
<contractDependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>+</version>
</contractDependency>
<contractsMode>REMOTE</contractsMode>
<packageWithBaseClasses>com.abnamro.internship.bank.accountservice.pacts</packageWithBaseClasses>
</configuration>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<version>${spring-cloud-contract.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.12</artifactId>
<version>3.5.11</version>
<configuration>
<serviceProviders>
<serviceProvider>
<name>account-service</name>
<pactBrokerUrl>http://localhost:8888/</pactBrokerUrl>
</serviceProvider>
</serviceProviders>
</configuration>
</plugin>
SCC Verifier required Base Class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = AccountServiceApplication.class)
public abstract class Account_serviceContractsBase {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private AccountRepository accountRepository;
#Before
public void setup() {
Account account = new Account(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"), Integer.toUnsignedLong(1),
0.00d);
accountRepository.save(account);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
System.out.println(account.getAccountUUID());
}
#After
public void teardown() {}
}
SCC generated test:
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ContractsTest extends Account_serviceContractsBase {
#Test
public void validate_0_account_service_pact() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/api/accounts/8ea0f76b-b7a6-49eb-b25c-073b664d2de3");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).isEqualTo("application/json;charset=UTF-8");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['accountUUID']").isEqualTo("8ea0f76b-b7a6-49eb-b25c-073b664d2de3");
assertThatJson(parsedJson).field("['customerId']").isEqualTo(1);
assertThatJson(parsedJson).field("['balance']").isEqualTo(0.0);
}
}
Running the test gives the following exception:
java.lang.IllegalArgumentException: json string can not be null or empty
I can't figure out why the string is either empty or null. Running the application and using Postman to test the endpoint works just fine.
This is part of the Controller:
#RestController
#RequestMapping("/api")
public class AccountController {
...
#GetMapping("/accounts/{accountUUID}")
public ResponseEntity<Account> retrieveAccount(#PathVariable("accountUUID") UUID accountUUID) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return new ResponseEntity<>(accountService.retrieveAccount(accountUUID),
httpHeaders, HttpStatus.OK);
}
I'm missing something but I can't find what. Thoughts?
EDIT: The (generated) test passed when just using SCC.
Turned out my Base Class wasn't setup properly. Working Base Class which simply mocks the called service method and returns a test account:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = AccountServiceApplication.class)
public abstract class Account_serviceContractsBase {
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private AccountService accountService;
#Before
public void setup() {
Account account = new Account();
account.setAccountUUID(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
account.setCustomerId(1L);
account.setBalance(0.00d);
when(accountService.retrieveAccount(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"))).thenReturn(account);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
#After
public void teardown() {}
}

Spring pure annotation with autowired

I am trying to do a full annotations (no xml) implementation of Spring. The autowired members are not being populated. From my research, there are 3 things to do:
Set up a config file that manages the bean
Use #Autowired to get the bean to the file
Instantiate an application context to get the ball rolling
It is difficult to find a complete example of this which uses annotations only, so I don't have much to reference. Most examples use at least some xml.
There is no error message, so I don't have any idea where the problem is. The value is just null. Here are my files:
Trivial.java
public class Trivial {
public TrivialBean trivialBean;
#Autowired
public void setTrivialBean(TrivialBean trivialBean) {
this.trivialBean = trivialBean;
}
public static void main(String...args) {
ApplicationContext context
= new AnnotationConfigApplicationContext(
TrivialConfig.class);
new Trivial().go();
}
private void go() {
System.out.println("trivialBean: " + trivialBean);
}
}
TrivialBean.java
public class TrivialBean {
public String foo = "TEST TEST TEST";
#Override
public String toString() {
return foo;
}
}
TrivialConfig.java
#Configuration
public class TrivialConfig {
#Bean
public TrivialBean trivialBean() {
return new TrivialBean();
}
}
I would expect this to output trivialBean: TEST TEST TEST, but is just outputs trivialBean: null
For the #Autowired in Trivial to work, you need to have Trivial instantiated by Spring. new Trivial() won't work. For your sample to work, I think you need the following:
Configure Trivial as a bean.
Change new Trivial() to context.getBean(Trivial.class).
However, note that it is considered bad practice to use context.getBean under normal circumstances.
Regular autowiring in annotation-based container configuration
In order for autowiring to work, the lifecycle of the instance of Trivial has to be managed by the Spring container.
Example
TrivialBean.java is the same
public class TrivialBean {
public String foo = "TEST TEST TEST";
#Override
public String toString() {
return foo;
}
}
TrivialConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class TrivialConfig {
#Bean
public TrivialBean trivialBean() {
return new TrivialBean();
}
#Bean
public Trivial trivial() {
return new Trivial();
}
}
Trivial.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Trivial {
public TrivialBean trivialBean;
#Autowired
public void setTrivialBean(TrivialBean trivialBean) {
this.trivialBean = trivialBean;
}
public static void main(String... args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TrivialConfig.class);
Trivial trivial = context.getBean(Trivial.class);
trivial.go();
}
private void go() {
System.out.println("trivialBean: " + trivialBean);
}
}
Output
trivialBean: TEST TEST TEST
Please consult Spring documentation for more information on Annotation-based container configuration.
AspectJ compile-time weaving and #Configurable
It is possible to autowire TrivialBean instance into Trivial instance created by new.
spring-aspects.jar contains an annotation-driven aspect that allows dependency injection for objects created outside of the control of the container. However, it should not be used in new Spring-based projects. It is intended to be used for legacy projects, where for some reason some instances are created outside of the Spring container.
Example for Spring 4.2.0 (the latest at the moment), AspectJ 1.8.6 (the latest at the moment), Maven and Java 1.8.
Additional dependencies on spring-aspects and aspectjrt
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
Compile time weaving via AspectJ Maven plugin
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<encoding>UTF-8</encoding>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<Xlint>warning</Xlint>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
TrivialBean.java is the same
public class TrivialBean {
public String foo = "TEST TEST TEST";
#Override
public String toString() {
return foo;
}
}
TrivialConfig.java
#EnableSpringConfigured is analogous to <context:spring-configured>. It signals the current application context to apply dependency injection to classes that are instantiated outside of the Spring bean factory.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
#Configuration
#EnableSpringConfigured
public class TrivialConfig {
#Bean
public TrivialBean trivialBean() {
return new TrivialBean();
}
}
Trivial.java
#Configurable applies Spring-driven configuration to Trivial
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
#Configurable
public class Trivial {
public TrivialBean trivialBean;
#Autowired
public void setTrivialBean(TrivialBean trivialBean) {
this.trivialBean = trivialBean;
}
public static void main(String... args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TrivialConfig.class);
Trivial trivial = new Trivial();
trivial.go();
}
private void go() {
System.out.println("trivialBean: " + trivialBean);
}
}
Output
trivialBean: TEST TEST TEST
It works! Please consult Spring documentation for more information on AspectJ and #Configurable.

Resources