09 December 2008

Replacing Oracle JAX-RPC Libraries

As of the OC4J 10.1.3.4 release, the Oracle WS JAX-RPC libraries have been isolated into their own named/versioned shared-library. This means that they can be selectively removed from the visibility of an application, allowing alternative implementations to be provided as needed:

The shared-library is defined as:
Shared Library Name:    oracle.ws.jaxrpc
Shared Library Version: 1.1
Parent Name: api
Parent Version: 1.4.0

Modifiable: no

Library Compatible: yes

Code Sources:
/D:/java/oc4j-10134-dev/webservices/lib/jaxr-api.jar
/D:/java/oc4j-10134-dev/webservices/lib/jaxrpc-api.jar
/D:/java/oc4j-10134-dev/webservices/lib/saaj-api.jar
/D:/java/oc4j-10134-dev/webservices/lib/jws-api.jar
The section in the doc that describes this is here: Using JAX-WS RI

20 November 2008

Dependency Injection in Taglibs

Helping with some migration material recently for OC4J --> WLS, a question was asked about whether OC4J 10.1.3.x supported dependency injection with its Tag library implementation.

It's known that OC4J 10.1.3.1+ supports dependency injection in the Web container as described here, but the specifics of whether that extended to tag librarieswas not mentioned.

A subsequent quick test verified that dependency injection does indeed work for tags using the  SimpleTag (SimpleTagSupport) and BodyTag (BodyTagSupport).
package sab.testdi.tag;

import java.io.*;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import sab.testdi.ejb.CalcLocal;

public class CalcAdd extends SimpleTagSupport {

@EJB(name="Calc")
CalcLocal calc;


int v1 = 0;
int v2 = 0;

public void setV1(int v1) {
this.v1 = v1;
}

public void setV2(int v2) {
this.v2 = v2;
}

public void doTag() throws JspException, IOException {
PrintWriter out = new PrintWriter(this.getJspContext().getOut());
out.printf("<div style='font-family: courier'>");
if(calc != null) {
out.printf("%s+%s=%s", v1, v2, calc.add(v1, v2));
} else {
out.printf("bugger, calc is null!");
}
out.println("</div>");
}
}

The taglib.tld file defines the tag:
<?xml version = '1.0' encoding = 'windows-1252'?>
<taglib xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0"
xmlns="http://java.sun.com/xml/ns/j2ee">
<display-name>calc</display-name>
<tlib-version>1.2</tlib-version>
<short-name>calc</short-name>
<uri>/webapp/calc</uri>
<tag>
<description>A short description...</description>
<display-name>add</display-name>
<name>add</name>
<tag-class>sab.testdi.tag.CalcAdd</tag-class>
<body-content>empty</body-content>
<attribute>
<name>v1</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>v2</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<example><calc:add v1="100" v2="25"/></example>
</tag>
</taglib>
And finally, a JSP can use it by importing the JAR file containing the tag libraru into the WEB-INF/lib directory (where it is nicely auto-discovered) and making a call to the <calc:add .../> tag.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ taglib uri="/webapp/calc" prefix="calc"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
<title>Index</title>
</head>
<body style="font-family: arial">
<h2>Testing Taglib Dependency Injection</h2>
<p>
<calc:add v1="12" v2="12"/></p>
</body>
</html>

When the JSP is accessed it successfully displays 12+12=24 which is rendered by the tag library after being calculated via the injected EJB reference.

18 November 2008

Configuring shared-libraries settings post deployment

With the introduction of the shared-library mechanism in OC4J 10.1.3.x, it is possible to configure the set of libraries that an application has access to. This is done at the application level using the tag to include a named shared-library, or its counterpart, to remove a library from the view of the application.

These settings are made in the OC4J specific orion-application.xml deployment descriptor for the application, typically either as a hard-coded entry in the packaged EAR file that is being deployed, or they are set during the deployment process using the Class Loading Task in the ASC console.

In the current releases, there are no ways to easily change these settings once the application has been deployed. One approach that has been used is to perform a redeployment operation of the application and make the change via the ASC console. Obviously this requires a redeployment to be performed. While you can also manually edit the resulting orion-application.xml file to add/remove/change the settings, this provides no validation that the settings are correct.

