RestTemplateBuilder bean - spring-mvc

My application interfaces with different rest endpoints and each one needs a specialized RestTemplate object. I am using RestTemplateBuilder to create each of the RestTemplate objects. Is it ok to clone the RestTemplateBuilder object provided by spring boot and make the changes?
#Configuration
public class Config {
#Bean
public RestTemplate googleRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.basicAuthorization("user", "pwd123").build();
}
#Bean
public RestTemplate twitterRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.setConnectTimeout(5000).build();
}
}
Here, i get the singleton builder bean injected to both the bean methods. The problem i am trying to overcome is that the mutating of the same builder object. In my example, my 2nd rest template does not need basic authorization but i inadvertently get it.

You can differentiate your RestTemplates using #Qualifier annotation like below.
#Configuration
public class Config {
#Bean
#Qualifier("googleRestTemplate")
public RestTemplate googleRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.basicAuthorization("user", "pwd123").build();
}
#Bean
#Qualifier("twitterRestTemplate")
public RestTemplate twitterRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.setConnectTimeout(5000).build();
}
}
Then you can use any of RestTemplate beans in your controllers with these #Qualifiers.
#Autowired
#Qualifier("googleRestTemplate")
private RestTemplate restTemplate;

Why would you do that?
Add a config class:
#Configuration
class Config {
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
}
inside your Controller that will consume your end-points:
#Autowired
RestTemplate restTempl;
#RequestMapping(value = "/consume", method = RequestMethod.POST)
public void samplePostMethod() {
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<String>("Data", headers);
RequestDetails requestDetails = new RequestDetails("http://localhost:8082/endpoint1", HttpMethod.POST);
ResponseEntity<String> response = restTempl.exchange(requestDetails.getUrl(), requestDetails.getRequestType(),
entity, String.class);
RequestDetails requestDetailss = new RequestDetails("http://localhost:8082/endpoint2", HttpMethod.POST);
ResponseEntity<String> responses = restTempl.exchange(requestDetailss.getUrl(), requestDetailss.getRequestType(),
entity, String.class);
logger.log("{} {}", response, responses);
return response.getBody();
}

Related

RedisSentinelConfiguration using spring.redis.sentinel.nodes with spring boot

Trying to configure spring boot application with spring-session and redis but having below issue. Not able to resolve it.
Constructor threw exception; nested exception is java.lang.IllegalStateException: BeanFactory has not been injected into #Configuration class
This code works fine for me
#Configuration
#EnableRedisHttpSession
public class HttpSessionConfig {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(SLAVE_PREFERRED)
.build();
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("192.168.56.50", 26379)
.sentinel("192.168.56.50", 26380)
.sentinel("192.168.56.50", 26381);
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
}
but not this code using PropertySource.
Spring document says:-
**RedisSentinelConfiguration can also be defined with a PropertySource, which lets you set the following properties:
Configuration Properties
spring.redis.sentinel.master: name of the master node.
spring.redis.sentinel.nodes: Comma delimited list of host:port pairs.**
#Configuration
#EnableRedisHttpSession
#PropertySource(name="application", value="classpath:application.properties")
public class HttpSessionConfig {
#Resource
ConfigurableEnvironment environment;
#Bean
public PropertiesPropertySource propertySource() {
return (PropertiesPropertySource) environment.getPropertySources().get("defaultProperties");
}
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(SLAVE_PREFERRED)
.build();
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration(propertySource());
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
}
application.properties
server.port=8090
spring.security.user.name=admin
spring.security.user.password=admin
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=192.168.56.50:26379,192.168.56.50:26380,192.168.56.50:26381
spring.application.name=spring-session-demo
The format of sentinel nodes property is comma separated key:value pairs. So you can extract host and port by java split() function.
#Autowired
private Environment env;
#Bean
public LettuceConnectionFactory connectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
String master = env.getProperty("spring.redis.sentinel.master");
String nodes = env.getProperty("spring.redis.sentinel.nodes");
sentinelConfig.master(master);
for (String node : nodes.split(",")) {
String split[] = node.split(":");
sentinelConfig.sentinel(split[0], Integer.parseInt(split[1]));
}
...
}

