In this post we first set-up a WebLogic environment that uses the WebLogic Messaging Bridge to forward messages. Next, we use the store-and-forward service to do the same. We will use Spring to test the set-ups. We end the post by looking at some performance considerations.

Messaging bridge

The WebLogic messaging bridge is a forwarding mechanism that provides interoperability between WebLogic JMS implementations, and between JMS and other messaging products. Use the Messaging Bridge to integrate messaging applications between:

  • Any two implementations of WebLogic JMS, including those from separate releases of WebLogic Server
  • WebLogic JMS implementations that reside in separate WebLogic domains
  • WebLogic JMS and a third-party JMS product

A messaging bridge instance forwards messages between a pair of bridge source and target destinations. These destinations are mapped to a pair of bridge source and target destinations. The messaging bridge reads messages from the source bridge destination and forwards those messages to the target bridge destination. For WebLogic JMS and third-party JMS products, a messaging bridge communicates with source and target destinations using the Java EE Connector Architecture (JCA) resource adapters provided with WebLogic Server. Two resource adapters are provided, i.e.,

  • eis.jms.WLSConnectionFactoryJNDIXA – Provides transaction semantics. Used when the required quality of service (QOS) is exactly-once (the message will be delivered from the sending destination to the receiving destination using XA transactions so that the receiver gets exactly one copy of each message). This envelopes a received message and sends it within a user transaction (XA/JTA). The following requirements apply to use of this resource adapter:
    • Any WebLogic Server implementation being bridged must be release 7.0 or later
    • The source and target JMS connection factories must be configured to use the XAConnectionFactory
  • eis.jms.WLSConnectionFactoryJNDINoTX – Provides no transaction semantics. Used when the required QOS is atmost-once (Makes sure that the receiving destination receives only a single copy of the message or does not receive it at all) or duplicate-okay (the bridge acknowledges receiving the message from the source destination only after writing it to the target destination, because this is done outside the scope of a transaction, failures after writing the message to the target and before acknowledging the source can result in duplicate messages being delivered but should never result in a message being lost, this type of delivery is better known as at-least-once delivery). If the requested QOS is atmost-once, the resource adapter uses AUTO_ACKNOWLEDGE mode. If the requested QOS is duplicate-okay, CLIENT_ACKNOWLEDGE is used.

An instance of the messaging bridge maps each source destination with a target destination. Each destination is configured using one of the adapters. Each bridge instance is targeted to a specific WebLogic Server instance. If the source is a distributed destination, the JMS consumer load balancing rules will associate the bridge with a single destination. In this case, it is best to connect a separate bridge instance to each member of the source destination. This leads to a proliferation of bridge instances that must be reconfigured if the cluster membership changes. The messaging bridge must be used when storing and forwarding messages between JMS destinations where one or both destinations are either hosted by foreign JMS providers or running on older versions of WebLogic Server (prior 9.0) that do not support the store-and-forward service.

Configuration

To set-up a messaging bridge, we need to first set-up the JMS resources involved in the bridge, i.e., the connection factories and destinations. In the example below we use two WebLogic environments. On one WebLogic environment we create the following:

  • Persistent store targeted to the admin server
  • JMS Server targeted to the admin server
  • JMS Module targeted to the admin server with the following resources:
    • Connection Factory default targeting enabled, with JNDI jms/BridgeConnectionFactory (and is XA enabled)
    • Destination (Queue) targeted through a sub deployment to the JMS Server, with JNDI jms/BridgeCompanyQueue

