FeignClient is changing the format of LocalDate passed to it - rest

I'm have a #FeignClient in my app:
#FeignClient(name="${mongo.service.id}", url="${mongo.service.url}")
public interface MongoCustomerClaimInterface {
#GetMapping(path = "/api/customerClaim/countClaims/{businessDate}")
List<TransactionClaimStatusData> countClaimsByStatusToBusinessDate(
#PathVariable #DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate businessDate);
}
I call the feign method and passing it a formatted LocalDate variable, and printing it to the log:
LocalDate businessDate = getBusinessDate();
LocalDate formattedDate = LocalDate.parse(businessDate.toString(),
DateTimeFormatter.ISO_DATE);
log.info("formattedDate: " + formattedDate);
claimStatusDataList = mongoCustomerClaimInterface.countClaims(formattedDate);
The call generates 404 error and log:
2020-24-02 18:10:25.433 INFO DashboardServiceImpl - formattedDate: 2020-02-23
2020-24-02 18:10:25.440 DEBUG
RequestMappingHandlerMapping:
Looking up handler method for path /api/customerClaim/countClaims/2/23/20
RequestMappingHandlerMapping:
Did not find handler method for [/api/customerClaim/countClaims/2/23/20]
Although I pass a date in the format yyyy-mm-dd so it will match:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
Feign somehow changes the date and then no matching url is found
How can I prevent Feign from doing this and configure a uniform formatter?

So apparently Feign isn't working with all of SpringMvc annotations. #DateTimeFormat, as great as it is, is a SpringMvc annotation and NOT a FeignClient annotation.
I solved this by doing several things:
Created a MessageConvertersConfiguration class in my MvcConfig class.
In it, I created a LocalDate & LocalDateTime converter bean and added it to the converters list that this configurer uses when an http message arrives:
#Configuration
public class MvcConfig {
#Configuration
#EnableWebMvc
public class MessageConvertersConfiguration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(localDateTimeConverter()));
}
#Bean
public ObjectMapper localDateTimeConverter() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule localDateTimeModule = new SimpleModule();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH:mm:ss");
localDateTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
localDateTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
localDateTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
localDateTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
mapper.registerModules(localDateTimeModule);
return mapper;
}
}
}
Created a configuration class for my feign client. In it, I instantiated a SpringMvcContract. Because Feign is created before SpringMvc, the converters we just defined won't affect feign without this contract:
#Configuration
public class FeignConfig {
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
}
Eventually, I added to configuration attribute to my FeignClient:
#FeignClient(name="${mongo.service.id}", url="${mongo.service.url}", configuration = FeignConfig.class)
public interface MongoCustomerClaimInterface {
#GetMapping(path = "/api/customerClaim/countClaimsByStatusToBusinessDate/{businessDate}")
List<TransactionClaimStatusData> countClaimsByStatusToBusinessDate(#PathVariable #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate businessDate);
}

Related

Spring Cloud Config - #ConditionalOnProperty and #Configuration behavior

I'm having some issues with #ConditionaOnProperty and #Configuration behavior not being updated based on the changes in the application properties (config file).
Here's what I have
Configuration
#Configuration
public class RandomRestConfig {
#Value("${external.message.root.uri}")
private String rootUri;
#Bean
#RefreshScope
public RestTemplate randomRestTemplate() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.build();
}
}
Conditional service
#Service
#RefreshScope
#ConditionalOnProperty(value = "external.message.enabled", havingValue = "true")
public class RandomRestService {
#Autowired
#Qualifier("randomRestTemplate")
private RestTemplate restTemplate;
public String getMessageFromService() {
final var response = restTemplate.getForEntity("/trips/trip-text", String.class);
return response.getBody();
}
}
Usage via controller
#RefreshScope
#RestController
public class MessageRestController {
#Value("${message: No message found}")
private String message;
private RandomRestService randomRestService;
public MessageRestController(Optional<RandomRestService> optionalRestService) {
optionalRestService.ifPresent(service -> this.randomRestService = service);
}
#GetMapping("/external-message")
String getExternalMessage() {
if (randomRestService == null) {
throw new RuntimeException("Invalid request - rest is disabled");
}
return randomRestService.getMessageFromService();
}
}
Now, what I'm trying to achieve are
Change the value of the rootUri. I changed it in the config file but it didn't take effect, the old URI is still in effect.
Change the value of external.message.enabled from false to true, but the service is still null in MessageRestController. I was expecting that the bean will be updated.
Now, with both scenarios, I manually triggered the actuator /refresh endpoint and both properties were visible in the response.
[
"config.client.version",
"external.message.root.uri",
"external.message.enabled"
]
Am I missing something? Or is it possible at all?
Thank you!

