Validating multiple messages on the same JMS endpoint in Citrus Framework - java

I'm sending a single message that produces multiple messages, two of which arrive on the same JMS endpoint.
runner.send(sendMessageBuilder -> sendMessageBuilder.endpoint(inputMessage.getEndpoint())
.messageType(MessageType.XML)
.payload(inputMessage.getPayload())
.header(JMSOUTPUTCORRELATIONID, correlationId));
for(OutputMessage outputMessage : inputMessage.getOutputMessages()) {
runner.receive(receiveMessageBuilder -> receiveMessageBuilder.endpoint(outputMessage.getEndpoint())
.schemaValidation(false)
.payload(outputMessage.getPayload())
.header(JMSOUTPUTCORRELATIONID, correlationId));
}
When validating two messages on the same endpoint I'm having trouble finding a way to match them to their respective expected outputs.
I was wondering if Citrus has a built in way to do this or if I could build in a condition that checks the other expected outputs if the first one fails.

I've added a custom validator.
List<OutputMessage> outputMessages = inputMessage.getOutputMessages();
while(outputMessages.size() > 0) {
OutputMessage outputMessage = outputMessages.get(0);
runner.receive(receiveMessageBuilder -> receiveMessageBuilder.endpoint(outputMessage.getEndpoint())
.schemaValidation(true)
.validator(new MultipleOutputMessageValidator(outputMessages))
.header(JMSOUTPUTCORRELATIONID, correlationId));
}
The validator is provided with the the list of expected outputs that have not yet been validated. It will then try to validate each of the expected outputs in the list against the received message and if the validation is succesful removes that expected output from the list.
public class MultipleOutputMessageValidator extends DomXmlMessageValidator {
private static Logger log = LoggerFactory.getLogger(MultipleOutputMessageValidator.class);
private List<OutputMessage> controlMessages;
public MultipleOutputMessageValidator(List<OutputMessage> controlMessages) {
this.controlMessages = controlMessages;
}
#Override
public void validateMessagePayload(Message receivedMessage, Message controlMessage, XmlMessageValidationContext validationContext, TestContext context) throws ValidationException {
Boolean isValidated = false;
for (OutputMessage message : this.controlMessages) {
try {
super.validateMessagePayload(receivedMessage, message, validationContext, context);
isValidated = true;
controlMessages.remove(message);
break;
} catch (ValidationException e) {
// Do nothing for now
}
}
if (!isValidated) {
throw new ValidationException("None of the messages validated");
}
}
}

You should use JMS message selectors so you can "pick" one of the messages from that queue based on a technical identifier. This selector can be a JMS message header for instance (in your case the header JMSOUTPUTCORRELATIONID). This way you make sure to receive the message that you want to validate first.
Example usage:
receive(action -> action.endpoint(someEndpoint)
.selector("correlationId='Cx1x123456789' AND operation='getOrders'"));
Citrus message selector support is described here

Related

How do I send a message directly to a parking lot queue, prevent requeue and exit the program flow?

I currently have 4 queues:
test-queue
test-queue-short-term-dead-letter
test-queue-long-term-dead-letter
test-queue-parking-lot
When a message comes into test-queue, I do a check to see if the message is in the correct format. If it isn't I want to send the message directly to the parking lot queue.
I can't use AmqpRejectAndDontRequeue() because it will automatically send the message to the configured DLQ (test-queue-short-term-dead-letter).
Using RabbitTemplate.convertAndSend() with another exception such as BadRequestException doesn't work. The message goes to the parking lot queue as expected, however the same message will stay in the test-queue
Using RabbitTemplate.convertAndSend() on it's own won't work as the program continues execution.
All queues are bound to a single direct exchange, each with unique routing keys. The test-queue is configured with the following arguments:
x-dead-letter-exchange: ""
x-dead-letter-routing-key: <shortTermDeadLetterKey>
Receiver:
#RabbitListener(queues = "test-queue")
public void receiveMessage(byte[] person) {
String personString = new String(person);
if (!personString.matches(desiredRegex)) {
rabbitTemplate.convertAndSend("test-exchange", "test-queue-parking-lot",
"invalid person");
log.info("Invalid person");
}
...some other code which I dont want to run as the message has arrived in the incorrect format
}
The problem was solved by manually acknowledging the message and returning from the method.
#RabbitListener(queues = "test-queue")
public void receiveMessage(byte[] person, Channel channel,
#Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception) {
String personString = new String(person);
if (!personString.matches(desiredRegex)) {
rabbitTemplate.convertAndSend("test-exchange", "test-queue-parking-lot",
"invalid person");
log.info("Invalid person");
channel.basicAck(tag, false);
return;
}
...some other code which I dont want to run as the message has arrived in the incorrect format
}