On the other environment we create the following:

  • A cluster consisting of two managed servers
  • Two JMS servers each targeted to a migratable target belonging to a managed server in the cluster (note that a migratable target is automatically created when a managed server is clustered
  • Two persistent stores each targeted to a managed server in the cluster
  • JMS module targeted to the cluster with the following resources:
    • Connection Factory default targeting enabled, with JNDI jms/ConnectionFactory (and is XA enabled)
    • Uniform distributed destination (queue) targeted through a subdeployment to the JMS Servers, with JNDI jms/CompanyQueue

Next, we configure two JMS bridge destinations. One source destination from which the messaging bridge instance reads messages and one target destination where the messaging bridge instance sends the messages it receives from the source destination. In the admin console

  • Open the tree services, messaging, bridges
  • Click JMS bridge destinations, click new and enter the following parameters:
    • Name: SourceDestination
    • Adapter JNDI Name: eis.jms.ConnectionFactoryJNDINoTx
    • Adapter Classpath: When connecting to a third-party JMS product, the bridge destination must supply the product’s CLASSPATH in the WebLogic Server CLASSPATH.
    • Connection URL: t3://192.168.1.50:7001
    • Connection Factory JNDI Name: jms/BridgeConnectionFactory
    • Destination JNDI Name: jms/BridgeCompanyQueue
  • Click OK

The target destination is configured as

  • Open the tree services, messaging, bridges
  • Click JMS bridge destinations, click new and enter the following parameters:
    • Name: TargetDestination
    • Adapter JNDI Name: eis.jms.ConnectionFactoryJNDINoTx
    • Adapter Classpath: When connecting to a third-party JMS product, the bridge destination must supply the product’s CLASSPATH in the WebLogic Server CLASSPATH.
    • Connection URL: t3://192.168.1.50:9001,192.168.1.50:9002
    • Connection Factory JNDI Name: jms/ConnectionFactory
    • Destination JNDI Name: jms/CompanyQueue
  • Click OK

The actual messaging bridge can be created as follows:

  • Open the tree services, messaging, bridges
  • Click new and enter the following parameters:
    • Name: WebLogicToWebLogicBridge
    • Selector: can be left empty
    • Quality Of Service: atmost-once
    • Started: enabled
  • Click next and select the source destination in our case this is SourceDestination
  • Click next and select the messaging provider in our case this is WebLogic Server 7.0 or higher
  • Click next and select the target destination in our case this is TargetDestination
  • Click next and select the messaging provider for the target in our case this is WebLogic Server 7.0 or higher
  • Click next and target the messaging bridge to the server that holds to the source destination, which is our case is the adminserver
  • Click next and click finish

Note that after the creation the messaging bridge can be fine tuned.

WLST

The following script shows an example how the messaging bridge can be set-up using WLST

print 'CONNECT TO ADMIN SERVER';
connect('weblogic', 'magic12c', 't3://192.168.1.50:7001');

print 'START EDIT MODE';
edit();
startEdit();

print 'CREATE SOURCE JMS BRIDGE DESTINATION';
cmo.createJMSBridgeDestination('SourceDestination');
sourcedestination = cmo.lookupJMSBridgeDestination('SourceDestination');
sourcedestination.setClasspath('');
sourcedestination.setConnectionURL('t3://192.168.1.50:7001');
sourcedestination.setAdapterJNDIName('eis.jms.WLSConnectionFactoryJNDINoTX');
sourcedestination.setConnectionFactoryJNDIName('jms/BridgeConnectionFactory');
sourcedestination.setDestinationJNDIName('jms/BridgeCompanyQueue');

print 'CREATE TARGET JMS BRIDGE DESTINATION';
cmo.createJMSBridgeDestination('TargetDestination');
targetdestination = cmo.lookupJMSBridgeDestination('TargetDestination');
targetdestination.setClasspath('');
targetdestination.setConnectionURL('t3://192.168.1.50:9001,192.168.1.50:9002');
targetdestination.setAdapterJNDIName('eis.jms.WLSConnectionFactoryJNDINoTX');
targetdestination.setConnectionFactoryJNDIName('jms/ConnectionFactory');
targetdestination.setDestinationJNDIName('jms/CompanyQueue');

print 'CREATE MESSAGING BRIDGE';
cmo.createMessagingBridge('Bridge');
bridge = cmo.lookupMessagingBridge('Bridge');
adminserver = cmo.lookupServer('AdminServer');
targets = bridge.getTargets();
targets.append(adminserver);
bridge.setTargets(targets);
bridge.setSourceDestination(sourcedestination);
bridge.setTargetDestination(targetdestination);
bridge.setStarted(true);
bridge.setSelector('');
bridge.setQualityOfService('Atmost-once');

print 'SAVE AND ACTIVATE CHANGES';
save();
activate(block='true');

Test

To test if the set-up works we will use a Spring client. Make sure the wlclient and wljmsclient jar files are on the classpath. To set-up a JMS producer using Spring we can use the following configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="sourceJndiTemplate" class="org.springframework.jndi.JndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
                <prop key="java.naming.provider.url">t3://192.168.1.50:7001</prop>
            </props>
        </property>
    </bean>
    <bean id="sourceConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="sourceJndiTemplate"/>
        <property name="jndiName" value="jms/BridgeConnectionFactory"/>
    </bean>
    <bean id="sourceDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="sourceJndiTemplate"/>
        <property name="jndiName" value="jms/BridgeCompanyQueue"/>
    </bean>
    <bean id="sourceJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="sourceConnectionFactory"/>
        <property name="defaultDestination" ref="sourceDestination"/>
    </bean>
    <bean id="sender" class="model.logic.JMSSender">
        <property name="jmsTemplate" ref="sourceJmsTemplate"/>
    </bean>
</beans>

To send a message by using the configured JMSTemplate we can use the following class:

package model.logic;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import java.util.Random;

public class JMSSender {

    private Random generator = new Random();
    private JmsTemplate jmsTemplate;

    public JMSSender() {
    }

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendMessage() {
        getJmsTemplate().send(new MessageCreator(){
            public Message createMessage(Session session) throws JMSException {
                TextMessage message = session.createTextMessage();
                message.setText(Long.toString(Math.abs(generator.nextLong()), 36));
                return message;
            }
        });
    }
}

To run the class we can use:

package model.test;

import model.logic.JMSSender;
import model.utils.SpringUtilities;

public class JMSTest {

    public static void main(String[] args) {
        JMSSender jmsSender = SpringUtilities.getJMSSender();
        jmsSender.sendMessage();
    }
}

To consume a message we create another client using Spring’s SimpleMessageListenerContainer. We have the following configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="targetJndiTemplate" class="org.springframework.jndi.JndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
                <prop key="java.naming.provider.url">t3://192.168.1.50:9001,192.168.1.50:9002</prop>
            </props>
        </property>
    </bean>
    <bean id="targetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="targetJndiTemplate"/>
        <property name="jndiName" value="jms/ConnectionFactory"/>
    </bean>
    <bean id="targetDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="targetJndiTemplate"/>
        <property name="jndiName" value="jms/CompanyQueue"/>
    </bean>
    <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="targetConnectionFactory"/>
        <property name="destination" ref="targetDestination"/>
        <property name="messageListener" ref="receiver"/>
    </bean>
    <bean id="receiver" class="model.logic.JMSReceiver"/>
</beans>

To receive a message asynchronously we use:

package model.logic;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class JMSReceiver implements MessageListener {
    public JMSReceiver() {
    }

    public void onMessage(Message message) {
        TextMessage text = (TextMessage)message;
        try {
            message.acknowledge();
            System.out.println("received the following message: " + text.getText());
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

Store-and-forward service

The Store-and-Forward (SAF) service enables WebLogic Server to deliver messages reliably between applications that are distributed across WebLogic Server instances. JMS modules utilize the SAF service to enable local JMS message producers to reliably send messages to remote queues or topics without worrying about the availability of the remote environment. If the destination is not available at the moment the messages are sent, either because of network problems or system failures, then the messages are saved on a local server instance, and are forwarded to the remote destination once it becomes available. The SAF Service should be used when forwarding JMS messages between WebLogic Server 9.x or later domains. The SAF service can deliver messages:

  • Between two stand-alone server instances
  • Between server instances in a cluster
  • Across two clusters in a domain
  • Across separate domains

When not to use the SAF service:

  • Forwarding messages to prior releases of WebLogic Server
  • Interoperating with third-party JMS products. For these tasks, we should use the WebLogic Messaging Bridge
  • When using temporary destinations with the JMSReplyTo field to return a response to a request

Additionally, when using JMS SAF, an application can only receive messages directly from a remote server, and only when the remote server is available. Both the store-and-forward service and the messaging bridge provide JMS applications a message forwarding agent-based technology. A thing to note is that the store-and-forward service uses an internal duplicate elimination algorithm that does not require XA transactions and thus will perform better for the exactly-once quality of service. The store-and-forward service uses agents to store and forward the messages between the sending and receiving sides. We must configure store-and-forward agents to support sending, receiving, or both depending on the set-up being used:

  • JMS sending – only a sending agent
  • JMS receiving – no agent needed
  • Web Services Reliable Messaging sending – sending and receiving agent
  • Web Services Reliable Messaging receiving – receiving agent

SAF agents are similar to JMS servers in that they have persistent stores, paging directories, and destinations, as well as quotas, thresholds, and other similar configuration parameters. The primary difference is that SAF agents only support imported destinations, a local representation of remote destinations to which messages are stored locally and then forwarded. SAF agents also support targeting to migratable targets to enable SAF agent service migration. Reliability in SAF is time-based in that the time-to-live attribute determines how long the agent will attempt to forward the message before expiring it. When setting a time-to-live on one of the SAF objects, a value of -1 means that the value is not set, 0 means that the message never expires, and a positive value defines the number of milliseconds after the message was created that the message will expire. If a message expires, SAF error handling provides four expiration policies from which to choose: Discard, Log, Redirect, and Always-forward. Always-forward ignores the time-to-live setting on the imported destinations and any message expiration time and forwards the message even after it has expired. Typically, this option would be used if the application had expiration policies set up on the remote destinations and we want the expired messages to be handled using these policies.

Configuration

To set-up a store-and-forward service we can use the following steps. First, we create a SAF agent:

  • In the admin console click services, messaging, store and forward agent
  • Click new and enter the following parameters:
    • name: SAFAgent
    • persistent store: AdminFileStore (or create a new one)
    • agent type: sending-only
  • Click next and enter the following parameters:
    • target: AdminServer (the same as the target of the persistent store)

Next, create a JMS module or use an existing one and create a subdeployment for the SAF agent

  • Click subdeployments, click new and enter the following parameters:
    • subdeployment name: SAFSubDeployment
  • Click next and select as target the created SAF agent
  • Click finish

Subsequently, we create a remote SAF context

  • Click the configuration tab of the JMS module
  • Click new, select remote SAF context, click next and enter the following parameters:
    • name: RemoteSAFContext
    • URL: t3://192.168.1.50:9001,192.168.1.50:9002
    • username: weblogic (admin username)
    • password: magic12c (admin password)
    • confirm password: magic12c
  • Click OK

Next, create SAF imported destinations

  • Click the configuration tab of the JMS module
  • Click new, select SAF imported destinations, click next and enter the following parameters:
    • name: SAFImportedDestinations
    • JNDI prefix: jms/ (this can be used for the local destination counterparts of remote destinations to which the SAF agent is connecting)
    • remote SAF context: RemoteSAFContext
  • Click next and click advanced targeting
  • Select SAFSubDeployment and click finish

In the last step we will add remote queues to the created SAF imported destination

  • Click the create SAF imported destination
  • Click the queues, configuration tab, click new and enter the following parameters:
    • name: SAFQueue
    • remote JNDI name: jms/CompanyQueue
  • Click OK
  • Click SAFQueue and set the local JNDI name to SAFCompanyQueue (note that we can now look up the local queue by using jms/SAFCompanyQueue, in which jms/ is the JNDI prefix we set earlier)
  • Save the configuration

To check if the configuration is correct check the server logging, something like the following should be present

####<Apr 16, 2012 10:43:49 AM CEST> <Info> <JMS> <axis-into-ict.nl> <AdminServer> <[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1334565829666> <BEA-040506> <The JMS store-and-forward (SAF) forwarder has successfully connected to the remote destination "t3://192.168.1.50:9001,192.168.1.50:9002/jms/CompanyQueue".>

When the servers to which the SAF agent is connecting are not present the following exception is observed

####<Apr 16, 2012 10:43:07 AM CEST> <Info> <JMS> <axis-into-ict.nl> <AdminServer> <[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1334565787556> <BEA-040507> <The JMS store-and-forward (SAF) forwarder failed to connect to the remote destination "t3://192.168.1.50:9001,192.168.1.50:9002/jms/CompanyQueue", because of javax.naming.CommunicationException [Root exception is java.net.ConnectException: t3://192.168.1.50:9001,192.168.1.50:9002: Destination unreachable; nested exception is:
	java.net.ConnectException: Connection refused; No available router to destination]
	at weblogic.jndi.internal.ExceptionTranslator.toNamingException(ExceptionTranslator.java:40)
	at weblogic.jndi.WLInitialContextFactoryDelegate.toNamingException(WLInitialContextFactoryDelegate.java:767)
	at weblogic.jndi.WLInitialContextFactoryDelegate.getInitialContext(WLInitialContextFactoryDelegate.java:366)
	at weblogic.jndi.Environment.getContext(Environment.java:315)
	at weblogic.jndi.Environment.getContext(Environment.java:285)
	at weblogic.jndi.Environment.createInitialContext(Environment.java:208)
	at weblogic.jndi.Environment.getInitialContext(Environment.java:192)
	at weblogic.jndi.Environment.getInitialContext(Environment.java:170)
	at weblogic.jndi.Environment.getContext(Environment.java:215)
	at weblogic.jms.forwarder.Forwarder.getInitialContext(Forwarder.java:428)
	at weblogic.jms.forwarder.Forwarder.connectTarget(Forwarder.java:447)
	at weblogic.jms.forwarder.Forwarder.reconnect(Forwarder.java:270)
	at weblogic.jms.forwarder.Forwarder.timerExpired(Forwarder.java:335)
	at weblogic.timers.internal.TimerImpl.run(TimerImpl.java:293)
	at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:545)
	at weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
	at weblogic.work.ExecuteThread.run(ExecuteThread.java:221)
Caused by: java.net.ConnectException: t3://192.168.1.50:9001,192.168.1.50:9002: Destination unreachable; nested exception is:
	java.net.ConnectException: Connection refused; No available router to destination
	at weblogic.rjvm.RJVMFinder.findOrCreateInternal(RJVMFinder.java:216)
	at weblogic.rjvm.RJVMFinder.findOrCreate(RJVMFinder.java:170)
	at weblogic.rjvm.ServerURL.findOrCreateRJVM(ServerURL.java:165)
	at weblogic.jndi.WLInitialContextFactoryDelegate$1.run(WLInitialContextFactoryDelegate.java:345)
	at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:363)
	at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:146)
	at weblogic.jndi.WLInitialContextFactoryDelegate.getInitialContext(WLInitialContextFactoryDelegate.java:340)
	... 14 more
