How to log json response in Spring Boot? - 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
}

Related

How to configure a custom Kafka deserializer and get the consumed JSON data using a KafkaListener

I am trying to consume a JSON message using spring kafka. The message which is consumed by the consumer is like this.
{
"EventHeader": {
"entityName": "Account",
"changedFields": ["Id", "Name"]
},
"NewFields": {
"Id": "001",
"Name": "Test Account",
},
"OldFields": {}
}
So far I have created classes for "EventHeader", "NewFields","OldFields" ,and for "KafkaPayload". And also I have created a custom deserializer to deserialize this JSON payload.Here is my custom deserializer.
public class CustomDeserailizer <T extends Serializable> implements Deserializer<T> {
private ObjectMapper objectMapper = new ObjectMapper();
public static final String VALUE_CLASS_NAME_CONFIG = "value.class.name";
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
Deserializer.super.configure(configs, isKey);
}
#Override
public T deserialize(String topic, byte[] objectData) {
return (objectData == null) ? null : (T) SerializationUtils.deserialize(objectData);
}
#Override
public T deserialize(String topic, Headers headers, byte[] data) {
return Deserializer.super.deserialize(topic, headers, data);
}
#Override
public void close() {
Deserializer.super.close();
}
}
I have set the consumer configurations as below.
public class KafkaConfig {
#Bean
public KafkaConsumer<String, KafkaPayload> consumerFactory(){
Properties config = new Properties();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
config.put(ConsumerConfig.GROUP_ID_CONFIG, "groupId");
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, CustomDeserializer.class);
return new KafkaConsumer<>(config);
}
}
Now I need to show the consumed message through a #KafkaListener setting the consumer into ConsumerFactory. But I don't understand how to do that. This is my first time using kafka.So could anyone give me some idea about this?
This is how I am trying to do that.
#Bean
public ConcurrentKafkaListenerContainerFactory<String, KafkaPayload> kafkaListener(){
ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory();
factory.setConsumerFactory(consumerFactory());
return factory;
}
This is my KafkaListener
public class ConsumerService {
#KafkaListener(topics = "Topic", groupId = "sample-group",containerFactory = "kafkaListener")
public void consume(KafkaPayload kafkaPayload){
System.out.println("Consumed Message :"+ kafkaPayload);
}
}
Since you are using Spring Boot, just set the value deserializer class name as a property and Boot will automatically wire it into the container factory for your #KafkaListener. No need to define your own consumer factory or container factory.
spring.kafka.consumer.value-deserializer=com.acme.CustomDeserializer
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.integration.spring.kafka.consumer.value-deserializer

Not able to get error object in JSON format while using #Valid and MessageSource to get display errors in Spring boot

I am currently learning Spring REST and I am trying to build a demo spring boot app. Incase of DTO object has validation error I want to show it as below:
{
"errors": [
{
"code": "first_error_code",
"message": "1st error message"
"field":"field_name"
}
]
}
Where the code in above JSON should display the validation message that I have given in my entity class i.e
#NotEmpty(message = "{name.not.empty}")
String name;
then code should be name.not.empty and message should be taken from messages.properties file.
Now to achieve this, I used several tutorials. Below are the classes:
Main class: (Included MessageSource and LocalValidatorFactoryBean)
#SpringBootApplication
#EnableSwagger2
public class Demo3PathvariableApplication implements WebMvcConfigurer {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
public static void main(String[] args) {
SpringApplication.run(Demo3PathvariableApplication.class, args);
}
/*
* To enable matrix variables, configurePathMatch() method of WebMvcConfigurer
* needs to overriden. Matrix variables are disabled by default and the
* following configuration
*
* urlPathHelper.setRemoveSemicolonContent(false);
*
* should be present in the overriden method to enable the same. see below
* method.
*/
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST");
}
/* For Swagger Document Generation */
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("com.infytel.controller")).paths(PathSelectors.any()).build()
.useDefaultResponseMessages(false);
// To scan for RestControllers from this package and For disabling default
// response messages
}
}
Controller class:
#RestController
#RequestMapping("/customers")
#Api(value = "CustomerController, REST APIs that deal with Customer DTO")
public class CustomerController {
#Autowired
private CustomerService customerService;
#PostMapping(consumes = "application/json")
public ResponseEntity createCustomer(#RequestBody #Valid CustomerDTO customer, Errors errors) {
return ResponseEntity.ok(customerService.createCustomer(customer));
}
}
FieldErrorDTO.java:
public class FieldErrorDTO {
private String errorCode;
private String message;
private String field;
public FieldErrorDTO(String errorCode, String message, String field) {
this.errorCode = errorCode;
this.message = message;
this.field = field;
}
//Getter setter
ValidationErrorDTO.java:
public class ValidationErrorDTO {
private List<FieldErrorDTO> fieldErrors = new ArrayList<>();
public ValidationErrorDTO() {
super();
}
public void addFieldError(String errorCode, String message, String field) {
FieldErrorDTO error = new FieldErrorDTO(errorCode, message, field);
fieldErrors.add(error);
}
public List<FieldErrorDTO> getFieldErrors() {
return fieldErrors;
}
public void setFieldErrors(List<FieldErrorDTO> fieldErrors) {
this.fieldErrors = fieldErrors;
}
}
RestErrorHandler .java
#ControllerAdvice
public class RestErrorHandler {
#Autowired
private MessageSource messageSource;
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MethodArgumentNotValidException.class)
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getCode(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
return localizedErrorMessage;
}
}
messages.properties
name.not.empty=Please provide a name.
email.not.valid=Please provide valid email id.
age.adult.only=Age should be more than 18.
Now with all these config, I am able to see below JSON,
{
"fieldErrors": [
{
"errorCode": "NotEmpty",
"message": "Please provide a name.",
"field": "name"
},
{
"errorCode": "Email",
"message": "Please provide valid email id.",
"field": "email"
}
]
}
How do I acheive this requirement, where instead of "errorCode": "NotEmpty", I want show
"errorCode": "name.not.empty"
From CustomerDTO class?
To do so you need to change you processFieldErrors:
First remove "{}" from your anotations:
#NotEmpty(message = "name.not.empty")
String name;
Second:
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getDefaultMessage(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
And third, change your message.getMessage:
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError.getDefaultMessage(), null, currentLocale);
return localizedErrorMessage;
}
This way you would retrieve the key for the message. In your example it will be :
name.not.empty
Hope this helps

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;
}

