Skip to content

Commit b263de6

Browse files
committed
Support for message string properties - #39
1 parent 42f4777 commit b263de6

File tree

6 files changed

+328
-14
lines changed

6 files changed

+328
-14
lines changed

jms20subset/Message.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,7 @@ type Message interface {
4747
// Typical values returned by this method include
4848
// jms20subset.DeliveryMode_PERSISTENT and jms20subset.DeliveryMode_NON_PERSISTENT
4949
GetJMSDeliveryMode() int
50+
51+
SetStringProperty(name string, value string) JMSException
52+
GetStringProperty(name string) *string
5053
}

messageproperties_test.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright (c) IBM Corporation 2021
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
package main
11+
12+
import (
13+
"testing"
14+
15+
"github.com/ibm-messaging/mq-golang-jms20/jms20subset"
16+
"github.com/ibm-messaging/mq-golang-jms20/mqjms"
17+
"github.com/stretchr/testify/assert"
18+
)
19+
20+
/*
21+
* mq-golang: SetMP, DltMP, InqMP
22+
* https://github.com/ibm-messaging/mq-golang/blob/95e9b8b09a1fc167747de7d066c49adb86e14dda/ibmmq/mqi.go#L1080
23+
*
24+
* mq-golang sample application to set properties
25+
* https://github.com/ibm-messaging/mq-golang/blob/master/samples/amqsprop.go#L49
26+
*
27+
* JMS: SetStringProperty, GetStringProperty,
28+
* https://github.com/eclipse-ee4j/messaging/blob/master/api/src/main/java/jakarta/jms/Message.java#L1119
29+
*
30+
* JMS: PropertyExists, ClearProperties, GetPropertyNames
31+
*/
32+
33+
/*
34+
* Test the creation of a text message with a string property.
35+
*/
36+
func TestStringPropertyTextMsg(t *testing.T) {
37+
38+
// Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory
39+
cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles()
40+
assert.Nil(t, cfErr)
41+
42+
// Creates a connection to the queue manager, using defer to close it automatically
43+
// at the end of the function (if it was created successfully)
44+
context, ctxErr := cf.CreateContext()
45+
assert.Nil(t, ctxErr)
46+
if context != nil {
47+
defer context.Close()
48+
}
49+
50+
// Create a TextMessage and check that we can populate it
51+
msgBody := "RequestMsg"
52+
txtMsg := context.CreateTextMessage()
53+
txtMsg.SetText(msgBody)
54+
55+
propName := "myProperty"
56+
propValue := "myValue"
57+
58+
// Test the empty value before the property is set.
59+
// TODO - it would be nicer if this was nil rather than empty string, however it
60+
// doesn't look like that is supported by the mq-golang library itself.
61+
assert.Equal(t, "", *txtMsg.GetStringProperty(propName))
62+
63+
// Test the ability to set properties before the message is sent.
64+
retErr := txtMsg.SetStringProperty(propName, propValue)
65+
assert.Nil(t, retErr)
66+
assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName))
67+
assert.Equal(t, msgBody, *txtMsg.GetText())
68+
69+
// Set up objects for send/receive
70+
queue := context.CreateQueue("DEV.QUEUE.1")
71+
consumer, errCons := context.CreateConsumer(queue)
72+
if consumer != nil {
73+
defer consumer.Close()
74+
}
75+
assert.Nil(t, errCons)
76+
77+
// Now send the message and get it back again, to check that it roundtripped.
78+
errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, txtMsg)
79+
assert.Nil(t, errSend)
80+
81+
rcvMsg, errRvc := consumer.ReceiveNoWait()
82+
assert.Nil(t, errRvc)
83+
assert.NotNil(t, rcvMsg)
84+
85+
switch msg := rcvMsg.(type) {
86+
case jms20subset.TextMessage:
87+
assert.Equal(t, msgBody, *msg.GetText())
88+
default:
89+
assert.Fail(t, "Got something other than a text message")
90+
}
91+
92+
// Check property is available on received message.
93+
assert.Equal(t, propValue, *rcvMsg.GetStringProperty(propName))
94+
95+
}
96+
97+
/*
98+
* Test send and receive of a text message with a string property and no content.
99+
*/
100+
func TestStringPropertyTextMessageNilBody(t *testing.T) {
101+
102+
// Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory
103+
cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles()
104+
assert.Nil(t, cfErr)
105+
106+
// Creates a connection to the queue manager, using defer to close it automatically
107+
// at the end of the function (if it was created successfully)
108+
context, ctxErr := cf.CreateContext()
109+
assert.Nil(t, ctxErr)
110+
if context != nil {
111+
defer context.Close()
112+
}
113+
114+
// Create a TextMessage, and check it has nil content.
115+
msg := context.CreateTextMessage()
116+
assert.Nil(t, msg.GetText())
117+
118+
propName := "myProperty2"
119+
propValue := "myValue2"
120+
retErr := msg.SetStringProperty(propName, propValue)
121+
assert.Nil(t, retErr)
122+
123+
// Now send the message and get it back again, to check that it roundtripped.
124+
queue := context.CreateQueue("DEV.QUEUE.1")
125+
errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, msg)
126+
assert.Nil(t, errSend)
127+
128+
consumer, errCons := context.CreateConsumer(queue)
129+
if consumer != nil {
130+
defer consumer.Close()
131+
}
132+
assert.Nil(t, errCons)
133+
134+
rcvMsg, errRvc := consumer.ReceiveNoWait()
135+
assert.Nil(t, errRvc)
136+
assert.NotNil(t, rcvMsg)
137+
138+
switch msg := rcvMsg.(type) {
139+
case jms20subset.TextMessage:
140+
assert.Nil(t, msg.GetText())
141+
default:
142+
assert.Fail(t, "Got something other than a text message")
143+
}
144+
145+
// Check property is available on received message.
146+
assert.Equal(t, propValue, *rcvMsg.GetStringProperty(propName))
147+
148+
}
149+
150+
/*
151+
* Test the behaviour for send/receive of a text message with an empty string
152+
* body. It's difficult to distinguish nil and empty string so we are expecting
153+
* that the received message will contain a nil body.
154+
*/
155+
func TestStringPropertyTextMessageEmptyBody(t *testing.T) {
156+
157+
// Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory
158+
cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles()
159+
assert.Nil(t, cfErr)
160+
161+
// Creates a connection to the queue manager, using defer to close it automatically
162+
// at the end of the function (if it was created successfully)
163+
context, ctxErr := cf.CreateContext()
164+
assert.Nil(t, ctxErr)
165+
if context != nil {
166+
defer context.Close()
167+
}
168+
169+
// Create a TextMessage
170+
msg := context.CreateTextMessageWithString("")
171+
assert.Equal(t, "", *msg.GetText())
172+
173+
propAName := "myPropertyA"
174+
propAValue := "myValueA"
175+
retErr := msg.SetStringProperty(propAName, propAValue)
176+
assert.Nil(t, retErr)
177+
178+
propBName := "myPropertyB"
179+
propBValue := "myValueB"
180+
retErr = msg.SetStringProperty(propBName, propBValue)
181+
assert.Nil(t, retErr)
182+
183+
// Now send the message and get it back again.
184+
queue := context.CreateQueue("DEV.QUEUE.1")
185+
errSend := context.CreateProducer().Send(queue, msg)
186+
assert.Nil(t, errSend)
187+
188+
consumer, errCons := context.CreateConsumer(queue)
189+
assert.Nil(t, errCons)
190+
if consumer != nil {
191+
defer consumer.Close()
192+
}
193+
194+
rcvMsg, errRvc := consumer.ReceiveNoWait()
195+
assert.Nil(t, errRvc)
196+
assert.NotNil(t, rcvMsg)
197+
198+
switch msg := rcvMsg.(type) {
199+
case jms20subset.TextMessage:
200+
201+
// It's difficult to distinguish between empty string and no string (nil)
202+
// so we settle for giving back a nil, so that messages containing empty
203+
// string are equivalent to messages containing no string at all.
204+
assert.Nil(t, msg.GetText())
205+
default:
206+
assert.Fail(t, "Got something other than a text message")
207+
}
208+
209+
// Check property is available on received message.
210+
assert.Equal(t, propAValue, *rcvMsg.GetStringProperty(propAName))
211+
assert.Equal(t, propBValue, *rcvMsg.GetStringProperty(propBName))
212+
213+
}