Caused by: java.rmi.ConnectException: Destination unreachable; nested exception is:
	java.net.ConnectException: Connection refused; No available router to destination
	at weblogic.rjvm.ConnectionManager.bootstrap(ConnectionManager.java:470)
	at weblogic.rjvm.ConnectionManager.bootstrap(ConnectionManager.java:321)
	at weblogic.rjvm.RJVMManager.findOrCreateRemoteInternal(RJVMManager.java:260)
	at weblogic.rjvm.RJVMManager.findOrCreate(RJVMManager.java:197)
	at weblogic.rjvm.RJVMFinder.findOrCreateRemoteServer(RJVMFinder.java:238)
	at weblogic.rjvm.RJVMFinder.findOrCreateRemoteCluster(RJVMFinder.java:316)
	at weblogic.rjvm.RJVMFinder.findOrCreateInternal(RJVMFinder.java:205)
	... 20 more
.>

As can be seen from the previous log line (which in time is happening later), when the servers become available the SAF agent connects to the servers.

WLST

The following script shows an example how the save-and-forward service can be set-up using WLST

print 'CONNECT TO ADMIN SERVER';
connect('weblogic', 'magic12c', 't3://192.168.1.50:7001');

print 'START EDIT MODE';
edit();
startEdit();

print 'CREATE FILE STORE';
cmo.createFileStore('SAFFileStore');
saffilestore = cmo.lookupFileStore('SAFFileStore');
saffilestore.setDirectory('/home/oracle/weblogic12.1.1/configuration/applications/base_domain');
targets = saffilestore.getTargets();
adminserver = cmo.lookupServer('AdminServer');
targets.append(adminserver);
saffilestore.setTargets(targets);

