How do I pull a payload after the response to the client? - java

This is current code..
#Bean
public IntegrationFlow flowForHandlingPlainEncryptHistory() {
return IntegrationFlows.from(InputWithPlainEncryptHistory())
.handle(ENCRYPT_HISTORY_SERVICE, EXTRACT_ENCRYPT_HISTORY)
.channel(outputWithPlainStringOfXml()).get();
}
Methods that work in ENCRYPT_HISTORY
The INSERT into the DB and returns a success.
However, in order to improve speed
Unconditional return success, and then try to INSERT a DB.
#Bean
public IntegrationFlow flowForHandlingPlainEncryptHistory() {
return IntegrationFlows.from(InputWithPlainEncryptHistory())
.handle(ENCRYPT_HISTORY_SERVICE, "extractEncryptHistoryReturn")
.channel(outputWithPlainStringOfXml()
.handle(ENCRYPT_HISTORY_SERVICE, "extractEncryptHistoryInsert").get();
}
#Override
public Object extractEncryptHistoryReturn(Object payload) throws Exception {
log.debug("[INFO] extractEncryptHistoryReturn payload : {}", payload.toString());
Map<String, Object> result = initResult();
result.put(Constant.KEY_NAME_RESULT_CODE, Constant.CODE_SUCCESS);
result.put(Constant.KEY_NAME_RESULT_MSG, Constant.MSG_SUCCESS);
return result;
}
#Override
#Transactional
public void extractEncryptHistoryInsert(Object payload) throws Exception {
log.debug("[INFO] extractEncryptHistoryInsert payload : {}", payload.toString());
Map<String, Object> params = initParam(payload);
try {
long headerInfoSeq = insertHeaderInfo(params);
insertHeaderAclList(headerInfoSeq, (String) params.get("ACL_COUNT"), (String) params.get("ACL_LIST"));
} catch (Exception e) {
log.debug("[ERROR] extractEncryptHistory : Insert errors in the header information and acl list. {}", e.toString());
}
}
extractEncryptHistoryInsert payload coming to the method is not of the first payload.
What can I do to fix it?

You need a publish subscribe channel with each handler being a subscriber to it. Add a task executor so the two handlers run in parallel.
You can either have two IntegrationFlows starting with the same channel or use subflows in a single IntegrationFlow bean.

Related

how to properly wrap method.invoke() (reflect) in a reactive java?

I'm trying to make a service in reactive java,
another service will send me requests.
I catch these requests and want to run methods according to the incoming URI
Here I receive a request from another service and run the desired method:
public Mono<Response> requestResponse(Request message, ByteBuf metadata) {
return controller.startMethod(message)
.onErrorResume(error -> Mono.just(buildResponse(error.getMessage(), message, Status.STATUS_INTERNAL_SERVER_ERROR)))
.switchIfEmpty(Mono.just(buildResponse("NULL", message, Status.STATUS_BAD_REQUEST)))
.doOnNext(logResponse());
}
This is the launch itself, the method is located by the annotation and the http method
public Mono<Response> startMethod(Request message) {
for (Method method : clazz.getDeclaredMethods()) {
if (isNecessaryMethod(method, message)) {
try {
return (Mono<Response>) method.invoke(context.getBean(clazz), initParameters(method, message));
} catch (Throwable e) {
return Mono.error(e);
}
}
}
return Mono.error(new PathNotFound());
}
Subsequently, the method should run, this is an example:
#Request(url = "/save-token", method = POST)
public Mono<Response> saveToken(String token) {
return Mono.empty();
}
I would like to know how to properly process method.invoke(...) so that everything works reactively and correctly

Why does Gateway with void return is making async flow but it does sync when it return value? Spring Integration

I am new with Spring Integration. I was making some tests I realized the behavior of my app changes when the Gateway return void or return String. I'm trying to process the flow in the background (async) meanwhile I return a http message. So I did a async pipeline
#Bean
MessageChannel asyncChannel() {
return new QueueChannel(1);
}
#Bean
public MessageChannel asyncChannel2() {
return new QueueChannel(1);
}
#Bean
public MessageChannel asyncChannel3() {
return new QueueChannel(1);
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
PollerMetadata customPoller() {
PeriodicTrigger periodicTrigger = new PeriodicTrigger(2000, TimeUnit.MICROSECONDS);
periodicTrigger.setFixedRate(true);
periodicTrigger.setInitialDelay(1000);
PollerMetadata poller = new PollerMetadata();
poller.setMaxMessagesPerPoll(500);
poller.setTrigger(periodicTrigger);
return poller;
}
3 Activators
#ServiceActivator(inputChannel = "asyncChannel", outputChannel = "asyncChannel2")
public String async(String message) {
try {
Thread.sleep(5000);
log.info("Activator 1 " + message);
return message;
} catch (InterruptedException e) {
log.error("I don't want to sleep now");
}
return "";
}
#ServiceActivator(inputChannel = "asyncChannel2", outputChannel = "asyncChannel3")
public String async(String message){
log.info("Activator 2 "+ message);
try {
Thread.sleep(2000);
return message;
} catch (InterruptedException e) {
log.error("I don't want to sleep");
}
return "";
}
#ServiceActivator(inputChannel = "asyncChannel3")
public String result(String message) throws InterruptedException {
Thread.sleep(2000);
log.info("Activator 3 " + message);
return message;
}
I receive a message from Controller class
private final ReturningGateway returningGateway;
#PostMapping("/example")
public ResponseEntity post() {
returningGateway.processWhileResponse("Message example");
return ResponseEntity.ok(Map.of("Message","Http Done. Check the logs"));
}
The gateway
#Gateway(requestChannel = "asyncChannel")
public void processWhileResponse(String message_example);
The curious thing is when the gateway returns a void it making the process async so I can see the http message "Http Done. Check the logs" first, then I go to the logs and I see the async execution. but when the gateway returns a String I see the logs first and then the http message.
So I need the gateway returns a value but it keep the async way so I can get a http message
could you give a hand?
Sorry if I'm not using the right term. Thanks
So I need the gateway returns a value but it keep the async way so I can get a http message.
As long as you return some non-async type, it is going to block your code on the gateway call and wait for that return value to come back. Even if your flow behind that gateway is async, it still waits for a reply on the CountDownLatch barrier for replyChannel. In case of void return type there is no reply expectations and gateway exists immediately after sending a request message.
You may consider to have a Future as return type, but it still not clear when you would like to get the value: before returning from your controller method, or it is OK already after.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#async-gateway

I am not able to alter Spring cloud gate way response in Global filter based on response headers from down stream?

My Goal is to receive some token from downstream server response headers by using ServerHttpResponseDecorator without this I am not able to get response headers in GlobalFilter. based on token I am planning to alter downstream response by raising a custom exception and handled in ErrorWebExceptionHandler.
The problem is once I have read the response headers from downstream service even exception also not able to stop the flow I am getting an original response whatever is coming from downstream service but if I raised an exception before headers reading It is working as expected.
GlobalFilter Sample code
#Component
public class CustomFilter implements GlobalFilter, Ordered {
#Override
public int getOrder() {
return -2;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
#Override
public HttpHeaders getHeaders() {
String tokenFromHeader = super.getHeaders().getFirst("TOKEN");
String regIdFromHeader = super.getHeaders().getFirst("regId");
if (false) { // if (true) { It is hadled by exception handler as expected
// I have some Buginese logic here
throw new RuntimeException();
}
if (tokenFromHeader != null && regIdFromHeader != null) {
if (true) {
//I have some Buginese logic here
// No use I am getting original response from down streams
throw new RuntimeException();
}
}
return getDelegate().getHeaders();
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
}
Exception Handler
public class MyWebExceptionHandler implements ErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
byte[] bytes = ( "Some custom text").getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
exchange.getResponse().getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return exchange.getResponse().writeWith(Flux.just(buffer));
}
}
Expected out put is
Some custom text
But I am getting an original response

How to push data over reactive websocket with Spring in response to request?

I am getting started with reactive websockets using Spring Boot 2.1.3. I created a WebSocketHandler implementation like this:
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<EfficiencyData> flux = service.subscribeToEfficiencyData(1);
var publisher = flux.map( o -> {
try {
return objectMapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}).map(session::textMessage)
.delayElements(Duration.ofSeconds(1));
return session.send(publisher);
}
This works, if I connect, I get serialized EfficiencyData every second in my websocket client.
However, I want to react to a request coming from the websocket to tell the service for what id I want the data. I managed to get the request info like this:
#Override
public Mono<Void> handle(WebSocketSession session) {
return session.send(session.receive().map(webSocketMessage -> {
int id = Integer.parseInt(webSocketMessage.getPayloadAsText());
return session.textMessage("Subscribing with id " + id);
}));
Now I have no clue how to combine these 2 implementations?
I was hoping to do something like this:
#Override
public Mono<Void> handle(WebSocketSession session) {
return session.send(session.receive().map(webSocketMessage -> {
int id = Integer.parseInt(webSocketMessage.getPayloadAsText());
Flux<EfficiencyData> flux = service.subscribeToEfficiencyData(id);
var publisher = flux.map( o -> {
try {
return objectMapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}).map(session::textMessage)
.delayElements(Duration.ofSeconds(1));
return publisher; //Does not compile
}));
But that does not compile since publisher is a Flux<WebSocketMessage> and it should be a Publisher<WebSocketMessage>. How should this be handled?
EDIT:
Following the Javadoc example of WebSocketHandler, I tried this:
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<EfficiencyData> flux =
session.receive()
.map(webSocketMessage -> Integer.parseInt(webSocketMessage.getPayloadAsText()))
.concatMap(service::subscribeToEfficiencyData);
Mono<Void> input = flux.then();
Mono<Void> output = session.send(flux.map(data -> session.textMessage(data.toString()))).then();
return Mono.zip(input, output).then();
}
But that just disconnects the websocket client immediately without doing anything.
Use flatMap or concatMap in order to flatten returned publisher
To fix your issue you have to use operators that allows flatting of the returned value. For example
#Override
public Mono<Void> handle(WebSocketSession session) {
return session.send(
session.receive()
.flatMap(webSocketMessage -> {
int id = Integer.parseInt(webSocketMessage.getPayloadAsText());
Flux<EfficiencyData> flux = service.subscribeToEfficiencyData(id);
var publisher = flux
.<String>handle((o, sink) -> {
try {
sink.next(objectMapper.writeValueAsString(o));
} catch (JsonProcessingException e) {
e.printStackTrace();
return; // null is prohibited in reactive-streams
}
})
.map(session::textMessage)
.delayElements(Duration.ofSeconds(1));
return publisher;
})
);
}
Key takeaways
If the return type is a stream, use flatMap or concatMap (see the difference here
Never returns Null. In reactive-streams Null is prohibited value (see specification rules here
When mapping can end up with null -> use handle operator. See more expalation here

Spring Integration Java DSL with a defined IntegrationFlow - missing data in response and mismatched correlationIds

I am using Spring Integration Java DSL with a defined IntegrationFlow. I am seeing behavior where the response is missing pieces of data and the correlationId in the aggregator response does not match the value in the response that is received by calling service.
Background:
I have a JMeter performance test running on a server that uses random data and is running at 600 requests per minute. On my laptop, I have a SoapUI performance test running that hits the same server. The SoapUI project sends requests with the same search criteria (we are doing matching) at a rate of 60 requests per minute. The responses should all contain the same result data.
Approximately 0.5% of the time the response is returned with data missing. In these responses, the correlationId of the response that is logged from the aggregator and the correlationId of the response logged from the calling service (logged after the response is returned to the calling service and has already passed through the aggregator) do not match.
Any idea what is wrong? Please see code snippets below.
#Configuration
#EnableAutoConfiguration
#Import(.....AServiceConfig.class)
public class ServiceConfig {
#Bean(name = "inputChannel")
public DirectChannel inputChannel() {
return new DirectChannel();
}
#Bean(name = "outputChannel")
public QueueChannel outputChannel() {
return new QueueChannel();
}
#Bean(name = "transactionLogger")
public ourLogger ourTransactionLogger() {
return OurLoggerFactory.getLogger("ourAppTrx", new ourLoggerConfig(ourTransactionLoggerKey.values()));
}
public IntegrationFlow ourFlow() {
return IntegrationFlows.from(inputChannel())
.split(splitter(ourTransactionLogger()))
.channel(MessageChannels.executor(getExecutor()))
.handle(ourServiceActivator, "service")
.aggregate(t -> t.processor(ourAggregator, AGGREGATE))
.channel(outputChannel())
.get();
}
#Bean(name = "executor")
public Executor getExecutor()
{
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
//snippet from calling service
public InquiryResponse inquire(InquiryRequest request) {
inputChannel.send(MessageBuilder.withPayload(request).build());
Message<?> msgResponse = outputChannel.receive();
InquiryResponse response = (InquiryResponse) msgResponse.getPayload();
TransactionLogger.debug("correlationId + msgResponse.getHeaders().get("correlationId"));
TransactionLogger.debug("InquiryService inquire response = " + response.toString());
return response;
}
//snippet from aggregator
#Aggregator
public <T> InquiryResponse aggregate(List<Message> serviceResponses) {
InquiryResponse response = new InquiryResponse();
serviceResponses.forEach(serviceResponse -> {
Object payload = serviceResponse.getPayload();
if (payload instanceof AMatchResponse) {
response.setA(((AMatchResponse) payload).getA());
} else if (payload instanceof BValueResponse) {
response.setB(((BValueResponse) payload).getB());
} else if (payload instanceof BError) {
response.setB(new B().addBErrorsItem((BError) payload));
} else if (payload instanceof AError) {
response.setA(new A().AError((AError) payload));
} else {
transactionLogger.warn("Unknown message type received. This message will not be aggregated into the response. ||| model=" + payload.getClass().getName());
}
});
transactionLogger.debug("OurAggregator.response = " + response.toString());
return response;
}

Categories