To address this, for the next patch release of OC4J (10.1.3.5) we are looking to add a couple of new shared-library commands to the admin_client.jar utility to operate on existing, deployed applications. These commands will allow you to perform add or delete operations on either of the or configuration elements.

As an example, lets say you have an application in which you wish to use the Apache Xerces parser instead of the Oracle XML parser. This change can be performed during deployment as described here: http://www.oracle.com/technology/tech/java/oc4j/1013/how_to/how-to-swapxmlparser/doc/readme.html

But! When you test your application, it throws an Exception and you eventually realise that you forgot to make the change to the shared-libraries during the deployment of the application. The Oracle XML parser is still being used.

Using the new commands you would be able to change this on the deployed application as follows:

>java -jar admin_client.jar .... -addRemoveInheritedSharedLibrary -appName myapp -name oracle.xml


>java -jar admin_client.jar .... -addImportSharedLibrary -appName myapp -name apache.xml -minVersion 2.7 -maxVersion 2.7





17 November 2008

JEE5 web-app declaration

I seem to have trouble finding the correct definition of the web-app tag for JEE5/Servlet2.5 modules.

So for nothing more than a readily available memo to myself, here it is:
<web-app
version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

The schema is located here: http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd

XMLSchemas for WebLogic Server

The first set of XSD files for WebLogic Server have just been posted to the Oracle XSD hosting location: http://xmlns.oracle.com/weblogic.

The index page allows you to drill down into the version of WebLogic Server you are interested in, and ultimately locate the component specific XSD file.

For example, the JDBC Data Source XSD is located at: http://xmlns.oracle.com/weblogic/jdbc-data-source/1.0/jdbc-data-source.xsd

05 November 2008

Checking if FastSwap is enabled

In doing some work with the FastSwap feature of WebLogic Server recently, I wanted to be able to display whether FastSwap was enabled for the application.

I used the small hack below to determine whether FastSwap is enabled or not for the application. This examines the current ClassLoader that is being used and compares it with the FastSwap enabled ClassLoader.

private final String fastSwapLoader = "com.bea.wls.redef.RedefiningClassLoader";
...
private boolean isFastSwapEnabled() {
return Thread.currentThread().getContextClassLoader().getClass().getName().equals(fastSwapLoader);
}
If the classloader name changes in a future release, then of course this would break ... but it serves the purpose currently on WLS 10.3.

Another interesting area was to observe how the class name changed as the underlying class itself changed and FastSwap did its funky thing. I noticed that the second frame on the Thread stack contains the FastSwap modified classname. Grabbing that, you can keep track of the class name changes over time to display.

private String determineClassName() {
return Thread.currentThread().getStackTrace()[1].getClassName();
}

As the class changes over time, the names will look like the below:

  • sab.demo.fastswap.web.TestServlet_beaVersion0_1
  • sab.demo.fastswap.web.TestServlet_beaVersion1_11
  • sab.demo.fastswap.web.TestServlet_beaVersion2_12
  • ...

Starting WebLogic Server instance with local JMX access enabled

Needing to connect to a WLS instance with JConsole to view/use the MBeans?

Me too.

The simplest way I found to do it was to to add an entry to the setDomainEnv.cmd file for the specific managed instance:

set JAVA_OPTIONS=%JAVA_OPTIONS% -Dcom.sun.management.jmxremote

When you then run the managed instance, the setDomainEnv.cmd file is called and the additional property is added to the JVM when it is launched.

This is covering the case of starting the server instance from the command line -- if you are using Node Manager, then you'll need to set this in the configuration script that is used by the nodemanager.

14 August 2008

Formatting OC4J access logs

OC4J can be configured to log its web accesses to a specified access-log file.

The format of the log entry can be customized according to the documentation here:

http://download.oracle.com/docs/cd/B25221_04/web.1013/b14432/website.htm#BABFGJHD

Some format tokens that are mentioned, but not really called out are $cookie and $header.

Using these tokens, you can ask OC4J to inject the specified cookie/header value into the access-log.

For example, if you wanted to log the value of the JSESSIONID cookie for each request, you'd configure the format string as follows:

<access-log path="../log/default-web-access.log" format="
$ip - $user '$request' $status $cookie:JSESSIONID" />
When OC4J runs, it will log entries in default-web-access.log according to the pattern that was specified:
141.144.152.48 - 'oc4jadmin' 'GET /peek/ HTTP/1.1' 200 c2796b83b9d7a79cfa4c491792f6783fd64a00cd3a17d13548c189d8d6e6543f

13 August 2008

15 July 2008

Configuring a dataSource for MySQL using EM

If you try to use the EM console to configure an OC4J datasource that accesses a MySQL database, then you may run into a problem with EM reporting that the class you are specifying can't be found.

This troubles you somewhat because you've taken the usual approach of putting 3rd party libraries to be shared into the applib directory of the OC4J instance -- so the classes should be available!

Well the problem here is that when the datasource is being constructed by EM, the classloader that is used is one that is obtained from the ascontrol application. And if you look carefully at the configuration of the ascontrol application, you'll see that it specifically removes the global.libraries shared-library from its list of imports. And guess what -- the global.libraries shared-library is defined with a single code-source -- that being the contents of the applib directory. Therefore the ascontrol application does not see the mysqlconnector.jar file you put into the applib directory. At runtime OC4J would see it, so if you manually add the datasource definition it'll work fine -- but using EM to construct the MySQL datasource requires it to see the jar file.

The change to make this work is quite easy and you have few options:

1. You can edit the orion-application.xml file of the deployed ascontrol application, and comment out the line that removes the importing of the global.libraries.xml file:

<imported-shared-libraries>
<--
<remove-inherited name="global.libraries"/>
-->
<import-shared-library name="oracle.xml.security"/>
<import-shared-library name="oracle.xml.security"/>
</imported-shared-libraries>
2. You continue to use the convenience of applib to make the driver available container wide, but instead of reconfiguring the ascontrol application to not remove the global.libraries, you just drop the mysql-connector.jar file into the WEB-INF/lib directory of the ascontrol application, so it is able to locate the specified class from its own set of libraries. To to that, copy the mysql-connector.jar file into $ORACLE_HOME/j2ee/home/applications/ascontrol/ascontrol/web-inf/lib directory. And then restart either the ascontrol application, or the OC4J instance itself to ensure the new library is visible.

3. Instead of using the convenience of the applib directory, you can deploy the MySQL connector as a named/versioned shared-library and then specifically import it into applications where you need it. This would for example need to be done in the application you deploy that needs to connect to MySQL and where corresponding datasource is defined, and also in the ascontrol application itself. . To do the latter, since you can't use EM to configure itself, you'd need to edit the $ORACLE_HOME/j2ee/home/application-deployments/ascontrol/orion-application.xml file and manually add the import statement to pull in the MySQL shared-library:
  <imported-shared-libraries>
<remove-inherited name="global.libraries"/>
<import-shared-library name="oracle.xml.security"/>
<import-shared-library name="oracle.xml.security"/>
<import-shared-library name="mysql.connector"/>
</imported-shared-libraries>

02 July 2008

Using OC4J War File Manifest.mf Class-Path

There seemed to be some confusion around this area, so I posted a small example of using Manifest.mf Class-Path settings from a WAR file to OTN yesterday.

http://www.oracle.com/technology/tech/java/oc4j/10130/how_to/webapp-manifest-loading.zip

This example was build and tested against an OC4J 10.1.3 instance -- however it should work in the same way against 10.1.2 and 9.0.x releases. The app itself won't deploy to the earlier versions since the deployment files are using XSD. However the principle should be the same.

The use of OC4J 10.1.3 enables you to run classloader queries to observe the loader tree, see where code-sources are being loaded from, see which code-source classes are located in, etc.

Update: 3-July-2008

By removing any XSD elements from the deployment descriptors, the application referenced above can be rebuilt and successfully deployed to OC4J 10.1.2, whereupon the application works in exactly the same way as it does on 10.1.3. The only difference is in the output of the ClassLoader().toString method.