No mapping found for HTTP request with URI [/api/transactions] in DispatcherServlet with name ''

I thought this is a standard configuration. But I get a 404 back. Where else should I configure Spring Boot ?
#RestController
#RequestMapping("/api")
public class TransactionStatisticsController {
public static final Logger logger = LoggerFactory.getLogger(TransactionStatisticsController.class);
#RequestMapping(value = "/transactions",
method = RequestMethod.POST)
public ResponseEntity sendTransaction(#RequestBody Transaction request) {
logger.info( request.toString());
return new ResponseEntity(HttpStatus.OK);
}
}
This is my test.
#JsonTest
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public class TransactionStatisticsRestTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private JacksonTester<Transaction> json;
private static Transaction transaction;
#BeforeClass
public static void createTransaction(){
BigDecimal amount = new BigDecimal(12.3343);
transaction = new Transaction(amount.toString(),
"2010-10-02T12:23:23Z");
}
#Test
public void getTransactionStatus() throws Exception {
final String transactionJson = json.write(transaction).getJson();
mockMvc
.perform(post("/api/transactions")
.content(transactionJson)
.contentType(APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsBytes(transaction);
}
}
Request being made is
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/transactions
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
Body = {"amount":"12.3343000000000007077005648170597851276397705078125","timestamp":"2010-10-02T12:23:23Z[UTC]"}
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Update : I added a component scan pointing to a base package. I don't see that error now. Please see the comments where there is an answer.
As in the comment section ,there was only requirement was to bind a component scan base package location .
#Component scan -->Configures component scanning directives for use with #Configuration classes. Provides support parallel with Spring XML's element.
Either basePackageClasses() or basePackages() (or its alias value()) may be specified to define specific packages to scan. If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.
Please share your project folder architecture. It might be possible that your controller package is out of the main class package. That's why it is showing 404.
This code :
#RestController
#RequestMapping("/api")
public class TransactionStatisticsController {
public static final Logger logger = LoggerFactory.getLogger(TransactionStatisticsController.class);
#RequestMapping(value = "/transactions",
method = RequestMethod.POST)
public ResponseEntity sendTransaction(#RequestBody Transaction request) {
logger.info( request.toString());
return new ResponseEntity(HttpStatus.OK);
}
}
This should be into your main package where
#SpringBootApplication
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
}
this main class resides.
I hope, this will help.
Seems using #JsonTest does not even allow to load Application Context, results mapping is not loaded and its throw 404 so #JsonTest is not a replacement for #SpringBootTest, it is a way to easily test json serialization/de-serialization.
As per documentation:
you can use the #JsonTest annotation. #JsonTest auto-configures the
available supported JSON mapper, which can be one of the following
libraries:
Jackson ObjectMapper, any #JsonComponent beans and any Jackson Modules
Gson
Jsonb
If by using Gson and removing #JsonTest your test run fine..(add Gson Dependency in pom)
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public class DemoKj01ApplicationTests {
#Autowired
private MockMvc mockMvc;
private static Transaction transaction;
#BeforeClass
public static void createTransaction(){
BigDecimal amount = new BigDecimal(12.3343);
transaction = new Transaction(amount.toString(),
"2010-10-02T12:23:23Z");
}
#Test
public void getTransactionStatus() throws Exception {
//final String transactionJson = json.write(transaction).getJson();
Gson gson = new Gson();
String jsonRequest = gson.toJson(transaction);
mockMvc
.perform(post("/api/transactions")
.content(jsonRequest)
.contentType(APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
It is beacause of the trailing slas in #RequestMapping(value = "/transactions/", method = RequestMethod.POST)
Remove it and it will be ok : value = "/transactions/" => value = "/transactions"

SpringBootTest RestController in Spock Test results in 404

Given below code for Controller, when I start the spring boot application, I am able to make http calls to the resource /foo/id/{id} to fetch data.
However the same call from an Integration test is returning 404. Call did not trigger rest controller method. Test is not forwarding the http calls to the rest controller. What am I missing?
#RestController
#RequestMapping(“/foo”)
class FooResource {
#RequestMapping(method = RequestMethod.GET, path = “/id/{id}”)
String getData(#PathVariable int id) {
logger.error("===== FooResource.getData called with {}", id)
// more code
}
//more code
//Spock test case
#ContextConfiguration
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT
)
#EnableWebMvc
#AutoConfigureWebClient
#TestPropertySource(locations = "classpath:application-test.properties")
class IntegrationTest extends Specification {
#Autowired
RestTemplate restTemplate
#Configuration
#ImportResource(["classpath*:/test-properties.xml", "classpath*:/springintegration-config.xml"])
static class Beans {
#Bean
MessagingTemplate messagingTemplate() { new MessagingTemplate() }
#Bean
ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory(9010);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(30 * 1000)
.setReadTimeout(30 * 1000)
.build();
}
}
def ‘foo resource returns the expected data for Id'() {
given:
int id = new SecureRandom().nextInt()
TestRestTemplate restTemplate = new TestRestTemplate();
when:
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:9010/foo/id/1234", String.class)
then:
assert response.statusCode == HttpStatus.OK
}
Test case run log includes the below mappings already available
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped “{[/foo/id/{Id}],methods=[GET]}" onto public java.lang.String com.foo.bar.rest.FooResource.getData(int)
2018-06-15 13:54:38.680 DEBUG 20710 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'requestMappingHandlerMapping'
2018-06-15 13:54:38.680 DEBUG 20710 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'mvcPathMatcher'
2018-06-15 13:54:38.681 DEBUG 20710 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'mvcPathMatcher'
Changing the Annotations on IntegrationTest class resolved the issue.
//Spock test case
#SpringBootTest(
classes = TestConfig.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT
)
#AutoConfigureWebClient
#TestPropertySource(locations = "classpath:application-test.properties")
class IntegrationTest extends Specification {
#Autowired
RestTemplate restTemplate
#Configuration
#ImportResource(["classpath*:/test-properties.xml", "classpath*:/springintegration-config.xml"])
static class Beans {
#Bean
MessagingTemplate messagingTemplate() { new MessagingTemplate() }
#Bean
ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory(9010);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(30 * 1000)
.setReadTimeout(30 * 1000)
.build();
}
}
def ‘foo resource returns the expected data for Id'() {
given:
int id = new SecureRandom().nextInt()
TestRestTemplate restTemplate = new TestRestTemplate();
when:
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:9010/foo/id/1234", String.class)
then:
assert response.statusCode == HttpStatus.OK
}

Spring Boot sending empty json for custom FreeMarker login page

In Spring Boot I added #Configuration annotated with #EnableWebMvc for custom MVC. It uses Freemarker templates under src/main/resources/templates. Problem is the login page is getting send back to browser as empty json . Do I need to add extra content negotiation or something else? Thx
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public ContentNegotiatingViewResolver contentViewResolver() throws Exception {
ContentNegotiationManagerFactoryBean contentNegotiationManager = new ContentNegotiationManagerFactoryBean();
contentNegotiationManager.addMediaType("json", MediaType.APPLICATION_JSON);
FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();
viewResolver.setPrefix("/templates/");
viewResolver.setSuffix(".ftl");
MappingJackson2JsonView defaultView = new MappingJackson2JsonView();
defaultView.setExtractValueFromSingleKeyModel(true);
ContentNegotiatingViewResolver contentViewResolver = new ContentNegotiatingViewResolver();
contentViewResolver.setContentNegotiationManager(contentNegotiationManager.getObject());
contentViewResolver.setViewResolvers(Arrays.<ViewResolver> asList(viewResolver));
contentViewResolver.setDefaultViews(Arrays.<View> asList(defaultView));
return contentViewResolver;
}
....
}
#Controller
#SessionAttributes
public class LoginController {
#RequestMapping("/login")
public String login() {
return "login";
}
}
The login controller was missing the appropriate content type setting for the client HTTP GET, so I added this produces attribute.
#RequestMapping(value = "/login", produces="application/xml")

