Spring DefaultMessageListenerContainer and Loss of Topic Messages

As I start developing JMS based services, I need to determine how I will architecture consumers and what types of performance best practices should be considered. First I took the time to read through the Spring Documentation and Spring Recipes book, but then read through these next two posts:

Using Spring to Send JMS Messages
Using Spring to Receive JMS Messages

There are some excellent details in this article and after evaluation of the multiple implementation types for Spring based consumers, I decided to use DefaultMessageListenerContainer with the intregration of MessageListener. I also have needs for my consumers to have transactionality, in some cases I want the transaction to participate in a JTA and in other cases I want some more control over rollbacks and message handling by implementing JMS based transactions. Searching for some general understanding of JMS Performance Best Practices also brings to light the needs around certain configuration options. I took a look at articles like “JMS Performance like Best practices to improve performance in JMS“, which will talk about understanding when to and not to use persistent messages.

It seems like JMS has a plethora of configurations that really are specific to the unique case of each implementation of a JMS consumer, but for now, I will build DefaultMessageListenerContainers with most of the default capabilities regardless if I am listening to Queues or Topics.

Below is an example of a default DefaultMessageListenerContainer for a Queue

1
2
3
4
5
6
7
8
9
10
<bean id="sampleSubscribeServiceListenerContainer"  
    class="org.springframework.jms.listener.DefaultMessageListenerContainer"
    p:connectionFactory-ref="sampleMessagingConnectionFactory"
    p:destinationName="com/foo/dest/SampleQueue"
    p:destinationResolver-ref="sampleDestinationResolver"
    p:messageListener-ref="sampleSubscribeServiceMessageListener"
    p:transactionManager-ref="GlobalDataTransactionManager"/>
or
    <!-- Use instead of transactionManager-ref for JMS Transactions-->
    p:sessionTransacted="true"/>

And here is an example of a DefaultMessageListenerContainer for a Topic

1
2
3
4
5
6
7
8
9
10
11
<bean id="sampleSubscribeServiceListenerContainer"  
    class="org.springframework.jms.listener.DefaultMessageListenerContainer"
    p:connectionFactory-ref="sampleMessagingConnectionFactory"
    p:destinationName="com/foo/dest/SampleTopic"
    p:destinationResolver-ref="sampleDestinationResolver"
    p:messageListener-ref="sampleSubscribeServiceMessageListener"
    p:pubSubDomain="true"
    p:transactionManager-ref="GlobalDataTransactionManager"/>
or
    <!-- Use instead of transactionManager-ref for JMS Transactions-->
    p:sessionTransacted="true"/>

Everything was working perfectly until the amount of messages that need to be consumed increased and seemed to overwhelm the consumers of Topics, because messages were starting to get lost. For instance, I could either create a JMS Client or use HermesJMS to send a message to a Topic and then watch the server logs (as well as attach to the Topic via HermesJMS). I could essentially send 10 messages through and HermesJMS would receive all 10 and the consumer on the server would only show 9. The servers/apps were never restarted, so initially the concern wasn’t with regards to whether the messages were persistent or not. But once I searched for “defaultmessagelistenercontainer lose message topic”, I started receiving more details about the architecture of the different types of Spring containers.

Many discussions or forum postings simply told the developer that the consumer wasn’t working because it was not durable, but did that mean I couldn’t have a Topic consumer that didn’t have to be durable?

http://stackoverflow.com/questions/10851064/losing-messages-from-a-topic-with-defaultmessagelistenercontainer

Then I found a JIRA entry related to exactly the issue that was occurring:

https://jira.springsource.org/browse/SPR-7883

This gave a little more detail, but the true clarity was with the following forum post:

http://forum.springsource.org/showthread.php?33037-What-s-the-best-practice-for-using-JMS-in-spring

While DefaultMessageListenerContainer does use a pull approach behind the scenes, it is still considered a recommendable approach in many environments. After all, any kind of listening approach always boils down to some kind of receive loop on the underlying socket connection; it’s only really about the level at which it happens.

DefaultMessageListenerContainer’s advantage is the level of control that it gives, in particular with respect to thread management and transaction demarcation. DMLC is the only listener container that does not impose the thread management onto the JMS provider, that is, does not use/block JMS provider threads. It is also able to gracefully recover from JMS provider failure, such as connection loss. Furthermore, as noted, it is the only variant that supports external transaction managers, in particular for XA transactions.

