Skip to Content

Intro

Integration between SAP PI/PO and message oriented middleware / broker is one of commonly faced requirements – and this is where Java Message Service (JMS) is often used to fulfil this requirement. But JMS is a Java focused standard, so it is relatively complex to utilize JMS message broker in heterogeneous landscape of message producing and consuming systems and applications, which are running on various platforms and implemented using different programming languages. Even though some JMS brokers support variety of protocols which can be used by non-Java clients (a good example is support of Streaming Text Oriented Messaging Protocol (STOMP) for Ruby and Python applications and Microsoft Message Queuing Protocol (MSMQP) for .Net applications) and can be bridged to JMS, this brings complexity to configuration and maintenance of the JMS broker, increases technical debt, and in some cases is not technically feasible at all.

 

In heterogeneous landscape, an alternative to JMS broker and bridging to platform specific protocols is complete replacement of JMS broker with generic platform agnostic message broker – and this is where the one may benefit from using Advanced Message Queuing Protocol (AMQP). One of advantages of usage of AMQP in contrast to JMS, is that it is intended to unify message brokering across various platforms – in such a way that applications written in Java, PHP, JavaScript, Ruby, C#, Python and other languages can all communicate with a single message broker. It is important to have clear differentiation between JMS and AMQP: JMS, being a messaging standard, defines Java messaging API, whereas AMQP, being a messaging protocol, defines wire-level protocol and principles of message structure and transmission over the network, it doesn’t provide industry standard API.

 

Currently SAP PI/PO doesn’t support AMQP out of the box – in contrast to JMS adapter, there is no SAP standard adapter that implements AMQP client functionality and can communicate with message broker using AMQP.