print 'SAVE, ACTIVATE CHANGES AND START EDIT';
save();
activate(block='true');
startEdit();

print 'CREATE SAF Agent';
cmo.createSAFAgent('SAFAgent');
safagent = cmo.lookupSAFAgent('SAFAgent');
safagent.setStore(saffilestore);
safagent.setTargets(targets);
safagent.setServiceType('Sending-only');

print 'SAVE, ACTIVATE CHANGES AND START EDIT';
save();
activate(block='true');
startEdit();

print 'CREATE JMS MODULE';
cmo.createJMSSystemResource('jms-saf-module');
safmodule = cmo.lookupJMSSystemResource('jms-saf-module');
safmodule.setTargets(targets);
safmodule.createSubDeployment('SAFSubDeployment');
cd('/JMSSystemResources/jms-saf-module/SubDeployments/SAFSubDeployment');
set('Targets',jarray.array([ObjectName('com.bea:Name=SAFAgent,Type=SAFAgent')], ObjectName));
cd('/');

print 'SAVE, ACTIVATE CHANGES AND START EDIT';
save();
activate(block='true');
startEdit();

print 'OBTAIN JMS RESOURCE';
resource = safmodule.getJMSResource();

print 'CREATE CONNECTION FACTORY';
resource.createConnectionFactory('SAFConnectionFactory');
connectionfactory = resource.lookupConnectionFactory('SAFConnectionFactory');
connectionfactory.setJNDIName('jms/SAFConnectionFactory');
connectionfactory.setDefaultTargetingEnabled(true);
connectionfactory.getTransactionParams().setTransactionTimeout(3600);
connectionfactory.getTransactionParams().setXAConnectionFactoryEnabled(true);
connectionfactory.getSecurityParams().setAttachJMSXUserId(false);
connectionfactory.getClientParams().setClientIdPolicy('Restricted');
connectionfactory.getClientParams().setSubscriptionSharingPolicy('Exclusive');
connectionfactory.getClientParams().setMessagesMaximum(10);