DefaultMessageListenerContainer is also the only listener container variant that is compatible with both managed JMS and non-managed JMS, i.e. JMS in a J2EE environment as well as standalone JMS usage, since it sticks with standard JMS API that is compatible with all of J2EE’s JMS restrictions. SimpleMessageListenerContainer on the other hand uses “Session.setMessageListener”, not supported in a J2EE environment.

The only issue in a J2EE environment is thread management: On WebLogic and WebSphere, we recommend to specify a CommonJ WorkManagerTaskExecutor, which makes DMLC delegate to a server-managed thread pool, resulting in fully integrated thread management. Alternatively, simply stick with the default SimpleAsyncTaskExecutor, which works fine as well in many cases. (It will work just as well as Quartz does…)

Regarding transaction management: If you specify a “transactionManager” on DefaultMessageListenerContainer, it will wrap its receive loop in a transaction. This is typically used with JtaTransactionManager, where no transactional resources are actually bound: It’s only really marking the thread (and the listener’s JMS Session) as “XA-active” and waiting for something to happen. This should be pretty efficient with any decent JTA provider.

As a consequence, DefaultMessageListenerContainer is the variant to use in a J2EE environment, and also a primary choice for native JMS usage. It is often used with native JMS providers such as Tibco, talking to an external broker process (even when running in a J2EE server), typically without XA transactions. Alternatively, consider using SimpleMessageListenerContainer, but only for native JMS usage without XA, and only if your JMS provider gracefully handles thread management and connection recovery.