Spring Boot: How to use #RestController with a class whose variables come from a properties file

So I'm new to Spring and I'm basically trying to make a REST service for the first time. Some of the data I'd like to return is some data from a properties file.
This is my configuration bean:
#Configuration
#PropertySource("classpath:client.properties")
public class PropertyConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer
propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This is the class containing the info I want to return from the API. When I hover over the values, I can see that the property is being injected.
public class ProviderInfo {
#Value("${op.iss}") private String issuer;
#Value("${op.jwks_uri}") private String jwksURI;
#Value("${op.authz_uri}") private String authzURI;
#Value("${op.token_uri}") private String tokenURI;
#Value("${op.userinfo_uri}") private String userInfoURI;
// Getter methods
}
And this is the RestController
#RestController
public class ProviderInfoController {
#RequestMapping(value = "/provider-info", method = RequestMethod.GET)
public ProviderInfo providerInfo() {
return new ProviderInfo();
}
}
When I navigate to that endpoint, everything is null:
{"issuer":null,"jwksURI":null,"authzURI":null,"tokenURI":null,"userInfoURI":null}
Can anybody see what I'm doing wrong? Or if there is a better way to accomplish this in general?
Thanks!
The processing of the #Value annotations is done by Spring, so you need to get the ProviderInfo instance from Spring for the values to actually be set.
#RestController
public class ProviderInfoController {
#Autowired
private ProviderInfo providerInfo;
#RequestMapping(value = "/provider-info", method = RequestMethod.GET)
public ProviderInfo providerInfo() {
return providerInfo;
}
}
This also requires that Spring picks up and processes the ProviderInfo class.
Also, you need to add the ProviderInfo class to the Spring Bean life cycle using either #Component or #Service as follows:
#Component
public class ProviderInfo {
#Value("${op.iss}") private String issuer;
#Value("${op.jwks_uri}") private String jwksURI;
#Value("${op.authz_uri}") private String authzURI;
#Value("${op.token_uri}") private String tokenURI;
#Value("${op.userinfo_uri}") private String userInfoURI;
// Getter methods
}
Only then, you can use #Autowired inside ProviderInfoController class.

How to use #Inject and CDI for enterprise beans with JAX-RS 2.0 (Resteasy)

I have some problem finding out how get JAX-RS 2.0 to work with CDI on wildfly 10. I got some answer on another post that was a mix of JAX-RS 1.0/2.0 and they used other dependencies than the included libraries in Wildfly.
My objective is to inject a singleton enterprise bean that encapsulate business logic that resides in the same jar into my REST resource. The REST resource class is supposed to be request scoped and only deal with REST functionality (request and response). My POJO classes are JAXB notated.
How can I use JAX-RS 2.0 with the include CDI libraries in Wildfly 10?
The bean interface
#Local
public interface DateBean {
Date getLocalFormatDate();
}
The bean
#Singleton
public class DateBeanImpl implements DateBeanLocal {
private static final Logger LOG = Logger.getLogger("org.test.logger");
public DateBean() {
LOG.fine("DateBean");
}
#Override
public Date getLocalFormatDate() {
Calendar cal = Calendar.getInstance();
TimeZone localZone = TimeZone.getDefault();
cal.setTimeZone(localZone);
Date localTime = cal.getTime();
return localTime;
}
}
The REST resource
#Path("classroom")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class ClassRoomResource {
private static final Logger LOG = Logger.getLogger("org.clearbyte.logger");
#Inject private DateBean dateBean;
public ClassRoomResource() {
LOG.fine("ClassRoomResource");
}
#GET
#Path("{id}/getDummy")
public ClassRoom getDummy(#PathParam("id") long id) {
ClassRoom room = new ClassRoom();
room.setRoomName("Dummy");
room.setRoomNr(id);
return room;
}
#GET
#Path("localDate")
#Produces({MediaType.TEXT_HTML})
public Response getLocalformatDate() {
LOG.fine("DateBean variable: " +dateBean);
Date localDate = dateBean.getLocalDate();
LOG.fine("Local date: " +localDate);
return Response.status(Response.Status.OK)
.entity(localDate.toString())
.build();
}
}
The Resteasy implementation of JAX-RS 2.0 are included in Wildlfy 10, so there is no need to add further dependencies.
The interface doesn’t need #Localwhen is resides in the same jar/war for CDI to find it. To make the enterprise bean singleton in CDI use the #ApplicationScope, you can omit the #Singleton notation if you don't need a managed bean with read/write synchronisation.
#ApplicationScoped
public class DateBeanImpl implements DateBean {
private static final Logger LOG = Logger.getLogger("org.test.logger");
public DateBean() {
LOG.fine("DateBean");
}
#Override
public Date getLocalFormatDate() {
//DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
TimeZone localZone = TimeZone.getDefault();
cal.setTimeZone(localZone);
Date localTime = cal.getTime();
return localTime;
}
}
The make the REST resource request scoped use the #RequestScoped notation. Notice that the #Inject inject the interface and not the implementation of the bean.
#RequestScoped
#Path("classroom")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class ClassRoomResource {
private static final Logger LOG = Logger.getLogger("org.clearbyte.logger");
#Inject private DateBean dateBean;
...
No configuration of the web.xml is necessary if you a extend the jax-rs Application class.
#ApplicationPath("rest")
public class ClassRoomApp extends Application {
private final Set<Class<?>> resources = new HashSet<>();
public ClassRoomApp() {
resources.add(ClassRoomResource.class);
}
#Override
public Set<Class<?>> getClasses() {
return resources;
}
}