When running in 10.1.2, the output from the testservlet and testclass.jsp pages looks is shown below. In the output, you can see that the code-sources foobar.jar and myservlet.jar are loaded from the root level of the application deployment directory, as configured in the META-INF/MANIFEST.MF file; whereas that the simple.jar is loaded from the WEB-INF/lib directory of the web application as expected.

Note: the output has been formatted slightly to make it more presentable in this page view.
Test Class Access

Class Loader

sab.demo.warmanifest.beans.Foo
[ClassLoader: [
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\foobar.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\myservlet.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\webapp\WEB-INF\lib\simple.jar archive]
]]

sab.demo.warmanifest.beans.Bar
[ClassLoader: [
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\foobar.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\myservlet.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\webapp\WEB-INF\lib\simple.jar archive]
]]

sab.demo.warmanifest.beans.Simple
[ClassLoader: [
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\foobar.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\myservlet.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\webapp\WEB-INF\lib\simple.jar archive]
]]

sab.demo.warmanifest.web.TestServlet
[ClassLoader: [
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\foobar.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\myservlet.jar archive],
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\webapp\WEB-INF\lib\simple.jar archive]
]]

org.apache.log4j.Logger
[ClassLoader: [
[D:\java\oc4j-1012\j2ee\home\applications\webapp-manifest-loading\log4j-1.2.15.jar archive]
]]

oracle.jdbc.pool.OracleDataSource
sun.misc.Launcher$AppClassLoader@a39137

javax.servlet.http.HttpServlet
sun.misc.Launcher$AppClassLoader@a39137

30 June 2008

Application MBean NotificationListeners

And another example from an OTN question.

http://forums.oracle.com/forums/thread.jspa?messageID=2619654&tstart=0#2619654

I'm using OC4J 10.1.3 and I see, in Oracle MBean browser, that a mbean is created and registered each time an EAR is deployed :

"oc4j:j2eeType=J2EEApplication,name=,J2EEServer=standalone"

I tried to registerer to notifications of this mbean (j2ee.state.starting and j2ee.state.stopping) using web console. It works, I can see notifications in web console.

Now, I would to like to registered to these notifications in my application. I don't find sample in oc4j documentation on how doing this.

Has anybody already use this functionality ?


Update 03-July-2008: I posted an example of this on the OC4J How-To page. The zip file is available here:
http://www.oracle.com/technology/tech/java/oc4j/10130/how_to/application-jmx-listener.zip.

The short answer is that you can register for notifications from any of the OC4J MBeans using the standard JMX API -- no worries at all.

However there are a couple of tricks to keep in mind when working with OC4J.

Trick 1 : from within OC4J, an application has restricted access to the OC4J MBean set. We provide applications with a proxy to the MBeanServer that permits access to only the MBeans that the application itselt registers. Or in other words, it prohibits applications from seeing and using the OC4J MBeans when using the MBeanServerFactory.getMBeanServer() call from within the application code. There are basically two ways to deal with this.

I. You can disable the security aspect of the proxy. This can be done by setting the Java System property "oc4j.jmx.security.proxy.off" when OC4J is launched. Beware however that this applies to the entire container, so any deployed applications will have access to the OC4J MBeans. Beware.

II. You can establish what amounts to a loopback remote connection using the JMX Remote API. When doing this, you can authenticate yourself as a user from within the oc4j-app-administrators role, and thus the proxy you receive does not have any restrictions placed on it. The downside to doing this is that the connection must be constructed using the URL and username/password of the respective user you want to connect as. Therefore you are dealing with ports and password visibility issues.

With that understood, lets assume that you now have access to the OC4J MBeans using JMXConnector.

A JMX listener is simply a class that implements the javax.management.NotificationListener interface. You create a class that implements this interface, and then you register your interest with the MBeanServer for receiving Notifications that are emitted from a specific MBean using its addNotificationListener method.

Trick 2: in order to receive Notifications from an MBean, the connection you used to register with the MBeanServer must be maintained. If the connection closes, the link between the MBean and your Listener is gone. Therefore when you are designing your application, you need to use a model that allows the connection to be maintained across requests, but at the same time, you need to do it such that the connections do not place an undue resource tax on the operation of the server. You probably don't want to store a JMXConnector in each HttpSession of each client for example.