mqjms/ConsumerImpl.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ func (consumer ConsumerImpl) receiveInternal(gmo *ibmmq.MQGMO) (jms20subset.Mess
8181
gmo.Options |= syncpointSetting
8282
gmo.Options |= ibmmq.MQGMO_FAIL_IF_QUIESCING
8383

84+
// Include the message properties in the msgHandle
85+
gmo.Options |= ibmmq.MQGMO_PROPERTIES_IN_HANDLE
86+
cmho := ibmmq.NewMQCMHO()
87+
thisMsgHandle, _ := consumer.ctx.qMgr.CrtMH(cmho)
88+
gmo.MsgHandle = thisMsgHandle
89+
8490
// Apply the selector if one has been specified in the Consumer
8591
err := applySelector(consumer.selector, getmqmd, gmo)
8692
if err != nil {
@@ -106,8 +112,11 @@ func (consumer ConsumerImpl) receiveInternal(gmo *ibmmq.MQGMO) (jms20subset.Mess
106112
}
107113

108114
msg = &TextMessageImpl{
109-
bodyStr: msgBodyStr,
110-
MessageImpl: MessageImpl{mqmd: getmqmd},
115+
bodyStr: msgBodyStr,
116+
MessageImpl: MessageImpl{
117+
mqmd: getmqmd,
118+
msgHandle: &thisMsgHandle,
119+
},
111120
}
112121

113122
} else {
@@ -120,8 +129,11 @@ func (consumer ConsumerImpl) receiveInternal(gmo *ibmmq.MQGMO) (jms20subset.Mess
120129

121130
// Not a string, so fall back to BytesMessage
122131
msg = &BytesMessageImpl{
123-
bodyBytes: &trimmedBuffer,
124-
MessageImpl: MessageImpl{mqmd: getmqmd},
132+
bodyBytes: &trimmedBuffer,
133+
MessageImpl: MessageImpl{
134+
mqmd: getmqmd,
135+
msgHandle: &thisMsgHandle,
136+
},
125137
}
126138
}
127139

mqjms/ContextImpl.go

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,27 +114,71 @@ func (ctx ContextImpl) CreateConsumerWithSelector(dest jms20subset.Destination,
114114

115115
// CreateTextMessage is a JMS standard mechanism for creating a TextMessage.
116116
func (ctx ContextImpl) CreateTextMessage() jms20subset.TextMessage {
117-
return &TextMessageImpl{}
117+
118+
var bodyStr *string
119+
thisMsgHandle := createMsgHandle(ctx.qMgr)
120+
121+
return &TextMessageImpl{
122+
bodyStr: bodyStr,
123+
MessageImpl: MessageImpl{
124+
msgHandle: &thisMsgHandle,
125+
},
126+
}
127+
}
128+
129+
// createMsgHandle creates a new message handle object that can be used to
130+
// store and retrieve message properties.
131+
func createMsgHandle(qMgr ibmmq.MQQueueManager) ibmmq.MQMessageHandle {
132+
133+
// TODO - error handling on CrtMH
134+
cmho := ibmmq.NewMQCMHO()
135+
thisMsgHandle, _ := qMgr.CrtMH(cmho)
136+
137+
return thisMsgHandle
138+
118139
}
119140

120141
// CreateTextMessageWithString is a JMS standard mechanism for creating a TextMessage
121142
// and initialise it with the chosen text string.
122143
func (ctx ContextImpl) CreateTextMessageWithString(txt string) jms20subset.TextMessage {
123-
msg := TextMessageImpl{}
124-
msg.SetText(txt)
125-
return &msg
144+
145+
thisMsgHandle := createMsgHandle(ctx.qMgr)
146+
147+
msg := &TextMessageImpl{
148+
bodyStr: &txt,
149+
MessageImpl: MessageImpl{
150+
msgHandle: &thisMsgHandle,
151+
},
152+
}
153+
154+
return msg
126155
}
127156

128157
// CreateBytesMessage is a JMS standard mechanism for creating a BytesMessage.
129158
func (ctx ContextImpl) CreateBytesMessage() jms20subset.BytesMessage {
130-
return &BytesMessageImpl{}
159+
160+
var thisBodyBytes *[]byte
161+
thisMsgHandle := createMsgHandle(ctx.qMgr)
162+
163+
return &BytesMessageImpl{
164+
bodyBytes: thisBodyBytes,
165+
MessageImpl: MessageImpl{
166+
msgHandle: &thisMsgHandle,
167+
},
168+
}
131169
}
132170

133171
// CreateBytesMessageWithBytes is a JMS standard mechanism for creating a BytesMessage.
134172
func (ctx ContextImpl) CreateBytesMessageWithBytes(bytes []byte) jms20subset.BytesMessage {
135-
msg := BytesMessageImpl{}
136-
msg.WriteBytes(bytes)
137-
return &msg
173+
174+
thisMsgHandle := createMsgHandle(ctx.qMgr)
175+
176+
return &BytesMessageImpl{
177+
bodyBytes: &bytes,
178+
MessageImpl: MessageImpl{
179+
msgHandle: &thisMsgHandle,
180+
},
181+
}
138182
}
139183

140184
// Commit confirms all messages that were sent under this transaction.

0 commit comments

Comments
 (0)