Spring Java config message convertor priority

I have defined two convertors like this using Spring Java config. I always get a XML response unless I specified the 'Accept=applicaiton/json' in the HTTP header. Is there a way to set the default convertor to be JSON instead of XML convertor.
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"foo.bar"})
public class WebMvcConfig extends WebMvcConfigurerAdapter {
...
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
#Bean
public MappingJackson2XmlHttpMessageConverter xmlConverter() {
MappingJackson2XmlHttpMessageConverter xmlConverter = new MappingJackson2XmlHttpMessageConverter();
return xmlConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jsonConverter());
converters.add(xmlConverter());
super.configureMessageConverters(converters);
}
Here is my controller.
#RequestMapping(value = "/product")
public
#ResponseBody
BSONObject getProducts(#RequestParam String ids,
#RequestParam(required = false) String types) {
List<BSONObject> products = commonDataService.getData(ids, types);
return products;
}
Try the following configuration, it sets up the default Content negotiation strategy(based on article here):
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
Another option will be to provide other ways of specifying the content format, if Accept header is not feasible, an option could be to specify an extension /myuri/sample.json which would be returned as a json.

JAX-RS - MOXy JAXB - ClassCastException when transforming List<List<Object>> to JSON

In my app I'm using MOXy JAXB with JAX-RS (Jersey) on Glassfish server,
I have the following REST webservice:
#Named
#RequestScoped
#Path("/product")
public class ProductService extends BaseServiceFacade<Product, Integer, ProductVO> {
#EJB(mappedName="java:global/myAppEAR/myAppEJB/ProductServiceRest")
ProductServiceRestRemote productServiceRestRemote;
// ...
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/featuredlists")
public List<List<ProductVO>> featuredlists() {
return productServiceRestRemote.featuredlists();
}
}
When I try to test the REST service accessing:
localhost:8080/atlanteusPortal/rest/product/featuredlists
I get:
java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class
at org.eclipse.persistence.jaxb.rs.MOXyJsonProvider.getDomainClass(MOXyJsonProvider.java:267)
If I put a debug breakpoint before the method return I can see that the List<List<ProductVO>> chunkList is populated but it's not transformed into JSON
Can someone point out a solution to send a List<List<Object>> type via JSON using JAX-RS MOXy and Jersey?
I solved the issue using a workaround encapsulating List of Lists inside an object
called ProductListVO:
#XmlRootElement
public class ProductListVO extends BaseVO<String> {
private List<ProductVO> productVOs;
public List<ProductVO> getProductVOs() {
return productVOs;
}
public void setProductVOs(List<ProductVO> productVOs) {
this.productVOs = productVOs;
}
public static ProductListVO buildVO(List<Product> t) {
ProductListVO vo = new ProductListVO();
List<ProductVO> prodVOs = new ArrayList<ProductVO>();
StringBuilder sb = new StringBuilder();
for (Product product : t) {
sb.append(product.getId()).append('-');
prodVOs.add(ProductVO.buildVO(product));
}
vo.setId(sb.substring(0, sb.length() - 1));
vo.setProductVOs(prodVOs);
return vo;
}
}
in Service method:
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/featured")
public List<ProductListVO> featuredlists() {
return productServiceRestRemote.featuredLists();
}