Aspect for Rabbit MQ listener - java

My RMQ listener:
#RabbitListener(
bindings = #QueueBinding(
value = #Queue,
exchange = #Exchange(
value = "${rabbitmq.exchange.sales-exchange.name}",
type = "${rabbitmq.exchange.sales-exchange.type}",
ignoreDeclarationExceptions = "true"
),
key = "${rabbitmq.exchange.sales-exchange.sales-bk}"
)
)
public void listenSalesOrderCreatedMessage(
#Headers Map<String, Object> headers,
#Payload SalesDTO payload
)
{
log.info("Message Received!");
}
salesDTO:
package org.laptop.sale.event.subscriber.dto;
#JsonInclude(JsonInclude.Include.NON_NULL)
#Getter
#Setter
public class SalesOrderCreatedEventDTO implements Serializable {
#JsonProperty("vendorDetails")
#Valid
private VendorDetails vendorDetails;
#JsonInclude(JsonInclude.Include.NON_NULL)
#Getter
#Setter
public static class VendorDetails implements Serializable {
#JsonProperty("name")
private String name;
#JsonProperty("vendorCommission")
private Double vendorCommission;
}
}
My #Around Aspect to be executed before and after the message call:
package org.laptop.sale.event.subscriber;
#Component
#Aspect
#Slf4j
public class SubscriberAdvice {
#Around("execution(* org.laptop.sale.event.subscriber..*(..)))")
public void payloadValidationFailed(ProceedingJoinPoint joinPoint) {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
log.error("Exception in the process execution {}", throwable);
}
}
}
Invalid Message payload:
{
"vendorDetails": {
"name": "Vendor Name",
"vendorCommission": "wrongData"
}
}
Valid Message payload:
{
"vendorDetails": {
"name": "Vendor Name",
"vendorCommission": 8000.1
}
}
Here the the program flow is entering the aspect only incase of Valid payload. It's not entering the Aspect incase of Invalid Payload. I tried with #AfterThrowing as well. that did not work either

Related

Cannot catch exception in Controller in Spring Boot app

In my Spring Boot app, I implemented a global exception handler class as shown below:
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
protected ResponseEntity<Object> handleMethodArgumentNotValid() {
// code omitted for brevity
return ResponseEntity.unprocessableEntity().body(errorResponse);
}
// other type of exceptions
}
And in my Controller, I return ResponseEntity<ApiResponse> as shown below:
#GetMapping("/categories/{id}")
public ResponseEntity<ApiResponse<CategoryResponse>> findById(#PathVariable long id){
final CategoryResponse response = categoryService.findById(id);
return ResponseEntity.ok(
new ApiResponse<>(
Instant.now(clock).toEpochMilli(), Constants.SUCCESS, response));
}
Here is my ApiResponse class:
#Data
#AllArgsConstructor
public class ApiResponse<T> {
private Long timestamp;
private final String message;
private final T data;
public ApiResponse(Long timestamp, String message) {
this.timestamp = timestamp;
this.message = message;
this.data = null;
}
}
My problem is that; when there is an error, I cannot catch it on Controller and GlobalExceptionHandler directly return error in ResponseEntity<Object> type. When I send requests via Postman, I get the error message, but when I implemented a frontend app, I realized that this format is different than return type when there is no error.
So, I think I should manage the exception as well in the Controller and return the same type data to frontend so that the return value can easily be manipulated. How should I solve this problem? I do not want to use try-catch and do not move my business logic to the Controller. Instead, maybe I should change return type of ResponseEntity<Object> in the exception handler to a similar one in the Controller. Or may need to return exception to the Controller. What should I do?
Update: I had already implemented a custom exception class:
public class ElementAlreadyExistsException extends RuntimeException {
public ElementAlreadyExistsException() {
super();
}
public ElementAlreadyExistsException(String message) {
super(message);
}
public ElementAlreadyExistsException(String message, Throwable cause) {
super(message, cause);
}
}
And use it in my `GlobalExceptionHandler` as shown below:
#ExceptionHandler(ElementAlreadyExistsException.class)
#ResponseStatus(HttpStatus.CONFLICT)
public ResponseEntity<Object> handleElementAlreadyExistsException(ElementAlreadyExistsException ex, WebRequest request) {
return buildErrorResponse(ex, HttpStatus.CONFLICT, request);
}
And the errors are build as shown below:
private ResponseEntity<Object> buildErrorResponse(Exception ex,
HttpStatus httpStatus,
WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(httpStatus.value(), message);
return ResponseEntity.status(httpStatus).body(errorResponse);
}
And here is the response class that I use for exception:
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorResponse {
private final int status;
private final String message;
private String stackTrace;
private List<ValidationError> errors;
#Data
private static class ValidationError {
private final String field;
private final String message;
}
public void addValidationError(String field, String message) {
if (Objects.isNull(errors)) {
errors = new ArrayList<>();
}
errors.add(new ValidationError(field, message));
}
}

