From b263de6c0a3bd9fe968f4cdfac10e246bf7d5101 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Thu, 16 Dec 2021 21:10:22 +0000 Subject: [PATCH 01/12] Support for message string properties - #39 --- jms20subset/Message.go | 3 + messageproperties_test.go | 213 ++++++++++++++++++++++++++++++++++++++ mqjms/ConsumerImpl.go | 20 +++- mqjms/ContextImpl.go | 60 +++++++++-- mqjms/MessageImpl.go | 40 ++++++- mqjms/ProducerImpl.go | 6 ++ 6 files changed, 328 insertions(+), 14 deletions(-) create mode 100644 messageproperties_test.go diff --git a/jms20subset/Message.go b/jms20subset/Message.go index c92a871..3d48c29 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -47,4 +47,7 @@ type Message interface { // Typical values returned by this method include // jms20subset.DeliveryMode_PERSISTENT and jms20subset.DeliveryMode_NON_PERSISTENT GetJMSDeliveryMode() int + + SetStringProperty(name string, value string) JMSException + GetStringProperty(name string) *string } diff --git a/messageproperties_test.go b/messageproperties_test.go new file mode 100644 index 0000000..b5c3aef --- /dev/null +++ b/messageproperties_test.go @@ -0,0 +1,213 @@ +/* + * Copyright (c) IBM Corporation 2021 + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package main + +import ( + "testing" + + "github.com/ibm-messaging/mq-golang-jms20/jms20subset" + "github.com/ibm-messaging/mq-golang-jms20/mqjms" + "github.com/stretchr/testify/assert" +) + +/* + * mq-golang: SetMP, DltMP, InqMP + * https://github.com/ibm-messaging/mq-golang/blob/95e9b8b09a1fc167747de7d066c49adb86e14dda/ibmmq/mqi.go#L1080 + * + * mq-golang sample application to set properties + * https://github.com/ibm-messaging/mq-golang/blob/master/samples/amqsprop.go#L49 + * + * JMS: SetStringProperty, GetStringProperty, + * https://github.com/eclipse-ee4j/messaging/blob/master/api/src/main/java/jakarta/jms/Message.java#L1119 + * + * JMS: PropertyExists, ClearProperties, GetPropertyNames + */ + +/* + * Test the creation of a text message with a string property. + */ +func TestStringPropertyTextMsg(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage and check that we can populate it + msgBody := "RequestMsg" + txtMsg := context.CreateTextMessage() + txtMsg.SetText(msgBody) + + propName := "myProperty" + propValue := "myValue" + + // Test the empty value before the property is set. + // TODO - it would be nicer if this was nil rather than empty string, however it + // doesn't look like that is supported by the mq-golang library itself. + assert.Equal(t, "", *txtMsg.GetStringProperty(propName)) + + // Test the ability to set properties before the message is sent. + retErr := txtMsg.SetStringProperty(propName, propValue) + assert.Nil(t, retErr) + assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + assert.Equal(t, msgBody, *txtMsg.GetText()) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, txtMsg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + assert.Equal(t, msgBody, *msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + assert.Equal(t, propValue, *rcvMsg.GetStringProperty(propName)) + +} + +/* + * Test send and receive of a text message with a string property and no content. + */ +func TestStringPropertyTextMessageNilBody(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage, and check it has nil content. + msg := context.CreateTextMessage() + assert.Nil(t, msg.GetText()) + + propName := "myProperty2" + propValue := "myValue2" + retErr := msg.SetStringProperty(propName, propValue) + assert.Nil(t, retErr) + + // Now send the message and get it back again, to check that it roundtripped. + queue := context.CreateQueue("DEV.QUEUE.1") + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, msg) + assert.Nil(t, errSend) + + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + assert.Nil(t, msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + assert.Equal(t, propValue, *rcvMsg.GetStringProperty(propName)) + +} + +/* + * Test the behaviour for send/receive of a text message with an empty string + * body. It's difficult to distinguish nil and empty string so we are expecting + * that the received message will contain a nil body. + */ +func TestStringPropertyTextMessageEmptyBody(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage + msg := context.CreateTextMessageWithString("") + assert.Equal(t, "", *msg.GetText()) + + propAName := "myPropertyA" + propAValue := "myValueA" + retErr := msg.SetStringProperty(propAName, propAValue) + assert.Nil(t, retErr) + + propBName := "myPropertyB" + propBValue := "myValueB" + retErr = msg.SetStringProperty(propBName, propBValue) + assert.Nil(t, retErr) + + // Now send the message and get it back again. + queue := context.CreateQueue("DEV.QUEUE.1") + errSend := context.CreateProducer().Send(queue, msg) + assert.Nil(t, errSend) + + consumer, errCons := context.CreateConsumer(queue) + assert.Nil(t, errCons) + if consumer != nil { + defer consumer.Close() + } + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + + // It's difficult to distinguish between empty string and no string (nil) + // so we settle for giving back a nil, so that messages containing empty + // string are equivalent to messages containing no string at all. + assert.Nil(t, msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + assert.Equal(t, propAValue, *rcvMsg.GetStringProperty(propAName)) + assert.Equal(t, propBValue, *rcvMsg.GetStringProperty(propBName)) + +} diff --git a/mqjms/ConsumerImpl.go b/mqjms/ConsumerImpl.go index 33635f2..d176304 100644 --- a/mqjms/ConsumerImpl.go +++ b/mqjms/ConsumerImpl.go @@ -81,6 +81,12 @@ func (consumer ConsumerImpl) receiveInternal(gmo *ibmmq.MQGMO) (jms20subset.Mess gmo.Options |= syncpointSetting gmo.Options |= ibmmq.MQGMO_FAIL_IF_QUIESCING + // Include the message properties in the msgHandle + gmo.Options |= ibmmq.MQGMO_PROPERTIES_IN_HANDLE + cmho := ibmmq.NewMQCMHO() + thisMsgHandle, _ := consumer.ctx.qMgr.CrtMH(cmho) + gmo.MsgHandle = thisMsgHandle + // Apply the selector if one has been specified in the Consumer err := applySelector(consumer.selector, getmqmd, gmo) if err != nil { @@ -106,8 +112,11 @@ func (consumer ConsumerImpl) receiveInternal(gmo *ibmmq.MQGMO) (jms20subset.Mess } msg = &TextMessageImpl{ - bodyStr: msgBodyStr, - MessageImpl: MessageImpl{mqmd: getmqmd}, + bodyStr: msgBodyStr, + MessageImpl: MessageImpl{ + mqmd: getmqmd, + msgHandle: &thisMsgHandle, + }, } } else { @@ -120,8 +129,11 @@ func (consumer ConsumerImpl) receiveInternal(gmo *ibmmq.MQGMO) (jms20subset.Mess // Not a string, so fall back to BytesMessage msg = &BytesMessageImpl{ - bodyBytes: &trimmedBuffer, - MessageImpl: MessageImpl{mqmd: getmqmd}, + bodyBytes: &trimmedBuffer, + MessageImpl: MessageImpl{ + mqmd: getmqmd, + msgHandle: &thisMsgHandle, + }, } } diff --git a/mqjms/ContextImpl.go b/mqjms/ContextImpl.go index eb614f6..e041c15 100644 --- a/mqjms/ContextImpl.go +++ b/mqjms/ContextImpl.go @@ -114,27 +114,71 @@ func (ctx ContextImpl) CreateConsumerWithSelector(dest jms20subset.Destination, // CreateTextMessage is a JMS standard mechanism for creating a TextMessage. func (ctx ContextImpl) CreateTextMessage() jms20subset.TextMessage { - return &TextMessageImpl{} + + var bodyStr *string + thisMsgHandle := createMsgHandle(ctx.qMgr) + + return &TextMessageImpl{ + bodyStr: bodyStr, + MessageImpl: MessageImpl{ + msgHandle: &thisMsgHandle, + }, + } +} + +// createMsgHandle creates a new message handle object that can be used to +// store and retrieve message properties. +func createMsgHandle(qMgr ibmmq.MQQueueManager) ibmmq.MQMessageHandle { + + // TODO - error handling on CrtMH + cmho := ibmmq.NewMQCMHO() + thisMsgHandle, _ := qMgr.CrtMH(cmho) + + return thisMsgHandle + } // CreateTextMessageWithString is a JMS standard mechanism for creating a TextMessage // and initialise it with the chosen text string. func (ctx ContextImpl) CreateTextMessageWithString(txt string) jms20subset.TextMessage { - msg := TextMessageImpl{} - msg.SetText(txt) - return &msg + + thisMsgHandle := createMsgHandle(ctx.qMgr) + + msg := &TextMessageImpl{ + bodyStr: &txt, + MessageImpl: MessageImpl{ + msgHandle: &thisMsgHandle, + }, + } + + return msg } // CreateBytesMessage is a JMS standard mechanism for creating a BytesMessage. func (ctx ContextImpl) CreateBytesMessage() jms20subset.BytesMessage { - return &BytesMessageImpl{} + + var thisBodyBytes *[]byte + thisMsgHandle := createMsgHandle(ctx.qMgr) + + return &BytesMessageImpl{ + bodyBytes: thisBodyBytes, + MessageImpl: MessageImpl{ + msgHandle: &thisMsgHandle, + }, + } } // CreateBytesMessageWithBytes is a JMS standard mechanism for creating a BytesMessage. func (ctx ContextImpl) CreateBytesMessageWithBytes(bytes []byte) jms20subset.BytesMessage { - msg := BytesMessageImpl{} - msg.WriteBytes(bytes) - return &msg + + thisMsgHandle := createMsgHandle(ctx.qMgr) + + return &BytesMessageImpl{ + bodyBytes: &bytes, + MessageImpl: MessageImpl{ + msgHandle: &thisMsgHandle, + }, + } } // Commit confirms all messages that were sent under this transaction. diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index bf012ed..68300e4 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -24,7 +24,8 @@ import ( // MessageImpl contains the IBM MQ specific attributes that are // common to all types of message. type MessageImpl struct { - mqmd *ibmmq.MQMD + mqmd *ibmmq.MQMD + msgHandle *ibmmq.MQMessageHandle } // GetJMSDeliveryMode extracts the persistence setting from this message @@ -259,7 +260,7 @@ func (msg *MessageImpl) GetJMSTimestamp() int64 { // GetApplName retrieves the PutApplName field from the MQMD. // This method is not exposed on the JMS style interface and is mainly for testing purposes. -func (msg MessageImpl) GetApplName() string { +func (msg *MessageImpl) GetApplName() string { applName := "" // Note that if there is no MQMD then there is no correlID stored. @@ -272,3 +273,38 @@ func (msg MessageImpl) GetApplName() string { return applName } + +func (msg *MessageImpl) SetStringProperty(name string, value string) jms20subset.JMSException { + var retErr jms20subset.JMSException + + smpo := ibmmq.NewMQSMPO() + pd := ibmmq.NewMQPD() + + linkedErr := msg.msgHandle.SetMP(smpo, name, pd, value) + + if linkedErr != nil { + rcInt := int(linkedErr.(*ibmmq.MQReturn).MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, linkedErr) + } + + return retErr +} + +func (msg *MessageImpl) GetStringProperty(name string) *string { + + var valueStr string + impo := ibmmq.NewMQIMPO() + pd := ibmmq.NewMQPD() + + _, value, _ := msg.msgHandle.InqMP(impo, pd, name) + + switch valueTyped := value.(type) { + case string: + valueStr = valueTyped + default: + // TODO - other conversions + } + return &valueStr +} diff --git a/mqjms/ProducerImpl.go b/mqjms/ProducerImpl.go index d2ce3fb..c29888c 100644 --- a/mqjms/ProducerImpl.go +++ b/mqjms/ProducerImpl.go @@ -103,6 +103,9 @@ func (producer ProducerImpl) Send(dest jms20subset.Destination, msg jms20subset. putmqmd = typedMsg.mqmd } + // Pass up the handle containing the message properties + pmo.OriginalMsgHandle = *typedMsg.msgHandle + // Store the Put MQMD so that we can later retrieve "out" fields like MsgId typedMsg.mqmd = putmqmd @@ -121,6 +124,9 @@ func (producer ProducerImpl) Send(dest jms20subset.Destination, msg jms20subset. putmqmd = typedMsg.mqmd } + // Pass up the handle containing the message properties + pmo.OriginalMsgHandle = *typedMsg.msgHandle + // Store the Put MQMD so that we can later retrieve "out" fields like MsgId typedMsg.mqmd = putmqmd From c848f77f23bf11d413bd6053d34efd20a233518e Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Mon, 20 Dec 2021 18:44:25 +0000 Subject: [PATCH 02/12] Detect string properties that are not set - #39 --- jms20subset/Message.go | 4 +- messageproperties_test.go | 36 ++++++++++++++---- mqjms/MessageImpl.go | 79 ++++++++++++++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 18 deletions(-) diff --git a/jms20subset/Message.go b/jms20subset/Message.go index 3d48c29..b13edb3 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -48,6 +48,8 @@ type Message interface { // jms20subset.DeliveryMode_PERSISTENT and jms20subset.DeliveryMode_NON_PERSISTENT GetJMSDeliveryMode() int - SetStringProperty(name string, value string) JMSException + SetStringProperty(name string, value *string) JMSException + + // Returns string property, or nil if the property is not set. GetStringProperty(name string) *string } diff --git a/messageproperties_test.go b/messageproperties_test.go index b5c3aef..431dd94 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -56,16 +56,31 @@ func TestStringPropertyTextMsg(t *testing.T) { propValue := "myValue" // Test the empty value before the property is set. - // TODO - it would be nicer if this was nil rather than empty string, however it - // doesn't look like that is supported by the mq-golang library itself. - assert.Equal(t, "", *txtMsg.GetStringProperty(propName)) + assert.Nil(t, txtMsg.GetStringProperty(propName)) // Test the ability to set properties before the message is sent. - retErr := txtMsg.SetStringProperty(propName, propValue) + retErr := txtMsg.SetStringProperty(propName, &propValue) assert.Nil(t, retErr) assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) assert.Equal(t, msgBody, *txtMsg.GetText()) + // Send an empty string property as well + emptyPropName := "myEmptyString" + emptyPropValue := "" + retErr = txtMsg.SetStringProperty(emptyPropName, &emptyPropValue) + assert.Nil(t, retErr) + assert.Equal(t, emptyPropValue, *txtMsg.GetStringProperty(emptyPropName)) + + // Set a property then try to unset it by setting to nil + unsetPropName := "mySendThenRemovedString" + unsetPropValue := "someValueThatWillBeOverwritten" + retErr = txtMsg.SetStringProperty(unsetPropName, &unsetPropValue) + assert.Nil(t, retErr) + assert.Equal(t, unsetPropValue, *txtMsg.GetStringProperty(unsetPropName)) + retErr = txtMsg.SetStringProperty(unsetPropName, nil) + assert.Nil(t, retErr) + assert.Nil(t, txtMsg.GetStringProperty(unsetPropName)) + // Set up objects for send/receive queue := context.CreateQueue("DEV.QUEUE.1") consumer, errCons := context.CreateConsumer(queue) @@ -92,6 +107,13 @@ func TestStringPropertyTextMsg(t *testing.T) { // Check property is available on received message. assert.Equal(t, propValue, *rcvMsg.GetStringProperty(propName)) + // Check the empty string property. + assert.Equal(t, emptyPropValue, *rcvMsg.GetStringProperty(emptyPropName)) + + // Properties that are not set should return nil + assert.Nil(t, rcvMsg.GetStringProperty("nonExistentProperty")) + assert.Nil(t, rcvMsg.GetStringProperty(unsetPropName)) + } /* @@ -117,7 +139,7 @@ func TestStringPropertyTextMessageNilBody(t *testing.T) { propName := "myProperty2" propValue := "myValue2" - retErr := msg.SetStringProperty(propName, propValue) + retErr := msg.SetStringProperty(propName, &propValue) assert.Nil(t, retErr) // Now send the message and get it back again, to check that it roundtripped. @@ -172,12 +194,12 @@ func TestStringPropertyTextMessageEmptyBody(t *testing.T) { propAName := "myPropertyA" propAValue := "myValueA" - retErr := msg.SetStringProperty(propAName, propAValue) + retErr := msg.SetStringProperty(propAName, &propAValue) assert.Nil(t, retErr) propBName := "myPropertyB" propBValue := "myValueB" - retErr = msg.SetStringProperty(propBName, propBValue) + retErr = msg.SetStringProperty(propBName, &propBValue) assert.Nil(t, retErr) // Now send the message and get it back again. diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 68300e4..4c546df 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -274,13 +274,26 @@ func (msg *MessageImpl) GetApplName() string { return applName } -func (msg *MessageImpl) SetStringProperty(name string, value string) jms20subset.JMSException { +func (msg *MessageImpl) SetStringProperty(name string, value *string) jms20subset.JMSException { var retErr jms20subset.JMSException - smpo := ibmmq.NewMQSMPO() - pd := ibmmq.NewMQPD() + var linkedErr error + + if value != nil { + // Looking to set a value + var valueStr string + valueStr = *value + + smpo := ibmmq.NewMQSMPO() + pd := ibmmq.NewMQPD() - linkedErr := msg.msgHandle.SetMP(smpo, name, pd, value) + linkedErr = msg.msgHandle.SetMP(smpo, name, pd, valueStr) + } else { + // Looking to unset a value + dmpo := ibmmq.NewMQDMPO() + + linkedErr = msg.msgHandle.DltMP(dmpo, name) + } if linkedErr != nil { rcInt := int(linkedErr.(*ibmmq.MQReturn).MQRC) @@ -298,13 +311,59 @@ func (msg *MessageImpl) GetStringProperty(name string) *string { impo := ibmmq.NewMQIMPO() pd := ibmmq.NewMQPD() - _, value, _ := msg.msgHandle.InqMP(impo, pd, name) + _, value, err := msg.msgHandle.InqMP(impo, pd, name) - switch valueTyped := value.(type) { - case string: - valueStr = valueTyped - default: - // TODO - other conversions + if err == nil { + switch valueTyped := value.(type) { + case string: + valueStr = valueTyped + default: + // TODO - other conversions + } + } else { + + mqret := err.(*ibmmq.MQReturn) + if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { + // This indicates that the requested property does not exist. + // valueStr will remain with its default value of nil + return nil + } else { + // Err was not nil + fmt.Println(err) // TODO - finish error handling + } } return &valueStr } + +func (msg *MessageImpl) propertyExists(name string) bool { + + impo := ibmmq.NewMQIMPO() + pd := ibmmq.NewMQPD() + + impo.Options = ibmmq.MQIMPO_CONVERT_VALUE | ibmmq.MQIMPO_INQ_FIRST + for propsToRead := true; propsToRead; { + + gotName, gotValue, err := msg.msgHandle.InqMP(impo, pd, "%") + impo.Options = ibmmq.MQIMPO_CONVERT_VALUE | ibmmq.MQIMPO_INQ_NEXT + if err != nil { + mqret := err.(*ibmmq.MQReturn) + if mqret.MQRC != ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { + fmt.Println(err) + } else { + // Read all properties + return false + } + + propsToRead = false + } else if gotName == name { + // Found the matching property name (shortcut) + return true + } else { + fmt.Printf("no property match to '%s' - gotName: '%s' gotValue '%v' \n", name, gotName, gotValue) + } + + } + + // Went through all properties and didn't find a match + return false +} From 86ba88c8eb71d815ca83689c0db9ee381fe81b21 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Tue, 21 Dec 2021 19:45:11 +0000 Subject: [PATCH 03/12] PropertyExists, ClearProperties and GetPropertyNames - #39 --- jms20subset/Message.go | 11 ++ messageproperties_test.go | 310 ++++++++++++++++++++++++++++++++++++++ mqjms/MessageImpl.go | 83 ++++++++-- 3 files changed, 393 insertions(+), 11 deletions(-) diff --git a/jms20subset/Message.go b/jms20subset/Message.go index b13edb3..520d4e6 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -48,8 +48,19 @@ type Message interface { // jms20subset.DeliveryMode_PERSISTENT and jms20subset.DeliveryMode_NON_PERSISTENT GetJMSDeliveryMode() int + // TODO documentation SetStringProperty(name string, value *string) JMSException + // TODO documentation // Returns string property, or nil if the property is not set. GetStringProperty(name string) *string + + // TODO documentation + PropertyExists(name string) (bool, JMSException) + + // TODO documentation + GetPropertyNames() ([]string, JMSException) + + // TODO documentation + ClearProperties() JMSException } diff --git a/messageproperties_test.go b/messageproperties_test.go index 431dd94..9e8d624 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -28,6 +28,9 @@ import ( * https://github.com/eclipse-ee4j/messaging/blob/master/api/src/main/java/jakarta/jms/Message.java#L1119 * * JMS: PropertyExists, ClearProperties, GetPropertyNames + * boolean propertyExists(String name) throws JMSException; + * void clearProperties() throws JMSException; + * Enumeration getPropertyNames() throws JMSException; */ /* @@ -116,6 +119,313 @@ func TestStringPropertyTextMsg(t *testing.T) { } +/* + * Test the Exists and GetNames functions for message properties + */ +func TestPropertyExistsGetNames(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage and check that we can populate it + msgBody := "ExistsGetNames-test" + txtMsg := context.CreateTextMessage() + txtMsg.SetText(msgBody) + + propName := "myProperty" + propValue := "myValue" + + // Test the empty value before the property is set. + assert.Nil(t, txtMsg.GetStringProperty(propName)) + propExists, propErr := txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + allPropNames, getNamesErr := txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 0, len(allPropNames)) + + // Test the ability to set properties before the message is sent. + retErr := txtMsg.SetStringProperty(propName, &propValue) + assert.Nil(t, retErr) + assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 1, len(allPropNames)) + assert.Equal(t, propName, allPropNames[0]) + + propName2 := "myPropertyTwo" + propValue2 := "myValueTwo" + retErr = txtMsg.SetStringProperty(propName2, &propValue2) + assert.Nil(t, retErr) + assert.Equal(t, propValue2, *txtMsg.GetStringProperty(propName2)) + propExists, propErr = txtMsg.PropertyExists(propName2) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + // Check the first property again to be sure + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 2, len(allPropNames)) + assert.Equal(t, propName, allPropNames[0]) + assert.Equal(t, propName2, allPropNames[1]) + + // Set a property then try to unset it by setting to nil + unsetPropName := "mySendThenRemovedString" + unsetPropValue := "someValueThatWillBeOverwritten" + retErr = txtMsg.SetStringProperty(unsetPropName, &unsetPropValue) + assert.Nil(t, retErr) + assert.Equal(t, unsetPropValue, *txtMsg.GetStringProperty(unsetPropName)) + propExists, propErr = txtMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 3, len(allPropNames)) + retErr = txtMsg.SetStringProperty(unsetPropName, nil) + assert.Nil(t, retErr) + assert.Nil(t, txtMsg.GetStringProperty(unsetPropName)) + propExists, propErr = txtMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.False(t, propExists) + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 2, len(allPropNames)) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, txtMsg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + assert.Equal(t, msgBody, *msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + propExists, propErr = rcvMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + propExists, propErr = rcvMsg.PropertyExists(propName2) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + // Check GetPropertyNames + allPropNames, getNamesErr = rcvMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 2, len(allPropNames)) + assert.Equal(t, propName, allPropNames[0]) + assert.Equal(t, propName2, allPropNames[1]) + + // Properties that are not set should return nil + nonExistentPropName := "nonExistentProperty" + assert.Nil(t, rcvMsg.GetStringProperty(nonExistentPropName)) + propExists, propErr = rcvMsg.PropertyExists(nonExistentPropName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + // Check for the unset property + propExists, propErr = rcvMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.False(t, propExists) + +} + +/* + * Test the ClearProperties function for message properties + */ +func TestPropertyClearProperties(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage and check that we can populate it + msgBody := "ExistsClearProperties-test" + txtMsg := context.CreateTextMessage() + txtMsg.SetText(msgBody) + + propName := "myProperty" + propValue := "myValue" + + // Test the ability to set properties before the message is sent. + retErr := txtMsg.SetStringProperty(propName, &propValue) + assert.Nil(t, retErr) + assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + propExists, propErr := txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + allPropNames, getNamesErr := txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 1, len(allPropNames)) + assert.Equal(t, propName, allPropNames[0]) + + clearErr := txtMsg.ClearProperties() + assert.Nil(t, clearErr) + assert.Nil(t, txtMsg.GetStringProperty(propName)) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 0, len(allPropNames)) + + propName2 := "myPropertyTwo" + propValue2 := "myValueTwo" + + // Set multiple properties + retErr = txtMsg.SetStringProperty(propName, &propValue) + assert.Nil(t, retErr) + assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + retErr = txtMsg.SetStringProperty(propName2, &propValue2) + assert.Nil(t, retErr) + assert.Equal(t, propValue2, *txtMsg.GetStringProperty(propName2)) + propExists, propErr = txtMsg.PropertyExists(propName2) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + // Check the first property again to be sure + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 2, len(allPropNames)) + + // Set a property then try to unset it by setting to nil + unsetPropName := "mySendThenRemovedString" + unsetPropValue := "someValueThatWillBeOverwritten" + retErr = txtMsg.SetStringProperty(unsetPropName, &unsetPropValue) + assert.Nil(t, retErr) + assert.Equal(t, unsetPropValue, *txtMsg.GetStringProperty(unsetPropName)) + propExists, propErr = txtMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 3, len(allPropNames)) + assert.Equal(t, propName, allPropNames[0]) + assert.Equal(t, propName2, allPropNames[1]) + assert.Equal(t, unsetPropName, allPropNames[2]) + retErr = txtMsg.SetStringProperty(unsetPropName, nil) + assert.Nil(t, retErr) + assert.Nil(t, txtMsg.GetStringProperty(unsetPropName)) + propExists, propErr = txtMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.False(t, propExists) + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 2, len(allPropNames)) + assert.Equal(t, propName, allPropNames[0]) + assert.Equal(t, propName2, allPropNames[1]) + + clearErr = txtMsg.ClearProperties() + assert.Nil(t, clearErr) + assert.Nil(t, txtMsg.GetStringProperty(propName)) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + allPropNames, getNamesErr = txtMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 0, len(allPropNames)) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, txtMsg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + assert.Equal(t, msgBody, *msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + propExists, propErr = rcvMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + propExists, propErr = rcvMsg.PropertyExists(propName2) + assert.Nil(t, propErr) + assert.False(t, propExists) + + allPropNames, getNamesErr = rcvMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 0, len(allPropNames)) + + // Properties that are not set should return nil + nonExistentPropName := "nonExistentProperty" + assert.Nil(t, rcvMsg.GetStringProperty(nonExistentPropName)) + propExists, propErr = rcvMsg.PropertyExists(nonExistentPropName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + // Check for the unset property + propExists, propErr = rcvMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + // Finally try clearing everything on the received message + clearErr = rcvMsg.ClearProperties() + assert.Nil(t, clearErr) + assert.Nil(t, rcvMsg.GetStringProperty(propName)) + propExists, propErr = rcvMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + allPropNames, getNamesErr = rcvMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 0, len(allPropNames)) + +} + /* * Test send and receive of a text message with a string property and no content. */ diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 4c546df..ab9c340 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -274,6 +274,7 @@ func (msg *MessageImpl) GetApplName() string { return applName } +// TODO documentation func (msg *MessageImpl) SetStringProperty(name string, value *string) jms20subset.JMSException { var retErr jms20subset.JMSException @@ -305,6 +306,7 @@ func (msg *MessageImpl) SetStringProperty(name string, value *string) jms20subse return retErr } +// TODO documentation func (msg *MessageImpl) GetStringProperty(name string) *string { var valueStr string @@ -335,35 +337,94 @@ func (msg *MessageImpl) GetStringProperty(name string) *string { return &valueStr } -func (msg *MessageImpl) propertyExists(name string) bool { +// TODO documentation +func (msg *MessageImpl) PropertyExists(name string) (bool, jms20subset.JMSException) { + + found, _, retErr := msg.getPropertyInternal(name) + return found, retErr + +} + +// TODO documentation +func (msg *MessageImpl) GetPropertyNames() ([]string, jms20subset.JMSException) { + + _, propNames, retErr := msg.getPropertyInternal("") + return propNames, retErr +} + +// TODO documentation +// Two modes of operation; +// - supply non-empty name parameter to check whether that property exists +// - supply empty name parameter to get a []string of all property names +func (msg *MessageImpl) getPropertyInternal(name string) (bool, []string, jms20subset.JMSException) { impo := ibmmq.NewMQIMPO() pd := ibmmq.NewMQPD() + propNames := []string{} impo.Options = ibmmq.MQIMPO_CONVERT_VALUE | ibmmq.MQIMPO_INQ_FIRST for propsToRead := true; propsToRead; { - gotName, gotValue, err := msg.msgHandle.InqMP(impo, pd, "%") + gotName, _, err := msg.msgHandle.InqMP(impo, pd, "%") impo.Options = ibmmq.MQIMPO_CONVERT_VALUE | ibmmq.MQIMPO_INQ_NEXT + if err != nil { mqret := err.(*ibmmq.MQReturn) if mqret.MQRC != ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { - fmt.Println(err) + + rcInt := int(mqret.MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr := jms20subset.CreateJMSException(reason, errCode, mqret) + return false, nil, retErr + } else { - // Read all properties - return false + // Read all properties (property not available) + return false, propNames, nil } - propsToRead = false + } else if "" == name { + // We are looking to get back a list of all properties + propNames = append(propNames, gotName) + } else if gotName == name { - // Found the matching property name (shortcut) - return true - } else { - fmt.Printf("no property match to '%s' - gotName: '%s' gotValue '%v' \n", name, gotName, gotValue) + // We are just checking for the existence of this one property (shortcut) + return true, nil, nil } } // Went through all properties and didn't find a match - return false + return false, propNames, nil +} + +// TODO documentation +func (msg *MessageImpl) ClearProperties() jms20subset.JMSException { + + // Get the list of all property names, as we have to delete + // them individually + allPropNames, jmsErr := msg.GetPropertyNames() + + if jmsErr == nil { + + dmpo := ibmmq.NewMQDMPO() + + for _, propName := range allPropNames { + + // Delete this property + err := msg.msgHandle.DltMP(dmpo, propName) + + if err != nil { + rcInt := int(err.(*ibmmq.MQReturn).MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + jmsErr = jms20subset.CreateJMSException(reason, errCode, err) + break + } + } + + } + + return jmsErr + } From 46dbd19a7ec9f96dc2aae0bcb59196a2ea104928 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Tue, 21 Dec 2021 20:07:29 +0000 Subject: [PATCH 04/12] Documentation for Properties functions - #39 --- jms20subset/Message.go | 17 +++++++++++------ messageproperties_test.go | 4 ---- mqjms/MessageImpl.go | 30 ++++++++++++++++++------------ next-features.txt | 1 - 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/jms20subset/Message.go b/jms20subset/Message.go index 520d4e6..a6947ae 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -48,19 +48,24 @@ type Message interface { // jms20subset.DeliveryMode_PERSISTENT and jms20subset.DeliveryMode_NON_PERSISTENT GetJMSDeliveryMode() int - // TODO documentation + // SetStringProperty enables an application to set a string-type message property. + // + // value is *string which allows a nil value to be specified, to unset an individual + // property. SetStringProperty(name string, value *string) JMSException - // TODO documentation - // Returns string property, or nil if the property is not set. + // GetStringProperty returns the string value of a named message property. + // Returns nil if the named property is not set. GetStringProperty(name string) *string - // TODO documentation + // PropertyExists returns true if the named message property exists on this message. PropertyExists(name string) (bool, JMSException) - // TODO documentation + // GetPropertyNames returns a slice of strings containing the name of every message + // property on this message. + // Returns a zero length slice if no message properties are defined. GetPropertyNames() ([]string, JMSException) - // TODO documentation + // ClearProperties removes all message properties from this message. ClearProperties() JMSException } diff --git a/messageproperties_test.go b/messageproperties_test.go index 9e8d624..53cba39 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -27,10 +27,6 @@ import ( * JMS: SetStringProperty, GetStringProperty, * https://github.com/eclipse-ee4j/messaging/blob/master/api/src/main/java/jakarta/jms/Message.java#L1119 * - * JMS: PropertyExists, ClearProperties, GetPropertyNames - * boolean propertyExists(String name) throws JMSException; - * void clearProperties() throws JMSException; - * Enumeration getPropertyNames() throws JMSException; */ /* diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index ab9c340..cd623a0 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -274,7 +274,10 @@ func (msg *MessageImpl) GetApplName() string { return applName } -// TODO documentation +// SetStringProperty enables an application to set a string-type message property. +// +// value is *string which allows a nil value to be specified, to unset an individual +// property. func (msg *MessageImpl) SetStringProperty(name string, value *string) jms20subset.JMSException { var retErr jms20subset.JMSException @@ -306,7 +309,8 @@ func (msg *MessageImpl) SetStringProperty(name string, value *string) jms20subse return retErr } -// TODO documentation +// GetStringProperty returns the string value of a named message property. +// Returns nil if the named property is not set. func (msg *MessageImpl) GetStringProperty(name string) *string { var valueStr string @@ -337,26 +341,28 @@ func (msg *MessageImpl) GetStringProperty(name string) *string { return &valueStr } -// TODO documentation +// PropertyExists returns true if the named message property exists on this message. func (msg *MessageImpl) PropertyExists(name string) (bool, jms20subset.JMSException) { - found, _, retErr := msg.getPropertyInternal(name) + found, _, retErr := msg.getPropertiesInternal(name) return found, retErr } -// TODO documentation +// GetPropertyNames returns a slice of strings containing the name of every message +// property on this message. +// Returns a zero length slice if no message properties are defined. func (msg *MessageImpl) GetPropertyNames() ([]string, jms20subset.JMSException) { - _, propNames, retErr := msg.getPropertyInternal("") + _, propNames, retErr := msg.getPropertiesInternal("") return propNames, retErr } -// TODO documentation -// Two modes of operation; -// - supply non-empty name parameter to check whether that property exists -// - supply empty name parameter to get a []string of all property names -func (msg *MessageImpl) getPropertyInternal(name string) (bool, []string, jms20subset.JMSException) { +// getPropertiesInternal is an internal helper function that provides a largely +// identical implication for two application-facing functions; +// - PropertyExists supplies a non-empty name parameter to check whether that property exists +// - GetPropertyNames supplies an empty name parameter to get a []string of all property names +func (msg *MessageImpl) getPropertiesInternal(name string) (bool, []string, jms20subset.JMSException) { impo := ibmmq.NewMQIMPO() pd := ibmmq.NewMQPD() @@ -398,7 +404,7 @@ func (msg *MessageImpl) getPropertyInternal(name string) (bool, []string, jms20s return false, propNames, nil } -// TODO documentation +// ClearProperties removes all message properties from this message. func (msg *MessageImpl) ClearProperties() jms20subset.JMSException { // Get the list of all property names, as we have to delete diff --git a/next-features.txt b/next-features.txt index eb895aa..b9636e8 100644 --- a/next-features.txt +++ b/next-features.txt @@ -8,7 +8,6 @@ Not currently implemented: - MessageListener - SendToQmgr, ReplyToQmgr - Topics (pub/sub) -- Message Properties etc - Temporary destinations - Priority - Configurable option to auto-set the receive buffer length if the default 32kb is exceeded (less efficient that setting up front) From 7dbcecf1720a7f2e79c7b44ecfa2ff0940768f86 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Thu, 23 Dec 2021 20:56:17 +0000 Subject: [PATCH 05/12] Error handling on string properties - #39 --- jms20subset/Message.go | 2 +- messageproperties_test.go | 108 ++++++++++++++++++++++++++++---------- mqjms/ContextImpl.go | 11 +++- mqjms/MessageImpl.go | 13 +++-- 4 files changed, 100 insertions(+), 34 deletions(-) diff --git a/jms20subset/Message.go b/jms20subset/Message.go index a6947ae..748d533 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -56,7 +56,7 @@ type Message interface { // GetStringProperty returns the string value of a named message property. // Returns nil if the named property is not set. - GetStringProperty(name string) *string + GetStringProperty(name string) (*string, JMSException) // PropertyExists returns true if the named message property exists on this message. PropertyExists(name string) (bool, JMSException) diff --git a/messageproperties_test.go b/messageproperties_test.go index 53cba39..baf44a1 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -55,12 +55,16 @@ func TestStringPropertyTextMsg(t *testing.T) { propValue := "myValue" // Test the empty value before the property is set. - assert.Nil(t, txtMsg.GetStringProperty(propName)) + gotPropValue, propErr := txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) // Test the ability to set properties before the message is sent. retErr := txtMsg.SetStringProperty(propName, &propValue) assert.Nil(t, retErr) - assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + gotPropValue, propErr = txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, *gotPropValue) assert.Equal(t, msgBody, *txtMsg.GetText()) // Send an empty string property as well @@ -68,17 +72,23 @@ func TestStringPropertyTextMsg(t *testing.T) { emptyPropValue := "" retErr = txtMsg.SetStringProperty(emptyPropName, &emptyPropValue) assert.Nil(t, retErr) - assert.Equal(t, emptyPropValue, *txtMsg.GetStringProperty(emptyPropName)) + gotPropValue, propErr = txtMsg.GetStringProperty(emptyPropName) + assert.Nil(t, propErr) + assert.Equal(t, emptyPropValue, *gotPropValue) // Set a property then try to unset it by setting to nil unsetPropName := "mySendThenRemovedString" unsetPropValue := "someValueThatWillBeOverwritten" retErr = txtMsg.SetStringProperty(unsetPropName, &unsetPropValue) assert.Nil(t, retErr) - assert.Equal(t, unsetPropValue, *txtMsg.GetStringProperty(unsetPropName)) + gotPropValue, propErr = txtMsg.GetStringProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, unsetPropValue, *gotPropValue) retErr = txtMsg.SetStringProperty(unsetPropName, nil) assert.Nil(t, retErr) - assert.Nil(t, txtMsg.GetStringProperty(unsetPropName)) + gotPropValue, propErr = txtMsg.GetStringProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) // Set up objects for send/receive queue := context.CreateQueue("DEV.QUEUE.1") @@ -104,14 +114,22 @@ func TestStringPropertyTextMsg(t *testing.T) { } // Check property is available on received message. - assert.Equal(t, propValue, *rcvMsg.GetStringProperty(propName)) + gotPropValue, propErr = rcvMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, *gotPropValue) // Check the empty string property. - assert.Equal(t, emptyPropValue, *rcvMsg.GetStringProperty(emptyPropName)) + gotPropValue, propErr = rcvMsg.GetStringProperty(emptyPropName) + assert.Nil(t, propErr) + assert.Equal(t, emptyPropValue, *gotPropValue) // Properties that are not set should return nil - assert.Nil(t, rcvMsg.GetStringProperty("nonExistentProperty")) - assert.Nil(t, rcvMsg.GetStringProperty(unsetPropName)) + gotPropValue, propErr = rcvMsg.GetStringProperty("nonExistentProperty") + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) + gotPropValue, propErr = rcvMsg.GetStringProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) } @@ -141,7 +159,9 @@ func TestPropertyExistsGetNames(t *testing.T) { propValue := "myValue" // Test the empty value before the property is set. - assert.Nil(t, txtMsg.GetStringProperty(propName)) + gotPropValue, propErr := txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr := txtMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -152,7 +172,9 @@ func TestPropertyExistsGetNames(t *testing.T) { // Test the ability to set properties before the message is sent. retErr := txtMsg.SetStringProperty(propName, &propValue) assert.Nil(t, retErr) - assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + gotPropValue, propErr = txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, *gotPropValue) propExists, propErr = txtMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -165,7 +187,9 @@ func TestPropertyExistsGetNames(t *testing.T) { propValue2 := "myValueTwo" retErr = txtMsg.SetStringProperty(propName2, &propValue2) assert.Nil(t, retErr) - assert.Equal(t, propValue2, *txtMsg.GetStringProperty(propName2)) + gotPropValue, propErr = txtMsg.GetStringProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, *gotPropValue) propExists, propErr = txtMsg.PropertyExists(propName2) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -184,7 +208,9 @@ func TestPropertyExistsGetNames(t *testing.T) { unsetPropValue := "someValueThatWillBeOverwritten" retErr = txtMsg.SetStringProperty(unsetPropName, &unsetPropValue) assert.Nil(t, retErr) - assert.Equal(t, unsetPropValue, *txtMsg.GetStringProperty(unsetPropName)) + gotPropValue, propErr = txtMsg.GetStringProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, unsetPropValue, *gotPropValue) propExists, propErr = txtMsg.PropertyExists(unsetPropName) assert.Nil(t, propErr) assert.True(t, propExists) @@ -193,7 +219,9 @@ func TestPropertyExistsGetNames(t *testing.T) { assert.Equal(t, 3, len(allPropNames)) retErr = txtMsg.SetStringProperty(unsetPropName, nil) assert.Nil(t, retErr) - assert.Nil(t, txtMsg.GetStringProperty(unsetPropName)) + gotPropValue, propErr = txtMsg.GetStringProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr = txtMsg.PropertyExists(unsetPropName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -242,7 +270,9 @@ func TestPropertyExistsGetNames(t *testing.T) { // Properties that are not set should return nil nonExistentPropName := "nonExistentProperty" - assert.Nil(t, rcvMsg.GetStringProperty(nonExistentPropName)) + gotPropValue, propErr = rcvMsg.GetStringProperty(nonExistentPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr = rcvMsg.PropertyExists(nonExistentPropName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -282,7 +312,9 @@ func TestPropertyClearProperties(t *testing.T) { // Test the ability to set properties before the message is sent. retErr := txtMsg.SetStringProperty(propName, &propValue) assert.Nil(t, retErr) - assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + gotPropValue, propErr := txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, *gotPropValue) propExists, propErr := txtMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -293,7 +325,9 @@ func TestPropertyClearProperties(t *testing.T) { clearErr := txtMsg.ClearProperties() assert.Nil(t, clearErr) - assert.Nil(t, txtMsg.GetStringProperty(propName)) + gotPropValue, propErr = txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr = txtMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -308,10 +342,14 @@ func TestPropertyClearProperties(t *testing.T) { // Set multiple properties retErr = txtMsg.SetStringProperty(propName, &propValue) assert.Nil(t, retErr) - assert.Equal(t, propValue, *txtMsg.GetStringProperty(propName)) + gotPropValue, propErr = txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, *gotPropValue) retErr = txtMsg.SetStringProperty(propName2, &propValue2) assert.Nil(t, retErr) - assert.Equal(t, propValue2, *txtMsg.GetStringProperty(propName2)) + gotPropValue, propErr = txtMsg.GetStringProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, *gotPropValue) propExists, propErr = txtMsg.PropertyExists(propName2) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -329,7 +367,9 @@ func TestPropertyClearProperties(t *testing.T) { unsetPropValue := "someValueThatWillBeOverwritten" retErr = txtMsg.SetStringProperty(unsetPropName, &unsetPropValue) assert.Nil(t, retErr) - assert.Equal(t, unsetPropValue, *txtMsg.GetStringProperty(unsetPropName)) + gotPropValue, propErr = txtMsg.GetStringProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, unsetPropValue, *gotPropValue) propExists, propErr = txtMsg.PropertyExists(unsetPropName) assert.Nil(t, propErr) assert.True(t, propExists) @@ -341,7 +381,9 @@ func TestPropertyClearProperties(t *testing.T) { assert.Equal(t, unsetPropName, allPropNames[2]) retErr = txtMsg.SetStringProperty(unsetPropName, nil) assert.Nil(t, retErr) - assert.Nil(t, txtMsg.GetStringProperty(unsetPropName)) + gotPropValue, propErr = txtMsg.GetStringProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr = txtMsg.PropertyExists(unsetPropName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -353,7 +395,9 @@ func TestPropertyClearProperties(t *testing.T) { clearErr = txtMsg.ClearProperties() assert.Nil(t, clearErr) - assert.Nil(t, txtMsg.GetStringProperty(propName)) + gotPropValue, propErr = txtMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr = txtMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -399,7 +443,9 @@ func TestPropertyClearProperties(t *testing.T) { // Properties that are not set should return nil nonExistentPropName := "nonExistentProperty" - assert.Nil(t, rcvMsg.GetStringProperty(nonExistentPropName)) + gotPropValue, propErr = rcvMsg.GetStringProperty(nonExistentPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr = rcvMsg.PropertyExists(nonExistentPropName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -412,7 +458,9 @@ func TestPropertyClearProperties(t *testing.T) { // Finally try clearing everything on the received message clearErr = rcvMsg.ClearProperties() assert.Nil(t, clearErr) - assert.Nil(t, rcvMsg.GetStringProperty(propName)) + gotPropValue, propErr = rcvMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) propExists, propErr = rcvMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.False(t, propExists) @@ -471,7 +519,9 @@ func TestStringPropertyTextMessageNilBody(t *testing.T) { } // Check property is available on received message. - assert.Equal(t, propValue, *rcvMsg.GetStringProperty(propName)) + gotPropValue, propErr := rcvMsg.GetStringProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, *gotPropValue) } @@ -535,7 +585,11 @@ func TestStringPropertyTextMessageEmptyBody(t *testing.T) { } // Check property is available on received message. - assert.Equal(t, propAValue, *rcvMsg.GetStringProperty(propAName)) - assert.Equal(t, propBValue, *rcvMsg.GetStringProperty(propBName)) + gotPropValue, propErr := rcvMsg.GetStringProperty(propAName) + assert.Nil(t, propErr) + assert.Equal(t, propAValue, *gotPropValue) + gotPropValue, propErr = rcvMsg.GetStringProperty(propBName) + assert.Nil(t, propErr) + assert.Equal(t, propBValue, *gotPropValue) } diff --git a/mqjms/ContextImpl.go b/mqjms/ContextImpl.go index e041c15..0d72efd 100644 --- a/mqjms/ContextImpl.go +++ b/mqjms/ContextImpl.go @@ -10,6 +10,7 @@ package mqjms import ( + "fmt" "strconv" "github.com/ibm-messaging/mq-golang-jms20/jms20subset" @@ -130,9 +131,15 @@ func (ctx ContextImpl) CreateTextMessage() jms20subset.TextMessage { // store and retrieve message properties. func createMsgHandle(qMgr ibmmq.MQQueueManager) ibmmq.MQMessageHandle { - // TODO - error handling on CrtMH cmho := ibmmq.NewMQCMHO() - thisMsgHandle, _ := qMgr.CrtMH(cmho) + thisMsgHandle, err := qMgr.CrtMH(cmho) + + if err != nil { + // No easy way to pass this error back to the application without + // changing the function signature, which could break existing + // applications. + fmt.Println(err) + } return thisMsgHandle diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index cd623a0..1409267 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -311,9 +311,11 @@ func (msg *MessageImpl) SetStringProperty(name string, value *string) jms20subse // GetStringProperty returns the string value of a named message property. // Returns nil if the named property is not set. -func (msg *MessageImpl) GetStringProperty(name string) *string { +func (msg *MessageImpl) GetStringProperty(name string) (*string, jms20subset.JMSException) { var valueStr string + var retErr jms20subset.JMSException + impo := ibmmq.NewMQIMPO() pd := ibmmq.NewMQPD() @@ -332,13 +334,16 @@ func (msg *MessageImpl) GetStringProperty(name string) *string { if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { // This indicates that the requested property does not exist. // valueStr will remain with its default value of nil - return nil + return nil, nil } else { // Err was not nil - fmt.Println(err) // TODO - finish error handling + rcInt := int(mqret.MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, mqret) } } - return &valueStr + return &valueStr, retErr } // PropertyExists returns true if the named message property exists on this message. From a7141af422a75371e38cbba3707c8692ecf9303e Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Wed, 29 Dec 2021 19:33:57 +0000 Subject: [PATCH 06/12] Support for message int properties - #39 --- jms20subset/Message.go | 7 +++ messageproperties_test.go | 127 ++++++++++++++++++++++++++++++++++++-- mqjms/MessageImpl.go | 59 ++++++++++++++++++ 3 files changed, 189 insertions(+), 4 deletions(-) diff --git a/jms20subset/Message.go b/jms20subset/Message.go index 748d533..044eaa8 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -58,6 +58,13 @@ type Message interface { // Returns nil if the named property is not set. GetStringProperty(name string) (*string, JMSException) + // SetIntProperty enables an application to set a int-type message property. + SetIntProperty(name string, value int) JMSException + + // GetIntProperty returns the int value of a named message property. + // Returns 0 if the named property is not set. + GetIntProperty(name string) (int, JMSException) + // PropertyExists returns true if the named message property exists on this message. PropertyExists(name string) (bool, JMSException) diff --git a/messageproperties_test.go b/messageproperties_test.go index baf44a1..8f0e0f1 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -27,6 +27,11 @@ import ( * JMS: SetStringProperty, GetStringProperty, * https://github.com/eclipse-ee4j/messaging/blob/master/api/src/main/java/jakarta/jms/Message.java#L1119 * + * Double + * Boolean + * + * BytesMessage + * */ /* @@ -337,7 +342,7 @@ func TestPropertyClearProperties(t *testing.T) { assert.Equal(t, 0, len(allPropNames)) propName2 := "myPropertyTwo" - propValue2 := "myValueTwo" + propValue2 := 246811 // Set multiple properties retErr = txtMsg.SetStringProperty(propName, &propValue) @@ -345,11 +350,11 @@ func TestPropertyClearProperties(t *testing.T) { gotPropValue, propErr = txtMsg.GetStringProperty(propName) assert.Nil(t, propErr) assert.Equal(t, propValue, *gotPropValue) - retErr = txtMsg.SetStringProperty(propName2, &propValue2) + retErr = txtMsg.SetIntProperty(propName2, propValue2) assert.Nil(t, retErr) - gotPropValue, propErr = txtMsg.GetStringProperty(propName2) + gotPropValue2, propErr := txtMsg.GetIntProperty(propName2) assert.Nil(t, propErr) - assert.Equal(t, propValue2, *gotPropValue) + assert.Equal(t, propValue2, gotPropValue2) propExists, propErr = txtMsg.PropertyExists(propName2) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -593,3 +598,117 @@ func TestStringPropertyTextMessageEmptyBody(t *testing.T) { assert.Equal(t, propBValue, *gotPropValue) } + +/* + * Test the creation of a text message with an int property. + */ +func TestIntProperty(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage and check that we can populate it + msgBody := "IntPropertyRequestMsg" + txtMsg := context.CreateTextMessage() + txtMsg.SetText(msgBody) + + propName := "myProperty" + propValue := 6 + + // Test the empty value before the property is set. + gotPropValue, propErr := txtMsg.GetIntProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, 0, gotPropValue) + propExists, propErr := txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + // Test the ability to set properties before the message is sent. + retErr := txtMsg.SetIntProperty(propName, propValue) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetIntProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, gotPropValue) + assert.Equal(t, msgBody, *txtMsg.GetText()) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + propName2 := "myProperty2" + propValue2 := 246810 + retErr = txtMsg.SetIntProperty(propName2, propValue2) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetIntProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, gotPropValue) + + // Set a property then try to "unset" it by setting to 0 + unsetPropName := "mySendThenRemovedString" + unsetPropValue := 12345 + retErr = txtMsg.SetIntProperty(unsetPropName, unsetPropValue) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetIntProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, unsetPropValue, gotPropValue) + retErr = txtMsg.SetIntProperty(unsetPropName, 0) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetIntProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, 0, gotPropValue) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, txtMsg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + assert.Equal(t, msgBody, *msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + gotPropValue, propErr = rcvMsg.GetIntProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, gotPropValue) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + gotPropValue, propErr = rcvMsg.GetIntProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, gotPropValue) + + // Properties that are not set should return nil + gotPropValue, propErr = rcvMsg.GetIntProperty("nonExistentProperty") + assert.Nil(t, propErr) + assert.Equal(t, 0, gotPropValue) + gotPropValue, propErr = rcvMsg.GetIntProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, 0, gotPropValue) + propExists, propErr = txtMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) // exists, even though it is set to zero + +} diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 1409267..0fe294d 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -346,6 +346,65 @@ func (msg *MessageImpl) GetStringProperty(name string) (*string, jms20subset.JMS return &valueStr, retErr } +// SetIntProperty enables an application to set a int-type message property. +func (msg *MessageImpl) SetIntProperty(name string, value int) jms20subset.JMSException { + var retErr jms20subset.JMSException + + var linkedErr error + + smpo := ibmmq.NewMQSMPO() + pd := ibmmq.NewMQPD() + + linkedErr = msg.msgHandle.SetMP(smpo, name, pd, value) + + if linkedErr != nil { + rcInt := int(linkedErr.(*ibmmq.MQReturn).MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, linkedErr) + } + + return retErr +} + +// GetIntProperty returns the int value of a named message property. +// Returns 0 if the named property is not set. +func (msg *MessageImpl) GetIntProperty(name string) (int, jms20subset.JMSException) { + + var valueRet int + var retErr jms20subset.JMSException + + impo := ibmmq.NewMQIMPO() + pd := ibmmq.NewMQPD() + + _, value, err := msg.msgHandle.InqMP(impo, pd, name) + + if err == nil { + switch valueTyped := value.(type) { + case int64: + valueRet = int(valueTyped) + default: + // TODO - other conversions + //fmt.Println("Other type", value, reflect.TypeOf(value)) + } + } else { + + mqret := err.(*ibmmq.MQReturn) + if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { + // This indicates that the requested property does not exist. + // valueRet will remain with its default value of nil + return 0, nil + } else { + // Err was not nil + rcInt := int(mqret.MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, mqret) + } + } + return valueRet, retErr +} + // PropertyExists returns true if the named message property exists on this message. func (msg *MessageImpl) PropertyExists(name string) (bool, jms20subset.JMSException) { From 7973594f8340984d2654fceadf058285c1769d8d Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Thu, 30 Dec 2021 12:17:59 +0000 Subject: [PATCH 07/12] Support for double (float64) message properties - #39 --- jms20subset/Message.go | 7 +++ messageproperties_test.go | 114 ++++++++++++++++++++++++++++++++++++++ mqjms/MessageImpl.go | 59 ++++++++++++++++++++ 3 files changed, 180 insertions(+) diff --git a/jms20subset/Message.go b/jms20subset/Message.go index 044eaa8..a2bef80 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -65,6 +65,13 @@ type Message interface { // Returns 0 if the named property is not set. GetIntProperty(name string) (int, JMSException) + // SetDoubleProperty enables an application to set a double-type (float64) message property. + SetDoubleProperty(name string, value float64) JMSException + + // GetDoubleProperty returns the double (float64) value of a named message property. + // Returns 0 if the named property is not set. + GetDoubleProperty(name string) (float64, JMSException) + // PropertyExists returns true if the named message property exists on this message. PropertyExists(name string) (bool, JMSException) diff --git a/messageproperties_test.go b/messageproperties_test.go index 8f0e0f1..b9fc5fa 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -712,3 +712,117 @@ func TestIntProperty(t *testing.T) { assert.True(t, propExists) // exists, even though it is set to zero } + +/* + * Test the creation of a text message with an int property. + */ +func TestDoubleProperty(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage and check that we can populate it + msgBody := "DoublePropertyRequestMsg" + txtMsg := context.CreateTextMessage() + txtMsg.SetText(msgBody) + + propName := "myProperty" + propValue := float64(15867494.43857438) + + // Test the empty value before the property is set. + gotPropValue, propErr := txtMsg.GetDoubleProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, float64(0), gotPropValue) + propExists, propErr := txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + // Test the ability to set properties before the message is sent. + retErr := txtMsg.SetDoubleProperty(propName, propValue) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetDoubleProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, gotPropValue) + assert.Equal(t, msgBody, *txtMsg.GetText()) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + propName2 := "myProperty2" + propValue2 := float64(-246810.2255343676) + retErr = txtMsg.SetDoubleProperty(propName2, propValue2) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetDoubleProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, gotPropValue) + + // Set a property then try to "unset" it by setting to 0 + unsetPropName := "mySendThenRemovedString" + unsetPropValue := float64(12345.123456) + retErr = txtMsg.SetDoubleProperty(unsetPropName, unsetPropValue) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetDoubleProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, unsetPropValue, gotPropValue) + retErr = txtMsg.SetDoubleProperty(unsetPropName, 0) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetDoubleProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, float64(0), gotPropValue) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, txtMsg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + assert.Equal(t, msgBody, *msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + gotPropValue, propErr = rcvMsg.GetDoubleProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, gotPropValue) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + gotPropValue, propErr = rcvMsg.GetDoubleProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, gotPropValue) + + // Properties that are not set should return nil + gotPropValue, propErr = rcvMsg.GetDoubleProperty("nonExistentProperty") + assert.Nil(t, propErr) + assert.Equal(t, float64(0), gotPropValue) + gotPropValue, propErr = rcvMsg.GetDoubleProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, float64(0), gotPropValue) + propExists, propErr = txtMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) // exists, even though it is set to zero + +} diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 0fe294d..a7317a4 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -405,6 +405,65 @@ func (msg *MessageImpl) GetIntProperty(name string) (int, jms20subset.JMSExcepti return valueRet, retErr } +// SetDoubleProperty enables an application to set a double-type (float64) message property. +func (msg *MessageImpl) SetDoubleProperty(name string, value float64) jms20subset.JMSException { + var retErr jms20subset.JMSException + + var linkedErr error + + smpo := ibmmq.NewMQSMPO() + pd := ibmmq.NewMQPD() + + linkedErr = msg.msgHandle.SetMP(smpo, name, pd, value) + + if linkedErr != nil { + rcInt := int(linkedErr.(*ibmmq.MQReturn).MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, linkedErr) + } + + return retErr +} + +// GetDoubleProperty returns the double (float64) value of a named message property. +// Returns 0 if the named property is not set. +func (msg *MessageImpl) GetDoubleProperty(name string) (float64, jms20subset.JMSException) { + + var valueRet float64 + var retErr jms20subset.JMSException + + impo := ibmmq.NewMQIMPO() + pd := ibmmq.NewMQPD() + + _, value, err := msg.msgHandle.InqMP(impo, pd, name) + + if err == nil { + switch valueTyped := value.(type) { + case float64: + valueRet = valueTyped + default: + // TODO - other conversions + //fmt.Println("Other type", value, reflect.TypeOf(value)) + } + } else { + + mqret := err.(*ibmmq.MQReturn) + if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { + // This indicates that the requested property does not exist. + // valueRet will remain with its default value of nil + return 0, nil + } else { + // Err was not nil + rcInt := int(mqret.MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, mqret) + } + } + return valueRet, retErr +} + // PropertyExists returns true if the named message property exists on this message. func (msg *MessageImpl) PropertyExists(name string) (bool, jms20subset.JMSException) { From 0e09bbefdd369b5e485e24791bad51a204f22d11 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Thu, 30 Dec 2021 12:46:40 +0000 Subject: [PATCH 08/12] Support for boolean message properties - #39 --- bytesmessage_test.go | 8 +- jms20subset/Message.go | 7 + messageproperties_test.go | 272 +++++++++++++++++++++++++++++++++++++- mqjms/MessageImpl.go | 59 +++++++++ 4 files changed, 337 insertions(+), 9 deletions(-) diff --git a/bytesmessage_test.go b/bytesmessage_test.go index d3ad28b..707ed3c 100644 --- a/bytesmessage_test.go +++ b/bytesmessage_test.go @@ -89,7 +89,7 @@ func TestBytesMessageNilBody(t *testing.T) { assert.Equal(t, 0, msg2.GetBodyLength()) assert.Equal(t, []byte{}, *msg2.ReadBytes()) default: - assert.Fail(t, "Got something other than a text message") + assert.Fail(t, "Got something other than a bytes message") } } @@ -138,7 +138,7 @@ func TestBytesMessageWithBody(t *testing.T) { assert.Equal(t, len(msgBody), msg2.GetBodyLength()) assert.Equal(t, msgBody, *msg2.ReadBytes()) default: - assert.Fail(t, "Got something other than a text message") + assert.Fail(t, "Got something other than a bytes message") } } @@ -186,7 +186,7 @@ func TestBytesMessageInitWithBytes(t *testing.T) { assert.Equal(t, len(msgBody), msg2.GetBodyLength()) assert.Equal(t, msgBody, *msg2.ReadBytes()) default: - assert.Fail(t, "Got something other than a text message") + assert.Fail(t, "Got something other than a bytes message") } } @@ -231,7 +231,7 @@ func TestBytesMessageProducerSendBytes(t *testing.T) { assert.Equal(t, 13, msg2.GetBodyLength()) assert.Equal(t, msgBody, *msg2.ReadBytes()) default: - assert.Fail(t, "Got something other than a text message") + assert.Fail(t, "Got something other than a bytes message") } } diff --git a/jms20subset/Message.go b/jms20subset/Message.go index a2bef80..b12b165 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -72,6 +72,13 @@ type Message interface { // Returns 0 if the named property is not set. GetDoubleProperty(name string) (float64, JMSException) + // SetBooleanProperty enables an application to set a bool-type message property. + SetBooleanProperty(name string, value bool) JMSException + + // GetBooleanProperty returns the bool value of a named message property. + // Returns false if the named property is not set. + GetBooleanProperty(name string) (bool, JMSException) + // PropertyExists returns true if the named message property exists on this message. PropertyExists(name string) (bool, JMSException) diff --git a/messageproperties_test.go b/messageproperties_test.go index b9fc5fa..b5e7db6 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -27,10 +27,7 @@ import ( * JMS: SetStringProperty, GetStringProperty, * https://github.com/eclipse-ee4j/messaging/blob/master/api/src/main/java/jakarta/jms/Message.java#L1119 * - * Double - * Boolean - * - * BytesMessage + * Property conversion between types * */ @@ -714,7 +711,7 @@ func TestIntProperty(t *testing.T) { } /* - * Test the creation of a text message with an int property. + * Test the creation of a text message with a double property. */ func TestDoubleProperty(t *testing.T) { @@ -826,3 +823,268 @@ func TestDoubleProperty(t *testing.T) { assert.True(t, propExists) // exists, even though it is set to zero } + +/* + * Test the creation of a text message with a boolean property. + */ +func TestBooleanProperty(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a TextMessage and check that we can populate it + msgBody := "BooleanPropertyRequestMsg" + txtMsg := context.CreateTextMessage() + txtMsg.SetText(msgBody) + + propName := "myProperty" + propValue := true + + // Test the empty value before the property is set. + gotPropValue, propErr := txtMsg.GetBooleanProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, false, gotPropValue) + propExists, propErr := txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.False(t, propExists) + + // Test the ability to set properties before the message is sent. + retErr := txtMsg.SetBooleanProperty(propName, propValue) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetBooleanProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, gotPropValue) + assert.Equal(t, msgBody, *txtMsg.GetText()) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + propName2 := "myProperty2" + propValue2 := false + retErr = txtMsg.SetBooleanProperty(propName2, propValue2) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetBooleanProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, gotPropValue) + + // Set a property then try to "unset" it by setting to 0 + unsetPropName := "mySendThenRemovedString" + unsetPropValue := true + retErr = txtMsg.SetBooleanProperty(unsetPropName, unsetPropValue) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetBooleanProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, unsetPropValue, gotPropValue) + retErr = txtMsg.SetBooleanProperty(unsetPropName, false) + assert.Nil(t, retErr) + gotPropValue, propErr = txtMsg.GetBooleanProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, false, gotPropValue) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, txtMsg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg := rcvMsg.(type) { + case jms20subset.TextMessage: + assert.Equal(t, msgBody, *msg.GetText()) + default: + assert.Fail(t, "Got something other than a text message") + } + + // Check property is available on received message. + gotPropValue, propErr = rcvMsg.GetBooleanProperty(propName) + assert.Nil(t, propErr) + assert.Equal(t, propValue, gotPropValue) + propExists, propErr = txtMsg.PropertyExists(propName) + assert.Nil(t, propErr) + assert.True(t, propExists) // now exists + + gotPropValue, propErr = rcvMsg.GetBooleanProperty(propName2) + assert.Nil(t, propErr) + assert.Equal(t, propValue2, gotPropValue) + + // Properties that are not set should return nil + gotPropValue, propErr = rcvMsg.GetBooleanProperty("nonExistentProperty") + assert.Nil(t, propErr) + assert.Equal(t, false, gotPropValue) + gotPropValue, propErr = rcvMsg.GetBooleanProperty(unsetPropName) + assert.Nil(t, propErr) + assert.Equal(t, false, gotPropValue) + propExists, propErr = txtMsg.PropertyExists(unsetPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) // exists, even though it is set to zero + +} + +/* + * Test the creation of a bytes message with message properties. + */ +func TestPropertyBytesMsg(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + // Create a BytesMessage + msgBody := []byte{'b', 'y', 't', 'e', 's', 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's'} + bytesMsg := context.CreateBytesMessage() + bytesMsg.WriteBytes(msgBody) + assert.Equal(t, 15, bytesMsg.GetBodyLength()) + assert.Equal(t, msgBody, *bytesMsg.ReadBytes()) + + stringPropName := "myProperty" + stringPropValue := "myValue" + + // Test the empty value before the property is set. + gotPropValue, propErr := bytesMsg.GetStringProperty(stringPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) + + // Test the ability to set properties before the message is sent. + retErr := bytesMsg.SetStringProperty(stringPropName, &stringPropValue) + assert.Nil(t, retErr) + gotPropValue, propErr = bytesMsg.GetStringProperty(stringPropName) + assert.Nil(t, propErr) + assert.Equal(t, stringPropValue, *gotPropValue) + + // Send an empty string property as well + emptyPropName := "myEmptyString" + emptyPropValue := "" + retErr = bytesMsg.SetStringProperty(emptyPropName, &emptyPropValue) + assert.Nil(t, retErr) + gotPropValue, propErr = bytesMsg.GetStringProperty(emptyPropName) + assert.Nil(t, propErr) + assert.Equal(t, emptyPropValue, *gotPropValue) + + // Now an int property + intPropName := "myIntProperty" + intPropValue := 553786 + retErr = bytesMsg.SetIntProperty(intPropName, intPropValue) + assert.Nil(t, retErr) + gotIntPropValue, propErr := bytesMsg.GetIntProperty(intPropName) + assert.Nil(t, propErr) + assert.Equal(t, intPropValue, gotIntPropValue) + + // Now a double property + doublePropName := "myDoubleProperty" + doublePropValue := float64(3.1415926535) + retErr = bytesMsg.SetDoubleProperty(doublePropName, doublePropValue) + assert.Nil(t, retErr) + gotDoublePropValue, propErr := bytesMsg.GetDoubleProperty(doublePropName) + assert.Nil(t, propErr) + assert.Equal(t, doublePropValue, gotDoublePropValue) + + // Now a bool property + boolPropName := "myBoolProperty" + boolPropValue := true + retErr = bytesMsg.SetBooleanProperty(boolPropName, boolPropValue) + assert.Nil(t, retErr) + gotBoolPropValue, propErr := bytesMsg.GetBooleanProperty(boolPropName) + assert.Nil(t, propErr) + assert.Equal(t, boolPropValue, gotBoolPropValue) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, bytesMsg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + switch msg2 := rcvMsg.(type) { + case jms20subset.BytesMessage: + assert.Equal(t, len(msgBody), msg2.GetBodyLength()) + assert.Equal(t, msgBody, *msg2.ReadBytes()) + default: + assert.Fail(t, "Got something other than a bytes message") + } + + // Check property is available on received message. + propExists, propErr := rcvMsg.PropertyExists(stringPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) + gotPropValue, propErr = rcvMsg.GetStringProperty(stringPropName) + assert.Nil(t, propErr) + assert.Equal(t, stringPropValue, *gotPropValue) + + // Check the empty string property. + propExists, propErr = rcvMsg.PropertyExists(emptyPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) + gotPropValue, propErr = rcvMsg.GetStringProperty(emptyPropName) + assert.Nil(t, propErr) + assert.Equal(t, emptyPropValue, *gotPropValue) + + // Properties that are not set should return nil + nonExistPropName := "nonExistentProperty" + propExists, propErr = rcvMsg.PropertyExists(nonExistPropName) + assert.Nil(t, propErr) + assert.False(t, propExists) + gotPropValue, propErr = rcvMsg.GetStringProperty(nonExistPropName) + assert.Nil(t, propErr) + assert.Nil(t, gotPropValue) + + propExists, propErr = rcvMsg.PropertyExists(intPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) + gotIntPropValue, propErr = rcvMsg.GetIntProperty(intPropName) + assert.Nil(t, propErr) + assert.Equal(t, intPropValue, gotIntPropValue) + + propExists, propErr = rcvMsg.PropertyExists(doublePropName) + assert.Nil(t, propErr) + assert.True(t, propExists) + gotDoublePropValue, propErr = rcvMsg.GetDoubleProperty(doublePropName) + assert.Nil(t, propErr) + assert.Equal(t, doublePropValue, gotDoublePropValue) + + propExists, propErr = rcvMsg.PropertyExists(boolPropName) + assert.Nil(t, propErr) + assert.True(t, propExists) + gotBoolPropValue, propErr = rcvMsg.GetBooleanProperty(boolPropName) + assert.Nil(t, propErr) + assert.Equal(t, boolPropValue, gotBoolPropValue) + + allPropNames, getNamesErr := rcvMsg.GetPropertyNames() + assert.Nil(t, getNamesErr) + assert.Equal(t, 5, len(allPropNames)) + +} diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index a7317a4..489a2b0 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -464,6 +464,65 @@ func (msg *MessageImpl) GetDoubleProperty(name string) (float64, jms20subset.JMS return valueRet, retErr } +// SetBooleanProperty enables an application to set a bool-type message property. +func (msg *MessageImpl) SetBooleanProperty(name string, value bool) jms20subset.JMSException { + var retErr jms20subset.JMSException + + var linkedErr error + + smpo := ibmmq.NewMQSMPO() + pd := ibmmq.NewMQPD() + + linkedErr = msg.msgHandle.SetMP(smpo, name, pd, value) + + if linkedErr != nil { + rcInt := int(linkedErr.(*ibmmq.MQReturn).MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, linkedErr) + } + + return retErr +} + +// GetBooleanProperty returns the bool value of a named message property. +// Returns false if the named property is not set. +func (msg *MessageImpl) GetBooleanProperty(name string) (bool, jms20subset.JMSException) { + + var valueRet bool + var retErr jms20subset.JMSException + + impo := ibmmq.NewMQIMPO() + pd := ibmmq.NewMQPD() + + _, value, err := msg.msgHandle.InqMP(impo, pd, name) + + if err == nil { + switch valueTyped := value.(type) { + case bool: + valueRet = valueTyped + default: + // TODO - other conversions + //fmt.Println("Other type", value, reflect.TypeOf(value)) + } + } else { + + mqret := err.(*ibmmq.MQReturn) + if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { + // This indicates that the requested property does not exist. + // valueRet will remain with its default value of nil + return false, nil + } else { + // Err was not nil + rcInt := int(mqret.MQRC) + errCode := strconv.Itoa(rcInt) + reason := ibmmq.MQItoString("RC", rcInt) + retErr = jms20subset.CreateJMSException(reason, errCode, mqret) + } + } + return valueRet, retErr +} + // PropertyExists returns true if the named message property exists on this message. func (msg *MessageImpl) PropertyExists(name string) (bool, jms20subset.JMSException) { From fc468e9b595d4badf94e985b6a8e8f1d991e0ea4 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Thu, 30 Dec 2021 18:06:14 +0000 Subject: [PATCH 09/12] Convert string properties to other types - #39 --- messageproperties_test.go | 209 ++++++++++++++++++++++++++++++++++++++ mqjms/MessageImpl.go | 30 ++++++ 2 files changed, 239 insertions(+) diff --git a/messageproperties_test.go b/messageproperties_test.go index b5e7db6..15d0bc3 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -1088,3 +1088,212 @@ func TestPropertyBytesMsg(t *testing.T) { assert.Equal(t, 5, len(allPropNames)) } + +/* + * Test the conversion between different message property data types. + */ +func TestPropertyTypesStringConversion(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + msg := context.CreateTextMessage() + + unsetPropName := "thisPropertyIsNotSet" + + // Set up some different string properties + stringOfStringPropName := "stringOfString" + stringOfStringValue := "myValue" + stringOfEmptyStrPropName := "stringOfEmptyStr" + stringOfEmptyValue := "" + + stringOfIntPropName := "stringOfInt" + stringOfIntValue := "245" + stringOfIntPropName2 := "stringOfInt2" + stringOfIntValue2 := "-34678" + + stringOfBoolPropName := "stringOfBool" + stringOfBoolValue := "true" + stringOfBoolPropName2 := "stringOfBool2" + stringOfBoolValue2 := "false" + + stringOfDoublePropName := "stringOfDouble" + stringOfDoubleValue := "2.718527453" + stringOfDoublePropName2 := "stringOfDouble2" + stringOfDoubleValue2 := "-25675752.212345678" + + msg.SetStringProperty(stringOfStringPropName, &stringOfStringValue) + msg.SetStringProperty(stringOfEmptyStrPropName, &stringOfEmptyValue) + msg.SetStringProperty(stringOfIntPropName, &stringOfIntValue) + msg.SetStringProperty(stringOfIntPropName2, &stringOfIntValue2) + msg.SetStringProperty(stringOfBoolPropName, &stringOfBoolValue) + msg.SetStringProperty(stringOfBoolPropName2, &stringOfBoolValue2) + msg.SetStringProperty(stringOfDoublePropName, &stringOfDoubleValue) + msg.SetStringProperty(stringOfDoublePropName2, &stringOfDoubleValue2) + + // Now an int property + intPropName := "myIntProperty" + intPropValue := 553786 + retErr := msg.SetIntProperty(intPropName, intPropValue) + assert.Nil(t, retErr) + + // Now a double property + doublePropName := "myDoubleProperty" + doublePropValue := float64(3.1415926535) + retErr = msg.SetDoubleProperty(doublePropName, doublePropValue) + assert.Nil(t, retErr) + + // Now a bool property + boolPropName := "myBoolProperty" + boolPropValue := true + retErr = msg.SetBooleanProperty(boolPropName, boolPropValue) + assert.Nil(t, retErr) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, msg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + // Check string properties were set correctly + gotStringPropValue, gotStringErr := rcvMsg.GetStringProperty(stringOfStringPropName) + gotEmptyStrPropValue, gotEmptyStrErr := rcvMsg.GetStringProperty(stringOfEmptyStrPropName) + gotIntPropValue, gotIntErr := rcvMsg.GetStringProperty(stringOfIntPropName) + gotIntPropValue2, gotIntErr2 := rcvMsg.GetStringProperty(stringOfIntPropName2) + gotBoolPropValue, gotBoolErr := rcvMsg.GetStringProperty(stringOfBoolPropName) + gotBoolPropValue2, gotBoolErr2 := rcvMsg.GetStringProperty(stringOfBoolPropName2) + gotDoublePropValue, gotDoubleErr := rcvMsg.GetStringProperty(stringOfDoublePropName) + gotDoublePropValue2, gotDoubleErr2 := rcvMsg.GetStringProperty(stringOfDoublePropName2) + gotUnsetPropValue, gotUnsetErr := rcvMsg.GetStringProperty(unsetPropName) + assert.Nil(t, gotStringErr) + assert.Nil(t, gotEmptyStrErr) + assert.Nil(t, gotIntErr) + assert.Nil(t, gotIntErr2) + assert.Nil(t, gotBoolErr) + assert.Nil(t, gotBoolErr2) + assert.Nil(t, gotDoubleErr) + assert.Nil(t, gotDoubleErr2) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, stringOfStringValue, *gotStringPropValue) + assert.Equal(t, stringOfEmptyValue, *gotEmptyStrPropValue) + assert.Equal(t, stringOfIntValue, *gotIntPropValue) + assert.Equal(t, stringOfIntValue2, *gotIntPropValue2) + assert.Equal(t, stringOfBoolValue, *gotBoolPropValue) + assert.Equal(t, stringOfBoolValue2, *gotBoolPropValue2) + assert.Equal(t, stringOfDoubleValue, *gotDoublePropValue) + assert.Equal(t, stringOfDoubleValue2, *gotDoublePropValue2) + assert.Nil(t, gotUnsetPropValue) + + // Get the string properties back as int. + gotStrAsIntValue, gotStringErr := rcvMsg.GetIntProperty(stringOfStringPropName) + gotEmptyStrAsIntValue, gotEmptyStrErr := rcvMsg.GetIntProperty(stringOfEmptyStrPropName) + gotStrIntAsIntValue, gotIntErr := rcvMsg.GetIntProperty(stringOfIntPropName) + gotStrIntAsIntValue2, gotIntErr2 := rcvMsg.GetIntProperty(stringOfIntPropName2) + gotStrBoolAsIntValue, gotBoolErr := rcvMsg.GetIntProperty(stringOfBoolPropName) + gotStrBoolAsIntValue2, gotBoolErr2 := rcvMsg.GetIntProperty(stringOfBoolPropName2) + gotStrDoubleAsIntValue, gotDoubleErr := rcvMsg.GetIntProperty(stringOfDoublePropName) + gotStrDoubleAsIntValue2, gotDoubleErr2 := rcvMsg.GetIntProperty(stringOfDoublePropName2) + gotUnsetAsIntValue, gotUnsetErr := rcvMsg.GetIntProperty(unsetPropName) + assert.NotNil(t, gotStringErr) + assert.Equal(t, "1055", gotStringErr.GetErrorCode()) + assert.Equal(t, "MQJMS_E_BAD_TYPE", gotStringErr.GetReason()) + assert.NotNil(t, gotEmptyStrErr) + assert.Nil(t, gotIntErr) + assert.Nil(t, gotIntErr2) + assert.NotNil(t, gotBoolErr) + assert.NotNil(t, gotBoolErr2) + assert.NotNil(t, gotDoubleErr) + assert.NotNil(t, gotDoubleErr2) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, 0, gotStrAsIntValue) // non-nil err + assert.Equal(t, 0, gotEmptyStrAsIntValue) // non-nil err + assert.Equal(t, 245, gotStrIntAsIntValue) + assert.Equal(t, -34678, gotStrIntAsIntValue2) + assert.Equal(t, 0, gotStrBoolAsIntValue) // non-nil err + assert.Equal(t, 0, gotStrBoolAsIntValue2) // non-nil err + assert.Equal(t, 0, gotStrDoubleAsIntValue) // non-nil err + assert.Equal(t, 0, gotStrDoubleAsIntValue2) // non-nil err + assert.Equal(t, 0, gotUnsetAsIntValue) + + // Get the string properties back as bool. + gotStrAsBoolValue, gotStringErr := rcvMsg.GetBooleanProperty(stringOfStringPropName) + gotEmptyStrAsBoolValue, gotEmptyStrErr := rcvMsg.GetBooleanProperty(stringOfEmptyStrPropName) + gotStrIntAsBoolValue, gotIntErr := rcvMsg.GetBooleanProperty(stringOfIntPropName) + gotStrIntAsBoolValue2, gotIntErr2 := rcvMsg.GetBooleanProperty(stringOfIntPropName2) + gotStrBoolAsBoolValue, gotBoolErr := rcvMsg.GetBooleanProperty(stringOfBoolPropName) + gotStrBoolAsBoolValue2, gotBoolErr2 := rcvMsg.GetBooleanProperty(stringOfBoolPropName2) + gotStrDoubleAsBoolValue, gotDoubleErr := rcvMsg.GetBooleanProperty(stringOfDoublePropName) + gotStrDoubleAsBoolValue2, gotDoubleErr2 := rcvMsg.GetBooleanProperty(stringOfDoublePropName2) + gotUnsetAsBoolValue, gotUnsetErr := rcvMsg.GetBooleanProperty(unsetPropName) + assert.NotNil(t, gotStringErr) + assert.Equal(t, "1055", gotStringErr.GetErrorCode()) + assert.Equal(t, "MQJMS_E_BAD_TYPE", gotStringErr.GetReason()) + assert.NotNil(t, gotEmptyStrErr) + assert.NotNil(t, gotIntErr) + assert.NotNil(t, gotIntErr2) + assert.Nil(t, gotBoolErr) + assert.Nil(t, gotBoolErr2) + assert.NotNil(t, gotDoubleErr) + assert.NotNil(t, gotDoubleErr2) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, false, gotStrAsBoolValue) // non-nil err + assert.Equal(t, false, gotEmptyStrAsBoolValue) // non-nil err + assert.Equal(t, false, gotStrIntAsBoolValue) // non-nil err + assert.Equal(t, false, gotStrIntAsBoolValue2) // non-nil err + assert.Equal(t, true, gotStrBoolAsBoolValue) + assert.Equal(t, false, gotStrBoolAsBoolValue2) + assert.Equal(t, false, gotStrDoubleAsBoolValue) // non-nil err + assert.Equal(t, false, gotStrDoubleAsBoolValue2) // non-nil err + assert.Equal(t, false, gotUnsetAsBoolValue) + + // Get the string properties back as double. + gotStrAsDoubleValue, gotStringErr := rcvMsg.GetDoubleProperty(stringOfStringPropName) + gotEmptyStrAsDoubleValue, gotEmptyStrErr := rcvMsg.GetDoubleProperty(stringOfEmptyStrPropName) + gotStrIntAsDoubleValue, gotIntErr := rcvMsg.GetDoubleProperty(stringOfIntPropName) + gotStrIntAsDoubleValue2, gotIntErr2 := rcvMsg.GetDoubleProperty(stringOfIntPropName2) + gotStrBoolAsDoubleValue, gotBoolErr := rcvMsg.GetDoubleProperty(stringOfBoolPropName) + gotStrBoolAsDoubleValue2, gotBoolErr2 := rcvMsg.GetDoubleProperty(stringOfBoolPropName2) + gotStrDoubleAsDoubleValue, gotDoubleErr := rcvMsg.GetDoubleProperty(stringOfDoublePropName) + gotStrDoubleAsDoubleValue2, gotDoubleErr2 := rcvMsg.GetDoubleProperty(stringOfDoublePropName2) + gotUnsetAsDoubleValue, gotUnsetErr := rcvMsg.GetDoubleProperty(unsetPropName) + assert.NotNil(t, gotStringErr) + assert.Equal(t, "1055", gotStringErr.GetErrorCode()) + assert.Equal(t, "MQJMS_E_BAD_TYPE", gotStringErr.GetReason()) + assert.NotNil(t, gotEmptyStrErr) + assert.Nil(t, gotIntErr) + assert.Nil(t, gotIntErr2) + assert.NotNil(t, gotBoolErr) + assert.NotNil(t, gotBoolErr2) + assert.Nil(t, gotDoubleErr) + assert.Nil(t, gotDoubleErr2) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, float64(0), gotStrAsDoubleValue) // non-nil err + assert.Equal(t, float64(0), gotEmptyStrAsDoubleValue) // non-nil err + assert.Equal(t, float64(245), gotStrIntAsDoubleValue) + assert.Equal(t, float64(-34678), gotStrIntAsDoubleValue2) + assert.Equal(t, float64(0), gotStrBoolAsDoubleValue) // non-nil err + assert.Equal(t, float64(0), gotStrBoolAsDoubleValue2) // non-nil err + assert.Equal(t, float64(2.718527453), gotStrDoubleAsDoubleValue) + assert.Equal(t, float64(-25675752.212345678), gotStrDoubleAsDoubleValue2) + assert.Equal(t, float64(0), gotUnsetAsDoubleValue) + +} diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 489a2b0..90aa19e 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -21,6 +21,9 @@ import ( ibmmq "github.com/ibm-messaging/mq-golang/v5/ibmmq" ) +const MessageImpl_PROPERTY_CONVERT_FAILED_REASON string = "MQJMS_E_BAD_TYPE" +const MessageImpl_PROPERTY_CONVERT_FAILED_CODE string = "1055" + // MessageImpl contains the IBM MQ specific attributes that are // common to all types of message. type MessageImpl struct { @@ -380,9 +383,18 @@ func (msg *MessageImpl) GetIntProperty(name string) (int, jms20subset.JMSExcepti _, value, err := msg.msgHandle.InqMP(impo, pd, name) if err == nil { + + var parseErr error + switch valueTyped := value.(type) { case int64: valueRet = int(valueTyped) + case string: + valueRet, parseErr = strconv.Atoi(valueTyped) + if parseErr != nil { + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, + MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) + } default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) @@ -439,9 +451,18 @@ func (msg *MessageImpl) GetDoubleProperty(name string) (float64, jms20subset.JMS _, value, err := msg.msgHandle.InqMP(impo, pd, name) if err == nil { + + var parseErr error + switch valueTyped := value.(type) { case float64: valueRet = valueTyped + case string: + valueRet, parseErr = strconv.ParseFloat(valueTyped, 64) + if parseErr != nil { + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, + MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) + } default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) @@ -498,9 +519,18 @@ func (msg *MessageImpl) GetBooleanProperty(name string) (bool, jms20subset.JMSEx _, value, err := msg.msgHandle.InqMP(impo, pd, name) if err == nil { + + var parseErr error + switch valueTyped := value.(type) { case bool: valueRet = valueTyped + case string: + valueRet, parseErr = strconv.ParseBool(valueTyped) + if parseErr != nil { + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, + MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) + } default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) From 8ce7b38596b1d4c100a41a1655a8d96c4ca96d63 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Sat, 1 Jan 2022 15:57:57 +0000 Subject: [PATCH 10/12] Convert int properties to other types - #39 --- messageproperties_test.go | 158 +++++++++++++++++++++++++++++++++----- mqjms/MessageImpl.go | 16 ++++ 2 files changed, 155 insertions(+), 19 deletions(-) diff --git a/messageproperties_test.go b/messageproperties_test.go index 15d0bc3..58ba7b7 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -1090,7 +1090,7 @@ func TestPropertyBytesMsg(t *testing.T) { } /* - * Test the conversion between different message property data types. + * Test the conversion between string message properties and other data types. */ func TestPropertyTypesStringConversion(t *testing.T) { @@ -1140,24 +1140,6 @@ func TestPropertyTypesStringConversion(t *testing.T) { msg.SetStringProperty(stringOfDoublePropName, &stringOfDoubleValue) msg.SetStringProperty(stringOfDoublePropName2, &stringOfDoubleValue2) - // Now an int property - intPropName := "myIntProperty" - intPropValue := 553786 - retErr := msg.SetIntProperty(intPropName, intPropValue) - assert.Nil(t, retErr) - - // Now a double property - doublePropName := "myDoubleProperty" - doublePropValue := float64(3.1415926535) - retErr = msg.SetDoubleProperty(doublePropName, doublePropValue) - assert.Nil(t, retErr) - - // Now a bool property - boolPropName := "myBoolProperty" - boolPropValue := true - retErr = msg.SetBooleanProperty(boolPropName, boolPropValue) - assert.Nil(t, retErr) - // Set up objects for send/receive queue := context.CreateQueue("DEV.QUEUE.1") consumer, errCons := context.CreateConsumer(queue) @@ -1297,3 +1279,141 @@ func TestPropertyTypesStringConversion(t *testing.T) { assert.Equal(t, float64(0), gotUnsetAsDoubleValue) } + +/* + * Test the conversion between different int message properties and other data types. + */ +func TestPropertyTypesIntConversion(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + msg := context.CreateTextMessage() + + unsetPropName := "thisPropertyIsNotSet" + + // Set up some different int properties + intOnePropName := "intOne" + intOneValue := 1 + intZeroPropName := "intZero" + intZeroValue := 0 + intMinusOnePropName := "intMinusOne" + intMinusOneValue := -1 + + intLargePosPropName := "largePositive" + intLargePosValue := 48632675 + intLargeNegPropName := "largeNegative" + intLargeNegValue := -3789753467 + + msg.SetIntProperty(intOnePropName, intOneValue) + msg.SetIntProperty(intZeroPropName, intZeroValue) + msg.SetIntProperty(intMinusOnePropName, intMinusOneValue) + msg.SetIntProperty(intLargePosPropName, intLargePosValue) + msg.SetIntProperty(intLargeNegPropName, intLargeNegValue) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, msg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + // Check int properties were set correctly + gotOneValue, gotOneErr := rcvMsg.GetIntProperty(intOnePropName) + gotZeroValue, gotZeroErr := rcvMsg.GetIntProperty(intZeroPropName) + gotMinusOneValue, gotMinusOneErr := rcvMsg.GetIntProperty(intMinusOnePropName) + gotLargePosValue, gotLargePosErr := rcvMsg.GetIntProperty(intLargePosPropName) + gotLargeNegValue, gotLargeNegErr := rcvMsg.GetIntProperty(intLargeNegPropName) + gotUnsetPropValue, gotUnsetErr := rcvMsg.GetIntProperty(unsetPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, intOneValue, gotOneValue) + assert.Equal(t, intZeroValue, gotZeroValue) + assert.Equal(t, intMinusOneValue, gotMinusOneValue) + assert.Equal(t, intLargePosValue, gotLargePosValue) + assert.Equal(t, intLargeNegValue, gotLargeNegValue) + assert.Equal(t, 0, gotUnsetPropValue) + + // Convert back as string + gotStrOneValue, gotOneErr := rcvMsg.GetStringProperty(intOnePropName) + gotStrZeroValue, gotZeroErr := rcvMsg.GetStringProperty(intZeroPropName) + gotStrMinusOneValue, gotMinusOneErr := rcvMsg.GetStringProperty(intMinusOnePropName) + gotStrLargePosValue, gotLargePosErr := rcvMsg.GetStringProperty(intLargePosPropName) + gotStrLargeNegValue, gotLargeNegErr := rcvMsg.GetStringProperty(intLargeNegPropName) + gotStrUnsetPropValue, gotUnsetErr := rcvMsg.GetStringProperty(unsetPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, "1", *gotStrOneValue) + assert.Equal(t, "0", *gotStrZeroValue) + assert.Equal(t, "-1", *gotStrMinusOneValue) + assert.Equal(t, "48632675", *gotStrLargePosValue) + assert.Equal(t, "-3789753467", *gotStrLargeNegValue) + assert.Nil(t, gotStrUnsetPropValue) + + // Convert back as bool + gotBoolOneValue, gotOneErr := rcvMsg.GetBooleanProperty(intOnePropName) + gotBoolZeroValue, gotZeroErr := rcvMsg.GetBooleanProperty(intZeroPropName) + gotBoolMinusOneValue, gotMinusOneErr := rcvMsg.GetBooleanProperty(intMinusOnePropName) + gotBoolLargePosValue, gotLargePosErr := rcvMsg.GetBooleanProperty(intLargePosPropName) + gotBoolLargeNegValue, gotLargeNegErr := rcvMsg.GetBooleanProperty(intLargeNegPropName) + gotBoolUnsetPropValue, gotUnsetErr := rcvMsg.GetBooleanProperty(unsetPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, true, gotBoolOneValue) + assert.Equal(t, false, gotBoolZeroValue) + assert.Equal(t, false, gotBoolMinusOneValue) + assert.Equal(t, false, gotBoolLargePosValue) + assert.Equal(t, false, gotBoolLargeNegValue) + assert.Equal(t, false, gotBoolUnsetPropValue) + + // Convert back as double + gotDoubleOneValue, gotOneErr := rcvMsg.GetDoubleProperty(intOnePropName) + gotDoubleZeroValue, gotZeroErr := rcvMsg.GetDoubleProperty(intZeroPropName) + gotDoubleMinusOneValue, gotMinusOneErr := rcvMsg.GetDoubleProperty(intMinusOnePropName) + gotDoubleLargePosValue, gotLargePosErr := rcvMsg.GetDoubleProperty(intLargePosPropName) + gotDoubleLargeNegValue, gotLargeNegErr := rcvMsg.GetDoubleProperty(intLargeNegPropName) + gotDoubleUnsetPropValue, gotUnsetErr := rcvMsg.GetDoubleProperty(unsetPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, float64(1), gotDoubleOneValue) + assert.Equal(t, float64(0), gotDoubleZeroValue) + assert.Equal(t, float64(-1), gotDoubleMinusOneValue) + assert.Equal(t, float64(48632675), gotDoubleLargePosValue) + assert.Equal(t, float64(-3789753467), gotDoubleLargeNegValue) + assert.Equal(t, float64(0), gotDoubleUnsetPropValue) + +} diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 90aa19e..662a549 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -325,9 +325,18 @@ func (msg *MessageImpl) GetStringProperty(name string) (*string, jms20subset.JMS _, value, err := msg.msgHandle.InqMP(impo, pd, name) if err == nil { + + var parseErr error + switch valueTyped := value.(type) { case string: valueStr = valueTyped + case int64: + valueStr = strconv.FormatInt(valueTyped, 10) + if parseErr != nil { + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, + MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) + } default: // TODO - other conversions } @@ -463,6 +472,8 @@ func (msg *MessageImpl) GetDoubleProperty(name string) (float64, jms20subset.JMS retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) } + case int64: + valueRet = float64(valueTyped) default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) @@ -531,6 +542,11 @@ func (msg *MessageImpl) GetBooleanProperty(name string) (bool, jms20subset.JMSEx retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) } + case int64: + // Conversion from int to bool is true iff n=1 + if valueTyped == 1 { + valueRet = true + } default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) From 0ba66a353f6e26e4fcc38f727909f2d0c97bd87d Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Sat, 1 Jan 2022 17:15:07 +0000 Subject: [PATCH 11/12] Convert bool + double properties to other types - #39 --- messageproperties_test.go | 243 ++++++++++++++++++++++++++++++++++++++ mqjms/MessageImpl.go | 24 ++++ 2 files changed, 267 insertions(+) diff --git a/messageproperties_test.go b/messageproperties_test.go index 58ba7b7..f4de9a5 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -1417,3 +1417,246 @@ func TestPropertyTypesIntConversion(t *testing.T) { assert.Equal(t, float64(0), gotDoubleUnsetPropValue) } + +/* + * Test the conversion between different int message properties and other data types. + */ +func TestPropertyTypesBoolConversion(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + msg := context.CreateTextMessage() + + // Set up some different int properties + truePropName := "intOne" + trueValue := true + falsePropName := "intZero" + falseValue := false + + msg.SetBooleanProperty(truePropName, trueValue) + msg.SetBooleanProperty(falsePropName, falseValue) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, msg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + // Check bool properties were set correctly + gotTrueValue, gotTrueErr := rcvMsg.GetBooleanProperty(truePropName) + gotFalseValue, gotFalseErr := rcvMsg.GetBooleanProperty(falsePropName) + assert.Nil(t, gotTrueErr) + assert.Nil(t, gotFalseErr) + assert.Equal(t, trueValue, gotTrueValue) + assert.Equal(t, falseValue, gotFalseValue) + + // Convert back as string + gotStrTrueValue, gotTrueErr := rcvMsg.GetStringProperty(truePropName) + gotStrFalseValue, gotFalseErr := rcvMsg.GetStringProperty(falsePropName) + assert.Nil(t, gotTrueErr) + assert.Nil(t, gotFalseErr) + assert.Equal(t, "true", *gotStrTrueValue) + assert.Equal(t, "false", *gotStrFalseValue) + + // Convert back as int + gotIntTrueValue, gotTrueErr := rcvMsg.GetIntProperty(truePropName) + gotIntFalseValue, gotFalseErr := rcvMsg.GetIntProperty(falsePropName) + assert.Nil(t, gotTrueErr) + assert.Nil(t, gotFalseErr) + assert.Equal(t, 1, gotIntTrueValue) + assert.Equal(t, 0, gotIntFalseValue) + + // Convert back as double + gotDoubleTrueValue, gotTrueErr := rcvMsg.GetDoubleProperty(truePropName) + gotDoubleFalseValue, gotFalseErr := rcvMsg.GetDoubleProperty(falsePropName) + assert.Nil(t, gotTrueErr) + assert.Nil(t, gotFalseErr) + assert.Equal(t, float64(1), gotDoubleTrueValue) + assert.Equal(t, float64(0), gotDoubleFalseValue) + +} + +/* + * Test the conversion between different int message properties and other data types. + */ +func TestPropertyTypesDoubleConversion(t *testing.T) { + + // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory + cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() + assert.Nil(t, cfErr) + + // Creates a connection to the queue manager, using defer to close it automatically + // at the end of the function (if it was created successfully) + context, ctxErr := cf.CreateContext() + assert.Nil(t, ctxErr) + if context != nil { + defer context.Close() + } + + msg := context.CreateTextMessage() + + unsetPropName := "thisPropertyIsNotSet" + + // Set up some different int properties + doubleOnePropName := "intOne" + doubleOneValue := float64(1) + doubleZeroPropName := "intZero" + doubleZeroValue := float64(0) + doubleMinusOnePropName := "intMinusOne" + doubleMinusOneValue := float64(-1) + + doubleLargePosPropName := "largePositive" + doubleLargePosValue := float64(48632675) + doubleLargeNegPropName := "largeNegative" + doubleLargeNegValue := float64(-3789753467) + + doubleLargeDecimalPropName := "largePositiveDecimal" + doubleLargeDecimalValue := float64(3867493.68473625) + doubleLargeNegativeDecimalPropName := "largeNegativeDecimal" + doubleLargeNegativeDecimalValue := float64(-87654335674.383656) + + msg.SetDoubleProperty(doubleOnePropName, doubleOneValue) + msg.SetDoubleProperty(doubleZeroPropName, doubleZeroValue) + msg.SetDoubleProperty(doubleMinusOnePropName, doubleMinusOneValue) + msg.SetDoubleProperty(doubleLargePosPropName, doubleLargePosValue) + msg.SetDoubleProperty(doubleLargeNegPropName, doubleLargeNegValue) + msg.SetDoubleProperty(doubleLargeDecimalPropName, doubleLargeDecimalValue) + msg.SetDoubleProperty(doubleLargeNegativeDecimalPropName, doubleLargeNegativeDecimalValue) + + // Set up objects for send/receive + queue := context.CreateQueue("DEV.QUEUE.1") + consumer, errCons := context.CreateConsumer(queue) + if consumer != nil { + defer consumer.Close() + } + assert.Nil(t, errCons) + + // Now send the message and get it back again, to check that it roundtripped. + errSend := context.CreateProducer().SetTimeToLive(10000).Send(queue, msg) + assert.Nil(t, errSend) + + rcvMsg, errRvc := consumer.ReceiveNoWait() + assert.Nil(t, errRvc) + assert.NotNil(t, rcvMsg) + + // Check double properties were set correctly + gotDoubleOneValue, gotOneErr := rcvMsg.GetDoubleProperty(doubleOnePropName) + gotDoubleZeroValue, gotZeroErr := rcvMsg.GetDoubleProperty(doubleZeroPropName) + gotDoubleMinusOneValue, gotMinusOneErr := rcvMsg.GetDoubleProperty(doubleMinusOnePropName) + gotDoubleLargePosValue, gotLargePosErr := rcvMsg.GetDoubleProperty(doubleLargePosPropName) + gotDoubleLargeNegValue, gotLargeNegErr := rcvMsg.GetDoubleProperty(doubleLargeNegPropName) + gotDoubleLargePosDecimalValue, gotLargeDecPosErr := rcvMsg.GetDoubleProperty(doubleLargeDecimalPropName) + gotDoubleLargeNegDecimalValue, gotLargeDecNegErr := rcvMsg.GetDoubleProperty(doubleLargeNegativeDecimalPropName) + gotDoubleUnsetPropValue, gotUnsetErr := rcvMsg.GetDoubleProperty(unsetPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotLargeDecPosErr) + assert.Nil(t, gotLargeDecNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, float64(1), gotDoubleOneValue) + assert.Equal(t, float64(0), gotDoubleZeroValue) + assert.Equal(t, float64(-1), gotDoubleMinusOneValue) + assert.Equal(t, float64(48632675), gotDoubleLargePosValue) + assert.Equal(t, float64(-3789753467), gotDoubleLargeNegValue) + assert.Equal(t, float64(3867493.68473625), gotDoubleLargePosDecimalValue) + assert.Equal(t, float64(-87654335674.383656), gotDoubleLargeNegDecimalValue) + assert.Equal(t, float64(0), gotDoubleUnsetPropValue) + + // Convert back as int + gotIntOneValue, gotOneErr := rcvMsg.GetIntProperty(doubleOnePropName) + gotIntZeroValue, gotZeroErr := rcvMsg.GetIntProperty(doubleZeroPropName) + gotIntMinusOneValue, gotMinusOneErr := rcvMsg.GetIntProperty(doubleMinusOnePropName) + gotIntLargePosValue, gotLargePosErr := rcvMsg.GetIntProperty(doubleLargePosPropName) + gotIntLargeNegValue, gotLargeNegErr := rcvMsg.GetIntProperty(doubleLargeNegPropName) + gotIntLargePosDecimalValue, gotLargeDecPosErr := rcvMsg.GetIntProperty(doubleLargeDecimalPropName) + gotIntLargeNegDecimalValue, gotLargeDecNegErr := rcvMsg.GetIntProperty(doubleLargeNegativeDecimalPropName) + gotIntUnsetPropValue, gotUnsetErr := rcvMsg.GetIntProperty(unsetPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotLargeDecPosErr) + assert.Nil(t, gotLargeDecNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, 1, gotIntOneValue) + assert.Equal(t, 0, gotIntZeroValue) + assert.Equal(t, -1, gotIntMinusOneValue) + assert.Equal(t, 48632675, gotIntLargePosValue) + assert.Equal(t, -3789753467, gotIntLargeNegValue) + assert.Equal(t, 3867494, gotIntLargePosDecimalValue) + assert.Equal(t, -87654335674, gotIntLargeNegDecimalValue) + assert.Equal(t, 0, gotIntUnsetPropValue) + + // Convert back as string + gotStrOneValue, gotOneErr := rcvMsg.GetStringProperty(doubleOnePropName) + gotStrZeroValue, gotZeroErr := rcvMsg.GetStringProperty(doubleZeroPropName) + gotStrMinusOneValue, gotMinusOneErr := rcvMsg.GetStringProperty(doubleMinusOnePropName) + gotStrLargePosValue, gotLargePosErr := rcvMsg.GetStringProperty(doubleLargePosPropName) + gotStrLargeNegValue, gotLargeNegErr := rcvMsg.GetStringProperty(doubleLargeNegPropName) + gotStrLargePosDecimalValue, gotLargeDecPosErr := rcvMsg.GetStringProperty(doubleLargeDecimalPropName) + gotStrLargeNegDecimalValue, gotLargeDecNegErr := rcvMsg.GetStringProperty(doubleLargeNegativeDecimalPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotLargeDecPosErr) + assert.Nil(t, gotLargeDecNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, "1", *gotStrOneValue) + assert.Equal(t, "0", *gotStrZeroValue) + assert.Equal(t, "-1", *gotStrMinusOneValue) + assert.Equal(t, "4.8632675e+07", *gotStrLargePosValue) + assert.Equal(t, "-3.789753467e+09", *gotStrLargeNegValue) + assert.Equal(t, "3.86749368473625e+06", *gotStrLargePosDecimalValue) + assert.Equal(t, "-8.765433567438365e+10", *gotStrLargeNegDecimalValue) + + // Convert back as bool + gotBoolOneValue, gotOneErr := rcvMsg.GetBooleanProperty(doubleOnePropName) + gotBoolZeroValue, gotZeroErr := rcvMsg.GetBooleanProperty(doubleZeroPropName) + gotBoolMinusOneValue, gotMinusOneErr := rcvMsg.GetBooleanProperty(doubleMinusOnePropName) + gotBoolLargePosValue, gotLargePosErr := rcvMsg.GetBooleanProperty(doubleLargePosPropName) + gotBoolLargeNegValue, gotLargeNegErr := rcvMsg.GetBooleanProperty(doubleLargeNegPropName) + gotBoolLargePosDecimalValue, gotLargeDecPosErr := rcvMsg.GetBooleanProperty(doubleLargeDecimalPropName) + gotBoolLargeNegDecimalValue, gotLargeDecNegErr := rcvMsg.GetBooleanProperty(doubleLargeNegativeDecimalPropName) + assert.Nil(t, gotOneErr) + assert.Nil(t, gotZeroErr) + assert.Nil(t, gotMinusOneErr) + assert.Nil(t, gotLargePosErr) + assert.Nil(t, gotLargeNegErr) + assert.Nil(t, gotLargeDecPosErr) + assert.Nil(t, gotLargeDecNegErr) + assert.Nil(t, gotUnsetErr) + assert.Equal(t, true, gotBoolOneValue) + assert.Equal(t, false, gotBoolZeroValue) + assert.Equal(t, false, gotBoolMinusOneValue) + assert.Equal(t, false, gotBoolLargePosValue) + assert.Equal(t, false, gotBoolLargeNegValue) + assert.Equal(t, false, gotBoolLargePosDecimalValue) + assert.Equal(t, false, gotBoolLargeNegDecimalValue) + +} diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 662a549..2c0d501 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -337,6 +337,10 @@ func (msg *MessageImpl) GetStringProperty(name string) (*string, jms20subset.JMS retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) } + case bool: + valueStr = strconv.FormatBool(valueTyped) + case float64: + valueStr = fmt.Sprintf("%g", valueTyped) default: // TODO - other conversions } @@ -404,6 +408,17 @@ func (msg *MessageImpl) GetIntProperty(name string) (int, jms20subset.JMSExcepti retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) } + case bool: + if valueTyped { + valueRet = 1 + } + case float64: + s := fmt.Sprintf("%.0f", valueTyped) + valueRet, parseErr = strconv.Atoi(s) + if parseErr != nil { + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, + MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) + } default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) @@ -474,6 +489,10 @@ func (msg *MessageImpl) GetDoubleProperty(name string) (float64, jms20subset.JMS } case int64: valueRet = float64(valueTyped) + case bool: + if valueTyped { + valueRet = 1 + } default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) @@ -547,6 +566,11 @@ func (msg *MessageImpl) GetBooleanProperty(name string) (bool, jms20subset.JMSEx if valueTyped == 1 { valueRet = true } + case float64: + // Conversion from float64 to bool is true iff n=1 + if valueTyped == 1 { + valueRet = true + } default: // TODO - other conversions //fmt.Println("Other type", value, reflect.TypeOf(value)) From b6f5f71131508b7cd797f1a32e163f0c7a0d0217 Mon Sep 17 00:00:00 2001 From: Matt Roberts <32040584+matrober-uk@users.noreply.github.com> Date: Sun, 2 Jan 2022 15:06:10 +0000 Subject: [PATCH 12/12] Final tidy up for message property support - #39 --- README.md | 1 + jms20subset/Message.go | 2 +- messageproperties_test.go | 74 +++++++++++++++++++++++---------------- mqjms/MessageImpl.go | 58 ++++++++++++++++-------------- 4 files changed, 78 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index b7b4bac..4adb9ee 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ your own error handling or logging. * Send/receive a slice of bytes (BytesMessage) - [bytesmessage_test.go](bytesmessage_test.go) * Receive with wait [receivewithwait_test.go](receivewithwait_test.go) * Send a message as Persistent or NonPersistent - [deliverymode_test.go](deliverymode_test.go) +* Set a message property of type string, int, double or boolean - [messageproperties_test.go](messageproperties_test.go) * Get by CorrelationID - [getbycorrelid_test.go](getbycorrelid_test.go) * Request/reply messaging pattern - [requestreply_test.go](requestreply_test.go) * Send and receive under a local transaction - [local_transaction_test.go](local_transaction_test.go) diff --git a/jms20subset/Message.go b/jms20subset/Message.go index b12b165..7305bf7 100644 --- a/jms20subset/Message.go +++ b/jms20subset/Message.go @@ -84,7 +84,7 @@ type Message interface { // GetPropertyNames returns a slice of strings containing the name of every message // property on this message. - // Returns a zero length slice if no message properties are defined. + // Returns a zero length slice if no message properties are set. GetPropertyNames() ([]string, JMSException) // ClearProperties removes all message properties from this message. diff --git a/messageproperties_test.go b/messageproperties_test.go index f4de9a5..903e1b7 100644 --- a/messageproperties_test.go +++ b/messageproperties_test.go @@ -17,24 +17,10 @@ import ( "github.com/stretchr/testify/assert" ) -/* - * mq-golang: SetMP, DltMP, InqMP - * https://github.com/ibm-messaging/mq-golang/blob/95e9b8b09a1fc167747de7d066c49adb86e14dda/ibmmq/mqi.go#L1080 - * - * mq-golang sample application to set properties - * https://github.com/ibm-messaging/mq-golang/blob/master/samples/amqsprop.go#L49 - * - * JMS: SetStringProperty, GetStringProperty, - * https://github.com/eclipse-ee4j/messaging/blob/master/api/src/main/java/jakarta/jms/Message.java#L1119 - * - * Property conversion between types - * - */ - /* * Test the creation of a text message with a string property. */ -func TestStringPropertyTextMsg(t *testing.T) { +func TestPropertyStringTextMsg(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -133,6 +119,13 @@ func TestStringPropertyTextMsg(t *testing.T) { assert.Nil(t, propErr) assert.Nil(t, gotPropValue) + // Error checking on property names + emptyNameValue, emptyNameErr := rcvMsg.GetStringProperty("") + assert.NotNil(t, emptyNameErr) + assert.Equal(t, "2513", emptyNameErr.GetErrorCode()) + assert.Equal(t, "MQRC_PROPERTY_NAME_LENGTH_ERR", emptyNameErr.GetReason()) + assert.Nil(t, emptyNameValue) + } /* @@ -475,7 +468,7 @@ func TestPropertyClearProperties(t *testing.T) { /* * Test send and receive of a text message with a string property and no content. */ -func TestStringPropertyTextMessageNilBody(t *testing.T) { +func TestPropertyStringTextMessageNilBody(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -532,7 +525,7 @@ func TestStringPropertyTextMessageNilBody(t *testing.T) { * body. It's difficult to distinguish nil and empty string so we are expecting * that the received message will contain a nil body. */ -func TestStringPropertyTextMessageEmptyBody(t *testing.T) { +func TestPropertyStringTextMessageEmptyBody(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -599,7 +592,7 @@ func TestStringPropertyTextMessageEmptyBody(t *testing.T) { /* * Test the creation of a text message with an int property. */ -func TestIntProperty(t *testing.T) { +func TestPropertyInt(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -689,7 +682,7 @@ func TestIntProperty(t *testing.T) { gotPropValue, propErr = rcvMsg.GetIntProperty(propName) assert.Nil(t, propErr) assert.Equal(t, propValue, gotPropValue) - propExists, propErr = txtMsg.PropertyExists(propName) + propExists, propErr = rcvMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -704,16 +697,23 @@ func TestIntProperty(t *testing.T) { gotPropValue, propErr = rcvMsg.GetIntProperty(unsetPropName) assert.Nil(t, propErr) assert.Equal(t, 0, gotPropValue) - propExists, propErr = txtMsg.PropertyExists(unsetPropName) + propExists, propErr = rcvMsg.PropertyExists(unsetPropName) assert.Nil(t, propErr) assert.True(t, propExists) // exists, even though it is set to zero + // Error checking on property names + emptyNameValue, emptyNameErr := rcvMsg.GetStringProperty("") + assert.NotNil(t, emptyNameErr) + assert.Equal(t, "2513", emptyNameErr.GetErrorCode()) + assert.Equal(t, "MQRC_PROPERTY_NAME_LENGTH_ERR", emptyNameErr.GetReason()) + assert.Nil(t, emptyNameValue) + } /* * Test the creation of a text message with a double property. */ -func TestDoubleProperty(t *testing.T) { +func TestPropertyDouble(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -803,7 +803,7 @@ func TestDoubleProperty(t *testing.T) { gotPropValue, propErr = rcvMsg.GetDoubleProperty(propName) assert.Nil(t, propErr) assert.Equal(t, propValue, gotPropValue) - propExists, propErr = txtMsg.PropertyExists(propName) + propExists, propErr = rcvMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -818,16 +818,23 @@ func TestDoubleProperty(t *testing.T) { gotPropValue, propErr = rcvMsg.GetDoubleProperty(unsetPropName) assert.Nil(t, propErr) assert.Equal(t, float64(0), gotPropValue) - propExists, propErr = txtMsg.PropertyExists(unsetPropName) + propExists, propErr = rcvMsg.PropertyExists(unsetPropName) assert.Nil(t, propErr) assert.True(t, propExists) // exists, even though it is set to zero + // Error checking on property names + emptyNameValue, emptyNameErr := rcvMsg.GetStringProperty("") + assert.NotNil(t, emptyNameErr) + assert.Equal(t, "2513", emptyNameErr.GetErrorCode()) + assert.Equal(t, "MQRC_PROPERTY_NAME_LENGTH_ERR", emptyNameErr.GetReason()) + assert.Nil(t, emptyNameValue) + } /* * Test the creation of a text message with a boolean property. */ -func TestBooleanProperty(t *testing.T) { +func TestPropertyBoolean(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -917,7 +924,7 @@ func TestBooleanProperty(t *testing.T) { gotPropValue, propErr = rcvMsg.GetBooleanProperty(propName) assert.Nil(t, propErr) assert.Equal(t, propValue, gotPropValue) - propExists, propErr = txtMsg.PropertyExists(propName) + propExists, propErr = rcvMsg.PropertyExists(propName) assert.Nil(t, propErr) assert.True(t, propExists) // now exists @@ -932,10 +939,17 @@ func TestBooleanProperty(t *testing.T) { gotPropValue, propErr = rcvMsg.GetBooleanProperty(unsetPropName) assert.Nil(t, propErr) assert.Equal(t, false, gotPropValue) - propExists, propErr = txtMsg.PropertyExists(unsetPropName) + propExists, propErr = rcvMsg.PropertyExists(unsetPropName) assert.Nil(t, propErr) assert.True(t, propExists) // exists, even though it is set to zero + // Error checking on property names + emptyNameValue, emptyNameErr := rcvMsg.GetStringProperty("") + assert.NotNil(t, emptyNameErr) + assert.Equal(t, "2513", emptyNameErr.GetErrorCode()) + assert.Equal(t, "MQRC_PROPERTY_NAME_LENGTH_ERR", emptyNameErr.GetReason()) + assert.Nil(t, emptyNameValue) + } /* @@ -1092,7 +1106,7 @@ func TestPropertyBytesMsg(t *testing.T) { /* * Test the conversion between string message properties and other data types. */ -func TestPropertyTypesStringConversion(t *testing.T) { +func TestPropertyConversionString(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -1283,7 +1297,7 @@ func TestPropertyTypesStringConversion(t *testing.T) { /* * Test the conversion between different int message properties and other data types. */ -func TestPropertyTypesIntConversion(t *testing.T) { +func TestPropertyConversionInt(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -1421,7 +1435,7 @@ func TestPropertyTypesIntConversion(t *testing.T) { /* * Test the conversion between different int message properties and other data types. */ -func TestPropertyTypesBoolConversion(t *testing.T) { +func TestPropertyConversionBool(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() @@ -1499,7 +1513,7 @@ func TestPropertyTypesBoolConversion(t *testing.T) { /* * Test the conversion between different int message properties and other data types. */ -func TestPropertyTypesDoubleConversion(t *testing.T) { +func TestPropertyConversionDouble(t *testing.T) { // Loads CF parameters from connection_info.json and applicationApiKey.json in the Downloads directory cf, cfErr := mqjms.CreateConnectionFactoryFromDefaultJSONFiles() diff --git a/mqjms/MessageImpl.go b/mqjms/MessageImpl.go index 2c0d501..7f49b89 100644 --- a/mqjms/MessageImpl.go +++ b/mqjms/MessageImpl.go @@ -23,6 +23,8 @@ import ( const MessageImpl_PROPERTY_CONVERT_FAILED_REASON string = "MQJMS_E_BAD_TYPE" const MessageImpl_PROPERTY_CONVERT_FAILED_CODE string = "1055" +const MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_REASON string = "MQJMS_E_UNSUPPORTED_TYPE" +const MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_CODE string = "1056 " // MessageImpl contains the IBM MQ specific attributes that are // common to all types of message. @@ -316,7 +318,7 @@ func (msg *MessageImpl) SetStringProperty(name string, value *string) jms20subse // Returns nil if the named property is not set. func (msg *MessageImpl) GetStringProperty(name string) (*string, jms20subset.JMSException) { - var valueStr string + var valueStrPtr *string var retErr jms20subset.JMSException impo := ibmmq.NewMQIMPO() @@ -330,26 +332,30 @@ func (msg *MessageImpl) GetStringProperty(name string) (*string, jms20subset.JMS switch valueTyped := value.(type) { case string: - valueStr = valueTyped + valueStrPtr = &valueTyped case int64: - valueStr = strconv.FormatInt(valueTyped, 10) + valueStr := strconv.FormatInt(valueTyped, 10) + valueStrPtr = &valueStr if parseErr != nil { retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) } case bool: - valueStr = strconv.FormatBool(valueTyped) + valueStr := strconv.FormatBool(valueTyped) + valueStrPtr = &valueStr case float64: - valueStr = fmt.Sprintf("%g", valueTyped) + valueStr := fmt.Sprintf("%g", valueTyped) + valueStrPtr = &valueStr default: - // TODO - other conversions + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_REASON, + MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_CODE, parseErr) } } else { mqret := err.(*ibmmq.MQReturn) if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { // This indicates that the requested property does not exist. - // valueStr will remain with its default value of nil + // valueStr will remain with its default value return nil, nil } else { // Err was not nil @@ -357,9 +363,11 @@ func (msg *MessageImpl) GetStringProperty(name string) (*string, jms20subset.JMS errCode := strconv.Itoa(rcInt) reason := ibmmq.MQItoString("RC", rcInt) retErr = jms20subset.CreateJMSException(reason, errCode, mqret) + + valueStrPtr = nil } } - return &valueStr, retErr + return valueStrPtr, retErr } // SetIntProperty enables an application to set a int-type message property. @@ -404,10 +412,6 @@ func (msg *MessageImpl) GetIntProperty(name string) (int, jms20subset.JMSExcepti valueRet = int(valueTyped) case string: valueRet, parseErr = strconv.Atoi(valueTyped) - if parseErr != nil { - retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, - MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) - } case bool: if valueTyped { valueRet = 1 @@ -415,20 +419,22 @@ func (msg *MessageImpl) GetIntProperty(name string) (int, jms20subset.JMSExcepti case float64: s := fmt.Sprintf("%.0f", valueTyped) valueRet, parseErr = strconv.Atoi(s) - if parseErr != nil { - retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, - MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) - } default: - // TODO - other conversions - //fmt.Println("Other type", value, reflect.TypeOf(value)) + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_REASON, + MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_CODE, parseErr) } + + if parseErr != nil { + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_FAILED_REASON, + MessageImpl_PROPERTY_CONVERT_FAILED_CODE, parseErr) + } + } else { mqret := err.(*ibmmq.MQReturn) if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { // This indicates that the requested property does not exist. - // valueRet will remain with its default value of nil + // valueRet will remain with its default value return 0, nil } else { // Err was not nil @@ -494,15 +500,15 @@ func (msg *MessageImpl) GetDoubleProperty(name string) (float64, jms20subset.JMS valueRet = 1 } default: - // TODO - other conversions - //fmt.Println("Other type", value, reflect.TypeOf(value)) + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_REASON, + MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_CODE, parseErr) } } else { mqret := err.(*ibmmq.MQReturn) if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { // This indicates that the requested property does not exist. - // valueRet will remain with its default value of nil + // valueRet will remain with its default value return 0, nil } else { // Err was not nil @@ -572,15 +578,15 @@ func (msg *MessageImpl) GetBooleanProperty(name string) (bool, jms20subset.JMSEx valueRet = true } default: - // TODO - other conversions - //fmt.Println("Other type", value, reflect.TypeOf(value)) + retErr = jms20subset.CreateJMSException(MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_REASON, + MessageImpl_PROPERTY_CONVERT_NOTSUPPORTED_CODE, parseErr) } } else { mqret := err.(*ibmmq.MQReturn) if mqret.MQRC == ibmmq.MQRC_PROPERTY_NOT_AVAILABLE { // This indicates that the requested property does not exist. - // valueRet will remain with its default value of nil + // valueRet will remain with its default value return false, nil } else { // Err was not nil @@ -603,7 +609,7 @@ func (msg *MessageImpl) PropertyExists(name string) (bool, jms20subset.JMSExcept // GetPropertyNames returns a slice of strings containing the name of every message // property on this message. -// Returns a zero length slice if no message properties are defined. +// Returns a zero length slice if no message properties are set. func (msg *MessageImpl) GetPropertyNames() ([]string, jms20subset.JMSException) { _, propNames, retErr := msg.getPropertiesInternal("")