OpenAPI generator add/generate annotation to ApiClient constructor - spring

I use the newest OpenAPI generator 6.2.1 (https://github.com/OpenAPITools/openapi-generator) to generate an ApiClient with the resttemplate library, which works quite well.
In my application I have now two different RestTemplate beans. So Spring does not know which one to use in the ApiClient constructor.
Parameter 0 of constructor in com.xyz.ApiClient required a single bean, but 2 were found
There is also a hint to solve the problem:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
I don't want to mark one of the beans with #Primary because it is not the primary bean wanted to be used.
I would like to add the #Qualifier to the generated ApiClient constructor like this:
#Autowired
public ApiClient(#Qualifier("myClientProperties") RestTemplate restTemplate) {
this.restTemplate = restTemplate;
init();
}
How can I add the #Qualifier annotation to the generated constructor?
I read lots of openapi generator documentation but did not find anything helpful. There is a solution to add a annotation for models (additionalModelTypeAnnotations in the configOptions of OpenApi configuration).
I expect to generate a #Qualifier annotation to the ApiClient constructor.

You can disable the component scanning for the generated classes. Assuming, your root package is 'my.root.package' and you generate the classes into 'my.root.package.generated', annotate your App / Config class with the following:
#ComponentScan(basePackages = "my.root.package",
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX,
pattern = "my.root.package.generated.*"))
Then, you can create own (qualified) ApiClients based on your rest templates:
#Bean("rest-template-1")
public RestTemplate restTemplate1() {
return new RestTemplate();
}
#Bean("rest-template-2")
public RestTemplate restTemplate2() {
return new RestTemplate();
}
#Bean("api-client-1")
public ApiClient apiClient1(#Qualifier("rest-template-1") RestTemplate restTemplate) {
return new ApiClient(restTemplate);
}
#Bean("api-client-2")
public ApiClient apiClient2(#Qualifier("rest-template-2") RestTemplate restTemplate) {
return new ApiClient(restTemplate);
}
With those different qualified ApiClients, you init your API classes as you need them.

Related

Does spring inject any beans provided in the #Configuration class

If I have a #Configuration class where I have a bean like below, will the dataMap be resolved in the constructor of the DataService class. What type of dependency injection is this? Is it by type because the name for sure doesn't match?
#Bean
public Map<String, List<Data>> data() {
final Map<String, List<Data>> dataMap = new HashMap<>();
readings.put("1", new Data());
return dataMap;
}
and a class
#Service
public class DataService {
private final Map<String, List<Data>> information;
public DataService(Map<String, List<Data>> information) {
this.information = information;
}
}
#Configuration annotation serves as a placeholder to mention that whichever classes annotated with #Configuration are holding the bean definitions!
When Spring application comes up, spring framework will read these definitions and create beans (or simply objects) in IOC (Inversion of control) container These would be Spring managed objects/beans !
To answer your question, it should create a bean and this is a setter based injection!
However, your #Bean must be some user defined or business entity class in ideal scenarios!
Few links for you to refer to:
https://www.codingame.com/playgrounds/2096/playing-around-with-spring-bean-configuration
https://www.linkedin.com/pulse/different-types-dependency-injection-spring-kashif-masood/

Spring Boot Injection issue using several Profiles