As a demonstration of these tricks, I have pulled together a demo application to show what an example may look like.

Points of Interest:

JMXListenerBean -- this JavaBean implements the NotificationListener interface. When Notifications are received they are stored in a List maintained by the JavaBean. This enables the Notifications to be retrieved and rendered in a client of some form.

GlobalListenerMap -- the application uses one global class -- GlobalListenerMap -- to create and maintain the connection to the MBeanServer. This class is also where the individual MBean listeners are registered and stored for each client. The class is created and placed in the ServletContext by a ServletContextListener; it is also closed when the ServletContext is destroyed, where it closes its JMXConnector. When a new browser client asks to register a listener with a specific MBean, a servlet creates the actual listening class and submits it to the GlobalListenerMap to register with the MBeanServer. The GlobalListenerMap then stores the listener in List, which is then stored in a Map using the client sessionId as the key.

ServletContextListener -- a ServletContextListener is used to manage the GlobalListenerMap. When the ServletContext is created, it creates an instance of the GlobalListenerMap and places it in the ServletContext to be shared by all the artifacts of the application. When the ServletContext is destroyed, it closes down the GlobalListenerMap allowing it to close its JMXConnector. This class also implements the HttpSessionListener interface, so that as a client session ends/is invalidated, any listeners the client had previously registered are removed from the GlobalListenerMap.

Front End -- a simple front end to allow a client to register interest with an MBean is provided, and then view the notifications.

JMXListenerBean.java
package sab.demo.jmx.listener;

import java.text.DateFormat;
import java.util.*;
import java.util.logging.Logger;
import javax.management.*;

/**
* This JavaBean acts as a JMX NotificationListener. It
* gets populated with an MBean name and it can then be registered
* with a given MBeanServer. This is quick and dirty, it does no
* validate of the MBean name, etc.
*
* Any notifications this listener receives are stored in both the messages
* and notifications lists -- these can then be used on the client side as
* desired.
*
*/

public class JMXListenerBean implements NotificationListener {

Logger logger = Logger.getLogger(this.getClass().getCanonicalName());
ArrayList<String> messages = new ArrayList<String>();
ArrayList<Notification>notifications = new ArrayList<Notification>();

private final Date created = new Date();
private String mBean = null;
private final DateFormat DF = DateFormat.getDateTimeInstance();

public JMXListenerBean() {
logger.info("JMXListenerBean: constructor");
}

/**
* Register this listener with the given MBeanServer.
*
@param mbs - MBeanServerConnection
* @throws Exception
*/
public void register(MBeanServerConnection mbs) throws Exception{
logger.info("registering mbean:" + mBean);
if("".equalsIgnoreCase(mBean) || mBean == null) {
throw new Exception("MBean name must be set before calling register");
}

try {
ObjectName on = new ObjectName(mBean);
mbs.addNotificationListener(on, this, null, null);
} catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
}

public void unregister(MBeanServerConnection mbs) throws Exception{
logger.info("unregistering mbean:" + mBean);
if("".equalsIgnoreCase(mBean) || mBean == null) {
throw new Exception("MBean name must be set before calling register");
}

try {
ObjectName on = new ObjectName(mBean);
mbs.removeNotificationListener(on, this);
} catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
}


/**
* This is where the notifications are delivered and handled.
*
@param notification
* @param handback
*/
public void handleNotification(Notification notification,
Object handback) {
logger.info("handleNotification");
// Add the notification to the list
notifications.add(notification);
// Make a string from the notification
messages.add(
String.format("[%s]\t%s\t%s",
DF.format(new Date(notification.getTimeStamp())),
notification.getType(),
notification.getMessage()));
}

public void setMBean(String mbean) {
this.mBean = mbean;
}

public String getMBean() {
return mBean;
}

/**
* Get the list of messages.
*
@return List of messages as Strings
*/
public ArrayList<String> getMessages() {
return messages;
}

/**
* Get the list of raw notifications
*
@return List of notifications
*/
public ArrayList<Notification> getNotifications() {
return notifications;
}

@Override
public String toString() {
return mBean;
}
}

GlobalListenerMap.java

package sab.demo.jmx.map;

