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