Spring: how to get rootUri from RestTempalte - java

I'm using this configuration class to initialize RestTemplate:
#Configuration
public class RestTemplateConfig {
#Value("${endpoint-url}")
private String endpointUrl;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
}
And in one of my service's method I use the code:
RootUriTemplateHandler handler = (RootUriTemplateHandler) restTemplate.getUriTemplateHandler();
String uri = handler.getRootUri();
restTemplate.postForLocation(uri, request);
To get this URI. Is there an easier method to get this rootUri (without casting)? Or to execute the post request directly to rootUri?

restTemplate.getUriTemplateHandler().expand("/")

You can execute the post request directly to rootUri, as long as the uri you provide to the restTemplate.postForLocation starts with "/". In that case, Spring will automatically add the baseURI provided in the restTemplate constructor.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder,
#Value("${endpoint-url}") String endpointUrl) {
return builder
.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
}
In your service's method:
// Make a POST call to ${endpoint-url}/foobar
String uri = "/foobar"; // The leading "/" is important!
restTemplate.postForLocation(uri, request);

Sounds like you are trying to use RestTemplate to pass along the value of ${endpoint-url}. That slightly awkward looking cast works but you could perhaps consider one of these alternatives:
Create a provider which encapsulates the endpointUrl and your restTemplate and inject this provider wherever you need either the endpointUrl or the restTemplate. For example:
#Component
public class RestTemplateProvider {
#Value("${endpoint-url}")
private String endpointUrl;
private final RestTemplate restTemplate;
#Autowired
public RestTemplateProvider(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
public RestTemplate provide() {
return restTemplate;
}
public String getEndpointUrl() {
return endpointUrl;
}
}
Inject #Value("${endpoint-url}") private String endpointUrl; into which ever service class needs it.

Related

Could not autowire. No beans of 'RestTemplateBuilder' type found

I have a Java-Spring LIBRARY (NOT an application) which sends notifications via phone numbers. I'm using a Rest template to send a POST request. But instead of creating a new object of RestTemplate, I would want to use RestTemplateConfiguration #Configuration to do so.
IntelliJ version - 2020.2
Spring - 2.3.3.RELEASE
Java - 11
1st issue - When I'm trying to create a RestTemplateConfiguration class, I get the error -
Could not autowire. No beans of 'RestTemplateBuilder' type found.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
2nd issue If I create a new object of RestTemplateBuilder, it doesn't give the error, but this is not how I want this class to behave. Also, this doesn't seem to work when in the class (mentioned below) SamplePhoneNumbers.java, I try to use RestTemplate restTemplate. It is coming as null.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder;
return builder.build();
}
}
Here is the class which sends notifications and where I am trying to use the Rest Template.
#Slf4j
public class SamplePhoneNumbers implements SampleClassStratergy {
RestTemplate restTemplate;
private static SamplePhoneNumbers samplePhoneNumbers = null;
private String phoneNumbersNotificationServiceURL = Enums.NotificationURL.enum_value + Enums.PhoneNumberApi.enum_value;
SamplePhoneNumbers() {
this.restTemplate = new RestTemplate();
}
public static SamplePhoneNumbers getInstance() {
if(samplePhoneNumbers ==null) {
samplePhoneNumbers = new SamplePhoneNumbers();
}
return samplePhoneNumbers;
}
#Async
public void sendNotification(String title, String message, List<String> listOfPhoneNumbers) {
SmsMessage smsMessage= new SmsMessage(title, message, listOfPhoneNumbers, Collections.emptyList(), Collections.emptyList());
try {
HttpEntity<SmsMessage> newRequest = new HttpEntity<>(smsMessage);
restTemplate.postForObject(phoneNumbersNotificationServiceURL, newRequest, String.class);
log.info("Notification sent via phone number.");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
3rd issue: Is there any way I can get rid of getInstance() method so I don't have to handle Singleton logic? If I can create the class as a bean, that should work I guess, but I'm not sure how that can be achieved in this case.
Since this is a Library, it doesn't have a main method and no #SpringBootapplicationContext.
Can someone please assist me with the solution?
I also got the similar error while running Junit test case and resolved it by adding a bean of -
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder();
}
Do you want invoke your Java-Spring LIBRARY in a spring boot application? If so, you should add #ComponentScan in spring boot application main method as below.
pacakge com.prod
// for instance, Java-Spring LIRARY package is com.third
#SpringBootApplication
#ComponentScan(basePackages={"com.prod","com.third"})
public class Application {
Then you should do as below:
#Configuration
public class RestTemplateConfiguration {
#Bean(name="myRestTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
#Slf4j
#Component
public class SamplePhoneNumbers implements SampleClassStratergy {
#Autowired
#Qualifier("myRestTemplate")
RestTemplate restTemplate;
#Async
public void sendNotification(String title, String message, List<String> listOfPhoneNumbers) {
SmsMessage smsMessage= new SmsMessage(title, message, listOfPhoneNumbers, Collections.emptyList(), Collections.emptyList());
try {
HttpEntity<SmsMessage> newRequest = new HttpEntity<>(smsMessage);
restTemplate.postForObject(phoneNumbersNotificationServiceURL, newRequest, String.class);
log.info("Notification sent via phone number.");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}

Calling a method that has autowired values

I have a method where I am injecting a value to an argument of the method. How can I call the checkHealth without passing in an argument that will overwrite the value value being injected?
private Health checkHealth(#Qualifier("myRestTemplate") RestTemplate restTemplate) {
}
#Bean(name = "myRestTemplate")
public RestTemplate myRestTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.rootUri(uri)
.basicAuthentication("", "")
.build();
}
Well you cannot! Note that Spring's annotations work in its Spring's context and not when you manually try to do something. What it means is that, the injection will effectively happen if and only if Spring invokes the method checkHealth() and not when you call this method.
In your case, you do not need to pass in the argument. Simply call myRestTemplate() inside checkHealth() as:
private Health checkHealth() {
final RestTemplate template = myRestTemplate();
...
}
For your scenario to work, inject myRestTemplate as a field:
#Service
public class HealthServiceImpl implements HealthService {
#Autowired
#Qualifier("myRestTemplate")
private RestTemplate restTemplate;
private Health checkHealth() {
// do something with restTemplate
}
}
Otherwise, check #Prashant's answer on why injecting it as a method parameter does not work.
I ended up doing the below -
private RestTemplate restTemplate;
private Health checkHealth() {
ResponseEntity<String> response
= this.restTemplate.getForEntity(resourceUrl, String.class);
}
#Postconstruct
public RestTemplate myRestTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.rootUri(uri)
.basicAuthentication("", "")
.build();
}

Spring Boot - Mock a POST REST request to an external API

I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");

Unit test service class with mocks using TestNG

I am attempting to write a unit test for a generic service class like the following:
public class ApiService{
private RestTemplate restTemplate;
private ServiceDao serviceDao;
#Autowired
public ApiService(RestTemplate restTemplate, ServiceDao serviceDao) {
this.restTemplate = restTemplate;
this.serviceDao = serviceDao;
}
public ResponseEntity getObject(ObjectRequest request) {
// Service logic here
}
public ResponseEntity postObject(CreateObjectRequest request) {
// Service logic here
}
}
But am struggling with how to mock the restTemplate in the constructor of my service class such that when the test runs, data is not persisted.. I've looked into Mockito though don't see many examples or documentation regarding Mockito + TestNG in this context. Any help would be appreciated
First of all - if possible inject RestOperations in your service instead of RestTemplate. Then you will be able easily mock its behavior (note: RestTemplate implements RestOperations).
If using RestOperations is not possible - you can do something this:
RestTemplate myTemplate = Mockito.spy(new RestTemplate());
String expectedOutput = "hello mock";
String inputUrl = "https://stackoverflow.com/questions/53872148/unit-test-service-class-with-mocks-using-testng";
Mockito.doReturn(expectedOutput).when(myTemplate).getForObject(inputUrl, String.class);
String result = myTemplate.getForObject(inputUrl, String.class);
Assert.assertEquals(expectedOutput, result);
I've actually constructed a method using Mockito as follows... there might be a more elegant solution so I would be interested to see:
public class ServiceTest {
#BeforeMethod(groups="serviceTest")
public void beforeMethod() {
MockitoAnnotations.initMocks(this);
}
#Test(groups="serviceTest")
public void testGetService_returns200() {
when(serviceDao.getService(any(String.class), any(RestTemplate.class), any(HttpHeaders.class))).thenReturn(new ResponseEntity(new Object(), HttpStatus.OK));
ObjectRequest request = new ObjectRequest();
// set request values
ResponseEntity testResponse = apiService.getObject(request);
}
}

Constructing RestTemplates with Spring #Configuration

Say I have some application-wide configuration:
#Configuration
public class RestTemplateConfiguration {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public HttpClient anHttpClient() {
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
// set some properties
// ...
return HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager).build();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ClientHttpRequestInterceptor aRequestInterceptor() {
....
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ClientHttpRequestInterceptor anotherRequestInterceptor() {
....
}
}
Then I have some particular service that looks like:
#Service
public class MyService {
private final RestTemplate myParticularRestTemplate;
#Autowired
public MyService(RestTemplate myParticularRestTemplate) {
this.myParticularRestTemplate = myParticularRestTemplate;
}
/***
* Some incredible application logic
***/
#Configuration
public static class Config {
private int SOME_READ_TIMEOUT;
private int SOME_CONNECT_TIMEOUT;
#Bean
public RestTemplate myParticularRestTemplate(HttpClient anHttpClient, List<ClientHttpRequestInterceptor> interceptors) {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(anHttpClient);
clientHttpRequestFactory.setReadTimeout(SOME_READ_TIMEOUT);
clientHttpRequestFactory.setConnectTimeout(SOME_CONNECT_TIMEOUT);
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
}
I want the interceptors to be injected into RestTemplates system-wide, but, as it is, because I've got to access nested objects of the RestTemplate to set particular configuration, I need to instantiate the RestTemplate myself -- at least as I understand it.
Is there a nicer way to address this that doesn't require configuration for specific RestTemplates to know about the List<ClientHttpRequestInterceptor>?

Categories

Resources