How to send a message to errorChannel when an exception occurs in spring integration? - exception

Hi I have an spring integration flow where my message traverse through multiple Deserialization, transformation and serialization process.
I am unable to handle any exception that are thrown during the process. I have defined my error channel as below. How to interrupt the integration flow and send the message to error channel?
================ error channel ===============
#Bean("errorChannel")
public MessageChannel errorChannel() {
return new DirectChannel();
}
#Bean
#Router(inputChannel = "errorChannel")
public ErrorMessageExceptionTypeRouter handleError(){
ErrorMessageExceptionTypeRouter router = new ErrorMessageExceptionTypeRouter();
Map<String, String> mappings = new HashMap<>();
mappings.put(RuntimeException.class.getName(), "someChannel");
mappings.put(Exception.class.getName(), "someChannel");
router.setChannelMappings(mappings);
return router;
}
#ServiceActivator(inputChannel = "someChannel")
public void handleErrors(Message<?> message) {
LOGGER.info(" error caught in some channel {}", message);
}
============== error emitting channel ====================
#Bean
#ServiceActivator(inputChannel = "JSON_to_IM_in_Process")
public AbstractReplyProducingMessageHandler json_to_im_svc() throws Exception {
AbstractReplyProducingMessageHandler mh = new AbstractReplyProducingMessageHandler () {
#Override
public Message<?> handleRequestMessage(Message<?> inputMessage) {
String inputRecord = inputMessage.getPayload().toString();
Map<String, Object> newheader = new HashMap<String, Object>();
InputModel result = null;
******if(true) throw new RuntimeException("forcefully Exception ");******
try {
ObjectMapper mapper = new ObjectMapper();
result = mapper.readValue(inputRecord, InputModel.class);
newheader.put("Exception_Status", "Passed");
Message<?> outMsg = MessageBuilder.withPayload(result).copyHeaders(inputMessage.getHeaders())
.copyHeaders(newheader).build();
LOGGER.info("Deserialized to Input Model -------------->" + outMsg);
return outMsg;
} catch (Exception e) {
newheader.put("Exception_Status", "Failed");
String excep = "Exception in translating inputFormat to Object";
Message<?> outMsg = MessageBuilder.withPayload(excep).copyHeaders(inputMessage.getHeaders())
.copyHeaders(newheader).build();
LOGGER.info("Exception in translating inputFormat to Object", e.getMessage());
return outMsg;
}
}
I want to catch the exception in the errorChannel but unable to do so. I want to break the integration flow from the exception I want to execute "someChannel" and stop and send to the outboundRequest channel.
If the exception is inside the try{} block it is handled by the catch block. If the exception is outside the try block the stack trace is coming directly on the console.
What I need to do more to catch the exceptiopn in the errorChannel ?

Related

Junit test cases for delete operation with try and catch block - handling Exception

public UserNoteDefination deleteByNoteCd (UserNoteDefination request, LogDTO log) {
log.setComponent("ClientRequestService.deleteByNoteCd");
UserNoteDefination response new UserNoteDefination();
String tabllame = request.getTabName();
String noteCd-request.getNoteCd();
if (tabName.equalsIgnoreCase("UND_TABLE"))
{
try
userNoteDefRepository.deleteById(noteCd);
response.setMsg("Completed");
log.setPayload("[FROM:UI] clientRequest:Delete Successfully");
loggingService.log(GuiServiceConstants.INFO LEVEL, log);
}
catch (Exception e){
response.setMsg("Error");
log.setPayload("[FROM:UI] clientRequest:Error occured while delete");
loggingService.log(GuiServiceConstants.ERROR LEVEL, log);
}
return response;
}
This is my junit code in mockito
#Test public void deleteByNoteCd() throws Exception {
LogDTO log = new LogDTO(); log.setComponent("ClientRequestService.getClientRequest");
UserNoteDefination response = new UserNoteDefination();
response.setNoteCd("TEST7");
response.setMsg("Completed");
response.setTabName("UND TABLE");
UserNoteDefination rval - service.deleteByNoteCd(response, log);
assertNotNull(rval);
}
I'm unable to cover the exception part. Cab someone help

Return a string (for success or exception details) with PostJsonAsync - .NET Core REST API

I want my REST API to return a string with the exception details to the client. My client-side code is:
public async Task CreateUnit(UnitEntity unit)
{
try
{
var response = await _http.PostJsonAsync<HttpResponseMessage>("api/units", unit);
}
catch(Exception e)
{
//todo want to display error from the service and carry on
//throw;
}
}
And the code for the service / API is:
[HttpPost]
public HttpResponseMessage CreateUnit(UnitEntity unit)
{
try
{
Dal.CreateUnit(unit);
HttpResponseMessage response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.Created;
response.Content = new StringContent($"The unit {unit.UnitName} was created successfully");
return response;
}
catch(Exception e)
{
//todo send error message in Http response if possible
//throw;
return new HttpResponseMessage() {StatusCode = HttpStatusCode.NotAcceptable, Content = new StringContent(e.Message)};
}
}
I've tried just returning a string and it basically says that the JSON serialiser can't deserialise it (because it's already a raw string and not JSON). The code above throws an exception:
Deserialization of reference types without parameterless constructor is not supported. Type 'System.Net.Http.HttpContent'

Spring AMQP RPC consumer and throw exception

I have a consumer (RabbitListner) in RPC mode and I would like to know if it is possible to throw exception that can be treated by the publisher.
To make more clear my explication the case is as follow :
The publisher send a message in RPC mode
The consumer receive the message, check the validity of the message and if the message can not be take in count, because of missing parameters, then I would like to throw Exception. The exception can be a specific business exception or a particular AmqpException but I want that the publisher can handle this exception if it is not go in timeout.
I try with the AmqpRejectAndDontRequeueException, but my publisher do not receive the exception, but just a response which is empty.
Is it possible to be done or may be it is not a good practice to implement like that ?
EDIT 1 :
After the #GaryRussel response here is the resolution of my question:
For the RabbitListner I create an error handler :
#Configuration
public class RabbitErrorHandler implements RabbitListenerErrorHandler {
#Override public Object handleError(Message message, org.springframework.messaging.Message<?> message1, ListenerExecutionFailedException e) {
throw e;
}
}
Define the bean into a configuration file :
#Configuration
public class RabbitConfig extends RabbitConfiguration {
#Bean
public RabbitTemplate getRabbitTemplate() {
Message.addWhiteListPatterns(RabbitConstants.CLASSES_TO_SEND_OVER_RABBITMQ);
return new RabbitTemplate(this.connectionFactory());
}
/**
* Define the RabbitErrorHandle
* #return Initialize RabbitErrorHandle bean
*/
#Bean
public RabbitErrorHandler rabbitErrorHandler() {
return new RabbitErrorHandler();
}
}
Create the #RabbitListner with parameters where rabbitErrorHandler is the bean that I defined previously :
#Override
#RabbitListener(queues = "${rabbit.queue}"
, errorHandler = "rabbitErrorHandler"
, returnExceptions = "true")
public ReturnObject receiveMessage(Message message) {
For the RabbitTemplate I set this attribute :
rabbitTemplate.setMessageConverter(new RemoteInvocationAwareMessageConverterAdapter());
When the messsage threated by the consumer, but it sent an error, I obtain a RemoteInvocationResult which contains the original exception into e.getCause().getCause().
See the returnExceptions property on #RabbitListener (since 2.0). Docs here.
The returnExceptions attribute, when true will cause exceptions to be returned to the sender. The exception is wrapped in a RemoteInvocationResult object.
On the sender side, there is an available RemoteInvocationAwareMessageConverterAdapter which, if configured into the RabbitTemplate, will re-throw the server-side exception, wrapped in an AmqpRemoteException. The stack trace of the server exception will be synthesized by merging the server and client stack traces.
Important
This mechanism will generally only work with the default SimpleMessageConverter, which uses Java serialization; exceptions are generally not "Jackson-friendly" so can’t be serialized to JSON. If you are using JSON, consider using an errorHandler to return some other Jackson-friendly Error object when an exception is thrown.
What worked for me was :
On "serving" side :
Service
#RabbitListener(id = "test1", containerFactory ="BEAN CONTAINER FACTORY",
queues = "TEST QUEUE", returnExceptions = "true")
DataList getData() {
// this exception will be transformed by rabbit error handler to a RemoteInvocationResult
throw new IllegalStateException("mon expecion");
//return dataHelper.loadAllData();
}
On "requesting" side :
Service
public void fetchData() throws AmqpRemoteException {
var response = (DataList) amqpTemplate.convertSendAndReceive("TEST EXCHANGE", "ROUTING NAME", new Object());
Optional.ofNullable(response)
.ifPresentOrElse(this::setDataContent, this::handleNoData);
}
Config
#Bean
AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
var rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter jsonMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.registerModule(new JavaTimeModule());
var jsonConverter = new Jackson2JsonMessageConverter(objectMapper);
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = Map.of(
DataList.class.getName(), DataList.class,
RemoteInvocationResult.class.getName(), RemoteInvocationResult.class
);
classMapper.setIdClassMapping(idClassMapping);
jsonConverter.setClassMapper(classMapper);
// json converter with returned exception awareness
// this will transform RemoteInvocationResult into a AmqpRemoteException
return new RemoteInvocationAwareMessageConverterAdapter(jsonConverter);
}
You have to return a message as an error, which the consuming application can choose to treat as an exception. However, I don't think normal exception handling flows apply with messaging. Your publishing application (the consumer of the RPC service) needs to know what can go wrong and be programmed to deal with those possibilities.