print 'SAVE, ACTIVATE CHANGES AND START EDIT';
save();
activate(block='true');
startEdit();

print 'CREATE SAF REMOTE CONTEXT';
resource.createSAFRemoteContext('RemoteSAFContext');
safcontext = resource.lookupSAFRemoteContext('RemoteSAFContext');
safcontext.getSAFLoginContext().setLoginURL('t3://192.168.1.50:9001,192.168.1.50:9002');
safcontext.getSAFLoginContext().setUsername('weblogic');
safcontext.getSAFLoginContext().setPassword('magic12c');

print 'SAVE, ACTIVATE CHANGES AND START EDIT';
save();
activate(block='true');
startEdit();

print 'CREATE SAF IMPORTED DESTINATIONS';
resource.createSAFImportedDestinations('SAFImportedDestinations');
importeddestinations = resource.lookupSAFImportedDestinations('SAFImportedDestinations');
importeddestinations.setJNDIPrefix('jms/');
importeddestinations.setSAFRemoteContext(safcontext);
importeddestinations.setSAFErrorHandling(None);
importeddestinations.setTimeToLiveDefault(0);
importeddestinations.setUseSAFTimeToLiveDefault(false);
importeddestinations.setSubDeploymentName('SAFSubDeployment');

print 'SAVE, ACTIVATE CHANGES AND START EDIT';
save();
activate(block='true');
startEdit();