FWIW, there is a further approach for JMS listening, for native JMS usage only but with the provider pushing incoming messages – and with full XA support: the JCA 1.5 message endpoint mechanism. It requires a JMS 1.1 provider that ships a JCA 1.5 connector (e.g. ActiveMQ), running in a local JCA container within the application. This is the approach that the Jencks project (http://jencks.org) takes, as a third-party add-on to Spring: worth considering if XA transactions are needed, as alternative to DMLC.

-Juergen Hoeller

Bingo, now it all makes sense. So the core issue is not really the durable/non-durable, it is around the type of consumer listener (one that polls or one that pushes). And since I was using the default settings and DefaultMessageListenerContainer, the thread for the DefaultMessageListenerContainer would wake up and stay alive for a second and if it did not find a message, it would close and be recycled. So this happens every single second because of the default of the “receiveTimeout”. When this dropping of the connection happens, there is a potential for a message to be lost because at that split second or so, there is no consumer subscribed to the Topic (which is why the durable fixes this issue).

But this now exposes an interesting concept. In the case that an XA Transaction is being used, the XA AOP Pointcuts have been designed without a timeout to prevent run away threads. One of the suggestions in the discussion is to put a negative value in the “receiveTimeout”, but the problem is that this would potentially result in XA Timeouts anytime the thread didn’t find a message within the allotted XA timeout configuration. So to alleviate this problem, I will set the “receiveTimeout” to the XA AOP timeout (which for these consumers is 180 seconds). This will not fix the loss of messages, because at the end of the 180 seconds when the connection is recycled, I could still lose messages. But this will save the resources of reconnecting every single second waiting for a message. The only thing I need to be concerned is how do I configure the “receiveTimeout” for a Topic versus a Queue. Topics should essentially only have one concurrent consumer (otherwise they could potentially receive a message multiple times), but a Queue can have multiple concurrent consumers (to assist with performance). I just now need to be aware that if I set the “receiveTimeout” to 180 seconds and the concurrentConsumers=”5″ and happen to have 5 different DefaultMessageListenerContainers, then I potentially have a minimum of 25 threads waiting around for 180 seconds. So I just need to make sure I understand how many potential threads could be opened across my domain and any potential resource issues.

So in order to complete the configuration of a Topic consumer so that it does not lose messages, I will need to add configuration to define what is necessary to make it durable. This will be covered in another blog post.

Share and Enjoy

Deploying JMS/JDBC Resources with weblogic-maven-plugin

The weblogic-maven-plugin has given us the ability to easily deploy wars and also to deploy Optional Packages. I can use this capability of deploying wars to also deploy other artifacts such as JDBC and JMS Resources to our WebLogic Domain.

I can create JDBC and JMS Resources by logging into the WebLogic Domain and manually creating these resources (such as JDBC Data Sources and JMS Connection Factories/Queues/Topics). When the resources are created through the console, two modifications are done on the file system.

  1. %DOMAIN_HOME%\config\config.xml = contains configurations for domain and reference to JDBC Resource xml file
  2. %DOMAIN_HOME%\config\jdbc\%DATASOURCE_NAME%-%NUMBER%-jdbc.xml = the configuration for the data source

If I take a look at the jdbc.xml file, I will see the configuration which I created in the domain console:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version='1.0' encoding='UTF-8'?>
<jdbc-data-source xmlns="http://xmlns.oracle.com/weblogic/jdbc-data-source" xmlns:sec="http://xmlns.oracle.com/weblogic/security" xmlns:wls="http://xmlns.oracle.com/weblogic/security/wls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/jdbc-data-source http://xmlns.oracle.com/weblogic/jdbc-data-source/1.0/jdbc-data-source.xsd">
  <name>Sample Data Source</name>
  <jdbc-driver-params>
    <url>jdbc:oracle:thin:@sampleDbHost:1521:sampleDbName</url>
    <driver-name>oracle.jdbc.xa.client.OracleXADataSource</driver-name>
    <properties>
      <property>
        <name>user</name>
        <value>sampleDbUserName</value>
      </property>
    </properties>
    <password-encrypted>Kk67O8FmBQBMN7ws5W+PkYRb9dy1E2hMLb195PQfvqg=</password-encrypted>
  </jdbc-driver-params>
  <jdbc-connection-pool-params>
    <test-table-name>SQL SELECT 1 FROM DUAL</test-table-name>
  </jdbc-connection-pool-params>
  <jdbc-data-source-params>
    <jndi-name>sampleJndiName</jndi-name>
    <global-transactions-protocol>TwoPhaseCommit</global-transactions-protocol>
  </jdbc-data-source-params>
</jdbc-data-source>

The password-encrypted field is the password that I entered in plain text that has been encrypted based on the domain’s salt file (SerializedSystemIni.dat). So I could essentially take this file and remove all the database specific properties, and do a merge with our environment properties (per environment), to create a jdbc.xml configuration file (a file that creates a data source per environment). I can do this by creating an XML file (from this configuration), putting all the properties into an environment specific property file, use the maven resources configuration to expand the properties into the XML file, and then use the weblogic-maven-plugin to deploy the merged configuration file. I can also use the encryption project I mentioned before to encrypt a plain text password against a salt to include within a properties file.

So all I need to do is to remove properties that are specific to environment or a single data source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version='1.0' encoding='UTF-8'?>
<jdbc-data-source xmlns="http://xmlns.oracle.com/weblogic/jdbc-data-source"
    xmlns:sec="http://xmlns.oracle.com/weblogic/security"
    xmlns:wls="http://xmlns.oracle.com/weblogic/security/wls"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.oracle.com/weblogic/jdbc-data-source http://xmlns.oracle.com/weblogic/jdbc-data-source/1.0/jdbc-data-source.xsd">
    <name>sample</name>
    <jdbc-driver-params>
        <url>jdbc:oracle:thin:${at.symbol}(DESCRIPTION=(LOAD_BALANCE=yes)(ADDRESS=(PROTOCOL=TCP)(HOST=${sample.jdbc.hostname1})(PORT=${sample.jdbc.port}))(ADDRESS=(PROTOCOL=TCP)(HOST=${sample.jdbc.hostname2})(PORT=${sample.jdbc.port}))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=${sample.jdbc.service})))</url>
        <driver-name>oracle.jdbc.xa.client.OracleXADataSource</driver-name>
        <properties>
            <property>
                <name>user</name>
                <value>${sample.jdbc.username}</value>
            </property>
        </properties>
        <password-encrypted>${sample.jdbc.password.encrypted}</password-encrypted>
    </jdbc-driver-params>
    <jdbc-connection-pool-params>
        <initial-capacity>${sample.jdbc.capacity.initial}</initial-capacity>
        <max-capacity>${sample.jdbc.capacity.max}</max-capacity>
        <capacity-increment>1</capacity-increment>
        <test-connections-on-reserve>true</test-connections-on-reserve>
        <test-table-name>SQL SELECT 1 FROM DUAL</test-table-name>
        <statement-cache-size>10</statement-cache-size>
        <statement-cache-type>LRU</statement-cache-type>
    </jdbc-connection-pool-params>
    <jdbc-data-source-params>
        <jndi-name>${sample.jndi.name}</jndi-name>
        <global-transactions-protocol>TwoPhaseCommit</global-transactions-protocol>
    </jdbc-data-source-params>
    <jdbc-oracle-params>
        <fan-enabled>false</fan-enabled>
        <ons-node-list></ons-node-list>
        <ons-wallet-file></ons-wallet-file>
    </jdbc-oracle-params>