how to catch exception in spring boot rest api

i have a restcontroller with following Code
#RequestMapping(method = RequestMethod.POST, value = "/student")
public void addTopic(#RequestBody Student student) {
student.setPassword(bCryptPasswordEncoder.encode(student.getPassword()));
studentService.addStudent(student);
}
but if the json data doesn't match the Student object, or is wrong formatted an com.fasterxml.jackson.core.JsonParseException: Unexpected character ('"' (code 34)) ist thrown.
what is the best practice to prevent that
I've found that I need to catch JsonProcessingException (which JsonParseException extends from) in the #ExceptionHandler rather than JsonParseException
#ControllerAdvice
public class FeatureToggleControllerAdvice {
#ExceptionHandler(JsonProcessingException.class)
public ResponseEntity<JSONAPIDocument> handleJsonParseException(JsonProcessingException ex) {
final Error error = new Error();
error.setId(UUID.randomUUID().toString());
error.setStatus(HttpStatus.BAD_REQUEST.toString());
error.setTitle(ex.getMessage());
return new ResponseEntity<>(JSONAPIDocument
.createErrorDocument(Collections.singleton(error)), HttpStatus.NOT_FOUND);
}
}
Using JsonParseException in the above sample and nothing is caught, but using JsonProcessingException works as expected.
Use Spring ExceptionHandler to do that
You could specify an ExceptionHandler based on Exception types and also apply the error codes you want to use.
#ExceptionHandler(JsonParseException.class)
public JacksonExceptionHandler {
public ResponseEntity<String> handleError(final Exception exception) {
HttpStatus status = HttpStatus.BAD_REQUEST;
if (exception != null) {
LOGGER.warn("Responding with status code {} and exception message {}", status, exception.getMessage());
return new ResponseEntity<>(exception.getMessage(), status);
}
}
Furthermore you could make use of javax.validation to validate the entity you receive and then Spring Boot will do all the validation automagically. Just add #Valid to the body.

Java Unit test for Exception

public Document query(String uri) throws IOException, IllegalArgumentException
{
final HttpGet httpget = new HttpGet(uri);
final HttpResponse response = httpclient.execute(httpget);
final HttpEntity entity = response.getEntity();
Document doc = null;
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(entity.getContent());
}
catch (SAXException e)
{
LOGGER.error(e);
throw new IllegalArgumentException("parse error" + e);
}
catch (ParserConfigurationException e)
{
LOGGER.error(e);
throw new IllegalArgumentException("parameter factor is invalid: " + e);
}
catch (IllegalStateException e)
{
LOGGER.error(e);
throw new IllegalArgumentException("null entity contetents" + e);
}
return doc;
}
#Test(expected = IllegalArgumentException.class)
public void testQuery_ParseExceptionThrown() throws Exception
{
String uri ="some uri";
EasyMock.expect(httpClient.execute(EasyMock.isA(HttpGet.class))).andReturn(mockResponse);
EasyMock.expect(mockResponse.getEntity()).andReturn(mockEntity);
EasyMock.expect(mockEntity.getContent()).andReturn(new ByteArrayInputStream(REPSONSE_EXAMPLE.getBytes()));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
EasyMock.expect(builder.parse(EasyMock.isA(InputStream.class))).andThrow(
new IllegalArgumentException("expected"));
EasyMock.replay();
class.query(uri);
}
error:
java.lang.IllegalStateException: calling verify is not allowed in record state
at org.easymock.internal.MocksControl.verify(MocksControl.java:181)
at org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl.verify(EasyMockMethodInvocationControl.java:120)
at org.powermock.api.easymock.PowerMock.verify(PowerMock.java:1650)
at org.powermock.api.easymock.PowerMock.verifyAll(PowerMock.java:1586)
at com.amazon.ams.test.AbstractUnitTest.verifyMocks(AbstractUnitTest.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.internal.runners.MethodRoadie.runAfters(MethodRoadie.java:145)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:99)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:296)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:112)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:73)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:284)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:209)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:148)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:102)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:42)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
I keep getting some errors like
java.lang.AssertionError: Expected exception: org.xml.sax.SAXException
java.lang.IllegalStateException: calling verify is not allowed in record state
There are 3 exceptions I need to write Junit test to get into the exception. Does anyone know how to use powermock or easymock class to write the unit test for it?
If you have a mock for the builder using easymock you can throw Exceptions instead of return values:
EasyMock.expect(builder.parse(myContent)).andThrow( myException);
Where myException is an Exception instance you want to throw (created by new MyException(...));
EDIT: example test code:
#Test
public void parseThrowsIllegalStateException(){
//... creating mock factory, builder and entity not shown
//create new Exception to be thrown
IllegalStateException expectedException = new IllegalStateException("expected");
EasyMock.expect(mockBuilder.parse(mockContent).andThrow(expectedException);
EasyMock.replay(...);
//exercise your system under test which tries to parse the entity's Content
//...
}
EDIT 2: now that you posted your actual test code I think the problem might be these lines:
EasyMock.expect(mockEntity.getContent()).andReturn(new ByteArrayInputStream(REPSONSE_EXAMPLE.getBytes()));
...
EasyMock.expect(builder.parse(new ByteArrayInputStream(malformed_XML.getBytes()))).andThrow(new SAXException("expected"));
I don't think ByteArrayInputStream overrides equals() so it is using Object.equals(). The ByteArrayInputStreams won't be equal so EasyMock will never throw the Exception
I would change the builder.parse() expectation to:
EasyMock.expect(builder.parse(EasyMock.isA(InputStream.class))).andThrow(new SAXException("expected"));
Which will throw when parse is called no matter what the inputStream is.
As a side note, your error message the mentioned "calling verify is not allowed in record state" but I don't see any calls to verify() or verifyAll() anywhere.