One of options that can be used to overcome this standard limitation, is installation and usage of 3rd party adapter – for example, Advantco AMQP Adapter (https://www.advantco.com/product/adapter/amqp). Usage of this adapter was described by Peter Ha in his blogs Advantco AMQP Adapter for SAP PI. and SAP PI – RabbitMQ  AMQP 0-9-1 integration.

In this blog, I would like to describe another alternative which doesn’t require additional adapters and is based on usage of SAP PI/PO standard JMS adapter – to be more precise, usage of JMS based communication between SAP PI/PO and AMQP broker. In a demo, I use RabbitMQ AMQP broker – probably one of the most commonly used AMQP brokers at the moment. The idea behind this approach is to establish messaging interoperability between SAP PO and AMQP broker using JMS API which is well supported by JMS adapter of SAP PO.

RabbitMQ provides RabbitMQ JMS Connector which implements JMS 1.1 and enables JMS communication between JMS client (here, SAP PO) and RabbitMQ server. The connector consists of two parts:

  • JMS client library – to be deployed and used by JMS client;
  • JMS server plugin – to be installed on RabbitMQ server

Demo

Scope of the demo is to illustrate connectivity and interoperability of SAP PO system with RabbitMQ server where RabbitMQ is a receiver system. The same approach can be potentially used to configure scenarios where RabbitMQ is a sender system for SAP PO.

In RabbitMQ, following AMQP entities have been configured:

  • Durable exchange topic test.po.topic – used as a destination for messages produced by SAP PO;
  • Durable queue test.po.queue – used as a destination for arbitrary AMQP consumer.

For the exchange topic, binding to a queue has been created with routing key test_po.

The flow is depicted on the illustration below:

Flow.png

Configurations of exchange and queue in RabbitMQ are illustrated below:

RabbitMQ exchange.png

RabbitMQ queue.png

(if necessary, you may find overview of main concepts and terms used in RabbitMQ and AMQP in general at RabbitMQ – AMQP 0-9-1 Model Explained).

In the demo, I use RabbitMQ server which was downloaded from https://www.rabbitmq.com/ (release: RabbitMQ 3.5.6). JMS connector for RabbitMQ was downloaded from http://pivotal.io/ (release: JMS Client 1.4.4).

Installation and configuration of JMS connector is described in details in documentation provided by vendor (for example in “Installing and Configuring JMS Client for Pivotal RabbitMQ” located at Pivotal RabbitMQ Documentation | Pivotal Docs).

 

The first step is to install JMS server plugin on RabbitMQ server using command rabbitmq-plugins.

 

The next step is to deploy JMS driver (which is contained in downloaded RabbitMQ JMS client library) to SAP PO system. This is done using the standard approach which is applicable for deployment of any other JMS driver – for detailed steps, refer to the SAP Note 1138877. I used SAP SDA Maker Tool to simplify the process of the required SDA file assembly – regarding SDA Maker Tool, refer to the SAP Note 1987079. When assembling the SDA file, please note that not only RabbitMQ JMS client library shall be included in the archive, but also its dependencies, which all can be found in distribution of JMS connector for RabbitMQ and are listed in the documentation referred above.

 

After JMS driver for RabbitMQ is deployed to SAP PO system, we are ready to configure the receiver communication channel. The configured JMS receiver communication channel shall be configured to use generic JMS broker access mode – which is, transport protocol shall be Access JMS Provider Generically. There are already a couple of useful blogs which describe how to use generic JMS broker access mode in JMS adapter (that time, when integrating SAP PI/PO with Apache ActiveMQ JMS broker), so it may make sense to look through them to get more diversified outlook at this functionality:

Generally speaking, besides JMS common parameterization (tab “Parameters” > “Processing”), when configuring generic access to JMS broker, it is very important to provide advanced parameterization (tab “Parameters” > “Advanced”). Here we specify classes which are to be used by the communication channel when establishing connection to the broker (JMS connection factory implementing class) and to the destination registered on the broker (JMS destination implementing class), as well as arguments required to instantiate respective objects (connection and destination).

JMS receiver channel configuration.png

JMS connection factory implementing full class name is specified in parameter JMS.QueueConnectionFactoryImpl.classname, JMS destination implementing class – in parameter JMS.QueueImpl.classname. For each of specified classes, it is necessary to provide arguments which contain specification of connectivity details to the broker and destination details. There are two ways to achieve this:

  • Providing parameters as arguments for a constructor method of the respective class. Following this approach, parameters JMS.QueueConnectionFactoryImpl.constructor and JMS.QueueImpl.constructor (one constructor entry for each of classes specified earlier) should be added in communication channel advanced configuration. Argument values for each constructor entry are provided comma-delimited and preceded with argument type specification;
  • If the respective class has setter methods for effective parameters, they can be used instead of a constructor entry. Corresponding parameters in communication channel advanced configuration have follow naming convention JMS.QueueConnectionFactoryImpl.method.<method name> and JMS.QueueImpl.method.<method name>. Argument value for each setter method entry should also be preceded with argument type specification.

Please consult with documentation regarding connection factory and destination classes: not all classes may have setter methods, and it is critical to specify exactly same the amount of arguments of expected types as constructor method implementations expect (otherwise you will most likely observe NoSuchMethodException in SAP PO logs when this class constructor will be called by JMS adapter at runtime).

 

The complete list of parameters maintained in communication channel advanced configuration together with their values for this demo is as following:

 

Parameter Value
JMS.QueueConnectionFactoryImpl.classname com.rabbitmq.jms.admin.RMQConnectionFactory
JMS.QueueConnectionFactoryImpl.method.setHost java.lang.String e107184
JMS.QueueConnectionFactoryImpl.method.setPort java.lang.Integer 5672
JMS.QueueConnectionFactoryImpl.method.setUsername java.lang.String middleware_user
JMS.QueueConnectionFactoryImpl.method.setPassword java.lang.String TestPassword_2015!
JMS.QueueImpl.classname com.rabbitmq.jms.admin.RMQDestination
JMS.QueueImpl.method.setDestinationName java.lang.String Test_RabbitMQ_Topic
JMS.QueueImpl.method.setAmqpExchangeName java.lang.String test.po.topic
JMS.QueueImpl.method.setAmqpRoutingKey java.lang.String test_po
JMS.QueueImpl.method.setAmqpQueueName java.lang.String null
JMS.QueueImpl.method.setAmqp java.lang.Boolean true


If you prefer using arguments for a constructor method instead of setter methods, parameterization above can be re-written into following for JMS destination implementing class:

 

Parameter Value
JMS.QueueConnectionFactoryImpl.classname com.rabbitmq.jms.admin.RMQConnectionFactory
JMS.QueueConnectionFactoryImpl.method.setHost java.lang.String e107184
JMS.QueueConnectionFactoryImpl.method.setPort java.lang.Integer 5672
JMS.QueueConnectionFactoryImpl.method.setUsername java.lang.String middleware_user
JMS.QueueConnectionFactoryImpl.method.setPassword java.lang.String TestPassword_2015!
JMS.QueueImpl.classname com.rabbitmq.jms.admin.RMQDestination
JMS.QueueImpl.constructor java.lang.String Test_RabbitMQ_Topic, java.lang.String test.po.topic, java.lang.String test_po, java.lang.String null
JMS.QueueImpl.method.setAmqp java.lang.Boolean true

 

If you would like to establish connection to a non-default RabbitMQ virtual host, please also ensure that proper virtual host is specified in parameterization for invoked connection factory – this can be done by adding extra parameter JMS.QueueConnectionFactoryImpl.method.setVirtualHost with a value java.lang.String <virtual host name>.

 

The communication channel has been started successfully and got connected to RabbitMQ server:

JMS channel - startup.png


In sake of test, I send several messages for the developed scenario – few SOAP messages like the one below:

Message 1.png

PO messages.png

These messages can be immediately observed in the queue at RabbitMQ (which means, messages have been sent out by SAP PO, delivered to the topic in RabbitMQ and forwarded to the queue, from where it can now be retrieved and processed by some AMQP consumer application):

RabbitMQ - messages overview in topic.png

RabbitMQ - messages overview in queue.png

RabbitMQ - messages.png

Outro

As it can be seen, the nature of interoperability between SAP PO and RabbitMQ remains JMS based – even though message consumers are likely to be AMQP clients for RabbitMQ, message producer (SAP PO) is still a JMS client and doesn’t really utilize AMQP capabilities within this approach.

 

It should also be noted that the described implementation is vendor specific – in other words, the implementation and demo are based on functionality and libraries that are provided by RabbitMQ specifically for their AMQP broker. In case of using AMQP brokering application delivered by another vendor, the one should check documentation in order to verify if the used AMQP broker supports similar capabilities. Absence of generic solution and risk of it being inappropriate for some implementations of AMQP brokers is a major disadvantage of the described approach (in contrast to AMQP adapter which is designed to support literally any AMQP compatible broker) that should be taken into consideration when designing integration of SAP PO with the specific AMQP broker.

To report this post you need to login first.

21 Comments

You must be Logged on to comment or reply to a post.

  1. JUAN PEREZ

    Hi Vadim,

    I´m doing exactly the same, but i´m getting this error for class com.rabbitmq.jms.admin.RMQConnectionFactory:

    “A channel error occurred. Detailed error (if any): com.sap.aii.adapter.jms.api.connector.ConnectorException: Cannot construct connection factory using constructor: ConstructorInvocationException: Error executing constructor invocation: <br>{ConstructorInvocation<br>{className=com.rabbitmq.jms.admin.RMQConnectionFactory,<br>invokeParams=[]<br>}; java.lang.reflect.InvocationTargetException”

    Any suggestion???

    Warm regards

    Juan

    (0) 
    1. Vadim Klimov Post author

      Hi Juan,

      Can you please provide a list of parameters JMS.QueueConnectionFactoryImpl.* that you maintained in a communication channel, and their values (you may mask actual host/port/user/password, etc. – I’m interested in checking how the value is composed and which types are used)? You may also want to create a forum thread for this issue so that we don’t overload blog comments with a specific problem discussion.

      Regards,

      Vadim

      (1) 
      1. Piotr Radzki

        Hello Vladim,

        First of all – thanks for this blog!

        In my case parameters looks like that (I masked some of them) and I’m getting the following error.

        JMS.QueueConnectionFactoryImpl.classname com.rabbitmq.jms.admin.RMQConnectionFactory
        JMS.QueueConnectionFactoryImpl.method.setHost java.lang.String test.xxxx.xxx.xx
        JMS.QueueConnectionFactoryImpl.method.setPort java.lang.Integer xxxx
        JMS.QueueConnectionFactoryImpl.method.setUsername java.lang.String test
        JMS.QueueConnectionFactoryImpl.method.setPassword java.lang.String test
        JMS.QueueImpl.classname com.rabbitmq.jms.admin.RMQDestination
        JMS.QueueImpl.method.setDestinationName java.lang.String Test_RabbitMQ_Topic
        JMS.QueueImpl.method.setAmqpExchangeName java.lang.String e.test
        JMS.QueueImpl.method.setAmqpRoutingKey java.lang.String test
        JMS.QueueImpl.method.setAmqpQueueName java.lang.String q.test
        JMS.QueueImpl.method.setAmqp java.lang.Boolean true

        What about “Processing” tab of JMS receiver adapter, is there a need for any specific configuration or configuration using parameters is enough in “Advanced” tab ?

        On “Processing” tab of JMS receiver adapter I specified:

        Transactional JMS Session (Recommended) – is checked

        JMS ReplyTo Queue Name = RabbitMQReply

        JMS Queue/Topic User = test

        JMS Queue/Topic Password = test

         

        The rest I left as it was in default.

         

        Still getting this error:

        A channel error occurred. Detailed error (if any) : com.sap.aii.adapter.jms.api.connector.ConnectorException: Cannot construct connection factory using constructor: ConstructorInvocationException: Error executing constructor invocation:
        {ConstructorInvocation
        {className=com.rabbitmq.jms.admin.RMQConnectionFactory,
        invokeParams=[]
        }: java.lang.reflect.InvocationTargetException

        (0) 
        1. Vadim Klimov Post author

          Hello Piotr,

           

          Verbose trace of the failing communication channel at its startup shall provide mode details regarding what the issue is (InvocationTargetException is a higher level exception, which intention is to wrap the concrete nested exception thrown by an invoked method – here, constructor of the connection). If possible, it would be great to see XPI Inspector trace for this channel so that you can track all nested exceptions down to cause.

           

          Can you also give a try to setting:

          • Value of JMS.QueueImpl.method.setDestinationName same as value of JMS.QueueImpl.method.setAmqpExchangeName, which is an exchange name registered on RabbitMQ,
          • Value of JMS.QueueImpl.method.setAmqpQueueName equal to java.lang.String null.

          Together with this, please check if virtual hosts are used in RabbitMQ configuration – if so (and if not default virtual host is to be used), set additional parameter JMS.QueueConnectionFactoryImpl.method.setVirtualHost with a value java.lang.String <virtual host name>.

           

          I didn’t have to customize processing parameterization (tab ‘Processing’) – in my case, I left parameters on that tab with their default values.

           

          Regards,

          Vadim

          (1) 
          1. Piotr Radzki

            Hello Vadim,

            it was a while, in the meantime we found out that some of the libraries were missing in the initial deployment to SAP PO of RabbitMQ lib.

            After we deployed missing libraries and we started Communication Channel again we get “connection time out”. In general it was the issue related to Network setup and open connectivity between our SAP PO system and RabbitMQ system.

            After the connection is open now we are having the communication channel status as succesfully started and connected to destination.

            Unfortunatelly when we are testing and trying to send some dummy messages to RabbitMQ we are getting errors:

            reply-code=403, reply-text=ACCESS_REFUSED – access to exchange ‘jms.durable.topic’ in vhost ‘/’ refused for user ‘xxxxxx’, class-id=40, method-id=10

            instead of xxxx there is of course proper username.

            I think its related to the setup on RabbitMQ side and its addressed to my colelagues responsible for MQ. Looking forward to solve it finaly 🙂

            BR,

            Piotr

            (0) 
            1. Vadim Klimov Post author

              Hi Piotr,

              You are absolutely right: the error ACCESS_REFUSED that you mentioned, is not related to PO side, but is caused by missing write permissions for the given user to the given exchange on RabbitMQ side. RabbitMQ has good documentation about how permissions are managed and configured there: https://www.rabbitmq.com/access-control.html .

              Regards,

              Vadim

              (1) 
              1. Piotr Radzki

                RabbitMQ side was adjusted and I also removed one not necessary parameter from communication channel that I’ve set inititaly: JMS ReplyTo Queue Name.

                We have tested it and are able to send messages from SAP PO to RabbitMQ succesfully!

                Thanks for your blog it help a lot!

                Now I will setup scenario with Sender JMS adapter to allows RabbitMQ send messages to SAP PO. But I feel it will be much easier now.

                All the best for you Vadim!

                (1) 
                  1. Andrej Dukovski

                    Hi Piotr, hi everybody.

                    Ij

                    I have already successfully configured the receiver channel, thanks to Vadim.

                    But I have no luck with the sender channel.

                    Have you managed to successfully configure RabbitMQ sender?

                     

                    My ‘non-working’ configuration is as follows:

                     

                    JMS.QueueConnectionFactoryImpl.classname com.rabbitmq.jms.admin.RMQConnectionFactory
                    JMS.QueueConnectionFactoryImpl.method.setHost java.lang.String x.x.x.x
                    JMS.QueueConnectionFactoryImpl.method.setPort java.lang.Integer 5672
                    JMS.QueueConnectionFactoryImpl.method.setVirtualHost java.lang.String xxxxxxx
                    JMS.QueueConnectionFactoryImpl.method.setUsername java.lang.String middleware_user
                    JMS.QueueConnectionFactoryImpl.method.setPassword java.lang.String TestPassword
                    JMS.QueueImpl.classname com.rabbitmq.jms.admin.RMQDestination
                    JMS.QueueImpl.method.setDestinationName java.lang.String Test_RabbitMQ_Topic
                    JMS.QueueImpl.method.setAmqpExchangeName java.lang.String null
                    JMS.QueueImpl.method.setAmqpRoutingKey java.lang.String null
                    JMS.QueueImpl.method.setAmqpQueueName java.lang.String xxx.test.queue
                    JMS.QueueImpl.method.setAmqp java.lang.Boolean true

                     

                     

                    The channel is running (green) showing:

                     

                    connected to destination: RMQDestination{destinationName=’Test_RabbitMQ_Topic’, queue(permanent, amqp)’, amqpExchangeName=’null’, amqpRoutingKey=’null’, amqpQueueName=’xxx.test.queue’}

                     

                    but nothing is happening and no messages are pulled from the queue.

                    If I uncheck the “Use Message Listener Based Connector”, I get the following error:

                    Error receiving JMS message : while trying to invoke the method java.lang.Object.toString() of a null object returned from java.util.Map.get(java.lang.Object)

                     

                    I also wonder where to define the pooling interval (in processing tab or as an additional parameter)? Is the processing tab relevant at all? If so, should the listener based connector be checked or unchecked?

                     

                    Best regards,

                    Andrej

                     

                     

                    (0) 
                    1. Vadim Klimov Post author

                      Hi Andrej,

                      I recall we had the discussion on configuration of JMS adapter for polling messages from RabbitMQ with Sandra some time ago, where there were issues with particular parameterization required in JMS sender channel – hence, please have a look in a forum thread PI Sender JMS RabbitMQ Integration, it might be helpful in your case, too. Few points to pay attention to: a) in polling scenarios, JMS adapter shall poll from RabbitMQ’s queue (hence, topic specification in channel configuration is redundant), b) there are few JMS specific message properties that are advisable to be set in the message to make it successfully polled by JMS adapter (this originates from JMS specification).

                      As for polling interval, please use configuration parameters provided on the tab ‘Parameters’ > ‘Processing’.

                      Regards,

                      Vadim

                      (1) 
                      1. Andrej Dukovski

                        Dear Vadim,

                        Thank you very much for your quick reply. I solved the issue by following the thread you suggested.
                        Indeed, there was a problem with message properties.

                        Kind regards, Andrej

                        (0) 
                  2. Piotr Radzki

                    Hello Vadim,

                    I received requirement from my RabbitMQ colleague to provide 2 additional parameters ,they look like message header parameters but non JMS related:

                    type = ‘new’

                    content_type = ‘<object_name>’

                    Did you faced simillar requirement? As far as I can see its hard to push such parameters.

                    Do you know if its feasible using described here approach with JMS adapter and AMQP modules for Receiver adapter ?

                    BR,

                    Piotr

                    (0) 
                    1. Vadim Klimov Post author

                      Hi Piotr,

                      I’m not sure this kind of requirements can be fulfilled with the approached described here. Reason for this is, when we make use of RabbitMQ’s JMS client library, PI/PO system interacts with the message in the way as if it would be a generic JMS message (and has access to message’s JMS properties), but it will not have awareness of any additional properties that are not a part of JMS specification. Moreover, some properties are defaulted by RabbitMQ’s JMS client library – ‘content_type’ being one of them. It might be worth checking discussion we had in regards to a similar requirement – https://archive.sap.com/discussions/thread/3909380. Although it is now possible to set content type using native AMQP library of RabbitMQ, as to my knowledge and outlook into implementation of JMS library for RabbitMQ, this is not something that has been exposed within the JMS library.

                      Regards,

                      Vadim

                      (1) 
                      1. Piotr Radzki

                        Thanks Vadim, I have same understanding of the topic. Nevertheless thanks for looking back into this.

                        RabbitMQ colleagues made a workaround using RoutingKey and route the message properly based on that.

                         

                        BR,

                        Piotr

                        (0) 
                  3. Piotr Radzki

                    To elaborate more on the topic, what I was able to do so far is to influence message header parameters but not message properties:

                    I used described already appraoch specifying Additional JMS Message Properties:

                    I used adapter modules to populate value for these parameters:

                    It resulted with below messsage on RabbitMQ side, we have populated header parameters.

                    But actual requirement is to populate message properties as below:

                    Is it possible?

                     

                    (0) 
                    1. Vadim Klimov Post author

                      Not something I would be aware of. When we discussed possibility of manipulating header values for constructed messages earlier, this ended up with the similar approach you used – handling JMS headers of the message, not headers of the AMQP message.

                      (1) 
    2. Piotr Radzki

      Hello Juan,

       

      Did you solved this issue? Or maybe did you started a discussion in SCN?

      I’m facing exactly same error and looking actively for solution.

       

      Best Regards,

      Piotr

      (0) 
  2. Ravi Aalla

    Hi Vadim,

    We have tried SAP PI/PO with RabbitMQ AMQP Broker via JMS adapter as per your documentation and able to connect the server and publish the message from SAP PO JMS to RabbitMQ queue. However the message format is not readable i.e we tried to publish the soap message but the message contains the entire XML instead of actual message.Please find attached image for the sample message format in RabbitMQ queue.

    XML Message which we sent from SAP PO to RabbitMQ JMS Queue:

    <soapenv:Envelope xmlns:soapenv=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:urn=”urn::XXXXXXXXX:JMSPOC”>
    <soapenv:Header/>
    <soapenv:Body>
    <urn:MT_SOAP_Sen>
    <!–Zero or more repetitions:–>
    <Header>
    <ID>100</ID>
    <Text>Testing JMS</Text>
    </Header>
    </urn:MT_SOAP_Sen>
    </soapenv:Body>
    </soapenv:Envelope>

    Can you help us to resolve the issue.

    Regards,

    Ravi

    (0) 
    1. Vadim Klimov Post author

      Hello Ravi,

      It is likely to be related not to specifics of communication with RabbitMQ, but configuration of the used receiver communication channel in part of which sections of the message are passed to the JMS provider and how they are converted before the message is published to the JMS topic. Hence, in the JMS receiver communication channel, please check:

      • Tab ‘Parameters’ > ‘Processing’, configuration for ‘Mapping of message’. Here you can configure which part of the processed message will be dispatched to the JMS adapter;
      • Tab ‘Module’, which modules and with which configuration for them are configured before the module ‘SendBinarytoXIJMSService’ (the last in the sequence) is invoked. Here you can specify conversion logic for the processed message before it reaches the JMS adapter and gets published to the topic.

      Regards,

      Vadim

      (0) 
      1. Ravi Aalla

        Hi Vadim,

        Thanks for the reply.We have not used any conversion module,still we are facing same issue.I am attaching JMS adapter configuration screens. Please help me if anything is missed from my end.

         

        Thanks

        Ravi

        (0) 

Leave a Reply