Mono flatmap body runs twice

I have a java service.
Service receives a message, enriches it, stores it in a database, and sends it to kafka.
The service is made of reactive streams, DAO layer made of jdbc.
Controller:
#Api #Slf4j #Validated
#RestController
#ParametersAreNonnullByDefault
#RequiredArgsConstructor
public class IncomingMessageController {
private final IncomingMessageProcessingService processingService;
#PostMapping(value = "/incoming")
public Mono<ResponseEntity<String>> handleIncomingMessages(
#RequestBody VkIncomingMessage message
) {
return processingService.processMessage(message)
.then(Mono.just(ResponseEntity
.status(HttpStatus.OK)
.body("ok")));
}
}
Processing Service:
#Slf4j
#Service
#ParametersAreNonnullByDefault
public class IncomingMessageProcessingService {
private final List<IncomingMessageProcessor> incomingMessageProcessors;
public IncomingMessageProcessingService(
Optional<LoggingIncomingMessageProcessor> loggingIncomingMessageProcessor,
Optional<SendToKafkaIncomingMessageProcessor> kafkaIncomingMessageProcessor
) {
incomingMessageProcessors = new ArrayList<>();
loggingIncomingMessageProcessor.ifPresent(this.incomingMessageProcessors::add);
kafkaIncomingMessageProcessor.ifPresent(this.incomingMessageProcessors::add);
}
public Mono<VkIncomingDto> processMessage(VkIncomingMessage vkIncomingMessage) {
var vkIncomingDto = MessageMappingUtils.toVkIncomingDto(vkIncomingMessage);
var vkIncomingDtoMono = Mono.just(vkIncomingDto);
for (var processor : incomingMessageProcessors) {
vkIncomingDtoMono = processor.processIncomingMessage(vkIncomingDtoMono);
}
return vkIncomingDtoMono;
}
}
LoggingIncomingMessageProcessor:
#Slf4j
#Service
#RequiredArgsConstructor
#ParametersAreNonnullByDefault
#ConditionalOnProperty(
name = {"toggle.logging-incoming-processor"},
havingValue = "true",
matchIfMissing = true)
public class LoggingIncomingMessageProcessor
implements IncomingMessageProcessor {
private final IncomingMessagesDao incomingMessagesDao;
private final MessagesDao messagesDao;
private final IdGenerator idGenerator;
#Override
public Mono<VkIncomingDto> processIncomingMessage(Mono<VkIncomingDto> vkIncomingDtoMono) {
return vkIncomingDtoMono
.flatMap(vkIncomingDto -> {
var originalMsgOutLogin = getOriginalMsgOutLogin(Long.parseLong(vkIncomingDto.vkIncoming.getObject()
.getMessage()
.getMessageTag()));
var enrichedIncomingMessage = enrichVkIncomingMessages(vkIncomingDto, originalMsgOutLogin);
incomingMessagesDao.insert(enrichedIncomingMessage);
return Mono.just(enrichedIncomingMessage);
});
}
private VkIncomingDto enrichVkIncomingMessages(VkIncomingDto vkIncomingDto, String login) {
return vkIncomingDto.toBuilder()
.id(idGenerator.getNext())
.vkMessageId(Long.parseLong(Objects.requireNonNull(
vkIncomingDto.vkIncoming.getObject().getMessage().getMessageTag())))
.login(login)
.build();
}
private String getOriginalMsgOutLogin(Long originalMsgOutId) {
return messagesDao.getLoginToIdMap(Set.of(originalMsgOutId)).get(originalMsgOutId);
}
}
SendToKafkaIncomingMessageProcessor:
#Slf4j
#RequiredArgsConstructor
#Repository
#ParametersAreNonnullByDefault
#ConditionalOnProperty(
name = {"toggle.kafka-incoming-processor", "server.providermode"},
havingValue = "true",
matchIfMissing = true)
public class SendToKafkaIncomingMessageProcessor implements IncomingMessageProcessor {
private final VkIncomingMessageProducer messageProducer;
#Override
public Mono<VkIncomingDto> processIncomingMessage(Mono<VkIncomingDto> vkIncomingDtoMono) {
return vkIncomingDtoMono
.flatMap(vkIncomingDto -> messageProducer.sendMessage(MessageMappingUtils.toIncomingMessage(vkIncomingDto)))
.then(vkIncomingDtoMono);
}
}
I need to return an enriched message from LoggingIncomingMessageProcessor in order to send this information to Kafka.
The problem is that the code inside flatMap is run twice, resulting in 2 records being saved to the database and 1 message being sent to kafka.

