I am trying to take an input zip stream and grab the files from inside of it. Currently, I have this bean call attempt to send the stream to a dummy method so I can analyze the body in the debugger. The result is an empty string.
public void configure() throws Exception {
from("direct-vm://splitter-route")
.split(new ZipSplitter())
.streaming()
.convertBodyTo(String.class)
.choice()
.when(body().isNotNull())
.bean(this, "testCall")
.to("file:/tmp/myinputzip")
.end()
.end();
How can I get this to pass each individual file to my method?
Related
Let's take the following consumer method for a RabbitMQ queue. Ths payload received from the queue is in JSON format, so I register a bean returning a Jackson2JsonMessageConverter. This basically works fine.
Now I'd like to add a validation of the QueueResponse object, similar to when using Jackson in a #RestController, e.g. if the JSON field does not exist or contains an invalid value. In this case, I'd like the code to execute the catch block, i.e. throwing an AmqpRejectAndDontRequeueException.
Thus, I added #Payload #Valid as described in the documentation. But I don't know what to do in the validationErrorHandler method. I don't understand the return statement from the documentation. What would I need to do there to reach the catch block?
#RabbitListener(queues = QUEUE_NAME, messageConverter = "jackson2MessageConverter", errorHandler="validationErrorHandler")
public void consume(#Payload #Valid QueueResponse queueResponse) {
try {
processMessage(queueResponse);
} catch (Exception e) {
throw new AmqpRejectAndDontRequeueException(e.getMessage());
}
}
#Bean
public MessageConverter jackson2MessageConverter() {
return new Jackson2JsonMessageConverter(objectMapper);
}
// Not sure what to do here...
#Bean
public RabbitListenerErrorHandler validationErrorHandler() {
return (m, e) -> {
...
};
}
If the error handler exits normally, the message will be acknowledged (discarded).
If the error handler throws an exception, the message will either be requeued (and redelivered) or discarded (and optionally sent to a dead letter queue), depending on the exception type, container properties, and queue arguments.
Basically what I do in the RabbitListenerErrorHandler is the following:
check how many times I requeued a message by looking into the count property in the x-death header
then decide to either requeue the message by throwing an AmqpRejectAndDontRequeueException or not. In this last case, instead of just discard the message, send it to a parking-lot exchange (bound to a queue with no consumer, in my case these queues are monitored externally) with additional information (for instance the stack trace of the last failure)
I have a method in a restcontroller. I need to convert input request to String and send to another destination after method execution is totally completed, ideally I should not affect in method processing execution, method should execute without additional delay.
In non webflux application I can use afterCompletion in HandlerIntercepter. But due to use WebFlux I cannot use that.
How can I make additional processing after controller method execution ends, and response returned to caller?
If the logic you want to call is specific to this one controller, you could use doFinally. It is executed after your Stream (Mono/Flux) terminates. https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#doFinally-java.util.function.Consumer-
Example:
public class Playground {
ConverterService converterService;
PostProcessor postProcessor;
public Playground(ConverterService converterService, PostProcessor postProcessor) {
this.converterService = converterService;
this.postProcessor = postProcessor;
}
public Mono<String> endpoint(Integer input) {
return Mono.just(input)
.flatMap(integer -> converterService.convert(integer))
.doFinally(signalType -> postProcess(input));
}
private void postProcess(Integer input) {
this.postProcessor.process(input);
}
}
First the converterService is called. After the stream is terminated, the postProcessor is called in any case.
I have a following flow implemented in Spring Integration DSL:
Take feed from HTTP
Enrich errorChannel header (point of handling all exception downstream here).
Transform message to be a Collection
Split Collection into sepearate messages
Send each message to next processing channels
#Bean
public IntegrationFlow inboundHttpFlow(
Puller puller,
HeaderEnricher errorHandlingChannelHeaderEnricher,
FeedTransformer feedTransformer,
MessageChannel outputFeedIntegrationChannel
) {
final Consumer<SourcePollingChannelAdapterSpec> pollingSpec = spec ->
spec
.poller(Pollers.cron(SCHEDULE_EVERY_HALF_MINUTE)
.errorChannel(INBOUND_ERROR_CHANNEL));
return IntegrationFlows
.from(puller, pollingSpec)
.enrichHeaders(errorHandlingChannelHeaderEnricher)
.transform(feedTransformer)
.split()
.channel(outputFeedIntegrationChannel)
.get();
}
Where my errorHandlingChannelHeaderEnricher is:
#Component
public class ErrorHandlingChannelHeaderEnricher implements HeaderEnricher {
#Override
public void accept(HeaderEnricherSpec spec) {
spec.header(
MessageHeaders.ERROR_CHANNEL,
INBOUND_ERROR_CHANNEL,
true
);
}
}
When feedTransformer throws an exception in working app then it goes to set errorChannel as expected. But I don't know how to write a test to test if thrown exception goes to errorChannel defined in header?
When I'm trying to simulate it in test given way, it doesn't work because exception is thrown back into caller instead of errorChannel:
// given
Throwable transformerException = new IllegalStateException();
when(feedTransformerMock.apply(any())).thenThrow(transformerException);
// when
var testFeedMessage = MessageBuilder
.withPayload(pullerResult)
.build();
inboundHttpFlow.getInputChannel().send(testFeedMessage); // excetpion returns to caller here
// then
verify(errorHandlerSpy).accept(transformerException);
And exception is typical:
org.springframework.integration.transformer.MessageTransformationException: Failed to transform Message; nested exception is org.springframework.messaging.MessageHandlingException: nested exception is java.lang.IllegalStateException, failedMessage=GenericMessage [payload=test-payload, headers={errorChannel=inboundErrorChannel, id=f77a6a01-9bca-5af3-8352-7edb4c5e94b0, timestamp=1556019833867}]
, failedMessage=GenericMessage [payload=test-payload, headers={errorChannel=inboundErrorChannel, id=f77a6a01-9bca-5af3-8352-7edb4c5e94b0, timestamp=1556019833867}]
I assume that because of DirectChannel and lack of poller in this test example in compare to real flow (with poller).
Is there any way to simulate that throwing exception and checking if it really goes to errorChannel defined in header?
It's not clear what you are trying to test.
Why do you need to test the framework?
You don't need to enrich the headers since you already have an error channel on the poller.
You are correct; sending to a DirectChannel will result in the exception being thrown to the caller.
If you really want to test the framework, mock the Puller instead of sending to the input channel.
I have a filter like:
#Component
#Order(8)
public class LogReqFilter extends OncePerRequestFilter
{
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
{...}
}
it basically logs all requests and responses. When there is a 400 error however the response is blank. There is a global exception handler that replaces the body with the custom error:
#RestControllerAdvice
public class GlobalExceptHandler extends ResponseEntityExceptionHandler {
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleArgNotValid(MethodArgumentNotValidException ex
, HttpHeaders headers, HttpStatus status, WebRequest request) {...}
}
I noticed that the Global Exception handler is called after the filter and I think that this is why the response is blank in the filter. Is there a way to have the filter called after the Global Exception Handler?
Edited:
TLDR
No You can't change the order as per the OP Title because your GlobalError Handler is always in the layer that holds the ControllerAdvices and that is always between the RequestFilter layer and the Controller.
Long Version
You are somewhat right.
i refer you to this answer: https://stackoverflow.com/a/17716298/405749
expanding on this:
be aware that before and after depends from which you are looking from, ie.
inbound (before it reaches your controller code) OR
outbound (after the controller is done)
now you can plug in different components on this path, e.g. here RequestFilter and Controller Advice
depending on the method you implement, it gets called only on the inbound or outbound track or both, here:
doFilterInternal() is called inbound. Only if you call filterChain.doFilter(request, response); the request continues on the inbound track otherwise it stops here and the response is returned with whatever is already in its output stream or will be added by this filter or filters still be encountered on the way out
this is the call sequence assuming we only have this filter and advice
'inbound'-methods from request filters
'inbound'-methods from controller advice(s)
'outbound'-methods from controller advice(s)
'outbound'-methods from request filters
Now, the handleArgNotValid() only gets called in case such an exception is thrown and potentially adds content to the Response-Output stream. Your example doesnt show if you return an object here or not, but i guess from your OP that you dont. and as a consequence the output stream is empty.
Plz also note, that there is no easy way to dump/look into the output stream unless you wrap it, but that's another topic.
I'm using ResponseEntityExceptionHandler in a Spring Boot-based application, to capture errors in a single place and output a consistent error payload.
Everything works as expected.
ResponseEntityExceptionHandler has a method handleHttpMessageNotReadable that can be used to "react" on a client sending an invalid message (in my case a JSON payload).
Again, everything works as expected. If a client POST an invalid JSON document, the code in the handleHttpMessageNotReadable is executed correctly.
Now, for monitoring purposes, I would like to log the invalid JSON.
This is the code I'm using inside the handleHttpMessageNotReadable method, to get hold of the payload.
String line = null;
StringBuffer jb = new StringBuffer();
try {
BufferedReader reader = servletWebRequest.getRequest().getReader();
while ((line = reader.readLine()) != null)
jb.append(line);
} catch (IOException e) {
e.printStackTrace();
}
As expected, I get an exception because the stream has been already consumed by Jackson, for actually parsing the JSON payload:
java.lang.IllegalStateException: getInputStream() has already been called for this request
What would be a simple approach to access the original POST payload?
Would it be possible to throw a custom exception which would contain the original payload (MyException extends HttpMessageNotReadableException)?
The request body is converted from JSON into a java type by
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
If erros occur, the HttpMessageNotReadableException is thrown in the
super class org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#readJavaType
You can subclass MappingJackson2HttpMessageConverter, overwrite readJavaType(..) and try to throw your own exception or a customized HttpMessageNotReadableException there.
Most likely you will need to clone the input stream, use one to try to deserialize the JSON, and another to use in case you need to throw an exception.
This may impact performance, but if thats not a problem, you may give it a try....