</jdbc-data-source>

I then use the custom ext properties plugin to read in the custom properties and then use the resource configuration to expand the properties in the file:

1
2
3
4
5
6
<resources>
    <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
    </resource>
</resources>

Now I have an JDBC configuration file built specifically per environment and specific JDBC resource, by supplying the specific environment during the maven runtime. And now that I have a JDBC configuration file, I need to be able to deploy it to an environment and I do this the same as how I deployed wars to an environment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<plugin>
    <groupId>com.oracle.weblogic</groupId>
    <artifactId>weblogic-maven-plugin</artifactId>
    <version>10.3.4</version>
    <executions>
        <execution>
            <phase>compile</phase>
            <goals>
                <goal>deploy</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <adminurl>t3://${deploy.hostname}:${deploy.port}</adminurl>
        <user>${deploy.user}</user>
        <password>${deploy.password}</password>
        <upload>true</upload>
        <action>deploy</action>
        <targets>${target.names}</targets>
        <remote>true</remote>
        <verbose>true</verbose>
        <debug>true</debug>
        <name>${resource.name}${resource.suffix}</name>
        <source>${project.build.outputDirectory}/${deploy.env}/${folder.suffix}/${resource.name}${file.suffix}</source>
    </configuration>
</plugin>

I simply select the source location for the JDBC configuration and then deploy it with a name such as sampleDS. The data source will now be registered as an Application Data Source, which is different than a System Data Source (since I don’t seem to be able to use Application Data Sources with the creation of a JDBC Persistent Store for JMS).

Now that I understand how to do this with JDBC configurations, I can do it with JMS configuration as well. JMS gets more complicated though based on how WebLogic structures the JMS components into deployable units, as well as the introduction to very unique resources (ConnectionFactory, Queue, Topic, and Store-and-Forward). Also, WebLogic supports a concept known as Uniform Distributed Queue and Uniform Distributed Topic, which has different configurations within the XML file. Also, WebLogic requires specific targeting when deploying resources.

So let’s talk about some JMS best practices first, and these are also some great resources:

Next, let’s talk about some of the deployment and setup information I need:

  1. A JMS Server should be created for each Managed Server instance
  2. In order to have a JMS Server deploy to a Managed Server (migratable), I must not use the default store
  3. A Store-And-Forward Agent should be created for each Managed Server instance
  4. JMS Connection Factories should use default-targeting-enabled=true so they are targeted to the cluster
  5. Queues/Topics/SAF Imported Destinations should name the XML file as the subdeployment
  6. Subdeployments should be targeted at the JMS Server (one for each Managed Server) or Store-And-Forward Agents