springfox(swagger2) does not work with GsonHttpMessageConverterConfig

What I am trying to build is a spring-boot (v1.2.3) application and expose my Rest API with SpringFox(swagger2) v2.0.0
my Swagger Spring config
#EnableSwagger2
#Configuration
public class SwaggerConfig {
#Bean
public Docket myApi() {
return new Docket(DocumentationType.SWAGGER_2)
.genericModelSubstitutes(DeferredResult.class)
.useDefaultResponseMessages(false)
.forCodeGeneration(false)
.pathMapping("/my-prj");
}
}
I need to use gson to convert my pojo's to json, and I do it this way:
#Configuration
public class GsonHttpMessageConverterConfig {
#Bean
public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
}
The trouble is that if using GsonHttpMessageConverter, swagger v2 generates a wrong json:
{
"value": "{\"swagger\":\"2.0\",\"info\":{\"description\":\"Api Documentation\",\"version\":\"1.0\",\"title\":\"Api Documentation\",\"termsOfService\":\"urn:tos\",\"contact\":{\"name\":\"Contact Email\"},\"license\":{\"name\":\"Apache 2.0\",\"url\":\"http:
...
the JSON is prefixed with value and the real JSON becomes an escaped string.
here is how it should be if not using GsonHttpMessageConverter:
{
"swagger": "2.0",
"info": {
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a
...
Is there a solution to create a correct swagger JSON without value and escaping?
solved the issue by myself:
the issue was with serializing this class:
package springfox.documentation.spring.web.json;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.annotation.JsonValue;
public class Json {
private final String value;
public Json(String value) {
this.value = value;
}
#JsonValue
#JsonRawValue
public String value() {
return value;
}
}
to serialize it correct I implemented a SpringfoxJsonToGsonAdapter and added it to my gson config:
adapter:
public class SpringfoxJsonToGsonAdapter implements JsonSerializer<Json> {
#Override
public JsonElement serialize(Json json, Type type, JsonSerializationContext context) {
final JsonParser parser = new JsonParser();
return parser.parse(json.value());
}
}
gson config:
#Configuration
public class GsonHttpMessageConverterConfig {
#Bean
public GsonHttpMessageConverter gsonHttpMessageConverter() {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson());
return converter;
}
private Gson gson() {
final GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter());
return builder.create();
}
}
This is Oleg Majewski's solution for SpringFox + Gson problem translated to Kotlin:
internal class SpringfoxJsonToGsonAdapter : JsonSerializer<Json> {
override fun serialize(json: Json, type: Type, context: JsonSerializationContext): JsonElement
= JsonParser().parse(json.value())
}
#Configuration
open class GsonHttpMessageConverterConfig {
#Bean
open fun gsonHttpMessageConverter(): GsonHttpMessageConverter {
val converter = GsonHttpMessageConverter()
converter.gson = gson()
return converter
}
private fun gson(): Gson = GsonBuilder()
.registerTypeAdapter(Json::class.java, SpringfoxJsonToGsonAdapter())
.create()
}
Ran into a similar problem but found a little different solution which is also using the above mentioned serializer.
We define a Bean to be able to autowire Gson objects. For fixing the issue with Swagger the important part there is to also add "registerTypeAdapter" for the Json class.
#Configuration
public class GsonConfiguration {
#Bean
public Gson gson() {
return new GsonBuilder().registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()).create();
}
}
The content of SpringfoxJsonToGsonAdapter is the same as above and only listed here for completeness.
public class SpringfoxJsonToGsonAdapter implements JsonSerializer<Json> {
#Override
public JsonElement serialize(Json json, Type type, JsonSerializationContext context) {
final JsonParser parser = new JsonParser();
return parser.parse(json.value());
}
}
For using the Gson object just do something like this:
#Component
public class Foobar {
#Autowired
Gson gson;
#Autowired
public Foobar() {
// ... some constructor work ...
}
public void someMethod() {
System.out.println(gson.toJson(...)); // Fill in some object ;-)
}
}
This is the same solution as Oleg Majowski's. I am just getting rid of the SpringfoxJsonToGsonAdapter class using a lambda function instead:
#Bean
public GsonHttpMessageConverter gsonHttpMessageConverter() {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson());
return converter;
}
private Gson gson() {
final GsonBuilder builder = new GsonBuilder();
JsonSerializer<Json> jsonSerializer =
(Json json, Type type, JsonSerializationContext context) -> new JsonParser().parse(json.value());
builder.registerTypeAdapter(Json.class, jsonSerializer);
return builder.create();
}
A couple of things I found missing with the above instructions is the package and imports. When I first tried this, I used my own packages but swagger-ui.html still said there were no packages found. It appears the package is specific.
The classes below are exactly the same as above, but I included the entire class with package names and imports. Registering the adapter is the same as documented above.
First the JSON class
package springfox.documentation.spring.web.json;
import com.fasterxml.jackson.annotation.*;
public class Json {
private final String value;
public Json(String value) {
this.value = value;
}
#JsonValue
#JsonRawValue
public String value() {
return value;
}
}
and the adapter class:
package springfox.documentation.spring.web.json;
import java.lang.reflect.Type;
import com.google.gson.*;
public class SpringfoxJsonToGsonAdapter implements com.google.gson.JsonSerializer<Json> {
#Override
public JsonElement serialize(Json json, Type type, JsonSerializationContext context) {
final JsonParser parser = new JsonParser();
return parser.parse(json.value());
}
}