I can't catch NumberFormatException in spring boot api rest

this problem has me crazy and I don't know where to look anymore.
I have a rest api that receives a #RequestBody DTO
public ResponseEntity<JuntaCalificadoraDTO> edit(#Valid #RequestBody JuntaCalificadoraDTO juntaCalifDTO) {
......
This is the DTO that I receive and that I validate with java bean validations. Generate getter and setters with lombok
#Data
#NoArgsConstructor
public class JuntaCalificadoraDTO extends RepresentationModel<JuntaCalificadoraDTO> {
private Long id_process;
#NotNull #Min(1) #Positive
private Integer presidentJC;
#NotNull #Min(1) #Positive
private Integer secretaryJC;
#NotNull #Min(1) #Positive
private Integer representativeFuncJC;
}
Java bean validations does its job. It is valid that it is not zero, its minimum and also that it is positive. The problem is that it does not validate when I pass a letter to a variable, for example:
{
"id_process": 4455,
"presidentJC": "dd",
"secretaryJC": 33,
"representativeFuncJC": 3
}
It detects the error, and postman returns "400 Bad Request" but nothing else. Also the Intellij console doesn't show anything, no stack.
I need to catch that error which I imagine is a "NumberFormatException" but I haven't been able to. And I don't know why he hides it. I created a method in a #ControllerAdvice class but no success either.
#ExceptionHandler (value = {NumberFormatException.class})
public final ResponseEntity<Object> invalidNumberHandling(NumberFormatException ex) {
ApiError apiError = ApiError.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST)
.message("Number Format Exception")
.errors(List.of("El o los parĂ¡metros de entrada no son vĂ¡lidos"))
.details(ex.getMessage())
.build();
return new ResponseEntity<>(apiError, apiError.getStatus());
}
I will appreciate any guidance. And sorry for my bad english
You are close. It's actually an InvalidFormatException that is wrapped into a HttpMessageNotReadableException.
By catching the InvalidFormatException you have access to the field that failed and to the wrong value, so it should be enough for you create a meaningful response to the user.
See this sample application (Java 17) in case you have any doubts - if this doesn't work please let me know your Spring Boot version.
#SpringBootApplication
public class SO72312634 {
public static void main(String[] args) {
SpringApplication.run(SO72312634.class, args);
}
#Controller
static class MyController {
#PostMapping
public ResponseEntity<?> edit(#Valid #RequestBody JuntaCalificadoraDTO juntaCalifDTO) {
return ResponseEntity.ok().build();
}
}
#Bean
ApplicationRunner runner() {
return args -> new RestTemplate().exchange(RequestEntity
.post("http://localhost:8080")
.contentType(MediaType.APPLICATION_JSON)
.body("""
{
"id_process": 4455,
"presidentJC": "ds",
"secretaryJC": "abc",
"representativeFuncJC": 3
}
"""), String.class);
}
#ControllerAdvice
static class ExceptionAdvice {
#ExceptionHandler(value = InvalidFormatException.class)
public final ResponseEntity<Object> httpMessageNotReadable(InvalidFormatException ex) {
String failedPaths = ex.getPath().stream().map(JsonMappingException.Reference::getFieldName).collect(Collectors.joining());
return new ResponseEntity<>("Field %s has invalid value %s ".formatted(failedPaths, ex.getValue()), HttpStatus.BAD_REQUEST);
}
}
#Data
#NoArgsConstructor
public static class JuntaCalificadoraDTO {
private Long id_process;
#NotNull
#Min(1) #Positive
private Integer presidentJC;
#NotNull #Min(1) #Positive
private Integer secretaryJC;
#NotNull #Min(1) #Positive
private Integer representativeFuncJC;
}
}
Output:
Caused by: org.springframework.web.client.HttpClientErrorException$BadRequest: 400 : "Field presidentJC has invalid value ds "

Spring boot #ExceptionHandler not responding with custom response