How to setup OAuth2RestTemplate (Post Updated)

I am not sure if I have OAuth2RestTemplate configured correctly. I am getting the following error when I run the tester class.
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory#1df3248: defining beans [propertyConfigurer,dataSource,transactionManager,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,emf,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,accountRepository,questionRepository,org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor#0,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0,jpaQuestionService,jpaAccountService,passwordEncoder,accountHelper,tradeConfig,org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor#1,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,baseOAuth2ProtectedResourceDetails,oAuth2ProtectedResourceDetails,accessTokenRequest,oAuth2ClientContext,oAuth2RestTemplate]; root of factory hierarchy
Exception in thread "main" error="access_denied", error_description="Unable to obtain a new access token for resource 'null'. The provider manager is not configured to support it."
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:146)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:118)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:216)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:168)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:89)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:442)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:123)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:409)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:385)
at com..main(Tester.java:44)
Classes
#Configuration
public class AppConfig {
#Bean
//#Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES)
public BaseOAuth2ProtectedResourceDetails baseOAuth2ProtectedResourceDetails(){
BaseOAuth2ProtectedResourceDetails baseOAuth2ProtectedResourceDetails = new BaseOAuth2ProtectedResourceDetails();
baseOAuth2ProtectedResourceDetails.setClientId(clientId);
baseOAuth2ProtectedResourceDetails.setClientSecret(clientSecret);
return baseOAuth2ProtectedResourceDetails;
}
#Bean
public DefaultAccessTokenRequest accessTokenRequest(){
return new DefaultAccessTokenRequest();
}
#Bean
public OAuth2ClientContext oAuth2ClientContext(){
return new DefaultOAuth2ClientContext(accessTokenRequest());
}
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(){
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(baseOAuth2ProtectedResourceDetails(),oAuth2ClientContext());
return restTemplate;
}
}
Tester Class
public class Tester {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath*:jpa-app-context.xml");
ctx.refresh();
EntityManagerFactory emf = (EntityManagerFactory) ctx.getBean("emf");
EntityManager em = emf.createEntityManager();
TransactionSynchronizationManager.bindResource(emf , new EntityManagerHolder(em));
OAuth2RestTemplate oAuth2RestTemplate = (OAuth2RestTemplate) ctx.getBean("oAuth2RestTemplate");
//OAuth2RestTemplate oAuth2RestTemplate = ctx.getBean(OAuth2RestTemplate.class);
String uri="https:api..";
Object obj = oAuth2RestTemplate.exchange(uri, HttpMethod.POST, null, Object.class);
System.out.println("Tester Object: "+ obj.toString());
}
}
I faced the same exception, but with another protected resource type.
Generally, the exception raises only when AccessTokenProviderChain can't find an appropriate *AccessTokenProvider for particular *ProtectedResourceDetails instance. Meaning, when you try to do the following:
ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
resource.setAccessTokenUri(url);
resource.setClientId(clientId);
resource.setClientSecret(secret);
resource.setGrantType("password");
return resource;
The code expects a client_credentials grant type since we use a ClientCredentialsResourceDetails, but we pass password value.
Here the code that worked in my case:
private OAuth2ProtectedResourceDetails withOAuth2Authentication(final String url, final String clientId, final String secret) {
ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
resource.setAccessTokenUri(url);
resource.setClientId(clientId);
resource.setClientSecret(secret);
// here you can provide additional properties such as scope etc.
return resource;
}
#Bean
RestTemplate callbackClientV2() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
return new OAuth2RestTemplate(
withOAuth2Authentication(v2ServerUrl, v2Username, v2Password),
new DefaultOAuth2ClientContext(atr)
);
}

Resources