print 'ADD QUEUE TO THE SAF IMPORTED DESTINATIONS';
importeddestinations.createSAFQueue('SAFQueue');
safqueue = importeddestinations.lookupSAFQueue('SAFQueue');
safqueue.setRemoteJNDIName('jms/CompanyQueue');
safqueue.setLocalJNDIName('SAFCompanyQueue');

print 'SAVE AND ACTIVATE CHANGES';
save();
activate(block='true');

Test

To test the set-up we will again use Spring. To set-up the sending end, we will use the following configuration

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="sourceJndiTemplate" class="org.springframework.jndi.JndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
                <prop key="java.naming.provider.url">t3://192.168.1.50:7001</prop>
            </props>
        </property>
    </bean>
    <bean id="sourceConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="sourceJndiTemplate"/>
        <property name="jndiName" value="jms/SAFConnectionFactory"/>
    </bean>
    <bean id="sourceDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="sourceJndiTemplate"/>
        <property name="jndiName" value="jms/SAFCompanyQueue"/>
    </bean>
    <bean id="sourceJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="sourceConnectionFactory"/>
        <property name="defaultDestination" ref="sourceDestination"/>
    </bean>
    <bean id="timerListener" class="org.springframework.scheduling.commonj.ScheduledTimerListener">
        <property name="delay" value="10000"/>
        <property name="period" value="30000"/>
        <property name="runnable" ref="sender"/>
    </bean>
    <bean id="timerFactory" class="org.springframework.scheduling.commonj.TimerManagerFactoryBean">
        <property name="timerManagerName" value="java:comp/env/tm/default"/>
        <property name="resourceRef" value="true"/>
        <property name="scheduledTimerListeners">
            <list>
                <ref bean="timerListener"/>
            </list>
        </property>
    </bean>
    <bean id="sender" class="model.logic.JMSSender">
        <property name="jmsTemplate" ref="sourceJmsTemplate"/>
    </bean>
</beans>

in which the sender bean looks as follows

package model.logic;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import java.util.Random;

public class JMSSender implements Runnable {

    private Random generator = new Random();
    private JmsTemplate jmsTemplate;

    public JMSSender() {
    }

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendMessage() {
        getJmsTemplate().send(new MessageCreator(){
            public Message createMessage(Session session) throws JMSException {
                TextMessage message = session.createTextMessage();
                message.setText(Long.toString(Math.abs(generator.nextLong()), 36));
                return message;
            }
        });
    }