I have a global exception handler with #RestControllerAdvice and #ExceptionHandler(APIException.class) methods.
I have designed my own response class ValidationResponse.class which I am adding to Response entity class.
I want to respond with ValidationResponse but getting some generic response instead.
Global Exception Handler
#RestControllerAdvice
public class RestResponseExceptionHandler {
#ExceptionHandler(APIException.class)
public ResponseEntity<ValidationResponse> handleException(APIException ex) {
ValidationResponse validationResponse = new ValidationResponse(ex.getErrorCode(), ex.getErrorMessage());
return new ResponseEntity<>(validationResponse, ex.getHttpStatus());
}
}
Custom exception class
#Getter
#Setter
public class APIException extends RuntimeException {
private int errorCode;
private String errorMessage;
private HttpStatus httpStatus;
public APIException(int errorCode, String errorMessage, HttpStatus httpStatus) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.httpStatus = httpStatus;
}
public APIException(int errorCode, String errorMessage, HttpStatus httpStatus, Exception e) {
super(e);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.httpStatus = httpStatus;
}
}
Custom response design
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonInclude(Include.NON_NULL)
public class ValidationResponse {
public int errorCode;
public String errorMessage;
}
Expected response
{
"errorCode": 1010,
"errorMessage": "some custome validation message"
}
Current Response
{
"error-message" : "Request processing failed; nested exception is com.package.exception.APIException",
"error-code" : "GENERAL_ERROR",
"trace-id" : null,
"span-id" : null
}
#ControllerAdvice
public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(APIException.class)
public ResponseEntity<ValidationResponse> handleException(APIException ex, WebRequest webRequest) {
}
}
Try this

Multiple yml files configuring in corresponding Configuration class (Spring Boot)