So a sample file would look like this (without Store-And-Forward):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version='1.0' encoding='UTF-8'?>
<weblogic-jms xmlns="http://xmlns.oracle.com/weblogic/weblogic-jms" xmlns:sec="http://xmlns.oracle.com/weblogic/security" xmlns:wls="http://xmlns.oracle.com/weblogic/security/wls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-jms http://xmlns.oracle.com/weblogic/weblogic-jms/1.1/weblogic-jms.xsd">
  <connection-factory name="SampleConnectionFactory">
    <default-targeting-enabled>true</default-targeting-enabled>
    <jndi-name>com/foo/SampleConnectionFactory</jndi-name>
    <client-params>
      <client-id-policy>Restricted</client-id-policy>
      <subscription-sharing-policy>Exclusive</subscription-sharing-policy>
      <messages-maximum>10</messages-maximum>
    </client-params>
    <transaction-params>
      <xa-connection-factory-enabled>${sampleJMS.jms.xa.enabled}</xa-connection-factory-enabled>
    </transaction-params>
    <security-params>
      <attach-jmsx-user-id>false</attach-jmsx-user-id>
    </security-params>
  </connection-factory>
  <uniform-distributed-queue name="SampleBasicQueue">
    <sub-deployment-name>sampleJmsSubdeployment</sub-deployment-name>
    <delivery-params-overrides>
      <redelivery-delay>${sampleBasicQueue.jms.redelivery.delay}</redelivery-delay>
    </delivery-params-overrides>
    <delivery-failure-params>
      <error-destination>SampleBasicQueueErrorQueue</error-destination>
      <redelivery-limit>${sampleBasicQueue.jms.redelivery.limit}</redelivery-limit>
      <expiration-policy>Redirect</expiration-policy>
    </delivery-failure-params>
    <jndi-name>com/foo/dest/SampleBasicQueue</jndi-name>
    <saf-export-policy>${sampleBasicQueue.jms.saf.policy}</saf-export-policy>
  </uniform-distributed-queue>
  <uniform-distributed-queue name="SampleBasicQueueErrorQueue">
    <sub-deployment-name>sampleJmsSubdeployment</sub-deployment-name>
    <jndi-name>com/foo/dest/SampleBasicQueueErrorQueue</jndi-name>
  </uniform-distributed-queue>
  <uniform-distributed-queue name="SampleBasicTopicErrorQueue">
    <sub-deployment-name>sampleJmsSubdeployment</sub-deployment-name>
    <jndi-name>com/foo/dest/SampleBasicTopicErrorQueue</jndi-name>
  </uniform-distributed-queue>
  <uniform-distributed-topic name="SampleBasicTopic">
    <sub-deployment-name>sampleJmsSubdeployment</sub-deployment-name>
    <delivery-params-overrides>
      <redelivery-delay>${sampleBasicTopic.jms.redelivery.delay}</redelivery-delay>
    </delivery-params-overrides>
    <delivery-failure-params>
      <error-destination>SampleBasicTopicErrorQueue</error-destination>
      <redelivery-limit>${sampleBasicTopic.jms.redelivery.limit}</redelivery-limit>
      <expiration-policy>Redirect</expiration-policy>
    </delivery-failure-params>
    <jndi-name>com/foo/dest/SampleBasicTopic</jndi-name>
    <saf-export-policy>${sampleBasicTopic.jms.saf.policy}</saf-export-policy>
    <forwarding-policy>Replicated</forwarding-policy>
  </uniform-distributed-topic>
</weblogic-jms>

And here is an example with Store-And-Forward, which has a Remote Context (the connection to the remote server that the message will be forwarded to), and Remote Imported Destinations are the Queue/Topics on the remote machine that will be receiving the messages:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version='1.0' encoding='UTF-8'?>
<weblogic-jms xmlns="http://xmlns.oracle.com/weblogic/weblogic-jms" xmlns:sec="http://xmlns.oracle.com/weblogic/security" xmlns:wls="http://xmlns.oracle.com/weblogic/security/wls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-jms http://xmlns.oracle.com/weblogic/weblogic-jms/1.1/weblogic-jms.xsd">
  <connection-factory name="SampleConnectionFactory">
    <default-targeting-enabled>true</default-targeting-enabled>
    <jndi-name>com/foo/SampleConnectionFactory</jndi-name>
    <client-params>
      <client-id-policy>Restricted</client-id-policy>
      <subscription-sharing-policy>Exclusive</subscription-sharing-policy>
      <messages-maximum>10</messages-maximum>
    </client-params>
    <transaction-params>
      <xa-connection-factory-enabled>${sampleJMS.jms.xa.enabled}</xa-connection-factory-enabled>
    </transaction-params>
    <security-params>
      <attach-jmsx-user-id>false</attach-jmsx-user-id>
    </security-params>
  </connection-factory>
  <saf-imported-destinations name="SampleSAFImportedDestination">
        <sub-deployment-name>sampleJmsSubdeployment</sub-deployment-name>
        <saf-topic name="SampleEventTopic">
            <remote-jndi-name>com/foo/dest/SampleEventTopic</remote-jndi-name>
            <saf-error-handling>SampleEventErrorHandling</saf-error-handling>
        </saf-topic>
        <jndi-prefix xsi:nil="true"></jndi-prefix>
        <saf-remote-context>SampleRemoteSAFContext</saf-remote-context>
        <saf-error-handling>SampleErrorHandling</saf-error-handling>
        <time-to-live-default>0</time-to-live-default>
        <use-saf-time-to-live-default>false</use-saf-time-to-live-default>
  </saf-imported-destinations>
  <saf-remote-context name="SampleRemoteSAFContext">
        <saf-login-context>
          <loginURL>${remote.wls.jms.url}</loginURL>
          <username>${remote.wls.userId}</username>
          <password-encrypted>${remote.wls.password.encrypted}</password-encrypted>
        </saf-login-context>
        <compression-threshold>2147483647</compression-threshold>
    </saf-remote-context>
   <saf-error-handling name="SampleErrorHandling">
    <policy>Log</policy>
    <log-format xsi:nil="true"></log-format>
    <saf-error-destination xsi:nil="true"></saf-error-destination>
  </saf-error-handling>
   <saf-error-handling name="SampleEventErrorHandling">
    <policy>Log</policy>
    <log-format xsi:nil="true"></log-format>
    <saf-error-destination xsi:nil="true"></saf-error-destination>
  </saf-error-handling>