import java.sql.Date;
import java.text.DateFormat;
import java.util.*;
import java.util.logging.Logger;
import javax.management.*;
import javax.management.remote.*;
import sab.demo.jmx.listener.JMXListenerBean;

/**
* This class acts as a manager for JMXListenerBeans, and the OC4J MBeanServer
* that they are registered with. It enables an application to signal interest
* for notifications from an MBean, whereupon it handles the registration of the
* listener with the MBean. It maintains the JMXConnection while the class is
* in use, providing a route for the MBeanServer to get the notifications from
* the MBean to the listener.
*
*/

public class GlobalListenerMap {

private Date created = null;

private HashMap<String, List<JMXListenerBean>> sessionListenerList = new HashMap<String, List<JMXListenerBean>>();
private JMXConnector jmxConnector= null;
private Object MUTEX = new Object();
private boolean eagerCloseConnection = true;

private static final String URL = "service:jmx:rmi://localhost:23791";
private static final String USERNAME = "oc4jadmin";
private static final String PASSWORD = "welcome1";
private final Logger logger = Logger.getLogger(this.getClass().getName());

public GlobalListenerMap() {
this(true);
}

public GlobalListenerMap(boolean eagerCloseConnection) {
this.eagerCloseConnection = eagerCloseConnection;
created = new Date(System.currentTimeMillis());
}

public synchronized void addListenerForSession(String sessionId, String mbeanName) throws Exception {
JMXListenerBean listener = new JMXListenerBean();
listener.setMBean(mbeanName);
addListenerForSession(sessionId, listener);
}

public synchronized void addListenerForSession(String sessionId, JMXListenerBean listener) throws Exception {
List<JMXListenerBean> listeners = sessionListenerList.get(sessionId);
if(listeners == null) {
listeners = new ArrayList<JMXListenerBean>();
}
if(jmxConnector == null) {
jmxConnector = getJMXConnector();
}
MBeanServerConnection mbs = jmxConnector.getMBeanServerConnection();
listener.register(mbs);
listeners.add(listener);
sessionListenerList.put(sessionId, listeners);
}

public List<JMXListenerBean>getListenerListForSession(String sessionId) {
return sessionListenerList.get(sessionId);
}

public synchronized void removeListenerListForSession(String sessionId) {
List<JMXListenerBean> listeners = sessionListenerList.get(sessionId);
// Unregister any listeners this session had established
try {
for(JMXListenerBean listener: listeners) {
listener.unregister(jmxConnector.getMBeanServerConnection());
}
} catch(Exception e) {
// TODO: handle unregister errors gracefully, ignore for now
}

// Now remove the session from the list
sessionListenerList.remove(sessionId);

// Eaglerly close the connection if configured to do so
if(eagerCloseConnection && sessionListenerList.size()==0) {
try {
close(true);
} catch (Exception ex) {
// Ignore the still registered error
}
}
}

/**
* Performs a shutdown on the listener map, will throw an exception if there
* are listeners still registered.
*
@throws Exception
*/
public void shutdown() throws Exception {
// care
close(true);
}

public void shutdownDontCare() throws Exception {
// don't care
close(false);
}

/**
* Close the GlobalMapListener down.
*
@param care -- whether to care if there are still registered listeners.
* @throws Exception
*/
private synchronized void close(boolean care) throws Exception {
if(care) {
if (sessionListenerList.size() != 0) {
throw new Exception("Can't close myself as listeners are still registered");
}
}
sessionListenerList.clear();
if (jmxConnector != null) {
jmxConnector.close();
jmxConnector = null;
}
}

/**
* Get a JMXConnector using the hardcoded URL, USERNAME, PASSWORD
*
@return
* @throws Exception
*/
private JMXConnector getJMXConnector() throws Exception {
return getJMXConnector(URL, USERNAME, PASSWORD);
}

/**
* Get a JMXConnector using the supplied parameters.
*
@param url
* @param username
* @param password
* @return JMXConnector
* @throws Exception
*/
private JMXConnector getJMXConnector(String url, String username,
String password) throws Exception {
JMXConnector jmxcon = null;

// If not, create one and store it for future use.
JMXServiceURL jmxurl = new JMXServiceURL(url);
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);

// Get a connection
jmxcon = JMXConnectorFactory.newJMXConnector(jmxurl, env);
jmxcon.connect();
return jmxcon;
}

