Why does Jackson destructure my LocalDateTime when I add a CorsMapping configuration in Spring? - spring

I have a simple API which handles Entity objects (shown below).
#Data
public class Entity {
private LocalDateTime createdAt;
}
A controller which looks like the following:
#RestController
#AllArgsConstructor
class MyController {
private final EntityService entityService;
#GetMapping
Flux<Entity> getEntities() {
return entityService.all(); // returns all entities from some storage location...
}
}
When I make a GET request to my API, I receive the following response:
{
"createdAt": "2022-07-10T20:39:01.147915"
}
Now, my API is designed to be consumed from a different origin, which means I need to add some custom CORS config to my application.
For this, I have created the following:
#Configuration
#EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST")
.allowedOrigins("http://localhost:3000");
}
}
After adding this and changing nothing else in my API the response changes to:
{
"createdAt": [
2022,
7,
10,
20,
39,
1,
147915000
]
}
Does anyone know what is causing this behaviour? Thanks

Try to put this annotation on your 'createdAt' field
` #JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ", shape = JsonFormat.Shape.STRING)`
And check out this question

Related

How to log json response in Spring Boot?

If I define a response entity class and return it by my rest controller, controller will change this class to json string. I want to log this json string, how to do it? Thanks.
For example:
Response entity class:
#Data
#AllArgsConstructor
public class ResponseEntity {
String code;
String message;
}
Rest Controller:
#RestController
#RequestMapping("/")
public class StudentController {
#RequestMapping("test")
public ResponseEntity test(){
return new ResponseEntity(200,"success");
}
}
I want to record this json response in log
{"code":200,"message":"success"}
The simplest and handy way is convert it to JSON string manually and then log it. You can convert it to JSON by ObjectMapper.
#Log4j2
#RequestMapping("/")
public class StudentController {
#Autowired
private ObjectMapper objectMapper;
#RequestMapping("test")
public ResponseEntity test() throws Exception {
ResponseEntity entity = new ResponseEntity(200, "success");
log.debug("{}", objectMapper.writeValueAsString(entity));
return entity;
}
}
Or you can use Logstash json_event pattern for log4j if you need advanced feature.
In order to accomplish you desired behaviour the spring framework offers an utility class which does exactly the requested job for your.
Just declare this:
#Configuration
public class RequestLoggingFilterConfig {
#Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter
= new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(false);
filter.setAfterMessagePrefix("REQUEST DATA : ");
return filter;
}
}
And add to your logging file this appender:
<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter">
<level value="DEBUG" />
</logger>
Same result can be done with this application.properties property
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
this should be enough, but, in case you want a fine grained control on the logging operation you can create your own filter by extending AbstractRequestLoggingFilter .
add this in responseHandler class:--
package net.guides.springboot.springbootcrudrestapivalidation.response;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
public class ResponseHandler {
public static ResponseEntity<Object> generateResponse(String message, HttpStatus status, Object responseObj) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("message", message);
map.put("status", status.value());
map.put("data", responseObj);
return new ResponseEntity<Object>(map,status);
}
}
and then this in controller class:_-
#PostMapping( "employees")
public ResponseEntity<Object> Post(#Valid #RequestBody Employee employee) {
employeeRepository.save(employee);
try {
Employee emp = EmployeeRepository.Post(employee);
return ResponseHandler.generateResponse("Successfully added data!", HttpStatus.OK, emp);
} catch (Exception e) {
return ResponseHandler.generateResponse(e.getMessage(), HttpStatus.MULTI_STATUS,null);
}
output
{
"data": {
"id": 1,
"dob": "1994-10-17",
"doj": "2018-10-17",
"gender": "male",
"emailId": "abhih#gmail.com",
"age": "27",
"phoneNumber": "+918844945836",
"address": "Dhan",
"empName": "akash"
},
"message": "Successfully added data!",
"status": 200
}

How do I spring cloud gateway custom filter e2e test?

I have implemented custom GatewayFilterFactory filter. But I don't know how to test this filter with e2e setup.
I have referenced official spring-cloud-gateway AddRequestHeaderGatewayFilterFactoryTests test case code.
This is my custom filter code:
#Component
public class MyCustomFilter implements GatewayFilterFactory<MyCustomFilter.Config>, Ordered {
#Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((this::filter), getOrder());
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/* do some filtering */
}
#Override
public int getOrder() {
return 1000;
}
#Override
public Config newConfig() {
return new Config(MyCustomFilter.class.getSimpleName());
}
public static getConfig() {
return
}
#Getter
#Setter
public static class Config {
private String name;
Config(String name) {
this.name = name;
}
}
}
And this is my test code:
BaseWebClientTests class look exactly the same as official BaseWebClientTests class code
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#DirtiesContext
#ActiveProfiles("my-custom-filter")
public class MyCustomFilterTests extends BaseWebClientTests {
#LocalServerPort
protected int port = 0;
protected WebTestClient testClient;
protected WebClient webClient;
protected String baseUri;
#Before
public void setup() throws Exception {
setup(new ReactorClientHttpConnector(), "http://localhost:" + port);
}
protected void setup(ClientHttpConnector httpConnector, String baseUri) {
this.baseUri = baseUri;
this.webClient = WebClient.builder().clientConnector(httpConnector)
.baseUrl(this.baseUri).build();
this.testClient = WebTestClient
.bindToServer(httpConnector)
.baseUrl(this.baseUri)
.build();
}
#Test
public void shouldFailByFilterTests() {
/* This test should be failed but success :( */
testClient.get().uri("/api/path")
.exchange().expectBody(Map.class).consumeWith(result -> {
/* do assertion */
});
}
#EnableAutoConfiguration
#SpringBootConfiguration
#Import(DefaultTestConfig.class)
public static class TestConfig {
#Value("${test.uri}")
String uri;
#Bean
public MyCustomFilter myCustomFilter() {
return new MyCustomFilter();
}
#Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder, MyCustomFilter myCustomFilter) {
return builder.routes().route("my_custom_filter",
r -> r.path("/api/path")
.filters(f -> f.filter(myCustomFilter.apply(new MyCustomFilter.Config("STRING"))))
.uri(uri))
.build();
}
}
}
Lastly Target controller looks like this:
#RestController
#RequestMapping("/api/path")
public class HttpBinCompatibleController {
#GetMapping("/")
public Mono<BodyData> identity() {
return Mono.just(new BodyData("api success"));
}
#NoArgsConstructor
#AllArgsConstructor
#Getter
static class BodyData {
private String message;
}
}
What I understand how this filter factory test code works is that
custom filter: custom filter is setup inside TestConfig class testRouteLocator method
target controller: target controller is defined as HttpBinCompatibleController class
testClient sends the request, and custom should do some filtering, then target controller should receive the request from testClient.
What I expect from this shouldFailByFilterTests TC is that before request from testClient is sent to target controller, that request should be rejected by MyCustomFilter. But the request is sent to the target controller.
I think the request from testClient is not proxied by testRouteLocator but I'm not sure
Question
What is the cause of this problem?
Is there another way to test my own custom filter?
This problem was related to the version incompatibility between Spring Boot and Spring Cloud.
I was using Spring Boot version 2.1.7 and Spring Cloud version Greenwich.SR2.
Then I found this 'Release train Spring Boot compatibility' table on this link
Before I've noticed version incompatibility, for using #Configuration(proxyBeanMethods = false) feature, upgraded Spring Boot version to 2.2.x.
The solution is using 2.1.x branch BaseWebClientTests class.

