Wednesday, December 13, 2006

Reset OC4J Admin Password

Seems like a rash of questions recently about how to reset Oracle Application Server/OC4J's administrator password and for whatever reason Google doesn't index our doc (as per the feedback below, due to our robot.txt disallowing this - I don't know why this is the policy) and as a result isn't finding the entry in our doc on how to do this here:

http://download-west.oracle.com/docs/cd/B31017_01/core.1013/b28940/trouble_asc.htm#BCEDHFEI

Reproduced in its entirety below [1] and hopefully this will help Google point people to the right information.

One of the "tricks" you have to be aware of when doing this is that if you are using Application Server Control to manage a cluster of OC4J's, there is a default assumption that all the OC4J's use the same administrator password (you can override) - if you are doing this, step 5 of the instructions, which talks about removing the ASControl cached security file (passwords encrypted!) to re-enable cluster management, is important to heed when you do this.

[1]

Reset the oc4jadmin password using the following procedure while you are logged in as the user who installed the Oracle Application Server instance:

  1. Stop OC4J and the Application Server Control.

    Enter the following command in the Oracle home of the application server instance:

    (UNIX) ORACLE_HOME/opmn/bin/opmnctl stopproc ias-component=OC4J
    (Windows) ORACLE_HOME\opmn\bin\opmnctl stopproc ias-component=OC4J

  2. Locate and open the following file in a text editor:

    (UNIX)ORACLE_HOME/j2ee/home/config/system-jazn-data.xml
    (Windows)ORACLE_HOME\j2ee\home\config\system-jazn-data.xml

  3. Locate the line that defines the credentials property for the oc4jadmin user.

    The following example shows the section of system-jazn-data.xml with the encrypted credentials entry in boldface type:



    jazn.com

    .
    .
    .

    oc4jadmin
    OC4J Administrator
    OC4J Administrator
    {903}4L50lHJWIFGwLgHXTub7eYK9e0AnWLUH


  4. Replace the existing encrypted password with the new password.

    Be sure to prefix the password with an exclamation point (!). For example:

    !mynewpassword123

    The password for the oc4jadmin user should conform to following guidelines:

    • Must contain at least five characters, but not more than 30 characters.

    • Must begin with an alphabetic character. It cannot begin with a number, the underscore (_), the dollar sign ($), or the number sign (#).

    • At least one of the characters must be a number.

    • Can contain only the following characters; numbers, letters, and the following special characters: US dollar sign ($), number sign (#), or underscore (_).

    • Cannot contain any Oracle reserved words, such as VARCHAR.


    See Also:

    "The oc4jadmin User and Restrictions on its Password" in the Oracle Application Server Installation Guide

  5. Delete cached password data by deleting the contents of the following directory:

    (UNIX)
    ORACLE_HOME/j2ee/oc4jinstance /persistence/ascontrol/ascontrol/securestore/
    (Windows)
    ORACLE_HOME\j2ee\oc4jinstance\persistence\ascontrol\ascontrol\securestore/

  6. Start OC4J and the Application Server Control.

    After the restart, the Application Server Control will use your new Administrator (oc4jadmin) password, which will be stored in encrypted format within the system-jazn-data.xml file.

Sunday, December 03, 2006

OracleAS JVM Analysis in Application Server Control

Sometimes you are pleasantly surprised by some of the technologies your colleagues put out. I was in a meeting with the Application Server Control folks last week and was reminded that a fair bit of work was done around JVM analysis in OracleAS 10.1.3 Application Server Control.

Check these screen shots below out:

1. Starting with a quick look at how your heap is doing on your OracleAS instance (remember if you browse my blog, I am running a 6 instance OC4J OracleAS cluster on my laptop). On this one I have two JVMs running this OC4J:


2. At the bottom of this page, there is a more detailed link to look at the 2 JVM's that that summarized heap usage represents taking me down to statistics summarizing each JVM:


3. Then I can click on that JVM and see more breakdowns on CPI, active threads, response and load. At the bottom of this page there is one more button which asks me whether I am using Java SE 5.0 which in turns I can turn on specific MBeans to monitor it. Before clicking that I have to configure OracleAS to monitor those Mbeans as an optional performance monitoring option.


4. In the server properties, it is a one click option to start up monitoring of the Java SE 5.0 Mbeans:


5. And when I return to the last page I couldn't show before I see more detailed analysis of the threads, memory pools, resource usage, number of classes loaded amongst others:



Would be very interested in other metrics and performance issues you would like to see - my personal area of interest that I see customers facing is capturing this kind of information sensibly for analysis in a cluster versus on a per JVM basis ... which one of my JVMs is misbehaving and how can I now get a sensible thread dump to do the analysis.

Your views on the utility of this in your management console and what you really need versus surfacing well known metrics would be great. Thanks!

Saturday, December 02, 2006

OC4J HTTP Server on OracleAS 10.1.3.1

Here's a deep in the muck kind of post for those who want to know the gory internals of Oracle Application Server and two ways of having an HTTP server set up on it.

Oracle provides an HTTP server out of the box if you download OC4J stand-alone - the 80M download here - http://www.oracle.com/technology/software/products/ias/index.html. If you choose the second download (470M) along with what we call the SOA Suite (BPEL PM, Web Services Manager, Oracle Rules and our new ESB), you will get the managed environment owned by Oracle Process Manager and Notification Service (OPMN) and Oracle HTTP Server - a slight derivate of Apache HTTP Server packaged with Oracle DB RSFs amongst other things.

Sometimes people like to use the OC4J HTTP server which is extremely lightweight, written in Java and does great for high performance smaller environments or even popular in larger environments if you throw a hardware load balancer in front of several OC4J containers - emulating what OHS does out of the box. We have an install option for this lighter weight approach, shown here, where you get our J2EE server with the OC4J HTTP server rather than OHS (the OHS one been the first and second choices, OC4J HTTP being the highlighted choice):


But of course what almost invariably happens is that you choose the first and second choice and then realize you don't want the OHS listener - maybe too much to manage or in production you expect to use generic Apache, SunOne or IIS ... whatever it is - and decide to revert back to the OC4J HTTP server. While it isn't exactly hard, it isn't exactly perfectly intuitive either. So here's how to do it:

1. Shut down your application server:

$ORACLE_HOME\opmn\bin\opmnctl stopall

2. Go to your $ORACLE_HOME\opmn\conf\opmn.xml and find the line:

<ias-component id="HTTP_Server">

and replace it with:

<ias-component id="HTTP_Server" status="disabled" >

What you have done here is turned off the Oracle HTTP Server


3. Now lets turn on the OC4J HTTP Listener. Go to $ORACLE_HOME\opmn\conf\opmn.xml and find the line:

<port id="default-web-site" range="12501-12600" protocol="ajp"/>

and change it to

<port id="default-web-site" range="80" protocol="http"/>

I am assuming you want it on port 80 here; choose the appropriate HTTP port for your machine. You can also do this in Application Server Control screen shown below but you have to make sure OracleAS is running and then after making the change, bounce the OC4J instance you are doing this on. Frankly speaking, it is probably easier just to edit the file when the server is stopped for this kind of surgery.



4. Lastly, edit the file $ORACLE_HOME/j2ee/config/default-web-site.xml and you will see a line like this:

<web-site xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://xmlns.oracle.com/oracleas/schema/web-site-10_0.xsd" protocol="ajp13" port="0" display-name="OC4J 10g (10.1.3) Default Web Site" schema-major-version="10" schema-minor-version="0" >

Noted the bolded lines here - we need to make the default web site wire up to the ports and protocol in opmn.xml as follows:


<web-site xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://xmlns.oracle.com/oracleas/schema/web-site-10_0.xsd" protocol="http" port="80" display-name="OC4J 10g (10.1.3) Default Web Site" schema-major-version="10" schema-minor-version="0" >

5. Re-start the server:

$ORACLE_HOME/opmn/bin/opmnctl startall

Now your server should be happily using the Java based OC4J HTTP listener and not using the Apache based Oracle HTTP Server.


Thursday, November 23, 2006

Job Statistics - Product and Technology Choices for the Future

As a result of looking at Rick Hightower's blog on jobs for Spring and Hibernate - http://jroller.com/page/RickHigh?entry=stack_of_choice_jsf_hibernate - it seemed like playing with Indeed Jobs could almost be as entertaining as a video game (for about 5 minutes!).

It has some practical bearing on IT professionals because if you are working with a technology or product where jobs are rapidly going to zero and you have a family to feed or rent/mortgage to pay, it is good to think ahead. It is also a way to identify up and comers as Rick did with Spring.

One problem I had with Indeed engine was how easy it was to come out with misleading results - using "Spring" brought back jobs in places named Spring versus "Spring Java" which brought back jobs more relevant to Spring developers. "Oracle AS" brought back jobs using Oracle in places named or abbreviated "AS". You yourself may have an opinion or two on whether my take here is valid :-)

Trying to get something that seemed to bring back meaningful jobs by product, framework and open source, I have several possibly interesting graphs to share. I have my particular bias, like Rick did in his survey, of seeing how healthy the technologies and products I work on are from a job creation perspective.

The first is "Oracle Application Server", "WebLogic", "WebSphere", "Tomcat", "JBoss" and "Geronimo". I also wanted to transpose the size of the developer market that these server infrastructures represent against the job requirements for some of the commonly used frameworks and technologies so I added in "Spring Java", "Hibernate", "TopLink", "ADF", "BC4J". Here is what I got:

http://www.indeed.com/jobtrends?q=oracle+application+server%2C+jboss%2C+weblogic%2C+websphere%2C+sun+application+server%2C+spring+java%2C+hibernate%2C+adf%2C+bc4j%2C+toplink%2C+tomcat%2C+geronimo

and inline:


I was happy to see that Oracle Application Server came out on top, followed by WebSphere, WebLogic and Tomcat. JBoss was next and Geronimo at the bottom. Admittedly this is somewhat problematic for all products - e.g. WebSphere is a huge brand encompassing more than the application server and the jobs showed this - calling out WebSphere Application Server like here:

http://www.indeed.com/jobtrends?q=oracle+application+server%2C+jboss%2C+weblogic%2C+websphere+application+server%2C+sun+application+server%2C+spring+java%2C+hibernate%2C+adf%2C+bc4j%2C+toplink%2C+tomcat%2C+geronimo



makes Oracle Application Server and WebLogic the two dominant application server job generators out there. Correspondingly, I could see a slight DBA bias in the Oracle jobs though in general they were pretty reasonably reflective of middleware jobs, frequently being Oracle Application Server Administrators or developers.

The large size of Oracle Application Server users did match what I also have seen at our own events, as skewed as our own event obviously will be. The data point I compare to is unlike 4 years ago, at OpenWorld where we would have 100 people per room on Oracle Application Server sessions, now, this year depending on the topic you could see 300-400 people sometimes many more, particularly if the topic was SOA oriented. While this may have been an unusual event - it was an almost overwhelming event in terms of attendance (I believe about 40,000 people!) literally shutting down streets in San Francisco - it was pretty clear there is a huge community of folks asking about and working with Oracle middleware and naturally there is a matching set of jobs out there backed up by this graphic. An Oracle middleware economy so to speak!

One reason I did the breakout above is to show in terms of job opportunity, it appears the bulk of the jobs still fall into knowing the overall product offering versus just the technology. It is one thing to know JSF or EJB, it is another to be successful on a specific application server. Clearly you will be miles ahead knowing the technology, particularly standards based or defacto standards (ala Spring) but employers will frequently hire you on your specific product domain experience (e.g. I know how to cluster Oracle Application Server in a highly available infrastructure, versus I know how to build a servlet that does state replication - two related but different tasks).

Let's dig down in the mess of technologies at the bottom of the above graphic, hidden by the jobs in the server space, so like Rick, we have a better sense of job requirements in the underlying technologies that the upper level application servers support. Here the same thing can be illustrated but I added in JSP, JMS, servlet and EJB to see where these old stalwarts live in the minds of job recruiters:

http://www.indeed.com/jobtrends?q=spring+java%2C+hibernate%2C+adf%2C+bc4j%2C+toplink%2C+jms%2C+ejb%2C+ejb3%2C+jsp%2C+servlet



The interesting thing here is those old fashion baseline technologies of Java EE - JSP (the top orange line -- by far above all others), EJB (purple) and JMS (yellow)- still are the largest job requirements out there beyond these emerging frameworks - they are, so to speak, the meat and potatoes you need as a developer (the SQL of middleware).

Going one level deeper into Rick's analysis, there is absolutely no doubt that Spring, Hiberate and JSF, while the next level down, seem to be emerging into baseline requirements for developers to know for technology jobs. I think it is reasonable to conclude they are the meat and potatoes of the future:

http://www.indeed.com/jobtrends?q=spring+java%2C+hibernate%2C+adf%2C+bc4j%2C+toplink%2C+jsf


The next one I thought I would check out as EJB 3.0, JPA, OpenJPA, TopLink Essentials (the reference implementation for JPA persistence in the Java EE 5.0 Sun reference implementation as well as the JPA persistence provider in Oracle Application Server). They were hidden in the noise in the above graphic, possibly indicating they are still battling it out for mindshare and job requirements:

http://www.indeed.com/jobtrends?q=ejb3%2C+jpa%2C+openjpa%2C+ejb+3.0%2C+toplink+essentials



The numbers are substantially smaller but it is pretty clear since EJB 3.0 finalized several months ago and JPA has become a recognized term for persistence, there is a clear upswing. I suspect in 6 months to a year - particularly if I judge by the volume of e-mail and customers I get around EJB 3.0/JPA, we will see EJB3.0/JPA merge with the other three technologies - Hibernate, JSF, Spring.

It wouldn't be fair if we didn't do the old fashion .NET versus Java survey so here it is. I never know what is more valid in terms of comparing because recruiters lump languages with runtimes but for reference I did .NET, J2EE, Java, C# and PHP.

My personal conclusion based on reading the job descriptions from this query is that they tend to ask for "Java" when they mean "J2EE" or "Java EE" and it appears ".NET" is a great big catchall for all things Microsoft and perhaps not representative of the equivalent in Java/J2EE. You can make up your own mind and decide what the heck recuiters think when writing job descriptions by looking here ...

http://www.indeed.com/jobtrends?q=php%2C+ruby+on+rails%2C+java%2C+.net%2C+j2ee%2C+c%23



To me the reality of .NET versus Java/J2EE is developers have to know both rather than one or the other. Our recent Microsoft Office Interoperability documentation book which focuses on how Oracle Application Server components integrate with and interoperate with Microsoft, I think more closely represents most developers' reality, though clearly many organizations still like to strategically align with one community or the other.

Interested in Hibernate on Oracle Application Server? Check out some of these articles:

http://www.oracle.com/technology/tech/java/sash.html
http://www.oracle.com/technology/pub/articles/vohra_hibernate.html


Interested in JSF on Oracle Application Server? Check out some of these articles:

http://otn.oracle.com/jsf


Interested in EJB 3.0 and JPA on Oracle Application Server? Check out some of these articles:


http://otn.oracle.com/ejb3
http://otn.oracle.com/jpa

Interested in Spring on Oracle Application Server? Check out http://www.oracle.com/goto/spring - many of these articles and how-tos recently arrived also on http://www.springframework.org.

Friday, November 03, 2006

Java Process and Instance Data From OracleAS

In the Oracle Application Server it is standard practice to set up a cluster with multiple application server instances hosting multiple OC4J instances each run atop of multiple JVMs.

Frequently when executing a specific application instance (servlet/EJB/Web service), it can be useful to have the exact location you are running - which Application Server instance, which specific OC4J instance and sometimes even the exact JVM instance. Sometimes you need this for your application to make some application centric decision, but more often it is useful for debugging when working in large clusters.

This blog is inspired by Steve Button's work where he built a very cool utility for session tracking in a cluster which implicitly revealed this information - he needed it himself while building it to debug as well to show to the user.

So how do you determine this on Oracle Application Server? There are several system properties that will help you out, specifically:

  • oracle.home - the physical directory in which your application server is installed
  • oracle.oc4j.instancename - the name of your OC4J instance
  • oracle.ons.instancename - your OracleAS instance name
  • oracle.ons.indexid - a combination of your OC4J instance name, the group it belongs to and the actual JVM executing it (for example if there is > 1 JVM you have configured as per my previous post on this topic)
On my cluster - I have a lot of AS and OC4J instances running :-) ... but in the picture below you can see that for this sample, I have highlighted the pieces of interest. In particular, one of the Oracle Application Server instances is named is soa_javaee.MLEHMANN-LAP. In that instance I have several OC4J instances, one in particular called javaee_1 that is configured to run on top of 2 JVMs. That OC4J instance is grouped together with several others in a synchronized group called javaee_group. If you look to the bottom of the graphic below, you will see this:


To try out these properties, I deploy a servlet to that OC4J instance javaee_1 and within that servlet I have the following code:

out.println("

Oracle home name: " + System.getProperty("oracle.home"));
out.println("

OC4J Instance name: " + System.getProperty("oracle.oc4j.instancename"));
out.println("

AS Instance name: " + System.getProperty("oracle.ons.instancename"));
out.println("

Instance:Group:JVM PID: " + System.getProperty("oracle.ons.indexid"));

What it spits out the other side in my browser is:

Oracle home name: D:\oracle\product\soa_javaee
OC4J Instance name: javaee_1
AS Instance name: soa_javaee.MLEHMANN-LAP
Instance:Group:JVM PID: java_ee1.javaee_group.2

Pretty useful information ... try it out :-)

Friday, October 06, 2006

Process Management in ASControl

In OracleAS 10.1.3.0, there wasn't any gui for editing the process management settings of Oracle Application like there was in 10.1.2.x. In OracleAS 10.1.3.1 - a patch to 10.1.3.0 - this part of the release gives you that editability back and more. The underlying OPMN.xml is surfaced via JMX so it is fully programmable as well as surfaced in ASControl.

This screen shows how you can view and click down to edit port allocation:



This screen shows the OC4J port management and JDK options:


This screen shows the network and process management port configuration:


This tends to be a little less humbling for those who aren't familiar with the underlying XML files that I faithfully subject you to in my various posts :-)

Like before, download and try it here:

http://www.oracle.com/technology/software/products/ias/index.html

Thursday, October 05, 2006

Creating OC4J Instances in ASControl

In OracleAS 10.1.3.0 the OC4J instance creation was available as a command line option as documented here:

http://download-west.oracle.com/docs/cd/B25221_04/web.1013/b14432/topology.htm#sthref325

In OracleAS 10.1.3.1 the instance creation, like with OracleAS 10.1.2.x, is available in ASControl again. Below is a screen shot sequence of what it looks like:

1. Within Application Server Control instance click on the instance link:



2. Click on create OC4J instance:



3. Name it and determine which group of OC4J instances it will join:



And you are done. Nice and easy :-)

Try it yourself at:

http://www.oracle.com/technology/software/products/ias/index.html

Wednesday, October 04, 2006

Thread Dump with OracleAS on Windows

A colleague, Troy Hedges, asked me today about getting a stack trace of OC4J on Windows for an issue he was having. My first question was is it OracleAS managed with OPMN or stand-alone?
'
Normally when running stand-alone OC4J on Windows-- because you normally just run it with:

set JAVA_HOME=
set PATH=/bin;%PATH%;
java -jar oc4j.jar

-- the quick and dirty way to get a thread dump is to hit CTRL-BREAK and voila, thread dump. This is a nice feature of Java for those intent in seeing what really is hung up in their application.

However, when you go to the normal Oracle Application Server deployment environment on Windows which uses the Oracle Process Manager OPMN to manage OracleAS, getting a thread dump is a little trickier because OPMN is a second process which is spawning the JDKs which run each OC4J instance.

And because there is no kill -3 on Windows likeUnix/Linux you are left trying to figure out how to get a thread dump for a process (OPMN) containing the process you want the thread dump from (an OC4J instance) . While OPMN gives you all sorts of great stuff - death detection, process management, clustering etc, this layer adds one more step to debugging that needs to be understood.

Troy was about 98% there when he asked me - just about when he discovered the sendsignal.exe utility popular for many Windows users - and with it was pretty much done. I add here a bit of color commentary so others can reproduce what he did.

Looking under the covers of OPMN -- which we have done in several other posts here -- you can see that the configuration for the JDK that runs each OC4J instance managed by OPPMNis done inside of the configuration file <ORACLE_HOME>\opmn\conf\opmn.xml.

For this example here is the relevant bits from my OPMN.xml:

<ias-component id="cluster_group" status="enabled"> <process-type id="j2ee1" module-id="OC4J" status="enabled">
<module-data>
<category id="start-parameters">
<data id="java-options" value="-server -Djava.security.policy=$ORACLE_HOME/j2ee/j2ee1/config/java2.policy -Djava.awt.headless=true -Dhttp.webdir.enable=false"/>

</category>

<category id="stop-parameters">

<data id="java-options" value="-Djava.security.policy=$ORACLE_HOME/j2ee/j2ee1/config/java2.policy -Djava.awt.headless=true -Dhttp.webdir.enable=false"/>

</category>
</module-data>
<start timeout="600" retry="2"/>

<stop timeout="120"/>

<restart timeout="720" retry="2"/>

<port id="default-web-site" range="12501-12600" protocol="ajp"/>

<port id="rmi" range="12401-12500"/>

<port id="rmis" range="12701-12800"/>

<port id="jms" range="12601-12700"/>

<process-set id="cluster_group" numprocs="1"/>

</process-type>


and when you run this you will see in your Windows task manager a process for the OC4J instance -- the javaw.exe highlighted below -- and typically two opmn.exe processes per Application Server instance:


If you end the process from the task manager you will get the pleasure of seeing OPMN in action - it will promptly re-start that OC4J instance. And if you look at the logs located at:

<ORACLE_HOME>\opmn\logs\ias_component_id_name~OC4J instance name~process_set_id_name~JVM_number>.log

in my case the ias-component id name was cluster_group, the OC4J instance name was j2ee1 and the process set id name was cluster group (from the opmn.xml above) and I had only one JVM running, that translates into:

D:\soasuite\opmn\logs\cluster_group~j2ee1~cluster_group~1.log

And wouldn't you know it - no stack trace. What's up? Well, it wasn't quite the right signal to OC4J for it to do the stack trace - ending a process in the task manager simply isn't a kill -3.

Let's make that stack trace happen! First, make sure that in your start-options for the OC4J instance you do not have -Xrs set as a parameter to the JVM:

<category id="start-parameters">
<data id="java-options" value="-Xrs ...

Just remove it and re-start the application server.

Next download a quick and dirty utility that makes the equivalent of kill -3 on Windows possible - check out:

http://www.latenighthacking.com/projects/2003/sendSignal/

and install the sendSignal.exe

Then, run it using the PID you see in the task manager above - in my case 10452:


And now you will be rewarded by going back to your OPMN log file (
D:\soasuite\opmn\logs\cluster_group~j2ee1~cluster_group~1.log) with a nice stack trace:


--------
06/10/04 11:54:33 Start process
--------
06/10/04 11:55:25 Oracle Containers for J2EE 10g (10.1.3.1.0) initialized

--------
06/10/04 12:21:02 Start process
--------
06/10/04 12:21:16 Oracle Containers for J2EE 10g (10.1.3.1.0) initialized
Full thread dump Java HotSpot(TM) Server VM (1.5.0_06-b05 mixed mode):

"HTTPThreadGroup-4" prio=6 tid=0x00862df0 nid=0x272c in Object.wait() [0x0bc1f000..0x0bc1fc98]
at java.lang.Object.wait(Native Method)
- waiting on <0x02af4e60> (a EDU.oswego.cs.dl.util.concurrent.LinkedNode)
at EDU.oswego.cs.dl.util.concurrent.SynchronousChannel.poll(SynchronousChannel.java:376)
- locked <0x02af4e60> (a EDU.oswego.cs.dl.util.concurrent.LinkedNode)
at EDU.oswego.cs.dl.util.concurrent.PooledExecutor.getTask(PooledExecutor.java:787)
at ....

the rest deleted for brevity. OPMN ever vigilant will start up another OC4J but you will have what you want, which is a stack trace. And there you go, stack traces on managed Oracle Application Server instances on Windows.

Tuesday, October 03, 2006

Stopping an MDB via JMX - Cluster

Moving up a tiny bit in sophistication, my next goal was to do my start/stop of my MDB in a cluster. To illustrate this, I have created a cluster of two OC4J instances each called j2ee1, each in a separate OracleAS instance and they tied together for administrative operations in a group called cluster_group.

First let's take a look at this. You can see on each instance (soa_j2ee and soasuite) I have there is an OC4J instance called j2ee1 (they can be different names but I kept them the same).


If I go to the bottom of ASControl and click on cluster_group - the default out of the box is called default_group, but I have customized my configuration - you will be taken to the administrative screen that lets you do administrative operations across multiple OC4J instances simulataneously. In this case it will be against my two OC4J instances called j2ee1 each on different app server instances:


Within cluster_group, if I click on administration, I will see that there is a cluster mbean JMX browser that sits underneath this environment:


If I drill down on this browser a bit I can see the result of my cluster setup - two JVMProxy's each representing a j2ee1 instance, one on each OracleAS instance:




Clicking on one of them you can see that I can get its JVMProxy MBean:

ias:j2eeType=JVMProxy,name=1,J2EEServerGroup=cluster_group,J2EEServer=j2ee1,
ASInstance=soa_j2ee.MLEHMANN-CA


and the other, differentiated by the OracleAS instance name from the other:

ias:j2eeType=JVMProxy,name=1,J2EEServerGroup=cluster_group,J2EEServer=j2ee1,
ASInstance=soasuite.mlehmann-ca.ca.oracle.com


With that I think we are ready to see if we can update our JMX client from before to walk the cluster and turn off/on the MDB.

Remember, I have deployed my MDB application myMDB to each one of these servers which you can see if you go to the applications page of the cluster_group where the combined list of applications of both servers is displayed:


The solution is provided by Steve Button again, this time in Groovy code, but the translation to Java is pretty easy from here:

http://buttso.blogspot.com/2006/05/locating-oc4j-instances-via-opmn.html

As you may be able to interpret, our job is to iterate through our two j2ee1 OC4J instances in cluster_group, grab the MBean server for each instance and then shut off the MDB like we did for the single instance. The full client is here [1]

First and most importantly, instead of connecting to a specific OC4J instance like we did in the single instance case we connect to the cluster and get its Mbean Server - the trick is instead of going to your OC4J instance name like I did before ("/j2ee1") go to "/cluster". The term /cluster tells the MBean server to go to the cluster MBean server. The port used here, again, is the OPMN request port.

JMXConnector clusterConnect = omdb.connect("service:jmx:rmi:///opmn://127.0.0.1:6006/cluster", "oc4jadmin", "welcome1");
MBeanServerConnection mbs = clusterConnect.getMBeanServerConnection();

Then, we query the OC4J server instances in the cluster. I know that my servers of interest are in the J2EEServerGroup cluster_group and I know what I really want is the JVMProxy for each of those OC4J instances. The JVMProxy MBean will give me the methods that return information about where each of the OC4J instance MBean servers are (host and port) thus allowing me to connect to each of them programmatically:

ObjectName query = new ObjectName("ias:j2eeType=JVMProxy,J2EEServerGroup=cluster_group,*"); Set mbeans = mbs.queryNames(query, null);

Next, we need to do some variable set up - a variable for the service URL for each instance, a connection variable for each, an MBean server for each OC4J instance and finally the MessageDrivenBeanMBeanProxy to turn the MDB off/on:


String serviceURL = "";
JMXConnector instanceConnect = null;
ObjectName instanceObjectName = null;
MessageDrivenBeanMBeanProxy instanceMDBMBean = null;
MBeanServerConnection instanceMbs = null;


Next, we walk through the cluster and for each instance instantiate the JVMProxy Proxy MBean (whose naming convention doesn't quite follow my previous recommendation but it is the right MBean):

for(Iterator iterator = mbeans.iterator(); iterator.hasNext();) {
ObjectName objName = (ObjectName) iterator.next();
JVMMBeanProxy jvm = (JVMMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(mbs,
objName, JVMMBeanProxy.class, false);


From the JVMMBeanProxy we can construct the MBean service URL for that instance:

serviceURL = "service:jmx:rmi://" + jvm.getnode() + ":" + jvm.getrmiPort();

And then connect to it:

instanceConnect = omdb.connect(serviceURL, "oc4jadmin", "welcome1");
instanceMbs = instanceConnect.getMBeanServerConnection();

Then we can look up our MessageDrivenBean:

instanceObjectName = new ObjectName("oc4j:j2eeType=MessageDrivenBean,EJBModule=\"myMDB\",J2EEApplication=myMDB,J2EEServer=standalone,name=\"MessageTopicProcessor\""); instanceMDBMBean = (MessageDrivenBeanMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(instanceMbs, instanceObjectName, MessageDrivenBeanMBeanProxy.class, false);

And last but not least stop it:

instanceMDBMBean.stop();

Running this and browsing the myMDB application shows that all of its MessageProcessor MDB's are stopped as shown below:



There you go. Not a trivial example but works like a champ :-)


[1] Cluster MBean MDB Stop via JMX

ppackage demo.oc4j.jmx;

import java.net.URL;

import java.util.ArrayList;
import java.util.Hashtable;

import java.util.Iterator;
import java.util.Set;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;

import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import oracle.oc4j.admin.management.mbeans.proxies.JVMMBeanProxy;
import oracle.oc4j.admin.management.mbeans.proxies.MessageDrivenBeanMBeanProxy;

public class OperateOnMDBCluster {
public OperateOnMDBCluster() {
}



private JMXConnector connect (String URL, String username, String password) {

JMXConnector jmxCon = null;

try {

Hashtable credentials = new Hashtable();
credentials.put("login", username);
credentials.put("password", password);

// Properties required to use the OC4J ORMI protocol
Hashtable env = new Hashtable();
env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "oracle.oc4j.admin.jmx.remote");
env.put(JMXConnector.CREDENTIALS, credentials);

JMXServiceURL serviceUrl = new JMXServiceURL(URL);
jmxCon = JMXConnectorFactory.newJMXConnector(serviceUrl, env);

// Do it!
jmxCon.connect();

} catch (Exception ex) {
ex.printStackTrace();
}

return jmxCon;
}

public static void main(String[] args) {
try {
OperateOnMDBCluster omdb = new OperateOnMDBCluster();

JMXConnector clusterConnect = omdb.connect("service:jmx:rmi:///opmn://127.0.0.1:6006/cluster", "oc4jadmin", "welcome1");

MBeanServerConnection mbs = clusterConnect.getMBeanServerConnection();

// First get a list of active servers - note the trick of having to
// give it the ias prefix which is part of telling it
ObjectName query = new ObjectName("ias:j2eeType=JVMProxy,J2EEServerGroup=cluster_group,*");
Set mbeans = mbs.queryNames(query, null);

// Now walk through them and construct the JMX service connection from each
String serviceURL = "";
JMXConnector instanceConnect = null;
ObjectName instanceObjectName = null;
MessageDrivenBeanMBeanProxy instanceMDBMBean = null;
MBeanServerConnection instanceMbs = null;

for(Iterator iterator = mbeans.iterator(); iterator.hasNext();) {
ObjectName o = (ObjectName) iterator.next();
JVMMBeanProxy jvm = (JVMMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(mbs, o, JVMMBeanProxy.class, false);
serviceURL = "service:jmx:rmi://" + jvm.getnode() + ":" + jvm.getrmiPort();

//Now lets get the connection to this specific MBeanServer and get that MDB MBean
instanceConnect = omdb.connect(serviceURL, "oc4jadmin", "welcome1");
instanceMbs = instanceConnect.getMBeanServerConnection();
instanceObjectName = new ObjectName("oc4j:j2eeType=MessageDrivenBean,EJBModule=\"myMDB\",J2EEApplication=myMDB,J2EEServer=standalone,name=\"MessageTopicProcessor\"");
instanceMDBMBean = (MessageDrivenBeanMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(instanceMbs, instanceObjectName, MessageDrivenBeanMBeanProxy.class, false);
// And finally stop that sucker
System.out.println("Stopping MDB on: " + serviceURL);
instanceMDBMBean.stop();
}


// ObjectName myMDBObjectName = new ObjectName("oc4j:j2eeType=MessageDrivenBean,EJBModule=\"myMDB\",J2EEApplication=myMDB,J2EEServer=standalone,name=\"MessageTopicProcessor\"");
//
// MDBMBean.start();

System.out.println("Success!");

} catch (Exception ex) {
ex.printStackTrace();
} finally {
}
}

}






Stopping an MDB via JMX - Instance

Carrying on my ongoing thread on JMS and MDB, my next task was to figure out how to stop a MDB programmatically. If you browse around in the OracleAS 10.1.3 MBean browser you will see all sorts of operations you might want to perform programmatically and in my case, looking at the MDB I deployed earlier on this week (http://mike-lehmann.blogspot.com/2006/09/simple-mdb-with-oracle-database-jms.html) I was interested MBean operations available on it.

The trick behind MBeans - at least for starters, is simply finding the darn things. Fortunately, a lot of standard JMX tools can hook up to Oracle Application Server, including JConsole as Steve Button, Mr. JMX at Oracle, blogged about here - http://buttso.blogspot.com/2006/06/more-info-on-remote-jconsole.html.

In my case continuing the "take the easiest route" I simply used the MBean browser inside of ASControl. The steps are illustrated below where I first go to the administrative tab of ASControl, click the System MBean browser and lastly navigate to my MDB application (myMDB) and expand it to find my MDB MessageProcessor and the operations on available on it:







What I was interested in was starting and stopping that MDB within the application itself, and importantly I would like to do it programmatically in a single OC4J instance within an Oracle Applicaiton Server instance. It turns out this is pretty easy to do once you have a basic understanding of JMX. I will take the shortest route there rather than generalizing the solution here - just so you can see the bare minimum.

First you need to know the MBean name - at the top of the ASControl page for MessageTopicProcessor you will see the breakdown of the MDB name in JMX format:

oc4j:j2eeType=MessageDrivenBean,EJBModule="myMDB",J2EEApplication=myMDB,
J2EEServer=standalone,name="MessageTopicProcessor"

Then you write a bunch of boiler plate code to hook up to the MBean server and finally the few lines to lookup the Mbean and do the operation. The full code for doing this is in [1]. The 6 lines that
matter are these - they are pretty self explanatory once you see them:

First connect to that J2EE instance - in my case called j2ee1 - and get its MBean server. Note that the OPMN port used - 6006 is the request port of my OracleAS instance:

JMXConnector clusterConnect = omdb.connect("service:jmx:rmi:///opmn://127.0.0.1:6006/j2ee1", "oc4jadmin", "welcome1");
MBeanServerConnection mbs = clusterConnect.getMBeanServerConnection();

Then look up the MessageDrivenBean in the myMDB application:

ObjectName myMDBObjectName = new ObjectName("oc4j:j2eeType=MessageDrivenBean,
EJBModule=\"myMDB\",
J2EEApplication=myMDB,
J2EEServer=standalone,
name=\"MessageTopicProcessor\"");

Then instantiate a local proxy for that MBean:

MessageDrivenBeanMBeanProxy MDBMBean = (MessageDrivenBeanMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(mbs, myMDBObjectName, MessageDrivenBeanMBeanProxy.class, false);

And finally, stop it:

MDBMBean.stop();

This is using what is called a dynamic proxy, a feature of JMX 1.2 that OracleAS 10.1.3 supports which gives you the ability to work the MBean methods like ordinary Java methods rather than marshalling up the number of arguments and argument types as previously.

In general (there turns out to be exceptions), the way you determine the dynamic proxy is simply take your MBean name you are looking up - in this case MessageDrivenBean and add a "MBeanProxy" on the end of it and away you go. For J2EEApplication, another common MBean people will want to manipulate, it would be J2EEApplicationMBeanProxy.

What's nice about dynamic Mbeans is in your IDE you actually will get code insight into the methods available on your OracleAS MBean (assuming you have admin_client.jar in the classpath). Check this out:


To run this client I just needed to add adminclient.jar to my classpath (it is part of OC4J and JDeveloper in the $ORACLE_HOME\j2ee\home\lib) and away I went. I have been told that dynamic proxies may still have a dependency on oc4j-internal.jar but am not sure that will stick when 10.1.3.1 goes production. Note adminclient.jar is also part of the client distributable that you can download from here:

http://www.oracle.com/technology/software/products/ias/htdocs/utilsoft_preview.html

In the case of MDB, not only can you start and stop them via MBeans in OracleAS 10.1.3.1 you can also control it via an annotation for enabling and disabling them. The reason I mention this is that start and stop operations are runtime operations and do not persist - that is if you stop the MDB and then bounce the container the MDB will come back in a started mode. The enable flag, on the other hand, is a persistent property turning the MDB off or on.

Using the previous MessageProcessor bean as an example, the MDB could be deployed as disabled/not-started as follows with the MessageDrivenDeployment property containing an extra "enabled" attribute:

@MessageDrivenDeployment(resourceAdapter = "simpleOemsRA" enabled="false")

and then later running a client similar to that illustrated, first enabling it and then starting it. Obviously real life will have permutations but the combination of JMX, JMX Consoles and annotations, the ability to do what you need whether at deployment time or runtime is clearly possible. Note this particular enable/disable feature is new in OracleAS 10.1.3.1 and is available at a patch to OracleAS 10.1.3.0 for those interested - corresponding to bug 4619599.

And that's that. Thanks to Steve Button whose JMX code I pillaged down to this tiny sample - in the near future he is working to put out a set of helper classes that generalize the solution below as part of a bigger scripting solution using Groovy on top of JMX. This was my poor man's way to get a quick and dirty example out and about while they work getting 10.1.3.1 out the door!


[1] Full Code:

package demo.oc4j.jmx;

import java.net.URL;

import java.util.ArrayList;
import java.util.Hashtable;

import java.util.Iterator;
import java.util.Set;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;

import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import oracle.oc4j.admin.management.mbeans.proxies.JVMMBeanProxy;
import oracle.oc4j.admin.management.mbeans.proxies.MessageDrivenBeanMBeanProxy;

public class OperateOnMDBInstance {
public OperateOnMDBInstance() {
}



private JMXConnector connect (String URL, String username, String password) {

JMXConnector jmxCon = null;

try {

Hashtable credentials = new Hashtable();
credentials.put("login", username);
credentials.put("password", password);

// Properties required to use the OC4J ORMI protocol
Hashtable env = new Hashtable();
env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "oracle.oc4j.admin.jmx.remote");
env.put(JMXConnector.CREDENTIALS, credentials);

JMXServiceURL serviceUrl = new JMXServiceURL(URL);
jmxCon = JMXConnectorFactory.newJMXConnector(serviceUrl, env);

// Do it!
jmxCon.connect();

} catch (Exception ex) {
ex.printStackTrace();
}

return jmxCon;
}

public static void main(String[] args) {
try {
OperateOnMDBInstance omdb = new OperateOnMDBInstance();

JMXConnector clusterConnect = omdb.connect("service:jmx:rmi:///opmn://127.0.0.1:6006/j2ee1", "oc4jadmin", "welcome1");
MBeanServerConnection mbs = clusterConnect.getMBeanServerConnection();
ObjectName myMDBObjectName = new ObjectName("oc4j:j2eeType=MessageDrivenBean,EJBModule=\"myMDB\",J2EEApplication=myMDB,J2EEServer=standalone,name=\"MessageTopicProcessor\"");
MessageDrivenBeanMBeanProxy MDBMBean = (MessageDrivenBeanMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(mbs, myMDBObjectName, MessageDrivenBeanMBeanProxy.class, false);
MDBMBean.stop();
System.out.println("Success!");

} catch (Exception ex) {
ex.printStackTrace();
} finally {
}
}

}


Saturday, September 30, 2006

Simple MDB with Oracle Database JMS Provider

Continuing in my JMS explorations of late I was asked about clustering MDB on top of a simple topic backended by AQ. My first problem was I could not find a quick and dirty example of an MDB (EJB 3.0) using a JMS topic, the recommended JMS resource adapter approach of OracleAS 10.1.3 and on top of AQ. A simple hello world was all I was after.

Give me something very simple and I can go miles because the doc for JMS (http://download-west.oracle.com/docs/cd/B25221_04/web.1013/b14427/jms.htm#i1085966) and MDB (http://download-west.oracle.com/docs/cd/B25221_04/web.1013/b14428/mdb30cfg.htm#BCGFGDAI) explains all the advanced stuff and this OTN how-to (http://www.oracle.com/technology/tech/java/oc4j/1013/how_to/how-to-connect-to-oemsjmsd/doc/how-to-connect-to-oemsjmsd.html) gives a pretty rich example with a lot of detail on what the idiosyncracies are with setup.

However, despite this being easy, I also wanted quick set up using the tools provided by Oracle that combined setting up the resource adapter, configuring a topic and writing an MDB to process messages into one sequence of steps rather than 3 different tasks. This entry is devoted to getting going fast with such a sample - cobbled together from other similar examples, a bit of new stuff and a bit of begging and pleading from various people who built it all - thanks to Demed Lher (JMS PM), Debu Panda (EJB PM) and the ASControl 10.1.3 folks who made this possible.

Here goes:

1. Create a JMS user on your database and grant them AQ rights. I am using XE and you can tell I have a tough password policy:

sqlplus sys/welcome1@xe as sysdba

grant connect, resource, aq_administrator_role to jmsuser identified by jmsuser;
grant execute on sys.dbms_aqadm to jmsuser;
grant execute on sys.dbms_aq to jmsuser;
grant execute on sys.dbms_aqin to jmsuser;
grant execute on sys.dbms_aqjms to jmsuser;
exec dbms_aqadm.grant_system_privilege('ENQUEUE_ANY','jmsuser');
exec dbms_aqadm.grant_system_privilege('DEQUEUE_ANY','jmsuser');

2. Create a AQ topic - in this case JMSDEMO_TOPIC:

sqlplus jmsuser/jmsuser@xe
exec dbms_aqadm.create_queue_table(queue_table=>'JMSDEMO_QUEUE_TABLE', queue_payload_type=>'sys.aq$_jms_text_message',multiple_consumers=>true);
exec dbms_aqadm.create_queue(queue_name=>'JMSDEMO_TOPIC', queue_table=>'JMSDEMO_QUEUE_TABLE');
exec dbms_aqadm.start_queue(queue_name=>'JMSDEMO_TOPIC');
commit;

3. Create a data-source in OracleAS to point to the database user JMSUSER. I used Application Server Control to do this here by creating a connection pool called oemsdbPool and a data source called oemsdbDS:

Which could also be put directly into your data-sources.xml using this snippet:

<managed-data-source connection-pool-name="oemsdbPool" jndi-name="jndi/oemsdbDS" name="oemsdbDS"/>
<connection-pool name="oemsdbPool">
<connection-factory factory-class="oracle.jdbc.pool.OracleDataSource" user="jmsuser" password="jmsuser" url="jdbc:oracle:thin:@//127.0.0.1:1521/xe"/>
</connection-pool>

4. In order to use the resource provider approach for my MDB, I need to configure up the OEMS Database provider. Unlike 10.1.3.0 where this was a manual and configuration process, it is a nicely automated procedure in OracleAS 10.1.3.1 (I am using the Developer Preview).

The following 3 screen shots show how trivial the Application Server Control team have made this.

a. In the administration tab, click on the OEMS database persistence configuration

b. Click on the deploy button to deploy it

c. Name the resource adapter/provider and hook it up to the data source created previously. When you click on finish, you will be asked to restart the default application. Mine never came back so I ended up using opmnctl to do a full OC4J instance re-start ... I believe this is a bug in the developer preview that was fixed after its August release.



You can then see and do further configuration of the adapter on the adapters page. From a lot of doc to about a 4 click operation, I have to say the ASControls folks who I have the luck to know reasonably well, did a great simplification job of a complex area!


5. Now for my application clients using the OEMS Database provider, I need to surface my topic with some logical names. This I found this confusing though correctly documented but again ASControl made short work of exactly the things I needed to do:

a. Make sure an appropriate connection factory is available by going to the connection factory tab of my newly created resource adapter (picture below), name it (I gave it the adapter name - simpleOemsRA/MyTCF) and give it a private connection pool for simplicity.




b. Name my administered topic object (oracle.j2ee.ra.jms.generic.AdminObjectTopicImpl from the drop down list box) to provide a JNDI mapping to the physical database topic JMSDEMO_TOPIC and finally hook it up to the resource provider created during deployment.

This has two parts so here are they are in detail:

b1. Unlike the OracleAS JMS screen you will see that you do *not* have to provide the JNDI location and JNDI name for the Topic. You just give a JNDI location. You can provide a JNDI name but you would have to manually edit the underlying oc4j-connectors.xml. What this is "encouraging" is the use of autowrapping of the destinations. So enter your JNDI name - I chose simpleOemsRA/AutoWrap because this JNDI name will be the "automatic" wrapper for all my topics. See later for some details on this.

b2. Second the screen asks for the resource provider name and defaults it to ojmsrp when you should be using the resource provider name you used when deploying the resource adapter - in our case simpleOemsRP - both b1 and b2 are shown in the screen sequence below.




To get a sense of some comfort that things are working at this stage, if you go back to the Administer OEMS tab of the Application Server Control you should be able to see the physical AQ JMSDEMO_TOPIC in the list of available topics that are hooked up to the environment like below with a JNDI URL of something like java:comp/resource/simpleOemsRP/Topics/JMSDEMO_TOPIC:


What's up here? I can see that from my configuration of my resource provider having a database connection we created earlier it automatically discovered my topic. That's cool! But what is also interesting is the resolution of the physical JNDI name - java:comp/resource/simpleOemsRP/Topics/JMSDEMO_TOPIC - where did that /Topics/ come from?

It turns out that is part of how OEMS Database provider queues and topics are surfaced through the resource provider
- the prefix /Topics/ and /Queues/ are prefixed on the database queue/topic name. As such, taking an example, a database topic named JMSDEMO_TOPIC will have the physical suffix of Topics/JMSDEMO_TOPIC. Likewise with queues, a database queue named JMSDEMO_QUEUE will have a physical suffix of Queues/JMSDEMO_QUEUE. This is doc'd more formally just under the table this URL points at:

http://download-east.oracle.com/docs/cd/B25221_04/web.1013/b14427/jms.htm#sthref295

The end result when working with MDBs and clients, the JNDI location you will use for this example is going to have the logical name of (using my example): simpleOemsRA/Autowrap/Topics/JMSDEMO_TOPIC which in turn will resolve to the physical name java:comp/resource/simpleOemsRP/Topics/JMSDEMO_TOPIC. Check out my MDB at the end of this blog where I provide this setting in the destination name. If you want to manually wrap this in order to obfusticate the JMSDEMO_TOPIC name you can as well but this is not avialable from the ASControl screen.

5. With that, everything - at least the minimal - is done and it is possible to write an MDB. Again following the lazy man's approach I will use EJB 3.0 so I can do it all in as little configuration as possible. Mucking around my MDB pretty much wires up to the configuration done above and does a simple printout of the message off the queue. It looks like the following code at the end of this [1] blog - code liberally stolen from Debu Panda's EJB 3.0 MDB samples at http://otn.oracle.com/ejb3.

6. As I was doing this in JDeveloper, I simply packaged this guy up in an ear and then used the admin_client deployment tool to deploy it as follows:

SET JAVA_HOME=d:\jdk150
SET PATH=d:\jdk150\bin
SET ORACLE_HOME=d:\soasuite
java -jar %ORACLE_HOME%\j2ee\home\admin_client.jar deployer:oc4j:opmn://127.0.0.1:6003/home oc4jadmin welcome1 -deploy -file D:\mywork\oemsMDB\deploy\myMDB.ear -deploymentName myMDB


7. Then of course we need a client to throw messages on the queue. The easiest way is to write a simple Java client. Again in the spirit of begging and borrowing code, I have taken a sample from the JMS product manager, Demed LHer and slimmed it down to the bare essentials so it just does that one thing in [2].

To run that client you simply have to compile it with the right classpath:

set AS_HOME=D:\soasuite
set DB_HOME=D:\oraclexe\app\oracle\product\10.2.0\server
set JAVA_HOME=D:\jdk150 CLASSPATH=.;%DB_HOME%/RDBMS/jlib/aqapi13.jar;%DB_HOME%/RDBMS/jlib/jmscommon.jar;%DB_HOME%/RDBMS/jlib/xdb.jar;%DB_HOME%/lib/xmlparserv2.jar;%DB_HOME%/jdbc/lib/ojdbc14.jar;%DB_HOME%/jlib/orai18n.jar;%DB_HOME%/jlib/jndi.jar;%J2EE_HOME%/lib/jta.jar
javac -classpath %CLASSPATH% Send.java
java Send "JMSDEMO_TOPIC"

And there you go. If you look in your ORACLE_HOME\opmn\logs you should see the MDB sucking the above message off the topic and spitting out the results using its System.out.println.

I can't say it was trivial, but broken down to its simplest form, hopefully it is a building block that can be generalized for others!

[1] My MDB:

package demo.mdb;

import java.util.Date;

import javax.ejb.MessageDriven;
import oracle.j2ee.ejb.MessageDrivenDeployment;
import javax.ejb.ActivationConfigProperty;
import javax.jms.Message;
import javax.jms.MessageListener;

import javax.jms.Message;

@MessageDriven(
activationConfig = {
@ActivationConfigProperty(
propertyName="ConnectionFactoryJndiName", propertyValue="simpleOemsRA/MyTCF"),
@ActivationConfigProperty(
propertyName="DestinationName", propertyValue="simpleOemsRA/AutoWrap/Topics/JMSDEMO_TOPIC"),
@ActivationConfigProperty(
propertyName="DestinationType", propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(
propertyName="SubscriptionDurability", propertyValue="Durable"),
@ActivationConfigProperty(
propertyName="SubscriptionName", propertyValue="MySubscription"),
@ActivationConfigProperty(
propertyName="messageSelector", propertyValue="RECIPIENT = 'MDB'")
})


// associate MDB with the database JMS resource adapter
@MessageDrivenDeployment(resourceAdapter = "simpleOemsRA")
/**
* This is a very simple example of a Message-Driven Bean configured to listen
* on an AQ Topic using the generic resource adapter for JMS. It listens to the
* configured Topic and gets notified via an invocation of it's
* onMessage() method when a message has been posted to the Topic. This
* bean simply prints out the contents of the message.
*/
public class MessageTopicProcessor {


public void onMessage(Message message) {
System.out.println("onMessage() - " + message);
try {

String subject = message.getStringProperty("subject");
String inmessage = message.getStringProperty("message");
System.out.println("Message received\n\tDate: "
+ new java.util.Date() + "\n\tSubject: " + subject
+ "\n\tMessage: " + inmessage + "\n");
}

catch (Throwable ex) {
ex.printStackTrace();
}
}

}


[2] Sample Database JMS Client

/*
*
* If using Oracle XE, the following jar files are required in your classpath to
* run this example:
*
* .;%DB_HOME%/RDBMS/jlib/aqapi13.jar;%DB_HOME%/RDBMS/jlib/jmscommon.jar;
* %DB_HOME%/RDBMS/jlib/xdb.jar;%DB_HOME%/lib/xmlparserv2.jar;
* %DB_HOME%/jdbc/lib/ojdbc14.jar;%DB_HOME%/jlib/orai18n.jar;%DB_HOME%/jlib/jndi.jar;%J2EE_HOME%/lib/jta.jar
*
*/

// Java infrastructure packages
import java.lang.*;
import java.util.Properties;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

// JMS packages
import oracle.AQ.*;
import oracle.jms.*;
import javax.jms.*;

/* -------------------------------------------------------
* Send
* ------------------------------------------------------- */
public class Send
{

// Here's the XML payload to put in a text message
private final static String SOME_XML =
"\n" +
"\n" +
" \n" +
" John\n" +
" W\n" +
" Doe\n" +
"
\n"+
"
";

public static void main (String args [])
throws java.sql.SQLException, ClassNotFoundException, JMSException
{

TopicConnectionFactory tcfact =null;
TopicConnection tconn =null;
Topic topic =null;
TopicSession tsess =null;
TopicPublisher publisher = null;
TopicSubscriber subscriber = null;
TextMessage txtmsg, dtxtmsg;
String destName = "JMSDEMO_TOPIC";
String dbHost = "127.0.0.1";
String dbPort = "1521";
String dbSid = "XE";
String dbDriver = "thin";
String dbUser = "jmsuser";
String dbPassword = "jmsuser";

System.out.println("\n-------------------------------------------------------------");
System.out.println("OEMS.155 - simple JMS Send / JMS 1.02 / Database AQ / no JNDI");
System.out.println("-------------------------------------------------------------");


// get destination to Send on from command-line
try {
destName = args[0];
} catch (Exception e) {
System.out.println("** please provide a destination to send on [" + args.length + "]");
usage();
System.exit(0);
}


try {
// get connection factory - we are not going through JNDI here
tcfact = AQjmsFactory.getTopicConnectionFactory(dbHost, dbSid, Integer.parseInt(dbPort), dbDriver);
System.out.println("Connection factory = " + tcfact.toString());
// create connection
tconn = tcfact.createTopicConnection(dbUser,dbPassword);
System.out.println("Created connection = " + tconn.toString());
// create session
tsess = tconn.createTopicSession(true, Session.CLIENT_ACKNOWLEDGE);
System.out.println("Created session = " + tsess.toString());
// start connection
tconn.start() ;
System.out.println("started connection");
// get topic
topic = ((AQjmsSession)tsess).getTopic(dbUser,destName) ;
System.out.println("Got topic = " + topic.toString());
System.out.println("started session = " + tsess.toString());

} catch (JMSException e) {
System.err.println("** JMS: failed starting session. Do not forget to run the SQL scripts to create necessary tables in RDBMS.\n");
e.printStackTrace();
System.exit(-1);
}


try {
publisher = tsess.createPublisher(topic);
txtmsg = tsess.createTextMessage(SOME_XML) ;
System.out.println("\ndestination: " + topic + "\nmessage :\n\n" + SOME_XML);
publisher.publish(topic, txtmsg) ;
tsess.commit() ;
System.out.println("\nmessage was sent with ID="+txtmsg.getJMSMessageID());
} catch (Exception e) {
System.err.println("** Problem publishing messaging:\n");
e.printStackTrace();
}

// Cleaning up before exiting
try {
//((AQjmsDestination)topic).stop(tsess, true, true, false);
tsess.close() ;
tconn.close() ;
} catch (Exception e) {
System.err.println("** Problem terminating session and connection:\n");
e.printStackTrace();
}

}

/*-----------------------------------------------------------------------
* usage
* prints program usage
*----------------------------------------------------------------------*/

private static void usage() {
System.err.println("\nUsage: Send ");
System.err.println("Ex : Send JMSDEMO_TOPIC");
}
}

Friday, September 29, 2006

Propagating JMS Messages to Multiple Targets

I was investigating the Oracle JMS Router (Doc: http://download-west.oracle.com/docs/cd/B25221_04/web.1013/b14427/jms.htm#sthref378 ) for a customer today as a mechanism for propagating messages from queues and topics to other queues and topics and thought I would point out a couple of things.

I was trying to implement a picture that looks like the one below using the JMS Router - the JMS router maintains a backlog of jobs that run watching whatever source queues and topics you set up and propagates them to your target destination queues and topics:



What the customer was trying to do was propagate an inbound message to 3 different systems. The strategy they were looking at was to have a topic and then on each system have an MDB that listened to the topic and moved it into a queue for processing by each system. The issue with the approach was the number of MDB that had to be written and maintained over time. With one topic and three target queues this may be a reasonable approach but once you get to dozens of topics each with many targets, writing code gets pretty unreasonable.

The JMS Router lets you make this a configuration exercise. You simply add a job to the JMS router environment to listen to the inbound topic and tell it which queue you want it propagated to. In this case because there are 3 target queues, I simply need to add 3 jobs.

How hard is this to do? Well, in OracleAS 10.1.3 the JMS Router is pre-deployed and ready to go. It took me about 10 minutes to configure this up - I had a handy Java client for throwing messages at a topic so once configured, I could see it in action pretty quickly using Hermes as discussed in an earlier post. You can configure it in two ways:

1. Edit the jms.xml file and add its configuration
2. Use the MBean browser in OracleAS 10.1.3 to do this

For a step by step how-to check out this document on OTN:

http://www.oracle.com/technology/tech/java/oc4j/1013/how_to/how-to-use-JMS-router/doc/How-to-Use-JMS-Router.html

For my configuration I have included the bits from jms.xml here - while this looks verbose you will see the configuration is identical per job with one change being the job name and the target queue:

<jms-router max-local-concurrency="-1" >
<router-job
job-name="job1"
max-retries="16"
polling-interval="5"
pause-job="false"
use-exception-queue="false"
batch-size="30"
>
<message-source>OracleASjms/Topics/jms/demoTopic</message-source>
<source-connection-factory>OracleASjms/MyCF</source-connection-factory>
<message-selector></message-selector>
<subscriber-name>OracleASRouter_job1</subscriber-name>
<source-log-queue>OracleASjms/Queues/OracleASRouter_LOGQ</source-log-queue>
<message-target>OracleASjms/Queues/jms/demoQueue</message-target>
<target-connection-factory>OracleASjms/MyCF</target-connection-factory>
<target-log-queue>OracleASjms/Queues/OracleASRouter_LOGQ</target-log-queue>
<exception-queue>default</exception-queue>
</router-job>
<router-job
job-name="job2"
max-retries="16"
polling-interval="5"
pause-job="false"
use-exception-queue="false"
batch-size="30"
>
<message-source>OracleASjms/Topics/jms/demoTopic</message-source>
<source-connection-factory>OracleASjms/MyCF</source-connection-factory>
<message-selector></message-selector>
<subscriber-name>OracleASRouter_job2</subscriber-name>
<source-log-queue>OracleASjms/Queues/OracleASRouter_LOGQ</source-log-queue>
<message-target>OracleASjms/Queues/jms/demoQueue2</message-target>
<target-connection-factory>OracleASjms/MyCF</target-connection-factory>
<target-log-queue>OracleASjms/Queues/OracleASRouter_LOGQ</target-log-queue>
<exception-queue>default</exception-queue>
</router-job>

<jms-router max-local-concurrency="-1" >
<router-job
job-name="job3"
max-retries="16"
polling-interval="5"
pause-job="false"
use-exception-queue="false"
batch-size="30"
>
<message-source>OracleASjms/Topics/jms/demoTopic</message-source>
<source-connection-factory>OracleASjms/MyCF</source-connection-factory>
<message-selector></message-selector>
<subscriber-name>OracleASRouter_job3</subscriber-name>
<source-log-queue>OracleASjms/Queues/OracleASRouter_LOGQ</source-log-queue>
<message-target>OracleASjms/Queues/jms/demoQueue3</message-target>
<target-connection-factory>OracleASjms/MyCF</target-connection-factory>
<target-log-queue>OracleASjms/Queues/OracleASRouter_LOGQ</target-log-queue>
<exception-queue>default</exception-queue>
</router-job>

</jms-router>


and a screen shot of me adding one job via the MBean browser here - this defaults a number of parameters so the minimal needed here is just the source message, source connection factory, target destination and target connection factory. Because I am lazy, this is how I did it -- then once I had one job entered I copied and pasted in jms.xml and res-start the server. Using ASControl this way lets you do it without a re-start as it goes through the proper JMX entry points. This one begs for a groovy script - more on that later - to automate configuration.



If you need a simple way to route messages to multiple targets and don't want to be writing a lot of code to make it happen manually, this could be your answer. Nice to see this baked right into the product with a minimum of fuss or muss to make it work.