/**
* Just print out some debug messages
*/

public void debug() {
logger.fine("GlobalListenerMap: created " +
DateFormat.getDateTimeInstance().format(created));
logger.fine("Sessions in use: " + sessionListenerList.keySet().size());
for(String sessionid: sessionListenerList.keySet()) {
logger.fine("[" + sessionid + "]");
for(JMXListenerBean listener: sessionListenerList.get(sessionid)) {
logger.fine(listener.getMBean() + ", has received [" +
listener.getNotifications().size() +
"] notifications" );
}
}
}

/**
* Quick and dirty query to get the list of mbean names for the deployed
* applications.
*
@return Set of Stringified ObjectNames
*/
public Set<String> getJ2EEApplicationNameList() {
logger.info("entering");
SortedSet<String> appnames = new TreeSet<String>();
Set<ObjectName>mbeans = null;
try {
if(jmxConnector == null) {
jmxConnector = getJMXConnector();
}
ObjectName query = new ObjectName("*:j2eeType=J2EEApplication,*");
mbeans = (Set<ObjectName>)
jmxConnector.getMBeanServerConnection().queryNames(query, null);
for(ObjectName mbean: mbeans) {
appnames.add(mbean.getCanonicalName());
}
} catch (Exception ex) {
ex.printStackTrace();
}
return appnames;
}

}
ServletContextListener

package sab.demo.jmx.listener;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import sab.demo.jmx.map.GlobalListenerMap;


public class ServletContextListener implements javax.servlet.ServletContextListener,
HttpSessionListener {
private static final String GLM_KEY = "GlobalListenerMap";
private ServletContext context = null;
private HttpSession session = null;
private final Logger logger = Logger.getLogger(this.getClass().getName());

/**
* Create a new GlobalListenerMap and put it in the context for use by
* the application artifacts.
*
@param event
*/
public void contextInitialized(ServletContextEvent event) {
logger.fine("contextInitialized");
context = event.getServletContext();
GlobalListenerMap glm = new GlobalListenerMap();
context.setAttribute(GLM_KEY, glm);

}

/**
* When the application is being shutdown, close the GlobalListenerMap so
* that the JMXConnector is closed.
*
@param event
*/
public void contextDestroyed(ServletContextEvent event) {
context = event.getServletContext();
logger.info("Closing GlobalListenerMap");
GlobalListenerMap glm = (GlobalListenerMap)context.getAttribute(GLM_KEY);
try {
if(glm!=null) {
glm.shutdownDontCare();
}
} catch(Exception e) {
e.printStackTrace();
}
}

/**
* When a new session is created ... don't really do anything.
*
@param event
*/
public void sessionCreated(HttpSessionEvent event) {
session = event.getSession();
ServletContext context = event.getSession().getServletContext();
GlobalListenerMap glm = (GlobalListenerMap)context.getAttribute(GLM_KEY);
// Could proactively create a session list here if we wanted
if(glm == null && logger.isLoggable(Level.SEVERE)) {
logger.severe("GlobalListenerMap in ServletContext is null");
}

}

/**
* When a specific user session is closed, remove any listeners that may
* have been registered by them from the GlobalListenerMap.
*
@param event
*/
public void sessionDestroyed(HttpSessionEvent event) {
session = event.getSession();
logger.info("Removing listeners for session: " + session.getId());
// Clean up ..
ServletContext context = event.getSession().getServletContext();
GlobalListenerMap glm = (GlobalListenerMap)context.getAttribute(GLM_KEY);
if(glm!=null) {
glm.removeListenerListForSession(session.getId());
}
}
}
RegistrationServlet

package sab.demo.jmx.web;

import java.io.IOException;
import java.io.PrintWriter;

import java.util.HashSet;
import java.util.Hashtable;


import java.util.List;
import java.util.logging.Logger;

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

import javax.servlet.*;
import javax.servlet.http.*;