I have more than one yml files in Spring Boot in resource classpath location like following structure of Spring Boot. Initially I have written only for application-abc.yml and at the time all the values of this file was loading in their corresponding class but when I have added on another file application-xyz.yml then also it loads into their corresponding configuration classes but at this time only loading the values of application-xyz.yml in both the configuration classes. So, want help to configure values of both the files in their corresponding configuration files in a single build :
-src
-main
-java
-packages
-config
-ApplicationAbcConfig.java
-ApplicationConfig.java
-ApplicationFactory.java
-ApplicationXyzConfig.java
-Authentication.java
-Operations.java
-Payload.java
-RequestPayload.java
-ResponsePayload.java
-services
-YmlConfigurationSelection.java
-resources
-application.yml
-application-abc.yml
-application-xyz.yml
-MultipleYmlDemoProject.java
Content of application-abc.yml
authentication:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes1
- attributes2
response:
- sequence: 1
attributes:
- attributes3
- attributes4
operations:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes5
- attributes6
response:
- sequence: 1
attributes:
- attributes7
- attributes8
Content of application-xyz.yml
authentication:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes9
- attributes10
response:
- sequence: 1
attributes:
- attributes11
- attributes12
operations:
name: name
type: type
payload:
request:
- sequence: 1
attributes:
- attributes13
- attributes14
response:
- sequence: 1
attributes:
- attributes15
- attributes16
Content of ApplicationConfig.java
public interface ApplicationConfig {
public Authentication getAuthentication();
public void setAuthentication(Authentication authentication);
public Operations getOperations();
public void setOperations(Operations operations);
}
Content of Authentication.java
public class Authentication {
private String name;
private String type;
private Payload payload;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
}
Content of Operations.java
public class Operations {
private String name;
private String type;
private Payload payload;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
}
Content of Payload.java
public class Payload {
private List<RequestPayload> request;
private List<ResponsePayload> response;
public List<RequestPayload> getRequest() {
return request;
}
public void setRequest(List<RequestPayload> request) {
this.request = request;
}
public List<ResponsePayload> getResponse() {
return response;
}
public void setResponse(List<ResponsePayload> response) {
this.response = response;
}
}
Content of RequestPayload.java
public class RequestPayload {
private String sequece;
private List<String> attributes;
public String getSequece() {
return sequece;
}
public void setSequece(String sequece) {
this.sequece = sequece;
}
public List<String> getAttributes() {
return attributes;
}
public void setAttributes(List<String> attributes) {
this.attributes = attributes;
}
}
Content of ResponsePayload.java
public class ResponsePayload {
private String sequece;
private List<String> attributes;
public String getSequece() {
return sequece;
}
public void setSequece(String sequece) {
this.sequece = sequece;
}
public List<String> getAttributes() {
return attributes;
}
public void setAttributes(List<String> attributes) {
this.attributes = attributes;
}
}
Content of ApplicationAbcConfig.java
#Configuration
#SpringBootConfiguration
#EnableConfigurationProperties
#org.springframework.context.annotation.PropertySource("classpath:application-abc.yml")
public class ApplicationAbcConfig implements ApplicationConfig, PropertySourceFactory {
private Authentication authentication;
private Operations operations;
#Override
public Authentication getAuthentication() {
return authentication;
}
#Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
#Override
public Operations getOperations() {
return operations;
}
#Override
public void setOperations(Operations operations) {
this.operations = operations;
}
#Override
public PropertySource<?> createPropertySource(#Nullable String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}
Content of ApplicationXyzConfig.java
#Configuration
#SpringBootConfiguration
#EnableConfigurationProperties
#org.springframework.context.annotation.PropertySource("classpath:application-xyz.yml")
public class ApplicationXyzConfig implements ApplicationConfig, PropertySourceFactory {
private Authentication authentication;
private Operations operations;
#Override
public Authentication getAuthentication() {
return authentication;
}
#Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
#Override
public Operations getOperations() {
return operations;
}
#Override
public void setOperations(Operations operations) {
this.operations = operations;
}
#Override
public PropertySource<?> createPropertySource(#Nullable String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}
Content of ApplicationFactory.java
#Component
public class ApplicationFactory {
#Autowired
private ApplicationAbcConfig applicationAbcConfig;
#Autowired
private ApplicationXyzConfig applicationXyzConfig;
public ApplicationConfig getApplicationPropertiesConfig(String application) {
if (application.equalsIgnoreCase("abc")) {
return applicationAbcConfig;
} else if (application.equalsIgnoreCase("xyz")) {
return applicationXyzConfig;
} else {
return null;
}
}
}
Content of YmlConfigurationSelection.java
public class YmlConfigurationSelection {
#Autowired
private ApplicationFactory applicationFactory;
private ApplicationConfig applicationConfig;
public Object accessingProperties(String application) {
applicationConfig = applicationFactory.getApplicationPropertiesConfig(application);
return null;
}
}
Content of MultipleYmlDemoProject.java
#SpringBootApplication
#SpringBootConfiguration
#PropertySource(factory = ApplicationAbcConfig.class, value = "classpath:application-abc.yml")
#PropertySource(factory = ApplicationXyzConfig.class, value = "classpath:application-xyz.yml")
public class MultipleYmlDemoProject {
public class MultipleYmlDemo {
public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(YamlPropertysourceApplication.class, args);
ConfigurableEnvironment env = ctx.getEnvironment();
}
}
}
It looks like you have an old spring application that was attempted to be migrated to spring boot.
Spring boot works natively with yaml files, so if you do the integration in a spring boot way, it will be possible to delete a lot of boilerplate code that you have to maintain. Also the naming of configurations is problematic: the names application-<something>.yml are reserved to be used with spring boot profiles, maybe if you'll rename to myprops-abc/xyz.yaml it will behave in a different way, I can't say for sure.
All-in-all I suggest you the following way, which is much better IMO:
Both configuration sets must be loaded into one configuration, so create a configuration properties file that denotes this one configuration:
#ConfigurationProperties(prexix="security")
public class SecurityConfigProperties {
private SecurityRealm abc;
private SecurityRealm xyz;
// getters, setters
}
public class SecurityRealm {
private Authentication autentication;
private Operations operations;
// getters setters
}
public class Authentication {...}
private class Operations {...}
Now place all the content from abc and xyz yaml into one file application.yaml and give a 'security' prefix:
security:
abc: // note, this 'abc' matches the field name of the configuration
authentication:
...
operations:
....
xyz:
authentication:
...
operations:
....
OK, everything is mapped, create the configuration like this:
#Configuration
#EnableConfigurationProperties(SecurityConfigProperties.class)
public class SecurityConfiguration {
#Bean
public SecurityBeanForABC(SecurityConfigProperties config) {
return new SecurityBeanForABC(config.getAbc().getAuthentication(), config.getAbc().getOperations());
}
... the same for XYZ
}
Note that with this approach you only maintain a configuration mapping in java objects and there is no code for loading / resolving properties) - everything is done by spring boot automatically. If you configure a special annotation processor and have a descent IDE you can get even auto-completion facilities for those yaml properties but its out of scope for this question. The point is that doing things in a way directly supported by spring boot has many advantages :)

Categories