How to send MQ message without RFH header in C? - c

How to send MQ message without RFH header in C or in other words how do i send NonJMS MQ message using 'C' library interface?
Basically, is there any 'C' equivalent of
((com.ibm.mq.jms.MQQueue) queue).setTargetClient(JMSC.MQJMS_CLIENT_NONJMS_MQ);
Following 'C' MQ calls I am making
MQCONNX(qmgrName, &mqcno, &hConn_, &compCode, &cReason);
MQOPEN(hConn_, &od, openOptions, &hObj_, &openCode, &reason)
MQCRTMH(hConn_, &cmho, &hMsg, &createCode, &reason)
MQSETMP(hConn_, hMsg, &smpo, &prop, &pd, MQTYPE_STRING, propVal.length(), propVal, &compCode, &reason);
pmo.Version = MQPMO_VERSION_3;
pmo.OriginalMsgHandle = hMsg;
MQPUT(hConn_, hObj_, NULL, &pmo, msg._theMessage.length(), buffer, &compCode, &reason);
MQDLTMH(hConn_, &hMsg, &dmho, &compCode, &reason);
pmo.OriginalMsgHandle = hMsg //This line is causing RFH header
MQ Receiver is giving following output. I am using C++ MQ interface to receive the message because that's what existing code is doing and need to make sure that C generated msgs can be read by C++ receiver
2024489 - 2019-09-26 09:00:05.691154 Receiver: Received Message from MQ of size 490
2024489 - 2019-09-26 09:00:05.691163 Receiver: Received Message from MQ --> RFH ^B
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string (this=0x6ce7938,
__str="RFH \002\000\000\000P\000\000\000\"\002\000\000\063\003\000\000MQSTR \000\000\000\000\270\004\000\000(\000\000\000<usr><GROUP_ID>1</GROUP_ID></usr> corrId: \"CORR_ID\"\nchannel: \"HIFI\"\nemp
Ids {\n empId {\n type: \"CALLER_NO\"\n value: \"123456"...)

The IBM MQ classes for JMS API and the XMS APIs (C++ and .NET) are the only APIs that default to sending a RFH2 header.
The setting below that you mention is specific to the JMS API (there would be something similar or the same for XMS) and tells the API that the app receiving the message is not a JMS app so do not send the RFH2 header:
((com.ibm.mq.jms.MQQueue) queue).setTargetClient(JMSC.MQJMS_CLIENT_NONJMS_MQ);
If you are using the C API to send messages it will NOT have a RFH2 header so there is no setting to turn off what is not sent.

There are 2 ways for a C program to handle JMS (aka MQRFH2) messages.
As you saw, the default behavior is for GMO Options field to have MQGMO_PROPERTIES_AS_Q_DEF and the queue's Property Control attribute to be set to Compatibility. Hence, when your application gets a message, it will have the MQRFH2 structure.
If you changed the GMO Options field to have MQGMO_PROPERTIES_IN_HANDLE then when your application gets a message, it will receives just the message payload and all of the message properties will be available via the message handle.
In the sample MQ programs included with IBM MQ, there is one called amqsbcg0.c. There are 2 builds of it: amqsbcg (bindings mode) and amqsbcgc (client mode).
It takes up to 3 parameters: QueueName, QMgrName and PropertyOptions
(1) If you run it without any property options or property options set to 0 then it will set GMO Options field to have MQGMO_PROPERTIES_AS_Q_DEF. Hence, if the message on the queue is a JMS message then the program will output an MQRFH2 structure.
(2) If you run it with property options set to 1 then it will set GMO Options field to have MQGMO_PROPERTIES_IN_HANDLE. Hence, if the message on the queue is a JMS message then then program will output the message properties followed by the message payload.

Related

How to get AMQP Message properties in Apache Camel AMQP Component

I have a Springboot application using Apache Camel AMQP component to comsume messages from a Solace Queue. To send a message to the Queue I use Postman and the Solace REST API. In order to differentiate the message type I add Content-Type to the header of the Http request in Postman. I used SDKPerf to check the message header consumed from solace and the message header is found under "HTTP Content Type" along with other headers.
However, I can't seem to find a way to get this Content-Type from Camel Side. In the documentation it says
String header = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class);
However this always produces null. Any Ideas how to get the message properties in Camel?
EDIT: I think it's actually due to the fact that Camel is using QPid JMS, and there is no JMS API way of getting the Content Type, it's not in the spec. Even though AMQP 1.0 does support content-type as a property. But yeah, my suggestion of a custom property below is still probably the way I would go.
https://camel.apache.org/components/3.20.x/amqp-component.html
https://www.amqp.org/sites/amqp.org/files/amqp.pdf
Edited for clarity & corrections. TL/DR: use a custom user property header.
The SMF Content Type header in the original (REST) message is passed through to the consumed AMQP message as a property content-type, however the JMS API spec does not expose this; there is no way in standard JMS to retrieve this value. It is, however, used by the broker to set the type of message (e.g. TextMessage). Check "Content-Type Mapping to Solace Message Types" in the Solace docs.
Using Solace's SDKPerf AMQP JMS edition to dump the received message to console (note this uses QPid libraries):
./sdkperf_jmsamqp.sh -cip=amqp://localhost:5672 -stl=a/b/c
-md -q
curl http://localhost:9000/TOPIC/a/b/c -d 'hello' -H 'Content-Type: text'
^^^^^^^^^^^^^^^^^^ Start Message ^^^^^^^^^^^^^^^^^^^^^^^^^^^
JMSDeliveryMode: PERSISTENT
JMSDestination: a/b/c
JMSExpiration: 0
JMSPriority: 4
JMSTimestamp: 0
JMSRedelivered: false
JMSCorrelationID: null
JMSMessageID: null
JMSReplyTo: null
JMSType: null
JMSProperties: {JMSXDeliveryCount:1;}
Object Type: TextMessage
Text: len=5
hello
The header does not get mapped through, but does get used to set the message type. If I remove that HTTP header, the received AMQP message is binary. But since other types of Content-Types also map to TextMessages (e.g. application/json, application/xml, etc.), the fact you're receiving a TextMessage is not enough to infer exactly what Content-Type you published your REST message with.
For completeness, I used WireShark with an AMQP decoder, and you can see the header present on the received AMQP message:
Frame 3: 218 bytes on wire (1744 bits), 218 bytes captured (1744 bits) on interface \Device\NPF_Loopback, id 0
Null/Loopback
Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
Transmission Control Protocol, Src Port: 5672, Dst Port: 60662, Seq: 2, Ack: 1, Len: 174
Advanced Message Queueing Protocol
Length: 174
Doff: 2
Type: AMQP (0)
Channel: 2
Performative: transfer (20)
Arguments (5)
Message-Header
Durable: True
Message-Annotations (map of 1 element)
x-opt-jms-dest (byte): 1
Message-Properties
To: a/b/c
Content-Type: text <----------
Application-Properties (map of 1 element)
AaronEncoding (str8-utf8): CustomText
AMQP-Value (str32-utf8): hello
So my suggestion is this:
Set an additional custom header, a User Property, which will get passed through to the AMQP message:
curl http://localhost:9000/TOPIC/a/b/c -d 'hello' -H 'Solace-User-Property-AaronEncoding: CustomText' -H 'Content-Type: text'
JMSDestination: a/b/c
JMSProperties: {AaronEncoding:CustomText;JMSXDeliveryCount:1;}
Object Type: TextMessage
Text: len=5
hello
My always-goto guide for Solace REST interactions: https://docs.solace.com/API/RESTMessagingPrtl/Solace-REST-Message-Encoding.htm
Hope that helps!
It may have a different name in Camel. Try either printing all the headers or stop it in the debugger and examine the incoming message.

How to read acknowledgement after send action in Citrus with Camel MLLP Component

I have an application that receives a HL7 message and sends back an acknowledgement response. I'm using Citrus with the Camel MLLP component to send this HL7 message. What I'm trying to achieve is to be able to read the acknowledgement to compare it.
I currently have a test with hl7Payload, a String variable with HL7 content. In my Citrus context I have:
<citrus-camel:sync-endpoint id="mllpOutEndpoint"
camel-context="mllpContext"
endpoint-uri="mllp:localhost:6662"/>
I tried extracting the header I found on the Camel documentation:
send("mllpOutEndpoint")
.fork(true)
.messageType(MessageType.PLAINTEXT)
.payload(hl7Payload)
.extractFromHeader("CamelMllpAcknowledgementString", "receivedAck");
echo("${receivedAck}");
But I get this error:
com.consol.citrus.exceptions.UnknownElementException: Could not find header element CamelMllpAcknowledgementString in received header
Everything works fine without extractFromHeader(). The application receives my HL7 message, sends back an ACK and the test passes, but I'm struggling to get this ACK content back to make further tests. What am I missing here?
I got it:
async().actions(
send("mllpOutEndpoint")
.messageType(MessageType.PLAINTEXT)
.payload(hl7Payload),
receive("mllpOutEndpoint")
.header("CamelMllpAcknowledgementType", "AA")
);

Camel errorHandler / deadLetterChannel REST response

I have a Camel rest endpoint (Jetty) which validates and processes incoming requests. Besides specific Exception handlers (onException) it uses a DLQ error handler (errorHandler(deadLetterChannel...)) which is setup to retry 3 times - if unsuccessful the message is moved to the DLQ.
My question is, how do I still return a user friendly error message back to the client if an unexpected Exception occurs rather than the full Exception body? Is there some config I'm missing on the errorHandler?
I've tried to find some examples on the camel unit tests (DeadLetterChannelHandledExampleTest) and camel in action 2 (Chapter 11) but none seemed to have specific examples for this scenario.
Code is:
.from(ROUTE_URI)
.errorHandler(deadLetterChannel("{{activemq.webhook.dlq.queue}}")
.onPrepareFailure(new FailureProcessor())
.maximumRedeliveries(3)
.redeliveryDelay(1000))
.bean(ParcelProcessor.class, "process");
Thank you for your help!
Use a 2nd route as the DLQ, eg direct:dead and then send the message first to the real DLQ, and then do the message transformation afterwards to return a friendly response.
errorHandler(deadLetterChannel("direct:dead")
from("direct:dead")
.to("{{activemq.webhook.dlq.queue}}")
.transform(constant("Sorry something was wrong"));

Camel with RabbitMQ exception only occurs on second message - mis-spelt exchange name

I'm using Camel within a Spring boot application and integrate with RabbitMQ but am encountering strange behaviour.
My app has Restful endpointswhich convert the http request to a RabbitMQ message and publish this to a predefined exchange. There is a separate consumer app which listens to a queue and processes the messages.
I have deliberately entered an incorrect rabbitmq exchange name (invalidxchangename)to check that the application will fail if the exchange does not exist however the camel context starts without error and when I send in a first request is does not report any error. This message gets lost as there is no matching RabbitMQ exchange. When I submit a second request I receive the following exception which I would have expected on route startup.
com.rabbitmq.client.AlreadyClosedException: channel is already closed due to channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'invalidxchangename' in vhost
EDIT:
I've tried a more simple example to show the issue in Camel.
I've created a simple route as follows:
from("file:in?fileName=in.txt").log(LoggingLevel.DEBUG, "in here!").to("rabbitmq://localhost:5762/invalidexchange?declare=false");
where there is an existing RabbitMQ exchange called validexchange (so I have deliberately made a typo in the RabbitMQ uri). I would expect the camel route to fail at startup since the exchange doesn't exist, or even the first time it tries to process a new in.txt file.
What I am actually seeing in the logs is that on start up it reports no error and only on the 2nd invocation of the route does it report an error.
2015-03-11 16:17:04.356 INFO 9756 : ID-SBMELW7W-06220-59960-1426051020468-0-2 >>> (route2) from(file://in?fileName=in.txt) --> log[in here!] <<< Pattern:InOnly, Headers:...
2015-03-11 16:17:04.360 INFO 9756 : ID-SBMELW7W-06220-59960-1426051020468-0-2 >>> (route2) log[in here!] --> rabbitmq://localhost:5762/customerchannel.exchang?declare=false <<< Pattern:InOnly, Headers:...
2015-03-11 16:17:45.073 INFO 9756 : ID-SBMELW7W-06220-59960-1426051020468-0-4 >>> (route2) from(file://in?fileName=in.txt) --> log[in here!] <<< Pattern:InOnly, Headers: ...
2015-03-11 16:17:45.079 INFO 9756 : ID-SBMELW7W-06220-59960-1426051020468-0-4 >>> (route2) log[in here!] --> rabbitmq://localhost:5762/customerchannel.exchang?declare=false <<< Pattern:InOnly, Headers:...
2015-03-11 16:17:45.092 ERROR 9756 : Failed delivery for (MessageId: ID-SBMELW7W-06220-59960-1426051020468-0-3 on ExchangeId: ID-SBMELW7W-06220-59960-1426051020468-0-4). Exhausted after delivery attempt: 1 caught: com.rabbitmq.client.AlreadyClosedException: channel is already closed due to channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'customerchannel.exchang' in vhost '/', class-id=60, method-id=40)
It looks like the first request is causing an error which closes the connection and logs the reason, and when you try to use the channel the second time it's returning an AlreadyClosedException with the message that caused the channel to close in the first call.
You can test this by trying to publish the second message to a different exchange name in the same channel and checking which exchange is in the error. E.g. publish the second message to invalidxchangename2 and you should still see invalidxchangename as the exchange in the error.
To fix, you should handle the publish result when you publish and re-establish the connection if there's an error.
If you want to be sure that a message got delivered to a RabbitMQ queue, then you have to use publisher confirms: https://www.rabbitmq.com/confirms.html
That you are able to publish a message it doesn't mean that the message will reach a queue. You could go to a mailbox and leave a letter inside, but between the time you left the letter there and a postman picked up, many things could have happened, for example, the mailbox catching fire and so on.

JGroups, TCP_NIO multiple messages sent to nowhere

Messages sent to ports I never specified in my configuration file.
this is my config:
[10-Jan-2011 11:02:22.917 GMT] ERROR org.jgroups.protocols.TCP_NIO - failed sending message to 192.168.50.41:8851 (116 bytes): java.lang.Exception: connection to 192.168.50.41:8851 could not be established
[10-Jan-2011 11:02:22.917 GMT] WARN org.jgroups.blocks.ConnectionTableNIO - Connection is not running, discarding message
Because you have a port_range of 2, so every discovery message is sent to all of the initial_hosts defined in TCPPING, plus port_range, e.g.
TCPPING.initial_hosts=A[1000],B[1000]
port_range=2
will send discovery requests to A:1000-1002, B:1000-1002.
TCPPING is used at startup for initial discovery and by MERGE2 (not in your stack)...

Resources