Spring boot 2.0 custom converter being ignored

I've either completely miss-understood converters, or all the examples/blogs/docs I've found are assuming I've done a step... My understanding is that when I post a String, the converter will pick it up and give the Controller a POJO instead.
The problem I have is the converter is never being called.
The payload coming into the controller is a standard JSON API string
{
"data":{
"attributes":{
"created-date":null,
},
"type":"steaks"
}
}
The converter:
#Slf4j
#Configuration
public class SteakConverter implements Converter<String, Steak> {
#Override
public Steak convert(String value) {
log.info("converter value {}", value);
return new Steak.builder().build();
}
}
The controller:
#Slf4j
#CrossOrigin
#RestController
public class SteakController {
#PostMapping("/steaks")
public ResponseEntity<String> create(#RequestBody Steak steak) {
}
}
From reading all the blogs and docs I can find this should be all that's needed. However I've also tried manually registering with the following:
#Slf4j
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
log.info("Converter registered");
registry.addConverter(new SteakConverter());
}
}
This is called during application startup.
The only thing I can think of is that the converter/controller doesn't think the payload is in fact a String, so is ignoring it?
How can I debug this, or just make it work?
Here is a working sample application that shows my problem.
Cheers

In Spring how to send enum as response in controller

I have Enum Class. I Need to send a Enum class Response from the Spring controller.
I am not able understand how to sent class as Response in spring controller. Please help me for that.
You can add anything which Jackson can de-serialize in a reponse
#RestController
public class HelloController {
#RequestMapping("/monday")
public ResponseEntity<DayOfWeek> monday() {
return new ResponseEntity<DayOfWeek>(DayOfWeek.MONDAY, HttpStatus.OK);
}
#RequestMapping("/days")
public ResponseEntity<List<DayOfWeek>> days() {
return new ResponseEntity<List<DayOfWeek>>(Arrays.asList(DayOfWeek.values()), HttpStatus.OK);
}
}
You can prove this to yourself with the following test, just do the Jacskon de-serialization manually
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class HelloControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void monday() throws Exception {
String json = new ObjectMapper().writeValueAsString(DayOfWeek.MONDAY);
mvc.perform(MockMvcRequestBuilders.get("/monday").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andExpect(content().string(equalTo(json)));
}
#Test
public void days() throws Exception {
String json = new ObjectMapper().writeValueAsString(Arrays.asList(DayOfWeek.values()));
mvc.perform(MockMvcRequestBuilders.get("/days").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andExpect(content().string(equalTo(json)));
}
}
If you wanna return all enum values than try something like this:
#GetMapping("enum")
public List<MyEnum> paymentMethods() {
return Arrays.asList(MyEnum.values());
}
public enum MyEnum {
FIRST, SECOND, THIRD;
}

Spring Boot partially replacing autoconfiguration

I've been exploring Spring Boot (1.2.4) to create a simple RESTful repository, using H2 & Jackson.
Like others I've noticed that, by default, #Id fields aren't in the returned Json (as described in this question: While using Spring Data Rest after migrating an app to Spring Boot, I have observed that entity properties with #Id are no longer marshalled to JSON ).
So I want to tweak the configuration to include them.
BUT I'm having trouble getting it all to work.
Basic entity class:
#Entity
public class Thing {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id; //I want this to appear in the RESTful JSON
private String someInformation;
protected Thing(){};
public String getSomeInformation() { return someInformation; }
public void setSomeInformation(String someInformation) { this.someInformation = someInformation; }
}
Basic repository interface:
public interface ThingRepository extends PagingAndSortingRepository<Thing, Long> {
List<Thing> findBySomeInformation(#Param("SomeInformation") String someInformation);
}
Simple starting application:
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
And my configuration class:
#Configuration
public class RepositoryRestMvcConfig extends SpringBootRepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Thing.class);
}
}
I can see from running Spring Boot with --debug (as described at http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html ) that my config has been found but it doesn't seem to have any effect.
Jackson works on the getters/setters on a class rather than the member variables themselves. So to make sure the id is in the JSON, simply add a getter for it:
#Entity
public class Thing {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id; //I want this to appear in the RESTful JSON
private String someInformation;
protected Thing(){};
//this is what is needed
public long getId() { return id; }
public String getSomeInformation() { return someInformation; }
public void setSomeInformation(String someInformation) { this.someInformation = someInformation; }
}

Resources