</weblogic-jms>

So for deploying JMS Resources, there is an additional piece of configuration that I need to add to the plugin. I have built that through a profile incase I have one -> many Managed Servers based on the environment I am deploying to. So the configuration looks the same as the JDBC configuration, except for the inclusion of this profile that contains a submoduletargets property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  <profile>
    <id>mgdServers1</id>
    <properties>
    <submoduletargets>
       sampleJmsSubdeployment@jmsServer1
        </submoduletargets>
    </properties>
  </profile>
  <profile>
    <id>mgdServers2</id>
    <properties>
    <submoduletargets>
      sampleJmsSubdeployment@jmsServer1,sampleJmsSubdeployment@jmsServer2
        </submoduletargets>
    </properties>
  </profile>

And this when deploying to Store-And-Forward Agents, I just change the name from the JMS Server name to the Agent name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  <profile>
    <id>mgdServers1</id>
    <properties>
    <submoduletargets>
       sampleJmsSubdeployment@safAgent1
        </submoduletargets>
    </properties>
  </profile>
  <profile>
    <id>mgdServers2</id>
    <properties>
    <submoduletargets>
      sampleJmsSubdeployment@safAgent1,sampleJmsSubdeployment@safAgent2
        </submoduletargets>
    </properties>
  </profile>

Of course the names of the JMS Servers and/or Store-And-Forward Agents need to match what you have the WebLogic Server resources named. Now when I deploy the resources, you will see the ConnectionFactory targeted to the Cluster, while the Queues/Topics/SAF resources are grouped within a subdeployment and that subdeployment is targeted to a JMS Server or SAF Agent. So if I change the targeted JMS Server or SAF Agent, every resource within that subdeployment will change its target.

One last thing to mention, especially with WebLogic Uniform Distributed Topics, is that you have to do special configuration within a Subscribe Service in order to use Durable Subscribe since WebLogic does not allow a Durable Subscribe against a Uniform Distributed Topic (unless you point to one of the specific members…by including a single JMS Server in the JNDI url…like jmsServer1@/com/foo/SampleBasicTopic). I will cover this in another posting.

Share and Enjoy

WebLogic Optional Packages and Dependency Maturity

So I mentioned in another post about a Dependency Maturity Model where I will pull the environmental properties from our WAR deployments. The way I accomplish this with WebLogic is by making the jar file that holds the environmental property like an OSGi bundle for WebLogic (which is an optional package). This is not a very complex exercise, it is really about giving a JAR information with it’s MANIFEST.MF to identify it’s versions and a JAR key, then referencing that within deployments that use the JAR (like a WAR).

I configure the maven-jar-plugin to put additional information to the MANIFEST.MF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifestEntries>
                <Manifest-Version>1.0</Manifest-Version>
                <Extension-Name>envProperties</Extension-Name>
                <Specification-Version>${env.spec.version}</Specification-Version>
                <Implementation-Version>${env.impl.version}</Implementation-Version>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