I'm having an issue using Profiles in Spring, basically, we use them to stub some parts of our microservice, which uses both a connection to DB and a connection to another webservice.
Previously I used one stub profile for both the DB and external webservice :
#Configuration
#EnableAutoConfiguration
#Profile("stub")
#ComponentScan(
basePackages = {"com.consumption.backend"},
excludeFilters = {
#ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.*.persistence.*"),
#ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.databackendapi.*")
})
public class StubConfiguration {
#Bean
public DataApi consumptionApi() { return new DataStubApi(); }
#Bean
public RefDayDao refDayDao() { return new RefDayInMemoryDao(); }
#Bean
public RefTypeHourDao refTypeHourDao() { return new RefTypeHourInMemoryDao(); }
}
and this works fine, however I would like to separate this stub into two stubs, one for the DB and one for the external webservices to ensure greater flexibility in our tests.
Stub for the DAO:
#Configuration
#EnableAutoConfiguration
#Profile("stubconfv3")
#ComponentScan(
basePackages = {"com.consumption.backend"},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.*.persistence.*")
)
public class StubConfV3Configuration {
#Bean
public RefDayDao refDayDao() { return new RefDayInMemoryDao(); }
#Bean
public RefTypeHourDao refTypeHourDao() { return new RefTypeHourInMemoryDao(); }
}
Stub for the external webservice :
#Configuration
#EnableAutoConfiguration
#Profile("stubdatabackend")
#ComponentScan(
basePackages = {"com.consumption.backend"},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.databackendapi.*")
)
public class StubDataBackendConfiguration {
#Bean
public DataApi consumptionApi() { return new DataStubApi(); }
}
The stub for the DAO seems to work fine, however I seem to have an issue with the external webservice API not excluding the implementation properly:
If I launch my application with stub profile everything works fine
If I launch my application with stubconfv3 and stubdatabackend I get an issue with injection, as two classes are found :
Parameter 0 of constructor in
com.consumption.backend.service.DataService required a single bean,
but 2 were found:
dataBackendApi: defined in file [C:\code\consumption-backend\databackend-api\target\classes\com\consumption\backend\databackendapi\DataBackendApi.class]
consumptionApi: defined by method 'consumptionApi' in class path resource
[com/consumption/backend/application/configuration/StubDataBackendConfiguration.class]
Action:
Consider marking one of the beans as #Primary, updating the
consumer to accept multiple beans, or using #Qualifier to identify the
bean that should be consumed
Either the exclusion is not working or there is some tricky thing as it seems to find the .class file in target instead of finding it at runtime in a class loader
Most likely, I do some stupid mistake and am not seeing it ...
EDIT : I noticed if I misstype stubconfv3 profile in StubConfV3Configuration, the injection issue doesn't appear anymore, so I guess you can't combine exclusionFilters in #ComponentScan in two different classes...
The first class will do a componentscan on everything but what she excludes, and the second will do the same, so packages excluded by the second class with still be scanned by the first class.
You could try to annotate your DataBackendApi with
#Profile("!stubdatabackend")
to include it when that profile is not used and exclude it otherwise

spring boot 1.3.3 create multiple resttemplate per env

I am on spring boot 1.3.3 version, I have a requirement where my spring boot application need to call endpoint(s) based on env passed,
which means if env passed as Dev i would need to call devendpoint,
if env passed as Dev1 then need to call dev1endpoint and so on.
So how can I do this ?
Do I need to create multiple restTemplate instances ?
Should I construct the resttemplate dynamically based on env passed ?
As part of constructing resttemplate i would also need to add appllicable interceptor based on env selected.
Plesae suggest.
You can have two beans of the same class. One can be labeled as the primary, and the use on the #Autowired can specify which one to use with the #Qualifier.
Example:
#Configuration
public class MyConfig {
#Bean
#Primary
public RestTemplate typicalConfig() {
// various configs on your rest template
return new RestTemplate();
}
#Bean
public RestTemplate lessTypical() {
// various alternate configurations
return new RestTemplate();
}
}
Now in your service class:
#Service
public class MyService {
#Autowired
RestTemplate typicalRestTemplate;
#Autowired
#Qualifier("lessTypical")
private RestTemplate alternateRestTemplate;
...
}
correct me if I didn't understand your question. I understand that you are going to have different environments but you are going to change this endpoints in runtime depends on some information or whatever, but I don't understand the part when you said you have to create multiple instances of restTemplate for that environments, from my experience on spring boot applications you don't have to do things like that, You just have to create your restTemplate configuration bean.
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
And then injected that object on your services class and do whatever you want with them. I recommend you to read the follow article about restTempalte may this could help you http://www.baeldung.com/rest-template

How to inject RestTemplate

I am not using xml configurations to define beans. Instead using component scanning and autowire to define and inject dependencies.
RestTemplate is part of springframework. How can I inject this class ?
You do it like any other #Bean in a #Configuration class, and inject with #Autowire - However you question suggest that you should read a little more of the Spring documentation.
#Bean
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(6);
template.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClients.custom().setConnectionManager(connectionManager).build()));
return template;
}
You almost always want to use it together with Apache HttpClient so you get connection pooling. If you need to use it with self-signed https certificates you need a bit more code (let me know if this is the case)

Spring Boot with Two MVC Configurations