Does Swagger UI support #PathVariable binding?

Currently when I'm testing the Swagger UI for a GET request that binds the "id" path variable to a data object, the dataType of the "id" field is Model, instead of a Long.
For instance, here is the method in the RestController:
#RequestMapping(value = "/{id}", method = GET)
public AwardVO getAwardById(#PathVariable("id") Award award) {
LOG.info("inside the get award method: "+award);
if (award == null) {
throw new AwardNotFoundException();
}
return new AwardVO(award);
}
Here is the resulting documentation:
So when I pass a Long to the input field, I don't receive the desired record. Is this type of binding supported in Swagger, or do I need to just need to do a lookup for the record and pass the PathVariable as a Long?
Version of Swagger: compile "com.mangofactory:swagger-springmvc:0.9.5"
SwaggerConfig:
#Configuration
#EnableSwagger
public class SwaggerConfig extends WebMvcConfigurerAdapter {
private SpringSwaggerConfig springSwaggerConfig;
#Autowired
public void setSpringSwaggerConfig(SpringSwaggerConfig springSwaggerConfig) {
this.springSwaggerConfig = springSwaggerConfig;
}
#Bean
public SwaggerSpringMvcPlugin customImplementation() {
return new SwaggerSpringMvcPlugin(this.springSwaggerConfig).apiInfo(
apiInfo())
.genericModelSubstitutes(ResponseEntity.class)
.includePatterns("/v1/.*", "/register/.*");
}
private ApiInfo apiInfo() {
ApiInfo apiInfo = new ApiInfo("API", "API",
"API terms of service", "email#gmail.com",
"API Licence Type", "API License URL");
return apiInfo;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Thanks.
It seems like it should work as expected if you replace #PathVariable with #ModelAttribute.
To answer your question, yes it does support #PathVariable, but only primitives or strings.

Resources