Load spring config programmatically - java

I have 3 micro services which all need use a 'common' library. I need to add a new Spring configuration to the common library. The problem is that Microservice A doesn't care about the new code and doesn't want to be forced to add config in order to get the application to run. I need a way of programmatically loading the config only for Microservice B and C.
New config in common library:
#Configuration
public class HttpConnectionConfiguration {
#Value("${http.connect.timeout}")
private int httpConnectTimeout;
#Value("${http.connect.request.timeout}")
private int httpConnectRequestTimeout;
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(getClientHttpRequestFactory());
return restTemplate;
}
private ClientHttpRequestFactory getClientHttpRequestFactory() {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(httpConnectTimeout)
.setConnectionRequestTimeout(httpConnectRequestTimeout)
.build();
CloseableHttpClient client = HttpClientBuilder
.create()
.useSystemProperties()
.setDefaultRequestConfig(config)
.build();
return new HttpComponentsClientHttpRequestFactory(client);
}
}
Config in Microservice B and C of application.yaml:
http:
connect:
timeout: 5000
request:
timeout: 5000
Microservice B and C start fine, but A gives this error: Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'http.connect.timeout' in value "${http.connect.timeout}
What is the best way around this problem without providing dummy values in microservice A?

Solution 1: use a configuration hierarchy with an additional #Configuration class that makes a RestTemplate bean, and activate it only for B and C (or when the property is available):
#Configuration
#ConditionalOnProperty("http.connect.timeout")
public class HttpConnectionConfiguration {
// rest of code ...
Solution 2: use defaults. Since the default timeout is -1, use that:
#Value("${http.connect.timeout:-1}")
private int httpConnectTimeout;
#Value("${http.connect.request.timeout:-1}")
private int httpConnectRequestTimeout;

You can specify default values inside #Value using SpEL Expression like below.
#Value("${http.connect.timeout:10}")
private int httpConnectTimeout;
#Value("${http.connect.request.timeout:10}")
private int httpConnectRequestTimeout;

Related

Why does Hazelcast not show entry in the Map when added via CachePut/Cacheable

I have a use case where by the #CachePut annotation adds an entry to the cache, and I have to retrieve it manually (via code).
I can see that the total backup count gives me 1 as the number of entries, but all the maps give me the size as 0. So, I am not sure what I am doing wrong.
Here's my code
HazelcastConfig.java
#Configuration
public class HazelcastConfig {
#Bean
public Config hazelcastConf() {
Config c = new Config()
.setInstanceName("hazelcast-instance")
.addMapConfig(
new MapConfig()
.setName("testmap")
.setEvictionConfig(
new EvictionConfig()
.setEvictionPolicy(EvictionPolicy.LRU)
.setMaxSizePolicy(MaxSizePolicy.PER_NODE)
.setSize(1000)
)
.setTimeToLiveSeconds(500000)
);
c.getNetworkConfig().getRestApiConfig().setEnabled(true);
c.getNetworkConfig().getRestApiConfig().enableGroups(RestEndpointGroup.DATA);
return c;
}
}
TestServiceImpl.java
#Service
public class TestServiceImpl implements TestService {
#Autowired
#Lazy
private RestTemplate restTemplate;
#Override
#CachePut(value = "testmap", key="1")
public String getId() {
System.out.println("--------------------------");
System.out.println("-------INSIDE getId-------");
String id = null;
CBObject obj = restTemplate.getForObject("http://localhost:3000/testCB", CBObject.class);
if (null != obj && null != obj.getId()) {
id = String.valueOf(obj.getId());
}
System.out.println("-------- EXIT getId-------");
System.out.println("--------------------------");
return id;
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
TestController.java
#RestController
#RequestMapping("/v1")
public class TestController {
#Autowired
private TestService testService;
#GetMapping("/testCB")
public ResponseEntity<?> doCB() {
Map<String, String> resp = new HashMap<>();
String id = testService.getId();
if (null != id) {
resp.put("id", id);
}
Config config = new HazelcastConfig().hazelcastConf();
System.out.println(config.getMapConfig("testmap").getTotalBackupCount()); // 1
HazelcastInstance hz = Hazelcast.getHazelcastInstanceByName(config.getInstanceName());
System.out.println(hz.getReplicatedMap("testmap").size()); // 0
System.out.println(hz.getMap("testmap").size()); // 0
System.out.println(hz.getMultiMap("testmap").size()); // 0
return ResponseEntity.status(HttpStatus.ACCEPTED).body(resp);
}
}
Have you explicitly enabled caching using the #EnableCaching annotation (Ref Doc, Javadoc)?
Also, see the guidance from Spring Boot in the Ref Doc on Caching if you are using Spring Boot.
Furthermore, when using Spring Boot, you can either add the command-line switch --debug to your launch command or set the debug property to true in Spring Boot application.properties to get output from the Auto-configuration that has been applied. In particular you will want to see that the CacheAutoConfiguration class has been processed.
If you are NOT using Spring Boot, then in addition to the #EnableCaching annotation, you will also need to explicitly declare a CacheManager bean, such as:
#Bean
HazelcastCacheManager cacheManager(HazelcastInstance hazelcaseInstance) {
return new HazelcastCacheManager(hazelcastInstance);
}
This will require the com.hazelcast:hazelcast-spring JAR dependency on your runtime classpath.
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-spring</artifactId>
<version>${hazelcast.version}</version>
<scope>runtime</scope>
</dependency>
For example.
NOTE: Do you not confuse the HazelcastCacheManager Spring Cache Abstraction CacheManager implementation, which requires the hazelcast-spring JAR and is required by Spring's Cache Abstraction either with or without Spring Boot, with Hazelcast's standard HazelcastCacheManager. These 2 classes are not the same thing.
Alternatively, you could also use Hazelcast as a JCache caching provider implementation in either Spring Framework or Spring Boot. The core Spring Framework offers support for using JCache as well. When using Spring Boot, you will need to specify the JCache cache provider type for Hazelcast (i.e. Embedded or Client/Server). I will leave this as an exercise for you to figure out.
Lastly, I recently built an example for my own testing purposes using Hazelcast as a caching provider in Spring Framework's Cache Abstraction using Spring Boot, if you would like to take a look.
Hope this helps!
Cheers!

Azure service bus configuration via code spring

I'm using the following dependency to send and receive messages from a azure service bus topic:
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-servicebus-jms</artifactId>
<version>4.2.0</version>
</dependency>
I'd like to create the configuration via code through a spring bean because I need to configure more than 1 connection string, so after read the documentation, I decided to create this bean:
#Bean
#Primary
public AzureServiceBusJmsProperties priceListJmsProperties() {
var properties = new AzureServiceBusJmsProperties();
properties.setConnectionString(connectionString);
properties.setPricingTier("standard");
properties.setTopicClientId(priceListTopicName);
return properties;
}
If I debug the object creation, I see that this object is been creating twice, the first one with the configuration that I've provided, and the second one with null data, and this is the reason of why I'm getting the following error because there is a validation in this object that throws an exception if certain field is not set in the properties file:
spring.jms.servicebus.connection-string' should be provided
I've tried creating a connection factory instead but for the reason above, I'm getting the same error.
Anyone knows how I can set this configuration as a bean instead of the application.properties file? Thanks in advance.
Following the #DeepDave-MT answer, I couldn't disable the jms autoconfiguration with the spring.jms.servicebus.enabled property, so I decided to exclude the ServiceBusJmsAutoConfiguration with the property spring.autoconfigure.exclude, you have to pass the package name to this property.
Then, in my config class, I just added the following beans:
#Bean
#Primary
public ConnectionFactory connectionFactory() {
var connectionFactory = new ServiceBusJmsConnectionFactory(connectionString);
var serviceBusConnectionString = new ServiceBusConnectionString(connectionString);
var remoteUri = String.format(AMQP_URI_FORMAT, serviceBusConnectionString.getEndpointUri(), 100000);
connectionFactory.setRemoteURI(remoteUri);
connectionFactory.setClientID(topicName);
connectionFactory.setUsername(serviceBusConnectionString.getSharedAccessKeyName());
connectionFactory.setPassword(serviceBusConnectionString.getSharedAccessKey());
return new CachingConnectionFactory(connectionFactory);
}
#Bean
#Primary
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory(#Qualifier("connectionFactory") ConnectionFactory connectionFactory) {
var topicFactory = new DefaultJmsListenerContainerFactory();
topicFactory.setConnectionFactory(connectionFactory);
topicFactory.setSubscriptionDurable(Boolean.TRUE);
topicFactory.setErrorHandler(priceListErrorHandler());
return topicFactory;
}
#Bean
#Primary
public AzureServiceBusJmsProperties jmsProperties() {
var properties = new AzureServiceBusJmsProperties();
properties.setConnectionString(connectionString);
properties.setPricingTier("standard");
properties.setTopicClientId(topicName);
return properties;
}

#Retryable annotation not working for non Spring Bean class method

I am new to spring-retry. Basically, for retrying calls to REST APIs, I have integrated spring-retry into my spring-boot application. To do this, I have made following changes:
Added spring-retry to pom.xml.
Added following configuration:
#Configuration
#EnableRetry
public class RetryConfiguration {
}
Finally added #Retryable annotation to the class (this class is not a Spring Bean) method that I would like to be retried for various exceptions as follows:
public class OAuth1RestClient extends OAuthRestClient {
#Override
#Retryable(maxAttempts = 3, value = {
Exception.class},
backoff = #Backoff(delay = 100, multiplier = 3))
public Response executeRequest(OAuthRequest request)
throws InterruptedException, ExecutionException, IOException {
System.out.println("Inside Oauth1 client");
return myService.execute(request);
}
Now, the executeRequest method is not retrying. I am not able to understand if I am missing anything here.
Could anyone please help? Thanks.
If your class is not Spring managed (e.g. #Component/#Bean) the
annotation processor for #Retryable won't pick it up.
You can always manually define a retryTemplate and wrap calls with it:
RetryTemplate.builder()
.maxAttempts(2)
.exponentialBackoff(100, 10, 1000)
.retryOn(RestClientException.class)
.traversingCauses()
.build();
and then
retryTemplate.execute(context -> myService.execute(request));
If you want to retry on multiple exception, this can happen via custom RetryPolicy
Map<Class(? extends Throwable), Boolean> exceptionsMap = new HashMap<>();
exceptionsMap.put(InternalServerError.class, true);
exceptionsMap.put(RestClientException.class, true);
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, exceptionsMap, true);
RetryTemplate.builder()
.customPolicy(policy)
.exponentialBackoff(100, 10, 1000)
.build();
FYI: RetryTemplate is blocking and you might want to explore a non-blocking async retry approach like async-retry. - and the retryOn() supports a list of exceptions.

Mockito test throwing "URI is not absolute" error

I am trying to mock this method with postForEntity call -
public AuthorizeClient(RestTemplateBuilder builder, Config config) {
this.grantedUrl = config.grantedUrl();
this.restTemplate = HttpClientHelper.getRestTemplate(builder, authorizationConfig);
}
private final RestTemplate restTemplate;
private String grantedUrl;
public List<Permission> getPermissions(
PermissionsRequest permissionsRequest) {
try {
var headers = new HttpHeaders();
var request = new HttpEntity<PermissionsRequest>(permissionsRequest, headers);
var permissions = restTemplate.postForEntity(grantedUrl, request, Permission[].class);
return Arrays.asList(permissions.getBody());
} catch (HttpClientErrorException err) {
logger.error(err);
throw err;
}
}
Here is my test case -
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
Config config = new Config();
#InjectMocks
AuthorizeClient authorizeClient = new AuthorizeClient(restTemplateBuilder, config);
#Mock
private RestTemplate restTemplate;
PermissionsRequest permissionsRequest;
ResponseEntity<Permission[]> expGrantedPermissions;
#Test
public void testAuthorizationPermissions() {
when(restTemplate.postForEntity(anyString(), any(), eq(Permission[].class))).thenReturn(expGrantedPermissions);
var res = authorizeClient.getAllGrantedPermissions(permissionsRequest);
assertNotNull(res);
}
I'm getting this error. Looks like mock is not created properly ..
java.lang.IllegalArgumentException: URI is not absolute
At this line -
var res = authorizeClient.getPermissions(permissionsRequest);
My AuthorizeClient is constructed like above..
Please suggest what am I missing.
Thanks in advance
From your example code I don't see a relation between your restTemplate mock and the AuthorizeClient class.
The problem is that your restTemplate field in your object is final. In this case - even so the #InjectMocks generally works with the new constructor - injection of the mock does not happen.
You might want to add the full stacktrace, but I would assume that config.grantedUrl() does not return a valid url.
I assume grantedUrl is a String. In that case you define behaviour for the wrong method. There is only a 4-param version of postForEntity, so you'll need to define the mock for that using Mockito.<Object>any() for the varargs parameter.
To fix this: You might want to mock the RestTemplateBuilder and define behaviour for the methods that are used by HttpClientHelper.getRestTemplate.
You might also want to consider using PowerMockito to mock the static method directly.
Alternatively you could refactor your code to pass the RestTemplate directly to the constructor instead of passing the builder. At least in your example the builder does not seem to be used for anything else within the class, so I would consider it a removable dependency.
Also using Constructor injection is considered the way to go by many.
I assume PermissionsRequest andPermission` are your classes, so I can't really test this specific case, but basically this should work:
Note that I assume a changed constructor for AuthorizeClient that accepts both config and restTemplate. Instead of using the annotation I setup the mock manually, because you used a real Config object in your example. If mocking both is an option, you can still use the #InjectMocks annotation.
#RunWith(MockitoJUnitRunner.class)
public class RestTemplateTest {
Config config = new Config();
#Mock
private RestTemplate restTemplate;
PermissionsRequest permissionsRequest;
ResponseEntity<Permission[]> expGrantedPermissions;
#Test
public void testAuthorizationPermissions() {
// init permissionsRequest and expGrantedPermissions, if you haven't done that
AuthorizeClient authorizeClient = new AuthorizeClient(config, restTemplate);
Mockito.when(restTemplate.postForEntity(Mockito.anyString(), Mockito.any(), Mockito.eq(Permission[].class), Mockito.<Object>any())).thenReturn(expGrantedPermissions);
List<Permission> res = authorizeClient.getAllGrantedPermissions(permissionsRequest);
assertNotNull(res);
}
}
Ps.:
For a standalone example of a different method on restTemplate you can check my answer here. This at least can help you verify that you mock the correct method.

Exists any <jaxws:endpoint /> annotation?

I'm using CXF with Spring to publish and to consume my WebServices in JBoss 5.1. All works fine.
However, there's a thing that's I think very tedious: to put a jaxws:endpoint tag for every WebService in applicationContext.xml.
There's realy no way to do that with annotations? Thanks for all.
As time pass, there arise some new possibilities.
Working with CXF/SpringBoot (SpringBoot: 1.2.3, CXF: 3.10, Spring: 4.1.6) there is a nice alternative in order to get rid of the jaxws:endpoint configuration in cxf-servlet.xml, as jonashackt pointed out in nabble.com. However, this solution is only possible if there is only one endpoint in the application (at least I did not succeed to configure more than one).
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean dispatcherServlet() {
CXFServlet cxfServlet = new CXFServlet();
return new ServletRegistrationBean(cxfServlet, "/api/*");
}
#Bean(name="cxf")
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public MyServicePortType myService() {
return new MyServiceImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), myService());
endpoint.publish("/MyService");
return endpoint;
}
Where MyServicePortType is a class having the #WebService annotation. This Endpoint is then called for URL's like "localhost:8080/api/MyService"
Of course these #Bean declarations may be placed in any other spring config class.
In contrary to the copied original solution I suggest to instantiate the Bus (cxf-Bean) by using the factory method instead of the direct "new SpringBus()":
BusFactory.newInstance().createBus()
There are some annotations to configure things that you can also put in <jaxws:endpoint>. An annotation to declare a CXF endpoint would be nice.
You can configure an endpoint using code instead of Spring XML. This can be handy if you have a lot of repetitive configuration that you can factor out. Or if you have certain endpoints configured differently in different environments.
For example:
#Autowired var authImpl: Auth = _
#Autowired var faultListener: FaultListener = _
def initWebServices() {
var sf: JaxWsServerFactoryBean = _
val propMap = mutable.HashMap[String, AnyRef]("org.apache.cxf.logging.FaultListener"->faultListener.asInstanceOf[AnyRef])
sf = new JaxWsServerFactoryBean
sf.setServiceBean(authImpl)
sf.setAddress("/auth")
sf.setServiceName(new QName("http://auth.ws.foo.com/", "auth", "AuthService"))
sf.setProperties(propMap)
sf.create
// more services...

Categories