    public void run() {
        sendMessage();
    }
}

To set-up the receiving end, we will use the following configuration

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="targetJndiTemplate" class="org.springframework.jndi.JndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
                <prop key="java.naming.provider.url">t3://192.168.1.50:9001,192.168.1.50:9002</prop>
            </props>
        </property>
    </bean>
    <bean id="targetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="targetJndiTemplate"/>
        <property name="jndiName" value="jms/ConnectionFactory"/>
    </bean>
    <bean id="targetDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="targetJndiTemplate"/>
        <property name="jndiName" value="jms/CompanyQueue"/>
    </bean>
    <bean id="taskExecutor" class="org.springframework.scheduling.commonj.WorkManagerTaskExecutor">
        <property name="workManagerName" value="java:comp/env/default"/>
        <property name="resourceRef" value="true"/>
    </bean>
    <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="targetConnectionFactory"/>
        <property name="destination" ref="targetDestination"/>
        <property name="messageListener" ref="receiver"/>
        <property name="taskExecutor" ref="taskExecutor"/>
    </bean>
    <bean id="receiver" class="model.logic.JMSReceiver"/>
</beans>

in which the receiver bean looks as follows

package model.logic;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class JMSReceiver implements MessageListener {
    public JMSReceiver() {
    }

    public void onMessage(Message message) {
        TextMessage text = (TextMessage)message;
        try {
            message.acknowledge();
            System.out.println("received the following message: " + text.getText());
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

We deploy the sender to the AdminServer and the receiver to the cluster (the server set-up can be found here). To deploy the application we add a web.xml with the following contents

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
           version="3.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- default weblogic work manager -->
    <resource-ref>
        <res-ref-name>default</res-ref-name>
        <res-type>commonj.work.WorkManager</res-type>
        <res-auth>Container</res-auth>
        <res-sharing-scope>Shareable</res-sharing-scope>
    </resource-ref>
    <!-- default weblogic timer manager -->
    <resource-ref>
        <res-ref-name>tm/default</res-ref-name>
        <res-type>commonj.timers.TimerManager</res-type>
        <res-auth>Container</res-auth>
        <res-sharing-scope>Shareable</res-sharing-scope>
    </resource-ref>
    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>test.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/testservlet</url-pattern>
    </servlet-mapping>
</web-app>

What happens when the applications run is that a JMS producer (sender) is connected to the SAF environment on the AdminServer. The JMS producer sends messages every 30 seconds, which is configured by using a commonj timer manager. The message send to the SAF queue is being forwarded to the jms/CompanyQueue (a distributed queue) to which a JMS consumer (receiver) is listening by using a SimpleMessageListenerContainer that is coupled to a commonj work manager in order to make sure the thread spawned by the listener is managed by the WebLogic Server. In the logging the following is observed

Logging server1 in the cluster
Apr 16, 2012 11:41:02 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
Apr 16, 2012 11:41:02 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.web.context.support.XmlWebApplicationContext@1e3c6703: display name [Root WebApplicationContext]; startup date [Mon Apr 16 11:41:02 CEST 2012]; root of context hierarchy
Apr 16, 2012 11:41:03 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-config.xml]
Apr 16, 2012 11:41:03 AM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.web.context.support.XmlWebApplicationContext@1e3c6703]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1e4efd06
Apr 16, 2012 11:41:03 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1e4efd06: defining beans [sourceJndiTemplate,targetJndiTemplate,sourceConnectionFactory,sourceDestination,sourceJmsTemplate,targetConnectionFactory,targetDestination,taskExecutor,timerListener,timerFactory,org.springframework.jms.listener.SimpleMessageListenerContainer#0,sender,receiver]; root of factory hierarchy
Apr 16, 2012 11:41:03 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 842 ms
received the following message: d8nv7v6vgm5j
received the following message: 1be4omcwwiy61
received the following message: 19y6wh44hora6
received the following message: ha5hp6gaw6ql
received the following message: xercuxsl5qwf
received the following message: 1vvx5lptldi8r
received the following message: 1cgsdl10dkokz
received the following message: qfmd6v14tbb6
received the following message: 53hsnn8dfmor
received the following message: 8uaecn6919mi
received the following message: 8i93wuglm4lj
received the following message: 11d5bkqc5vdnq
received the following message: wc9hpcqmzjj9
received the following message: 17zj64mnvj7d9
received the following message: 1dt5g68vv9d50
received the following message: 13ch0ee5ofrzb
received the following message: 121t0btwasrqs
received the following message: 1lr8qb6il7nac
received the following message: 1l6lm4lge9tjx
received the following message: 15bgb2y3qzxl8
received the following message: vlryb3s3cn1o
received the following message: 189ahnkk5me1n
received the following message: 1wlhom58c3u01

Logging server2 in the cluster
Apr 16, 2012 11:41:02 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
Apr 16, 2012 11:41:02 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.web.context.support.XmlWebApplicationContext@1e9e9ebc: display name [Root WebApplicationContext]; startup date [Mon Apr 16 11:41:02 CEST 2012]; root of context hierarchy
Apr 16, 2012 11:41:03 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-config.xml]
Apr 16, 2012 11:41:03 AM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.web.context.support.XmlWebApplicationContext@1e9e9ebc]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1ec29f17
Apr 16, 2012 11:41:03 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1ec29f17: defining beans [sourceJndiTemplate,targetJndiTemplate,sourceConnectionFactory,sourceDestination,sourceJmsTemplate,targetConnectionFactory,targetDestination,taskExecutor,timerListener,timerFactory,org.springframework.jms.listener.SimpleMessageListenerContainer#0,sender,receiver]; root of factory hierarchy
Apr 16, 2012 11:41:03 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 686 ms
received the following message: 1uwivfp2ngq1t
received the following message: bo4z7iakwqqv
received the following message: 1sdrelhucxxed
received the following message: 99xztxrc3f9f
received the following message: 1u8ej4ls0avqt
received the following message: hhkpei2bqyaw
received the following message: 2nid8k122rnd
received the following message: m2n72h6ajiao
received the following message: 1wpp89uiqttrw
received the following message: 18ecmmhgmmheu
received the following message: 1neja8ptey39a
received the following message: 1iu5em4cknpaa
received the following message: p89q16c2npbp
received the following message: 13gx5jqyzh57n
received the following message: 6lcjw5vjpttp
received the following message: 74wvvr4ey35s
received the following message: 1mcnjxzy2xp6f
received the following message: 12d6ybdpmi2wp

The extra messages in server1 are there because when running Spring as a client instead of being deployed to the WebLogic cluster, the receiver is only registered to one queue in the distributed queue. To test the set-up (without the timer manager and work manager configuration and) without deploying the application to WebLogic we can use

package model.test;

import model.logic.JMSSender;
import model.utils.SpringUtilities;

public class JMSTest {

    public static void main(String[] args) {
        JMSSender jmsSender = SpringUtilities.getJMSSender();

        while (true) {
            jmsSender.sendMessage();
            System.out.println("done sending message");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Performance considerations

The following documents provide information on various methods to improve performance:

Another tuning parameter to consider is the chunk size. A chunk is a unit of memory that the WebLogic Server network layer, both on the client and server side, uses to read data from and write data to sockets. To reduce memory allocation costs, a server instance maintains a pool of these chunks. For applications that handle large amounts of data per request, increasing the value on both the client and server sides can boost performance. The default chunk size is about 4K. Use the following properties to tune the chunk size and the chunk pool size:

  • weblogic.Chunksize – sets the size of a chunk (in bytes). The primary situation in which this may need to be increased is if request sizes are large. It should be set to values that are multiples of the network’s maximum transfer unit (MTU), after subtracting from the value any Ethernet or TCP header sizes. Set this parameter to the same value on the client and server.
  • weblogic.utils.io.chunkpoolsize – sets the maximum size of the chunk pool. The default value is 2048. The value may need to be increased if the server starts to allocate and discard chunks in steady state. To determine if the value needs to be increased, monitor the CPU profile or use a memory/ heap profiler for call stacks invoking the constructor weblogic.utils.io.Chunk.
  • weblogic.PartitionSize – sets the number of pool partitions used (default is 4). The chunk pool can be a source of significant lock contention as each request to access to the pool must be synchronized. Partitioning the thread pool spreads the potential for contention over more than one partition.

For example, we can set the chunksize by using -Dweblogic.Chunksize=65536 on the command-line.

References

[1] Configuring and Managing JMS for WebLogic Server.
[2] Configuring and Managing the Messaging Bridge for WebLogic Server.
[3] Configuring and Managing Store-and-Forward for WebLogic Server.
[4] Performance and Tuning for WebLogic Server.