Once this JAR is built through our Maven project, I need to deploy it out to WebLogic utilizing the maven-weblogic-plugin (for WLS 11). There are two interesting pieces of information regarding the link to the WebLogic Server 11 and WebLogic Server 12 versions of the plugin. The WebLogic Server 12 version appears to have a bug and does not support deploying libraries at all and therefore I have to utilize the WebLogic Server 11 version of the maven plugin. The second issue though is that the WebLogic Server 11 version of the plugin does not support the library capability through XML, it has to be supplied as a command line argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<plugin>
    <groupId>com.oracle.weblogic</groupId>
    <artifactId>weblogic-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>deploy</id>
            <phase>compile</phase>
            <goals>
                <goal>deploy</goal>
            </goals>
            <configuration>
                <adminurl>t3://${target.hostname}:${target.port}</adminurl>
                <user>${target.userId}</user>
                <password>${target.password}</password>
                <upload>true</upload>
                <action>deploy</action>
                <targets>${target.cluster}</targets>
                <remote>true</remote>
                <verbose>true</verbose>
                <library>true</library>
                <source>${project.build.directory}/lib/${property.name}.jar</source>
                <name>envProperties</name>
            </configuration>
        </execution>
    </executions>
</plugin>

So the main piece of configuration here that differentiates this from a normal WAR or EAR deployment is the true. This XML element is supposed to identify this deployment as an Optional Package, but this field does not work. In order to bypass this error, when I run the maven goal to fire off this plugin, I have to pass in a -Dlibrary=true for this resource to successfully deploy as an Optional Package (otherwise WebLogic will generate an error that this is an invalid resource type).

The next step is to make modifications to our WAR so that it can become aware of the Optional Package. I do this by modifying a maven plugin so that it can build references in the WAR’s MANIFEST.MF to the JAR’s MANIFEST.MF.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <configuration>
        <archive>
            <manifestEntries>
                <Manifest-Version>1.0</Manifest-Version>
                                           
                <Extension-List>envProperties cxfFull</Extension-List>
                <envProperties-Extension-Name>envProperties</envProperties-Extension-Name>
                <envProperties-Specification-Version>${env.spec.version}</envProperties-Specification-Version>
                <envProperties-Implementation-Version>${env.spec.version}</envProperties-Implementation-Version>
                                           
                <cxfFull-Extension-Name>cxfFull</cxfFull-Extension-Name>
                <cxfFull-Implementation-Version>${cxfFull.spec.version}</cxfFull-Implementation-Version>
                <cxfFull-Specification-Version>${cxfFull.spec.version}</cxfFull-Specification-Version>
            </manifestEntries>
        </archive>
        <packagingExcludes>WEB-INF/lib/cxf*.jar</packagingExcludes>
    </configuration>
</plugin>

There are some additional interesting items that I have in this configuration that I can go over, but let’s first talk about how I reference the environment property JAR.

1
2
3
4
<Extension-List>envProperties cxfFull</Extension-List>
<envProperties-Extension-Name>envProperties</envProperties-Extension-Name>
<envProperties-Specification-Version>${env.spec.version}</envProperties-Specification-Version>
<envProperties-Implementation-Version>${env.spec.version}</envProperties-Implementation-Version>

In the Extension-List tag, I list all the *-Extension-Name configurations that I have. So in the previous section of the configuration, I have an envProperties-Extension-Name=envProperties and a cxfFull-Extension-Name=cxfFull. I therefore need to list these values in the Extension-List element. The next step is to reference the Implementation/Specification Versions and I do this by using the *-Extension-Name value as the key in the name of the configuration. So if our key is envProperties, our *-Extension-Name becomes envProperties-Extension-Name, our *-Specification-Version becomes envProperties-Specification-Version, and our *-Implementation-Version becomes envProperties-Implementation-Version.

The other important piece of configuration here is the . I utilize this feature to remove jars (that I have designated as Optional Packages) so that they will not be included into the built war file. The reason for this again is that these jars will be available via Optional Packages and therefore I remove them from the war and therefore reduce the size of the war and increase performance for startup/deployment.

Now that I have been able to create the reference between the two, once the WAR is deployed, it will create a link between it and the environment properties JAR. In fact, if I log into our WebLogic console and click on our environment properties deployment and I can see all the other deployments that are using it. These relationships will be listed under the “Applications that reference this Library” table.

Because of this relationship, anytime I need to redeploy the environmental properties jar or any Optional Package, I will need to start/stop the web applications that use the specific Optional Package. This allows that web application to reload the new changes to the Optional Package (unfortunately it does not appear to be dynamic), but it also does not require us to restart the Admin/Managed Server instances.

For more additional information about the weblogic-maven-plugin, see my other post as to how I deploy wars using this plugin.

Share and Enjoy