import sab.demo.jmx.map.GlobalListenerMap;
import sab.demo.jmx.listener.JMXListenerBean;

public class RegistrationServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=windows-1252";

private final Logger logger = Logger.getLogger(this.getClass().getName());
private ServletConfig config = null;

public void init(ServletConfig config) throws ServletException {
super.init(config);
this.config = config;
}

public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {response.setContentType(CONTENT_TYPE);

try {
// Get the MBean name parameter
String mbean = request.getParameter("mbean");
if(mbean==null || "".equalsIgnoreCase(mbean)) {
throw new ServletException("MBean parameter was not provided.");
}
logger.info("Request for registering MBean: " + mbean);

ServletContext ctx = config.getServletContext();
GlobalListenerMap glm = (GlobalListenerMap) ctx.getAttribute("GlobalListenerMap");

// Create a new listener bean and add it to the global list
JMXListenerBean listener = new JMXListenerBean();
listener.setMBean(mbean);
glm.addListenerForSession(request.getSession().getId(), listener);

// Tack on some debug information to the output
if(request.getParameterMap().containsKey("debug")) {
glm.debug();
}
} catch (Exception ex) {
ex.printStackTrace();
request.setAttribute("javax.servlet.jsp.jspException", ex);
request.getRequestDispatcher("error.jsp").forward(request, response);
} finally {
}

request.getRequestDispatcher("listnotifications.jsp").forward(request, response);
}

public void doPost(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws ServletException, IOException {
doGet(httpServletRequest, httpServletResponse);
}
}
listnotifications.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ page import="sab.demo.jmx.listener.*"%>
<%@ page import="sab.demo.jmx.map.*"%>
<%@ page import="java.util.*"%>
<%@ page import="javax.management.*"%>
<%@ page import="java.text.DateFormat"%>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
<title>Index</title>
<STYLE TYPE="text/css">
td, body{font-family: Arial; font-size: 10pt;}
span.listener { color: #FF1111; font-weight: bold;}
td.state { text-transform: uppercase;}
</STYLE>
</head>
<body>
<a href="registerlistener.jsp">Register a Listener</a>
&nbsp;&nbsp;
<a href="listnotifications.jsp">Reload Page</a>
<h3>Registered Listeners</h3>

<%
DateFormat DF = DateFormat.getDateTimeInstance();
ServletContext ctx = config.getServletContext();
GlobalListenerMap glm = (GlobalListenerMap)ctx.getAttribute("GlobalListenerMap");
List<JMXListenerBean> listeners = glm.getListenerListForSession(session.getId());


if(listeners!=null) {
for (JMXListenerBean listener: listeners) {
out.println("<p><span class=\"listener\">" + listener.getMBean() + "</span></p>");
out.println("<table>");
for(Notification notification: listener.getNotifications()) {
String line = String.format("<tr font=\"arial\"><td>[%s]</td><td class=\"state\">%s</b></td><td>%s</td></tr>",
DF.format(new Date(notification.getTimeStamp())),
notification.getType(),
notification.getMessage());
out.println(line);
}
out.println("</table>");
}
}
%>
</body>
</html>
addregistration.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ page import="sab.demo.jmx.map.*"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
<title>Index</title>
<STYLE TYPE="text/css">
TD, BODY {font-family: Arial; font-size: 10pt;}
</STYLE>
</head>
<body>
<a href="listnotifications.jsp">Cancel</a>
<h3>Register MBean Listener</h3>
<form action="registrationservlet" method="post">
<p>
<table>
<tr>
<td>MBean Name:</td>
</tr>
<tr>
<td>
<select name="mbean">
<%
ServletContext ctx = config.getServletContext();
GlobalListenerMap glm = (GlobalListenerMap)ctx.getAttribute("GlobalListenerMap");
if(glm==null) System.out.println("glm was null");
for(String mbean: glm.getJ2EEApplicationNameList()) {
%>
<option><%=mbean%></option>
<%
}
%>


</td>
</tr>
<tr>
<td colspan="2" align="right">
<input type="submit"/>
</td>
</tr>
</table>
</p>
</form>
</font>
</body>
</html>

----------------
Listening to: The Stone Roses - She Bangs The Drums