Not able to print the XML Response from a SOAP webservice

I am not able to print the response from a Soap Webservice.
Seen few solutions by editing the generated stub code. But I cant edit the generated code as it gets restored to original form on every build. Looking for a solution where I can get the solution printed without change in generated code.
I am consuming the SOAP service from a Spring Boot microservice.
ServiceContext serviceConxt = omsSchedulingService._getServiceClient().getServiceContext();
OperationContext operationContext = serviceConxt.getLastOperationContext();
MessageContext inMessageContext = operationContext.getMessageContext("Out");
log.info(inMessageContext.getEnvelope().toString());
You can add a message handler for the soap message.
Then once you intercept the message with the handler, you can print out the response.
You will need to add the handler to the handler chain, depending on your project you can do that programatically or with config.
final class MyMessageHandler implements SOAPHandler<SOAPMessageContext>{
#Override
public void close(MessageContext context) {
handle(context);
}
private boolean handle(MessageContext context) {
if (context != null) {
try {
Object httpResponseCodeObj = context.get(SOAPMessageContext.HTTP_RESPONSE_CODE);
if (httpResponseCodeObj instanceof Integer)
httpResponseCode = ((Integer) httpResponseCodeObj).intValue();
if (context instanceof SOAPMessageContext) {
SOAPMessage message = ((SOAPMessageContext) context).getMessage();
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(512);
message.writeTo(byteOut);
String messageStr = byteOut.toString(getCharacterEncoding(message));
boolean outbound = Boolean.TRUE.equals(context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY));
Logger.info(loggingPrefix, outbound ? "SOAP request: " : "SOAP response: ", replaceNewLines(messageStr));
}
} catch (SOAPException e) {
Logger.error(e, loggingPrefix, "SOAPException: ", e.getMessage(), NEWLINE);
} catch (IOException e) {
Logger.error(e, loggingPrefix, "IOException: ", e.getMessage(), NEWLINE);
}
}
return true;
}
}
If you donĀ“t want to implement an interceptor the easiest way is to use the logging via vm arguments:
JAVA_OPTS=-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.showdatetime=true -Dorg.apache.commons.logging.simplelog.log.httpclient.wire=debug -Dorg.apache.commons.logging.simplelog.log.org.apache.commons.httpclient=debug
This way you should see the logging of your request / response with headers in console.
First you can get AxisConfiguration from client stub.
AxisConfiguration axisConf = stub._getServiceClient().getAxisConfiguration();
Processing incoming and outgoing messages is divided into phases. There is a list of phases (a flow) which is processed when everything works correctly (without errors) and also another for situations when some fault occurs e.g. when an exception is thrown during message processing. Every flow maybe incoming or outgoing so there are 4 flows altogether.
List<Phase> phasesIn = axisConf.getInFlowPhases(); // normal incoming communication i.e. response from webservice
List<Phase> phasesOut = axisConf.getOutFlowPhases(); // normal outgoing communication
List<Phase> phasesFaultIn = axisConf.getInFaultFlowPhases(); // faulty incoming communication e.g. when an exception occurs during message processing
List<Phase> phasesFaultOut = axisConf.getOutFaultFlowPhases(); // faulty outgoing communication
Some but not all phase names are defined in org.apache.axis2.phaseresolver.PhaseMetadata.
For example "Security" phase processed in Rampart module (module for Web Service Security) won't be found in PhaseMetadata.
You can add a handler to every phase, e.g.
for (Phase p : phasesOut) {
if (PhaseMetadata.PHASE_TRANSPORT_OUT.equals(p.getName())) {
p.addHandler(new MessageContentLoggerHandler());
}
}
Handler is a class which extends org.apache.axis2.handlers.AbstractHandler.
You just have to implement
public InvocationResponse invoke(MessageContext msgContext).
There you have access to MessageContext. Of course, you can get whole SOAP envelope like this:
msgContext.getEnvelope().toString()
and for example print it to your logs or save as a separate file.
Remember to put
return InvocationResponse.CONTINUE;
at the end of invoke method for a situation when handler processes the message successfully. Otherwise processing stops in this handler and a whole process won't get to any another phase.
If you need to see whole message with WSS headers, you can add your own phase. For example this adds your custom phase as the last in processing of outgoing message (so also after Rampart's security phase)
Phase phase = new Phase("SomePhase");
phase.addHandler(new SomeCustomHandler());
axisConf.getOutFlowPhases().add(phase);
Of course logging (and exposing in any other way) security headers in production environment is a very bad idea. Do it only for debugging purposes in some test environment.

Springboot Kafka Producer Error Handling with Cloud Stream Binder

I created a Springboot application to push message to a Kafka Topic. The application is working fine. What I am trying is to handle the exceptions when there is a failure while sending the messages to Kafka Topic. I am using the Error Channel to track the errors while sending a message. But the actual issue is, I am able to see the error message, but I am not able to see the Actual payload which got failed in the error message. Actually , I want to log that Payload.
The JSON Message that I am trying to send : {"key1":"value1"}
Service class :
#AllArgsConstructor
#EnableBinding(Source.class)
public class SendMessageToKafka {
private final Source source;
public void sendMessage(String sampleMessage) {
source.output.send(MessageBuilder.withPayLoad(sampleMessage).build());
}
#ServiceActivator(inputChannel = "errorChannel")
public void errorHandler(ErrorMessage em) {
System.out.println(em);
}
}
application.yml:
spring:
cloud:
stream:
bindings:
output:
producer:
error-channel-enabled: true
With the above configuration, when the Kafka server is down, the control is coming to the errorHandler method and printing the message. But I am not able to see the actual payload which is {"key1":"value1"} from the error message. How can I retrieve that from the error message?
You can filter the payload based on type, e.g. KafkaSendFailureException (https://docs.spring.io/spring-kafka/docs/2.2.0.RELEASE/reference/html/_spring_integration.html indicates the ErrorMessage payload is this type.)
After that what worked in my case is to cast it down to the original sent message as follows (analyze the objects via breakpoint to determine the appropriate value type, e.g. byte[]):
#ServiceActivator(inputChannel = "errorChannel")
public void errorHandler(ErrorMessage em) {
log.debug("got error message over errorChannel: {}", em);
if (null != em.getPayload() && em.getPayload() instanceof KafkaSendFailureException) {
KafkaSendFailureException kafkaSendFailureException = (KafkaSendFailureException) em.getPayload();
if (kafkaSendFailureException.getRecord() != null && kafkaSendFailureException.getRecord().value() != null
&& kafkaSendFailureException.getRecord().value() instanceof byte[]) {
log.warn("error channel message. Payload {}", new String((byte[])(kafkaSendFailureException.getRecord().value())));
}
}
}

Intercepting Spring Cloud Stream Messages from Consumer only

I am currently using Spring Cloud Stream with Kafka binders with a GlobalChannelInterceptor to perform message-logging for my Spring Boot microservices.
I have:
a producer to publish messages to a SubscribableChannel
a consumer to listen from the Stream (using the #StreamListener annotation)
Throughout the process when a message is published to the Stream from the producer and listened by the consumer, it is observed that the preSend method was triggered twice:
Once at producer side - when the message is published to the Stream
Once at consumer side - when the message is listened from the Stream
However, for my logging purposes, I only need to intercept and log the message at consumer side.
Is there any way to intercept the SCS message ONLY at one side (e.g. consumer side)?
I would appreciate any thoughts on this matter. Thank you!
Ref:
GlobalChannelInterceptor documentation - https://docs.spring.io/spring-integration/api/org/springframework/integration/config/GlobalChannelInterceptor.html
EDIT
Producer
public void sendToPushStream(PushStreamMessage message) {
try {
boolean results = streamChannel.pushStream().send(MessageBuilder.withPayload(new ObjectMapper().writeValueAsString(message)).build());
log.info("Push stream message {} sent to {}.", results ? "successfully" : "not", StreamChannel.PUSH_STREAM);
} catch (JsonProcessingException ex) {
log.error("Unable to parse push stream message.", ex);
}
}
Producer's streamChannel
public interface StreamChannel {
String PUSH_STREAM = "PushStream";
#Output(StreamChannel.PUSH_STREAM)
SubscribableChannel pushStream();
}
Consumer
#StreamListener(StreamChannel.PUSH_STREAM)
public void handle(Message<PushStreamMessage> message) {
log.info("Incoming stream message from {}, {}", streamChannel.pushStream(), message);
}
Consumer's streamChannel
public interface StreamChannel {
String PUSH_STREAM = "PushStream";
#Input(StreamChannel.PUSH_STREAM)
SubscribableChannel pushStream();
}
Interceptor (Common Library)
public class GlobalStreamInterceptor extends ChannelInterceptorAdapter {
#Override
public Message<?> preSend(Message<?> msg, MessageChannel mc) {
log.info("presend " + msg);
return msg;
}
#Override
public void postSend(Message<?> msg, MessageChannel mc, boolean sent) {
log.info("postSend " + msg);
}
}
Right, why don't follow GlobalChannelInterceptor options and don't apply
An array of simple patterns against which channel names will be matched.
?
So, you may have something like this:
#GlobalChannelInterceptor(patterns = Processor.INPUT)
Or use a custom name of input channel to your SCSt app.

How to receive message from wildfly jms queue using consumer

I encountered a knotty problem when receiving message from WildFly JMS queue. My code is below:
Session produceSession = connectionFactory.createConnection().createSession(false, Session
.CLIENT_ACKNOWLEDGE);
Session consumerSession = connectionFactory.createConnection().createSession(false, Session
.CLIENT_ACKNOWLEDGE);
ApsSchedule apsSchedule = new ApsSchedule();
boolean success;
MessageProducer messageProducer = produceSession.createProducer(outQueueMaxusOrder);
success = apsSchedule.sendD90Order(produceSession,messageProducer, d90OrderAps);
if (!success) {
logger.error("Can't send APS schedule msg ");
} else {
MessageConsumer consumer = consumerSession.createConsumer(inQueueDeliveryDate);
data = apsSchedule.receiveD90Result(consumerSession,consumer);
}
then getting into the receiveD90Result():
public DeliveryData receiveD90Result(Session session, MessageConsumer consumer) {
DeliveryData data = null;
try {
Message message = consumer.receive(10000);
if (message == null) {
return null;
}
TextMessage msg = (TextMessage) message;
String text = msg.getText();
logger.debug("Receive APS d90 result: {}", text);
ObjectMapper mapper = new ObjectMapper();
data = mapper.readValue(text, DeliveryData.class);
} catch (JMSException je) {
logger.error("Can't receive APS d90 order result: {}", je.getMessage());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
consumer.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
return data;
}
But when implementing the consumer.receive(10000), the project can't get a message from queue. If I use asynchronous way of MDB to listen the queue, I can get the message from queue. How to resolve it?
There are multiple modes you can choose to get a message from the queue. Message Queues are by default asynchronous in usage. There are however cases when you want to read it synchronously , for example sending a message with account number and using another queue to read the response and match it with a message id or a message correlation id. When you do a receive , the program is waiting for a message to arrive within that polling interval specified in receive.
The code snippet you have , as i see it uses the psuedo synchronous approach. If you have to use it as an MDB , you will have to implement message driven bean (EJB Resource) or message listener.
The way that MDB/Message Listener works is more event based , instead of a poll with a timeout (like the receive) , you implement a callback called onMessage() that is invoked every time there is a message. Instead of a synchronous call , this becomes asynchronous. Your application may require some changes both in terms of design.
I don't see where you're calling javax.jms.Connection.start(). In fact, it doesn't look like you even have a reference to the javax.jms.Connection instance used for your javax.jms.MessageConsumer. If you don't have a reference to the javax.jms.Connection then you can't invoke start() and you can't invoke close() when you're done so you'll be leaking connections.
Furthermore, connections are "heavy" objects and are meant to be re-used. You should create a single connection for both the producer and consumer. Also, if your application is not going to use the javax.jms.Session from multiple threads then you don't need multiple sessions either.

Categories