I have a Spring Boot app with a REST API, using Jackson for the JSON view configuration. It works great and I can get all the Spring Boot goodness.
However, I need to add an additional REST API that is similar but with different settings. For example, among other things, it needs a different Jackson object mapper configuration because the JSON will look quite a bit different (e.g. no JSON arrays). That is just one example but there are quite a few differences. Each API has a different context (e.g. /api/current and /api/legacy).
Ideally I'd like two MVC configs mapped to these different contexts, and not have to give up any of the automatic wiring of things in boot.
So far all I've been able to get close on is using two dispatcher servlets each with its own MVC config, but that results in Boot dropping a whole bunch of things I get automatically and basically defeats the reason for using boot.
I cannot break the app up into multiple apps.
The answer "you cannot do this with Boot and still get all its magic" is an acceptable answer. Seems like it should be able to handle this though.
There's several ways to achieve this. Based on your requirement , Id say this is a case of managing REST API versions.
There's several ways to version the REST API, some the popular ones being version urls and other techniques mentioned in the links of the comments.
The URL Based approach is more driven towards having multiple versions of the address:
For example
For V1 :
/path/v1/resource
and V2 :
/path/v2/resource
These will resolve to 2 different methods in the Spring MVC Controller bean, to which the calls get delegated.
The other option to resolve the versions of the API is to use the headers, this way there is only URL, multiple methods based on the version.
For example:
/path/resource
HEADER:
X-API-Version: 1.0
HEADER:
X-API-Version: 2.0
This will also resolve in two separate operations on the controller.
Now these are the strategies based on which multiple rest versions can be handled.
The above approaches are explained well in the following: git example
Note: The above is a spring boot application.
The commonality in both these approaches is that there will need to be different POJOS based on which Jackson JSON library to automatically marshal instances of the specified type into JSON.
I.e. Assuming that the code uses the #RestController [org.springframework.web.bind.annotation.RestController]
Now if your requirement is to have different JSON Mapper i.e. different JSON mapper configurations, then irrespective of the Spring contexts you'll need a different strategy for the serialization/De-Serialization.
In this case, you will need to implement a Custom De-Serializer {CustomDeSerializer} that will extend JsonDeserializer<T> [com.fasterxml.jackson.databind.JsonDeserializer] and in the deserialize() implement your custom startegy.
Use the #JsonDeserialize(using = CustomDeSerializer.class) annotation on the target POJO.
This way multiple JSON schemes can be managed with different De-Serializers.
By Combining Rest Versioning + Custom Serialization Strategy , each API can be managed in it's own context without having to wire multiple dispatcher Servlet configurations.
Expanding on my comment of yesterday and #Ashoka Header idea i would propose to register 2 MessageConverters (legacy and current) for custom media types. You can do this like that:
#Bean
MappingJackson2HttpMessageConverter currentMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
jsonConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("json", "v2")));
return jsonConverter;
}
#Bean
MappingJackson2HttpMessageConverter legacyMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
Pay attention to the custom media-type for one of the converters.
If you like , you can use an Interceptor to rewrite the Version-Headers proposed by #Ashoka to a custom Media-Type like so:
public class ApiVersionMediaTypeMappingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
try {
if(request.getHeader("X-API-Version") == "2") {
request.setAttribute("Accept:","json/v2");
}
.....
}
}
This might not be the exact answer you were looking for, but maybe it can provide some inspiration. An interceptor is registered like so.
If you can live with a different port for each context, then you only have to overwrite the DispatcherServletAutoConfiguration beans. All the rest of the magic works, multpart, Jackson etc. You can configure the Servlet and Jackson/Multipart etc. for each child-context separately and inject bean of the parent context.
package test;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#EnableAutoConfiguration(exclude = {
Application.Context1.class,
Application.Context2.class
})
public class Application extends WebMvcConfigurerAdapter {
#Bean
public TestBean testBean() {
return new TestBean();
}
public static void main(String[] args) {
final SpringApplicationBuilder builder = new SpringApplicationBuilder().parent(Application.class);
builder.child(Context1.class).run();
builder.child(Context2.class).run();
}
public static class TestBean {
}
#Configuration
#EnableAutoConfiguration(exclude = {Application.class, Context2.class})
#PropertySource("classpath:context1.properties")
public static class Context1 {
#Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
}
#Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test1");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
}
#Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean) {
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
}
}
#Configuration
#EnableAutoConfiguration(exclude = {Application.class, Context1.class})
#PropertySource("classpath:context2.properties")
public static class Context2 {
#Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
}
#Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test2");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
}
#Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean) {
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
}
}
}
The context1/2.properties files currently only contain a server.port=8080/8081 but you can set all the other spring properties for the child contexts there.
In Spring-boot ypu can use different profiles (like dev and test).
Start application with
-Dspring.profiles.active=dev
or -Dspring.profiles.active=test
and use different properties files named application-dev.properties or application-test.properties inside your properties directory.
That could do the problem.

Resources