JBoss.org Community Documentation

JBoss Application Server 4.2.2

Administration And Development Guide

Authors

Alessio Soldano

Andreadis Dimitris

Bill Burke

Brian Stansberry

Carlo de Wolf

Galder Zamarreno

Heiko Braun

Michael Yuan

Roger Pearse

Shelly Mc Gowan

Thomas Diesler

Edited by

Samson Kittoli

Myriam Malga

Translation of this book to French.

Fabian Decroux

Translation of this book to French.

Jasna Dimanoski

Translation of this book to German.

Yoko Nanamiya

Translation of this book to Japanese.

James Hashida

Translation of this book to Japanese.

Noriko Mizumoto

Translation of this book to Japanese.

Glaucia Freitas

Translation of this book to Portuguese.

Angela Garcia

Translation of this book to Spanish.

Jan 2008

Abstract

This book is a guide to the administration and configuration of the JBoss Application Server.


What this Book Covers
About JBoss
About Open Source
About Professional Open Source
I. Java EE 5 Application Configuration
1. Enterprise Applications with EJB3 Services
1.1. Session Beans
1.2. Entity Beans (a.k.a. Java Persistence API)
1.2.1. The persistence.xml file
1.2.2. Use Alternative Databases
1.2.3. Default Hibernate options
1.3. Message Driven Beans
1.4. Package and Deploy EJB3 Services
1.4.1. Deploy the EJB3 JAR
1.4.2. Deploy EAR with EJB3 JAR
2. Deployment
2.1. Deployable Application Types
2.2. Standard Server Configurations
II. JBoss AS Infrastructure
3. The JBoss JMX Microkernel
3.1. An Introduction to JMX
3.1.1. Instrumentation Level
3.1.2. Agent Level
3.1.3. Distributed Services Level
3.1.4. JMX Component Overview
3.2. JBoss JMX Implementation Architecture
3.2.1. The JBoss ClassLoader Architecture
3.2.2. Class Loading and Types in Java
3.2.3. JBoss XMBeans
3.3. Connecting to the JMX Server
3.3.1. Inspecting the Server - the JMX Console Web Application
3.3.2. Connecting to JMX Using RMI
3.3.3. Command Line Access to JMX
3.3.4. Connecting to JMX Using Any Protocol
3.4. Using JMX as a Microkernel
3.4.1. The Startup Process
3.4.2. JBoss MBean Services
3.4.3. Writing JBoss MBean Services
3.4.4. Deployment Ordering and Dependencies
3.5. JBoss Deployer Architecture
3.5.1. Deployers and ClassLoaders
3.6. Remote Access to Services, Detached Invokers
3.6.1. A Detached Invoker Example, the MBeanServer Invoker Adaptor Service
3.6.2. Detached Invoker Reference
4. Naming on JBoss
4.1. An Overview of JNDI
4.1.1. Names
4.1.2. Contexts
4.2. The JBossNS Architecture
4.3. The Naming InitialContext Factories
4.3.1. The standard naming context factory
4.3.2. The org.jboss.naming.NamingContextFactory
4.3.3. Naming Discovery in Clustered Environments
4.3.4. The HTTP InitialContext Factory Implementation
4.3.5. The Login InitialContext Factory Implementation
4.3.6. The ORBInitialContextFactory
4.4. JNDI over HTTP
4.4.1. Accessing JNDI over HTTP
4.4.2. Accessing JNDI over HTTPS
4.4.3. Securing Access to JNDI over HTTP
4.4.4. Securing Access to JNDI with a Read-Only Unsecured Context
4.5. Additional Naming MBeans
4.5.1. JNDI Binding Manager
4.5.2. The org.jboss.naming.NamingAlias MBean
4.5.3. org.jboss.naming.ExternalContext MBean
4.5.4. The org.jboss.naming.JNDIView MBean
4.6. J2EE and JNDI - The Application Component Environment
4.6.1. ENC Usage Conventions
5. Connectors on JBoss
5.1. JCA Overview
5.2. An Overview of the JBossCX Architecture
5.2.1. BaseConnectionManager2 MBean
5.2.2. RARDeployment MBean
5.2.3. JBossManagedConnectionPool MBean
5.2.4. CachedConnectionManager MBean
5.2.5. A Sample Skeleton JCA Resource Adaptor
5.3. Configuring JDBC DataSources
5.4. Configuring Generic JCA Adaptors
6. Transactions on JBoss
6.1. Transaction/JTA Overview
6.1.1. Pessimistic and optimistic locking
6.1.2. The components of a distributed transaction
6.1.3. The two-phase XA protocol
6.1.4. Heuristic exceptions
6.1.5. Transaction IDs and branches
6.2. JTS support
6.3. Web Services Transactions
6.4. Configuring JBoss Transactions
6.5. Local versus distributed transactions
7. Messaging on JBoss
7.1. JMS Examples
7.1.1. A Point-To-Point Example
7.1.2. A Pub-Sub Example
7.1.3. A Pub-Sub With Durable Topic Example
7.1.4. A Point-To-Point With MDB Example
7.2. JBoss MQ Overview
7.2.1. Invocation Layer
7.2.2. Security Manager
7.2.3. Destination Manager
7.2.4. Message Cache
7.2.5. State Manager
7.2.6. Persistence Manager
7.2.7. Destinations
7.3. JBoss MQ Configuration and MBeans
7.3.1. org.jboss.mq.il.jvm.JVMServerILService
7.3.2. org.jboss.mq.il.uil2.UILServerILService
7.3.3. org.jboss.mq.il.http.HTTPServerILService
7.3.4. org.jboss.mq.server.jmx.Invoker
7.3.5. org.jboss.mq.server.jmx.InterceptorLoader
7.3.6. org.jboss.mq.sm.jdbc.JDBCStateManager
7.3.7. org.jboss.mq.security.SecurityManager
7.3.8. org.jboss.mq.server.jmx.DestinationManager
7.3.9. org.jboss.mq.server.MessageCache
7.3.10. org.jboss.mq.pm.jdbc2.PersistenceManager
7.3.11. Destination MBeans
7.4. Specifying the MDB JMS Provider
7.4.1. org.jboss.jms.jndi.JMSProviderLoader MBean
7.4.2. org.jboss.jms.asf.ServerSessionPoolLoader MBean
7.4.3. Integrating non-JBoss JMS Providers
8. Security on JBoss
8.1. J2EE Declarative Security Overview
8.1.1. Security References
8.1.2. Security Identity
8.1.3. Security roles
8.1.4. EJB method permissions
8.1.5. Web Content Security Constraints
8.1.6. Enabling Declarative Security in JBoss
8.2. An Introduction to JAAS
8.2.1. What is JAAS?
8.3. The JBoss Security Model
8.3.1. Enabling Declarative Security in JBoss Revisited
8.4. The JBoss Security Extension Architecture
8.4.1. How the JaasSecurityManager Uses JAAS
8.4.2. The JaasSecurityManagerService MBean
8.4.3. The JaasSecurityDomain MBean
8.5. Defining Security Domains
8.5.1. Loading Security Domains
8.5.2. The DynamicLoginConfig service
8.5.3. Using JBoss Login Modules
8.5.4. Writing Custom Login Modules
8.6. The Secure Remote Password (SRP) Protocol
8.6.1. Providing Password Information for SRP
8.6.2. Inside of the SRP algorithm
8.7. Running JBoss with a Java 2 security manager
8.8. Using SSL with JBoss using JSSE
8.9. Configuring JBoss for use Behind a Firewall
8.10. How to Secure the JBoss Server
8.10.1. The JMX Console
8.10.2. The Web Console
8.10.3. The HTTP Invokers
8.10.4. The JMX Invoker
9. Web Services
9.1. Document/Literal
9.2. Document/Literal (Bare)
9.3. Document/Literal (Wrapped)
9.4. RPC/Literal
9.5. RPC/Encoded
9.6. Web Service Endpoints
9.7. Plain old Java Object (POJO)
9.8. The endpoint as a web application
9.9. Packaging the endpoint
9.10. Accessing the generated WSDL
9.11. EJB3 Stateless Session Bean (SLSB)
9.12. Endpoint Provider
9.13. WebServiceContext
9.14. Web Service Clients
9.14.1. Service
9.14.2. Dynamic Proxy
9.14.3. WebServiceRef
9.14.4. Dispatch
9.14.5. Asynchronous Invocations
9.14.6. Oneway Invocations
9.15. Common API
9.15.1. Handler Framework
9.15.2. Message Context
9.15.3. Fault Handling
9.16. DataBinding
9.16.1. Using JAXB with non annotated classes
9.17. Attachments
9.17.1. MTOM/XOP
9.17.2. SwaRef
9.18. Tools
9.18.1. Bottom-Up (Using wsprovide)
9.18.2. Top-Down (Using wsconsume)
9.18.3. Client Side
9.18.4. Command-line & Ant Task Reference
9.18.5. JAX-WS binding customization
9.19. Web Service Extensions
9.19.1. WS-Addressing
9.19.2. WS-BPEL
9.19.3. WS-Eventing
9.19.4. WS-Security
9.19.5. WS-Transaction
9.19.6. XML Registries
9.19.7. WS-Policy
9.20. JBossWS Extensions
9.20.1. Proprietary Annotations
9.21. Web Services Appendix
9.22. References
10. Additional Services
10.1. Memory and Thread Monitoring
10.2. The Log4j Service
10.3. System Properties Management
10.4. Property Editor Management
10.5. Services Binding Management
10.5.1. AttributeMappingDelegate
10.5.2. XSLTConfigDelegate
10.5.3. XSLTFileDelegate
10.5.4. The Sample Bindings File
10.6. RMI Dynamic Class Loading
10.7. Scheduling Tasks
10.7.1. org.jboss.varia.scheduler.Scheduler
10.8. The Timer Service
10.9. The BarrierController Service
10.10. Exposing MBean Events via SNMP
III. Legacy EJB Support
11. EJBs on JBoss
11.1. The EJB Client Side View
11.1.1. Specifying the EJB Proxy Configuration
11.2. The EJB Server Side View
11.2.1. Detached Invokers - The Transport Middlemen
11.2.2. The HA JRMPInvoker - Clustered RMI/JRMP Transport
11.2.3. The HA HttpInvoker - Clustered RMI/HTTP Transport
11.3. The EJB Container
11.3.1. EJBDeployer MBean
11.3.2. Container Plug-in Framework
11.4. Entity Bean Locking and Deadlock Detection
11.4.1. Why JBoss Needs Locking
11.4.2. Entity Bean Lifecycle
11.4.3. Default Locking Behavior
11.4.4. Pluggable Interceptors and Locking Policy
11.4.5. Deadlock
11.4.6. Advanced Configurations and Optimizations
11.4.7. Running Within a Cluster
11.4.8. Troubleshooting
11.5. EJB Timer Configuration
12. The CMP Engine
12.1. Example Code
12.1.1. Enabling CMP Debug Logging
12.1.2. Running the examples
12.2. The jbosscmp-jdbc Structure
12.3. Entity Beans
12.3.1. Entity Mapping
12.4. CMP Fields
12.4.1. CMP Field Declaration
12.4.2. CMP Field Column Mapping
12.4.3. Read-only Fields
12.4.4. Auditing Entity Access
12.4.5. Dependent Value Classes (DVCs)
12.5. Container Managed Relationships
12.5.1. CMR-Field Abstract Accessors
12.5.2. Relationship Declaration
12.5.3. Relationship Mapping
12.6. Queries
12.6.1. Finder and select Declaration
12.6.2. EJB-QL Declaration
12.6.3. Overriding the EJB-QL to SQL Mapping
12.6.4. JBossQL
12.6.5. DynamicQL
12.6.6. DeclaredSQL
12.6.7. EJBQL 2.1 and SQL92 queries
12.6.8. BMP Custom Finders
12.7. Optimized Loading
12.7.1. Loading Scenario
12.7.2. Load Groups
12.7.3. Read-ahead
12.8. Loading Process
12.8.1. Commit Options
12.8.2. Eager-loading Process
12.8.3. Lazy loading Process
12.8.4. Lazy loading result sets
12.9. Transactions
12.10. Optimistic Locking
12.11. Entity Commands and Primary Key Generation
12.11.1. Existing Entity Commands
12.12. Defaults
12.12.1. A sample jbosscmp-jdbc.xml defaults declaration
12.13. Datasource Customization
12.13.1. Type Mapping
12.13.2. Function Mapping
12.13.3. Mapping
12.13.4. User Type Mappings
IV. Clustering Guide
13. Clustering
13.1. Introduction
13.2. Cluster Definition
13.3. HAPartition
13.4. JBoss Cache channels
13.4.1. Service Architectures
13.4.2. Load-Balancing Policies
13.4.3. Farming Deployment
13.4.4. Distributed state replication services
14. Clustered JNDI Services
14.1. How it works
14.2. Client configuration
14.2.1. For clients running inside the application server
14.2.2. For clients running outside the application server
14.2.3. JBoss configuration
15. Clustered Session EJBs
15.1. Stateless Session Bean in EJB 2.x
15.2. Stateful Session Bean in EJB 2.x
15.2.1. The EJB application configuration
15.2.2. Optimize state replication
15.2.3. The HASessionState service configuration
15.2.4. Handling Cluster Restart
15.2.5. JNDI Lookup Process
15.2.6. SingleRetryInterceptor
15.3. Stateless Session Bean in EJB 3.0
15.4. Stateful Session Beans in EJB 3.0
16. Clustered Entity EJBs
16.1. Entity Bean in EJB 2.x
16.2. Entity Bean in EJB 3.0
16.2.1. Configure the distributed cache
16.2.2. Configure the entity beans for cache
16.2.3. Query result caching
17. HTTP Services
17.1. Configuring load balancing using Apache and mod_jk
17.2. Download the software
17.3. Configure Apache to load mod_jk
17.4. Configure worker nodes in mod_jk
17.5. Configuring JBoss to work with mod_jk
17.6. Configuring HTTP session state replication
17.7. Enabling session replication in your application
17.8. Using FIELD level replication
17.9. Monitoring session replication
17.10. Using Clustered Single Sign On
17.11. Clustered Singleton Services
17.11.1. HASingletonDeployer service
17.11.2. Mbean deployments using HASingletonController
17.11.3. HASingleton deployments using a Barrier
17.11.4. Determining the master node
18. Clustered JMS Services
18.1. High Availability Singleton Fail-over
18.1.1. Server Side Configuration
18.1.2. Non-MDB HA-JMS Clients
18.1.3. Load Balanced HA-JMS MDBs
19. JBossCache and JGroups Services
19.1. JGroups Configuration
19.2. Common Configuration Properties
19.3. Transport Protocols
19.3.1. UDP configuration
19.3.2. TCP configuration
19.3.3. TUNNEL configuration
19.4. Discovery Protocols
19.4.1. PING
19.4.2. TCPGOSSIP
19.4.3. TCPPING
19.4.4. MPING
19.5. Failure Detection Protocols
19.5.1. FD
19.5.2. FD_SOCK
19.5.3. VERIFY_SUSPECT
19.5.4. FD versus FD_SOCK
19.6. Reliable Delivery Protocols
19.6.1. UNICAST
19.6.2. NAKACK
19.7. Other Configuration Options
19.7.1. Group Membership
19.7.2. Flow Control
19.7.3. Fragmentation
19.7.4. State Transfer
19.7.5. Distributed Garbage Collection
19.7.6. Merging
19.7.7. Binding JGroups Channels to a particular interface
19.7.8. Isolating JGroups Channels
19.7.9. Changing the Group Name
19.7.10. Changing the multicast address and port
19.7.11. JGroups Troubleshooting
19.7.12. Causes of missing heartbeats in FD
A. Book Example Installation
B. Use Alternative Databases with JBoss AS
B.1. How to Use Alternative Databases
B.2. Install JDBC Drivers
B.2.1. Special notes on Sybase
B.3. Creating a DataSource for the External Database
B.4. Change Database for the JMS Services
B.5. Support Foreign Keys in CMP Services
B.6. Specify Database Dialect for Java Persistence API
B.7. Change Other JBoss AS Services to Use the External Database
B.7.1. The Easy Way
B.7.2. The More Flexible Way
B.8. A Special Note About Oracle DataBases

The primary focus of this book is the presentation of the standard JBoss 5.0.0 architecture components from both the perspective of their configuration and architecture. As a user of a standard JBoss distribution you will be given an understanding of how to configure the standard components. Note that this book is not an introduction to J2EE or how to use J2EE in applications. It focuses on the internal details of the JBoss server architecture and how our implementation of a given J2EE container can be configured and extended.

As a JBoss developer, you will be given a good understanding of the architecture and integration of the standard components to enable you to extend or replace the standard components for your infrastructure needs. We also show you how to obtain the JBoss source code, along with how to build and debug the JBoss server.

JBoss, a division of Red Hat, is the global leader in open source middleware software, combining enterprise-class JEMS open source software with the industry’s leading services and tools to provide simply a better way to transform your business to Service-Oriented Architecture (SOA).

JBoss, pioneered the disruptive Professional Open Source model, which combines the best of the open source and proprietary software worlds to make open source a safe choice for the enterprise and give CIOs peace of mind. This includes the royalty-free software, transparent development and active community inherent in open source and the accountability and professional support services expected of a traditional software vendor. The company finds innovative open source projects and professionalizes the project from a hobby into a livelihood by hiring the lead developer(s), often the founders themselves. JBoss provides the resources, core development and support services to enable popular open source projects to scale into enterprise-class software.

Coverage: North America and Europe on a direct basis. JBoss provides coverage worldwide via our extensive authorized partner network.

Mission Statement: JBoss' mission is to revolutionize the way enterprise middleware software is built, distributed, and supported through the Professional Open Source model. We are committed to delivering innovative and high quality technology and services that make JBoss the safe choice for enterprises and software providers.

Customers: Enterprise customers deploying JBoss technologies in mission-critical applications with professional services support from JBoss include Aviva Canada, Continental Airlines, La Quinta, NLG, MCI, Nielsen Media Research and Travelocity. For a current list of customer success stories, please visit the Customers section of our website.

Partners: JBoss works with software and hardware vendors, systems integrators and OEMs to deliver implementation services, frontline support, and certification for products embedded with JBoss technologies. For more information on the JBoss Certified Partner Program, please visit the Partners section of our website.

Professional Open Source(tm) from JBoss Inc. offers you:

  • Standards-based and stable Java Middleware technology

  • No cost open source product licenses

  • Backed by a professional and expert support staff

  • Comprehensive services including Professional Support, Training, and Consulting

  • A very large and active community of developers

  • An extensive worldwide network of authorized and certified partners

Benefits of Professional Open Source from JBoss Inc.:

  • Lowest possible total cost of ownership

  • Reliable and safe technology

  • Support, accountability, and trust from a stable company

  • Expedited problem resolution compared to commercial software vendors

The basic idea behind open source is very simple: When programmers can read, redistribute, and modify the source code for a piece of software, the software evolves. People improve it, people adapt it, people fix bugs. And this can happen at a speed that, if one is used to the slow pace of conventional software development, seems astonishing. Open Source is an often-misunderstood term relating to free software. The Open Source Initiative (OSI) web site provides a number of resources that define the various aspects of Open Source including an Open Source Definition at: http://www.opensource.org/docs/definition.html. The following quote from the OSI home page summarizes the key aspects as they relate to JBoss nicely:

 

We in the open source community have learned that this rapid evolutionary process produces better software than the traditional closed model, in which only very few programmers can see the source and everybody else must blindly use an opaque block of bits.

Open Source Initiative exists to make this case to the commercial world.

Open source software is an idea whose time has finally come. For twenty years it has been building momentum in the technical cultures that built the Internet and the World Wide Web. Now it's breaking out into the commercial world, and that's changing all the rules. Are you ready?

 
  --The Open Source Initiative

JBoss is the leader in the second generation of open source, which we have termed Professional Open Source. The Professional Open Source methodology is based on the following:

  1. We hire and pay experts in the open source community to write exceptional and innovative software full-time.

  2. We only use open source licenses that are friendly to end-user IT shops, independent software vendors, and the community itself.

  3. Directly and through our authorized partners, we deliver the best support services available; all of which are backed up by the real product experts.

  4. Unlike first generation open source providers, we control the direction and source code for our projects. We can ensure that all bug fixes and patches are rolled into future versions of our products.

  5. By combining enterprise-proven technology, business-friendly open source licenses, and world-class support services, we have made Professional Open Source the safe choice for end-user enterprises and independent software vendors alike.

EJB3 (Enterprise Java Bean 3.0) provides the core component model for Java EE 5 applications. An EJB3 bean is a managed component that is automatically wired to take advantage of all services the Java EE 5 server container provides, such as transaction, security, persistence, naming, dependency injection, etc. The managed component allows developers to focus on the business logic, and leave the cross-cutting concerns to the container as configurations. As an application developer, you need not create or destroy the components yourself. You only need to ask for an EJB3 bean from the Java EE container by its name, and then you can call its methods with all configured container services applied. You can get access to an EJB3 bean from either inside or outside of the Java EE container.

JBoss AS 4.2 and above supports EJB3 out of the box. Note that JBoss AS 5.0 supports the full EJB3 feature set.

The details of the EJB3 component programming model is beyond the scope of this guide. Most EJB3 interfaces and annotations are part of the Java EE 5 standard and hence they are the same for all Java EE 5 compliant application servers. Interested readers should refer to the EJB3 specification or numerous EJB3 books to learn more about EJB3 programming.

In this chapter, we only cover EJB3 configuration issues that are specific to the JBoss AS. For instance, we discuss the JNDI naming conventions for EJB3 components inside the JBoss AS, the optional configurations for the Hibernate persistence engine for entity beans, as well as custom options in the JBoss EJB3 deployer.

Session beans are widely used to provide transactional services for local and remote clients. To write a session bean, you need an interface and an implementation class.


@Local
public interface MyBeanInt {
  public String doSomething (String para1, int para2);
}

@Stateless
public class MyBean implements MyBeanInt {

  public String doSomething (String para1, int para2) {
    ... implement the logic ...
  } 
  
}    

        

When you invoke a session bean method, the method execution is automatically managed by the transaction manager and the security manager in the server. You can specify the transactional or security properties for each method using annotations on the method. A session bean instance can be reused by many clients. Depending on whether the server maintains the bean's internal state between two clients, the session bean can be stateless or stateful. Depending on whether the bean has a remote business interface clients outside of the current JVM can call upon the EJB3 bean. All these are configurable via standard annotations on the beans. Note that the transactional or security properties are only active when the bean is called through a business interface.

After you define a session bean, how does the client get a reference to it? As we discussed, the client does not create or destroy EJB3 components, it merely asks the server for a reference of an existing instance managed by the server. That is done via JNDI. In JBoss AS, the default local JNDI name for a session bean is dependent on the deployment packaging of the bean class.

  • If the bean is deployed in a standalone JAR file in the JBOSS_DIST/default/deploy directory, the bean is accessible via local JNDI name MyBean/local, where MyBean is the implementation class name of the bean as we showed earlier. The "local" JNDI in JBoss AS means that the JNDI name is relative to java:comp/env/.

  • If the JAR file containing the bean is packaged in an EAR file, the local JNDI name for the bean is myapp/MyBean/local, where myapp is the root name of the EAR archive file (e.g., myapp.ear, see later for the EAR packaging of EJB3 beans).

Of course, you should change local to remote if the bean interface is annotated with @Remote and the bean is accessed from outside of the server it is deployed on. Below is the code snippet to get a reference of the MyBean bean in a web application (e.g., in a servlet or a JSF backing bean) packaged in myapp.ear, and then invoke a managed method.


try {
  InitialContext ctx = new InitialContext();
  MyBeanInt bean = (MyBeanInt) ctx.lookup("myapp/MyBean/local");
} catch (Exception e) {
  e.printStackTrace ();
}

... ...

String result = bean.doSomething("have fun", 1);

... ...

        

What the client gets from the JNDI is essentially a "stub" or "proxy" of the bean instance. When the client invokes a method, the proxy figures out how to route the request to the server and marshal together the response.

If you do not like the default JNDI names, you can always specify your own JNDI binding for any bean via the @LocalBinding annotation on the bean implementation class. The JNDI binding is always "local" under the java:comp/env/ space. For instance, the following bean class definition results in the bean instances available under JNDI name java:comp/env/MyService/MyOwnName.


@Stateless
@LocalBinding (jndiBinding="MyService/MyOwnName")
public class MyBean implements MyBeanInt {

  public String doSomething (String para1, int para2) {
    ... implement the logic ...
  } 
  
}    
    
        

Injecting EJB3 Beans into the Web Tier

Java EE 5 allows you to inject EJB3 bean instances directly into the web application via annotations without explicit JNDI lookup. This behavior is supported in JBoss AS 5.0.0. The JBoss Application Server provides an integration framework called JBoss Seam. JBoss Seam brings EJB3 / JSF integration to new heights far beyond what Java EE 5 provides. Please see more details in the JBoss Seam reference guide bundled with the platform.

EJB3 session beans allow you to implement data accessing business logic in transactional methods. To actually access the database, you will need EJB3 entity beans and the entity manager API. They are collectively called the Java Persistence API (JPA).

EJB3 Entity Beans are Plain Old Java Objects (POJOs) that map to relational database tables. For instance, the following entity bean class maps to a relational table named customer. The table has three columns: name, age, and signupdate. Each instance of the bean corresponds to a row of data in the table.


@Entity
public class Customer {

  String name;

  public String getName () {
    return name;
  }
  
  public void setName (String name) {
    this.name = name;
  }
  
  int age;
  
  public int getAge () {
    return age;
  }
  
  public void setAge (int age) {
    this.age = age;
  }
  
  Date signupdate;
  
  public Date getSignupdate () {
    return signupdate;
  }
  
  public void setSignupdate (Date signupdate) {
    this.signupdate = signupdate;
  }
}    
 
        

Besides simple data properties, the entity bean can also contain references to other entity beans with relational mapping annotations such as @OneToOne, @OneToMany, @ManyToMany etc. The relationships of those entity objects will be automatically set up in the database as foreign keys. For instance, the following example shows that each record in the Customer table has one corresponding record in the Account table, multiple corresponding records in the Order table, and each record in the Employee table has multiple corresponding records in the Customer table.


@Entity
public class Customer {

  ... ...
  
  Account account;
  
  @OneToOne
  public Account getAccount () {
    return account;
  }
  
  public void setAccount (Accout account) {
    this.account = account;
  }
  
  Employee salesRep;
  
  @ManyToOne
  public Employee getSalesRep () {
    return salesRep;
  }
  
  public void setSalesRep (Employee salesRep) {
    this.salesRep = salesRep;
  }
  
  Vector <Order> orders;
  
  @OneToMany
  public Vector <Order> getOrders () {
    return orders;
  }
  
  public void setOrders (Vector <Order> orders) {
    this.orders = orders;
  }


        

Using the EntityManager API, you can create, update, delete, and query entity objects. The EntityManager transparently updates the underlying database tables in the process. You can obtain an EntityManager object in your EJB3 session bean via the @PersistenceContext annotation.


@PersistenceContext
EntityManager em;

Customer customer = new Customer ();
// populate data in customer

// Save the newly created customer object to DB
em.persist (customer);

// Increase age by 1 and auto save to database
customer.setAge (customer.getAge() + 1);

// delete the customer and its related objects from the DB
em.remove (customer);

// Get all customer records with age > 30 from the DB
List <Customer> customers = em.query (
     "select c from Customer where c.age > 30");

        

The detailed use of the EntityManager API is beyond the scope of this book. Interested readers should refer to the JPA documentation or Hibernate EntityManager documentation.

The EntityManager API is great, but how does the server know which database it is supposed to save / update / query the entity objects? How do we configure the underlying object-relational-mapping engine and cache for better performance and trouble shooting? The persistence.xml file gives you complete flexibility to configure the EntityManager.

The persistence.xml file is a standard configuration file in JPA. It has to be included in the META-INF directory inside the JAR file that contains the entity beans. The persistence.xml file must define a persistence-unit with a unique name in the current scoped classloader. The provider attribute specifies the underlying implementation of the JPA EntityManager. In JBoss AS, the default and only supported / recommended JPA provider is Hibernate. The jta-data-source points to the JNDI name of the database this persistence unit maps to. The java:/DefaultDS here points to the HSQL DB embedded in the JBoss AS. Please refer to Appendix B, Use Alternative Databases with JBoss AS on how to setup alternative databases for JBoss AS.


<persistence>
   <persistence-unit name="myapp">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         ... ...
      </properties>
   </persistence-unit>
</persistence>          

          

Inject EntityManager by persistence-unit name

Since you might have multiple instances of persistence-unit defined in the same application, you typically need to explicitly tell the @PersistenceContext annotation which unit you want to inject. For instance, @PersistenceContext(name="myapp") injects the EntityManager from the persistence-unit named "myapp".

However, if you deploy your EAR application in its own scoped classloader and have only one persistence-unit defined in the whole application, you can omit the "name" on @PersistenceContext. See later in this chapter for EAR packaging and deployment.

The properties element in the persistence.xml can contain any configuration properties for the underlying persistence provider. Since JBoss AS uses Hibernate as the EJB3 persistence provider, you can pass in any Hibernate options here. Please refer to the Hibernate and Hibernate EntityManager documentation for more details. Here we will just give an example to set the SQL dialect of the persistence engine to HSQL, and to create tables from the entity beans when the application starts and drop those tables when the application stops.


<persistence>
   <persistence-unit name="myapp">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         property name="hibernate.dialect" 
                  value="org.hibernate.dialect.HSQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>          

          

To use an alternative database other than the built-in HSQL DB to back your entity beans, you need to first define the data source for the database and register it in the JNDI. This is done via the *-ds.xml files in the deploy directory. Please see Section 5.3, “Configuring JDBC DataSources” for more details. Examples of *-ds.xml files for various databases are available in JBOSS_DIST/docs/examples/jca directory in the server.

Then, in the persistence.xml, you need to change the jta-data-source attribute to point to the new data source in JNDI (e.g., java:/MysqlDS if you are using the default mysql-ds.xml to setup a MySQL external database).

In most cases, Hibernate tries to automatically detect the database it connects to and then automatically selects an appropriate SQL dialect for the database. However, we have found that this detection does not always work, especially for less used database servers. We recommend you to set the hibernate.dialect property explicitly in persistence.xml. Here are the Hibernate dialect for database servers officially supported on the JBoss platform.

  • Oracle 9i and 10g: org.hibernate.dialect.Oracle9Dialect

  • Microsoft SQL Server 2005: org.hibernate.dialect.SQLServerDialect

  • PostgresSQL 8.1: org.hibernate.dialect.PostgreSQLDialect

  • MySQL 5.0: org.hibernate.dialect.MySQL5Dialect

  • DB2 8.0: org.hibernate.dialect.DB2Dialect

  • Sybase ASE 12.5: org.hibernate.dialect.SybaseDialect

Hibernate has many configuration properties. For the properties that you do not specify in the persistence.xml file, JBoss AS will provide a reasonable set of default values. The default Hibernate property values are specified in the JBOSS_DIST/server/default/deploy/ejb3.deployer/MEAT-INF/persistence.properties file. Below is the persistence.properties file bundled in JBoss AS 5.0. Notice the options that are commented out. They give you an idea of available properties in your persistence.xml file.


hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup
#hibernate.connection.release_mode=after_statement
#hibernate.transaction.flush_before_completion=false
#hibernate.transaction.auto_close_session=false
#hibernate.query.factory_class=org.hibernate.hql.ast.ASTQueryTranslatorFactory
#hibernate.hbm2ddl.auto=create-drop
#hibernate.hbm2ddl.auto=create
hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider
# Clustered cache with TreeCache
#hibernate.cache.provider_class=org.jboss.ejb3.entity.TreeCacheProviderHook
#hibernate.treecache.mbean.object_name=jboss.cache:service=EJB3EntityTreeCache
#hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.jndi.java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
hibernate.jndi.java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
hibernate.bytecode.use_reflection_optimizer=false
# I don't think this is honored, but EJB3Deployer uses it
hibernate.bytecode.provider=javassist

          

Messaging driven beans are specialized EJB3 beans that receive service requests via JMS messages instead of proxy method calls from the "stub". So, a crucial configuration parameter for the message driven bean is to specify which JMS message queue its listens to. When there is an incoming message in the queue, the server invokes the beans's onMessage() method, and passes in the message itself for processing. The bean class specifies the JMS queue it listens to in the @MessageDriven annotation. The queue is registered under the local JNDI java:comp/env/ name space.


@MessageDriven(activationConfig =
{
  @ActivationConfigProperty(propertyName="destinationType",
    propertyValue="javax.jms.Queue"),
  @ActivationConfigProperty(propertyName="destination",
    propertyValue="queue/MyQueue")
})
public class MyJmsBean implements MessageListener {

  public void onMessage (Message msg) {
    // ... do something with the msg ...
  }

  // ... ...
}
    
        

When a message driven bean is deployed, its incoming message queue is automatically created if it does not exist already. To send a message to the bean, you can use the standard JMS API.


try {
    InitialContext ctx = new InitialContext();
    queue = (Queue) ctx.lookup("queue/MyQueue");
    QueueConnectionFactory factory =
        (QueueConnectionFactory) ctx.lookup("ConnectionFactory");
    cnn = factory.createQueueConnection();
    sess = cnn.createQueueSession(false,
            QueueSession.AUTO_ACKNOWLEDGE);

} catch (Exception e) {
    e.printStackTrace ();
}
  
TextMessage msg = sess.createTextMessage(...);

sender = sess.createSender(queue);
sender.send(msg);
        
        

Please refer to the JMS specification or books to learn how to program in the JMS API.

EJB3 bean classes are packaged in regular JAR files. The standard configuration files, such as ejb-jar.xml for session beans, and persistence.xml for entity beans, are in the META-INF directory inside the JAR. You can deploy EJB3 beans as standalone services in JBoss AS or as part of an enterprise application (i.e., in an EAR archive). In this section, we discuss those two deployment options.

When you drop JAR files into the JBOSS_DIST/server/default/deploy/ directory, it will be automatically picked up and processed by the server. All the EJB3 beans defined in the JAR file will then be available to other applications deployed inside or outside of the server via JNDI names like MyBean/local, where MyBean is the implementation class name for the session bean. The deployment is done via the JBoss EJB3 deployer in JBOSS_DIST/server/default/ejb3.deployer/. The META-INF/persistence.properties file we discussed earlier to configure the default behavior of EJB3 entity manager is located in the EJB3 deployer.

The EJB3 deployer automatically scans JARs on the classpath to look for EJB3 annotations. When it finds classes with EJB3 annotations, it would deploy them as EJB3 services. However, scanning all JARs on the classpath could be very time-consuming if you have large applications with many JARs deployed. In the JBOSS_DIST/server/default/ejb3.deployer/META-INF/jboss-service.xml file, you can tell the EJB3 deployer to ignore JARs you know do not contain EJB3 beans. The non-EJB3 JAR files shipped with the JBoss AS are already listed in the jboss.ejb3:service=JarsIgnoredForScanning MBean service:


  ... ...
  <mbean code="org.jboss.ejb3.JarsIgnoredForScanning" 
         name="jboss.ejb3:service=JarsIgnoredForScanning">
      <attribute name="IgnoredJars">
         snmp-adaptor.jar,
         otherimages.jar,
         applet.jar,
         jcommon.jar,
         console-mgr-classes.jar,
         jfreechart.jar,
         juddi-service.jar,
         wsdl4j.jar,
         ... ...
         servlets-webdav.jar
      </attribute>
   </mbean>
  ... ...

		  

You can add any non-EJB3 JARs from your application to this list so that the server do not have to waste time scanning them. This could significantly improve the application startup time in some cases.

Most Java EE applications are deployed as EAR archives. An EAR archive is a JAR file that typically contains a WAR archive for the web pages, servlets, and other web-related components, one or several EJB3 JARs that provide services (e.g., data access and transaction) to the WAR components, and some other support library JARs required by the application. An EAR file also have deployment descriptors such as application.xml and jboss-app.xml. Below is the basic structure of a typical EAR application.


myapp.ear
|+ META-INF
   |+ applications.xml and jboss-app.xml
|+ myapp.war
   |+ web pages and JSP /JSF pages
   |+ WEB-INF
      |+ web.xml, jboss-web.xml, faces-config.xml etc.
      |+ lib
         |+ tag library JARs
      |+ classes
         |+ servlets and other classes used by web pages
|+ myapp.jar
   |+ EJB3 bean classes
   |+ META-INF
      |+ ejb-jar.xml and persistence.xml
|+ lib
   |+ Library JARs for the EAR

		  

Notice that in JBoss AS, unlike in many other application servers, you do not need to declare EJB references in the web.xml file in order for the components in the WAR file to access EJB3 services. You can obtain the references directly via JNDI as we discussed earlier in the chapter.

A typical application.xml file is as follows. It declares the WAR and EJB3 JAR archives in the EAR, and defines the web content root for the application. Of course, you can have multiple EJB3 modules in the same EAR application. The application.xml file could also optionally define a shared classpath for JAR files used in this application. The JAR file location defaults to lib in JBoss AS -- but it might be different in other application servers.


<application>
  <display-name>My Application</display-name>

  <module>
    <web>
      <web-uri>myapp.war</web-uri>
      <context-root>/myapp</context-root>
    </web>
  </module>

  <module>
    <ejb>myapp.jar</ejb>
  </module>
  
  <library-directory>lib</library-directory>

</application>

		  

The jboss-app.xml file provides JBoss-specific deployment configuration for the EAR application. For instance, it can specify the deployment order of modules in the EAR, deploy JBoss-specific application modules in the EAR, such as SARs (Service ARchive for MBeans) and HARs (Hibernate ARchive for Hibernate objects), provide security domain and JMX MBeans that can be used with this application, etc. You can learn more about the possible attributes in jboss-app.xml in its DTD: http://www.jboss.org/j2ee/dtd/jboss-app_4_2.dtd.

A common use case for jboss-app.xml is to configure whether this EAR file should be deployed in its own scoped classloader to avoid naming conflicts with other applications. If your EAR application is deployed in its own scoped classloader and it only has one persistence-unit defined in its EJB3 JARs, you will be able to use @PersistenceContext EntityManager to inject EntityManager to session beans without worrying about passing the persistence unit name to the @PersistenceContext annotation. The following jboss-app.xml specifies a scoped classloader myapp:archive=myapp.ear for the EAR application.


<jboss-app>
      <loader-repository>
      myapp:archive=myapp.ear
      </loader-repository>
</jboss-app>

		  

The EAR deployment is configured by the JBOSS_DIST/server/default/deploy/ear-deploy.xml file. This file contains three attributes as follows.


<server>
   <mbean code="org.jboss.deployment.EARDeployer"
          name="jboss.j2ee:service=EARDeployer">
      <!-- 
          A flag indicating if ear deployments should 
           have their own scoped class loader to isolate 
           their classes from other deployments.
      -->
      <attribute name="Isolated">false</attribute>
      
      <!-- 
          A flag indicating if the ear components should 
          have in VM call optimization disabled.
      -->
      <attribute name="CallByValue">false</attribute>
      
      <!-- 
          A flag the enables the default behavior of 
          the ee5 library-directory. If true, the lib 
          contents of an ear are assumed to be the default 
          value for library-directory in the absence of 
          an explicit library-directory. If false, there 
          must be an explicit library-directory.
      -->
      <attribute name="EnablelibDirectoryByDefault">true</attribute>
   </mbean>
</server>

		  

If you set the Isolated parameter to true, all EAR deployment will have scoped classloaders by default. There will be no need to define the classloader in jboss-app.xml. The CallByValue attribute specifies whether we should treat all EJB calls as remote calls. Remote calls have a large additional performance penalty compared with local call-by-reference calls, because objects involved in remote calls have to be serialized and de-serialized. For most of our applications, the WAR and EJB3 JARs are deployed on the same server, hence this value should be default to false and the server uses local call-by-reference calls to invoke EJB methods in the same JVM. The EnablelibDirectoryByDefault attribute specifies whether the lib directory in the EAR archive should be the default location for shared library JARs.

Deploying applications on JBoss AS is very easy. You just need to copy the application into the JBOSS_HOME/server/default/deploy directory. You can replace default with different server profiles such as all or minimal. We will cover those later in this chapter. JBoss AS constantly scans the deploy directory to pick up new applications or any changes to existing applications. So, you can "hot deploy" application on the fly while JBoss AS is still running.

You can deploy several different types of enterprise applications in JBoss AS:

Exploded Deployment

The WAR, EAR, and SAR deployment packages are really just JAR files with special XML deployment descriptors in directories like META-INF and WEB-INF. JBoss AS allows you to deploy those archives as expanded directories instead of JAR files. That allows you to make changes to web pages etc on the fly without re-deploying the entire application. If you do need to re-deploy the exploded directory without re-start the server, you can just "touch" the deployment descriptors (e.g., the WEB-INF/web.xml in a WAR and the META-INF/application.xml in an EAR) to update their timestamps.

Table of Contents

3. The JBoss JMX Microkernel
3.1. An Introduction to JMX
3.1.1. Instrumentation Level
3.1.2. Agent Level
3.1.3. Distributed Services Level
3.1.4. JMX Component Overview
3.2. JBoss JMX Implementation Architecture
3.2.1. The JBoss ClassLoader Architecture
3.2.2. Class Loading and Types in Java
3.2.3. JBoss XMBeans
3.3. Connecting to the JMX Server
3.3.1. Inspecting the Server - the JMX Console Web Application
3.3.2. Connecting to JMX Using RMI
3.3.3. Command Line Access to JMX
3.3.4. Connecting to JMX Using Any Protocol
3.4. Using JMX as a Microkernel
3.4.1. The Startup Process
3.4.2. JBoss MBean Services
3.4.3. Writing JBoss MBean Services
3.4.4. Deployment Ordering and Dependencies
3.5. JBoss Deployer Architecture
3.5.1. Deployers and ClassLoaders
3.6. Remote Access to Services, Detached Invokers
3.6.1. A Detached Invoker Example, the MBeanServer Invoker Adaptor Service
3.6.2. Detached Invoker Reference
4. Naming on JBoss
4.1. An Overview of JNDI
4.1.1. Names
4.1.2. Contexts
4.2. The JBossNS Architecture
4.3. The Naming InitialContext Factories
4.3.1. The standard naming context factory
4.3.2. The org.jboss.naming.NamingContextFactory
4.3.3. Naming Discovery in Clustered Environments
4.3.4. The HTTP InitialContext Factory Implementation
4.3.5. The Login InitialContext Factory Implementation
4.3.6. The ORBInitialContextFactory
4.4. JNDI over HTTP
4.4.1. Accessing JNDI over HTTP
4.4.2. Accessing JNDI over HTTPS
4.4.3. Securing Access to JNDI over HTTP
4.4.4. Securing Access to JNDI with a Read-Only Unsecured Context
4.5. Additional Naming MBeans
4.5.1. JNDI Binding Manager
4.5.2. The org.jboss.naming.NamingAlias MBean
4.5.3. org.jboss.naming.ExternalContext MBean
4.5.4. The org.jboss.naming.JNDIView MBean
4.6. J2EE and JNDI - The Application Component Environment
4.6.1. ENC Usage Conventions
5. Connectors on JBoss
5.1. JCA Overview
5.2. An Overview of the JBossCX Architecture
5.2.1. BaseConnectionManager2 MBean
5.2.2. RARDeployment MBean
5.2.3. JBossManagedConnectionPool MBean
5.2.4. CachedConnectionManager MBean
5.2.5. A Sample Skeleton JCA Resource Adaptor
5.3. Configuring JDBC DataSources
5.4. Configuring Generic JCA Adaptors
6. Transactions on JBoss
6.1. Transaction/JTA Overview
6.1.1. Pessimistic and optimistic locking
6.1.2. The components of a distributed transaction
6.1.3. The two-phase XA protocol
6.1.4. Heuristic exceptions
6.1.5. Transaction IDs and branches
6.2. JTS support
6.3. Web Services Transactions
6.4. Configuring JBoss Transactions
6.5. Local versus distributed transactions
7. Messaging on JBoss
7.1. JMS Examples
7.1.1. A Point-To-Point Example
7.1.2. A Pub-Sub Example
7.1.3. A Pub-Sub With Durable Topic Example
7.1.4. A Point-To-Point With MDB Example
7.2. JBoss MQ Overview
7.2.1. Invocation Layer
7.2.2. Security Manager
7.2.3. Destination Manager
7.2.4. Message Cache
7.2.5. State Manager
7.2.6. Persistence Manager
7.2.7. Destinations
7.3. JBoss MQ Configuration and MBeans
7.3.1. org.jboss.mq.il.jvm.JVMServerILService
7.3.2. org.jboss.mq.il.uil2.UILServerILService
7.3.3. org.jboss.mq.il.http.HTTPServerILService
7.3.4. org.jboss.mq.server.jmx.Invoker
7.3.5. org.jboss.mq.server.jmx.InterceptorLoader
7.3.6. org.jboss.mq.sm.jdbc.JDBCStateManager
7.3.7. org.jboss.mq.security.SecurityManager
7.3.8. org.jboss.mq.server.jmx.DestinationManager
7.3.9. org.jboss.mq.server.MessageCache
7.3.10. org.jboss.mq.pm.jdbc2.PersistenceManager
7.3.11. Destination MBeans
7.4. Specifying the MDB JMS Provider
7.4.1. org.jboss.jms.jndi.JMSProviderLoader MBean
7.4.2. org.jboss.jms.asf.ServerSessionPoolLoader MBean
7.4.3. Integrating non-JBoss JMS Providers
8. Security on JBoss
8.1. J2EE Declarative Security Overview
8.1.1. Security References
8.1.2. Security Identity
8.1.3. Security roles
8.1.4. EJB method permissions
8.1.5. Web Content Security Constraints
8.1.6. Enabling Declarative Security in JBoss
8.2. An Introduction to JAAS
8.2.1. What is JAAS?
8.3. The JBoss Security Model
8.3.1. Enabling Declarative Security in JBoss Revisited
8.4. The JBoss Security Extension Architecture
8.4.1. How the JaasSecurityManager Uses JAAS
8.4.2. The JaasSecurityManagerService MBean
8.4.3. The JaasSecurityDomain MBean
8.5. Defining Security Domains
8.5.1. Loading Security Domains
8.5.2. The DynamicLoginConfig service
8.5.3. Using JBoss Login Modules
8.5.4. Writing Custom Login Modules
8.6. The Secure Remote Password (SRP) Protocol
8.6.1. Providing Password Information for SRP
8.6.2. Inside of the SRP algorithm
8.7. Running JBoss with a Java 2 security manager
8.8. Using SSL with JBoss using JSSE
8.9. Configuring JBoss for use Behind a Firewall
8.10. How to Secure the JBoss Server
8.10.1. The JMX Console
8.10.2. The Web Console
8.10.3. The HTTP Invokers
8.10.4. The JMX Invoker
9. Web Services
9.1. Document/Literal
9.2. Document/Literal (Bare)
9.3. Document/Literal (Wrapped)
9.4. RPC/Literal
9.5. RPC/Encoded
9.6. Web Service Endpoints
9.7. Plain old Java Object (POJO)
9.8. The endpoint as a web application
9.9. Packaging the endpoint
9.10. Accessing the generated WSDL
9.11. EJB3 Stateless Session Bean (SLSB)
9.12. Endpoint Provider
9.13. WebServiceContext
9.14. Web Service Clients
9.14.1. Service
9.14.2. Dynamic Proxy
9.14.3. WebServiceRef
9.14.4. Dispatch
9.14.5. Asynchronous Invocations
9.14.6. Oneway Invocations
9.15. Common API
9.15.1. Handler Framework
9.15.2. Message Context
9.15.3. Fault Handling
9.16. DataBinding
9.16.1. Using JAXB with non annotated classes
9.17. Attachments
9.17.1. MTOM/XOP
9.17.2. SwaRef
9.18. Tools
9.18.1. Bottom-Up (Using wsprovide)
9.18.2. Top-Down (Using wsconsume)
9.18.3. Client Side
9.18.4. Command-line & Ant Task Reference
9.18.5. JAX-WS binding customization
9.19. Web Service Extensions
9.19.1. WS-Addressing
9.19.2. WS-BPEL
9.19.3. WS-Eventing
9.19.4. WS-Security
9.19.5. WS-Transaction
9.19.6. XML Registries
9.19.7. WS-Policy
9.20. JBossWS Extensions
9.20.1. Proprietary Annotations
9.21. Web Services Appendix
9.22. References
10. Additional Services
10.1. Memory and Thread Monitoring
10.2. The Log4j Service
10.3. System Properties Management
10.4. Property Editor Management
10.5. Services Binding Management
10.5.1. AttributeMappingDelegate
10.5.2. XSLTConfigDelegate
10.5.3. XSLTFileDelegate
10.5.4. The Sample Bindings File
10.6. RMI Dynamic Class Loading
10.7. Scheduling Tasks
10.7.1. org.jboss.varia.scheduler.Scheduler
10.8. The Timer Service
10.9. The BarrierController Service
10.10. Exposing MBean Events via SNMP

Modularly developed from the ground up, the JBoss server and container are completely implemented using component-based plug-ins. The modularization effort is supported by the use of JMX, the Java Management Extension API. Using JMX, industry-standard interfaces help manage both JBoss/Server components and the applications deployed on it. Ease of use is still the number one priority, and the JBoss Server architecture sets a new standard for modular, plug-in design as well as ease of server and application management.

This high degree of modularity benefits the application developer in several ways. The already tight code can be further trimmed down to support applications that must have a small footprint. For example, if EJB passivation is unnecessary in your application, simply take the feature out of the server. If you later decide to deploy the same application under an Application Service Provider (ASP) model, simply enable the server's passivation feature for that web-based deployment. Another example is the freedom you have to drop your favorite object to relational database (O-R) mapping tool, such as TOPLink, directly into the container.

This chapter will introduce you to JMX and its role as the JBoss server component bus. You will also be introduced to the JBoss MBean service notion that adds life cycle operations to the basic JMX management component.

The success of the full Open Source J2EE stack lies with the use of JMX (Java Management Extension). JMX is the best tool for integration of software. It prov ides a common spine that allows the user to integrate modules, containers, and plug-ins. Figure 3.1, “The JBoss JMX integration bus and the standard JBoss components” shows the role of JMX as an integration spine or bus into which components plug. Components are declared as MBean services that are then loaded into JBoss. The components may subsequently be administered using JMX.


Before looking at how JBoss uses JMX as its component bus, it would help to get a basic overview what JMX is by touching on some of its key aspects.

JMX components are defined by the Java Management Extensions Instrumentation and Agent Specification, v1.2, which is available from the JSR003 Web page at http://jcp.org/en/jsr/detail?id=3. The material in this JMX overview section is derived from the JMX instrumentation specification, with a focus on the aspects most used by JBoss. A more comprehensive discussion of JMX and its application can be found in JMX: Managing J2EE with Java Management Extensions written by Juha Lindfors (Sams, 2002).

JMX is a standard for managing and monitoring all varieties of software and hardware components from Java. Further, JMX aims to provide integration with the large number of existing management standards. Figure 3.2, “The Relationship between the components of the JMX architecture” shows examples of components found in a JMX environment, and illustrates the relationship between them as well as how they relate to the three levels of the JMX model. The three levels are:

  • Instrumentation , which are the resources to manage

  • Agents , which are the controllers of the instrumentation level objects

  • Distributed services , the mechanism by which administration applications interact with agents and their managed objects


The JMX specification notes that a complete definition of the distributed services level is beyond the scope of the initial version of the JMX specification. This was indicated by the component boxes with the horizontal lines in Figure 3.2, “The Relationship between the components of the JMX architecture”. The general purpose of this level is to define the interfaces required for implementing JMX management applications or managers. The following points highlight the intended functionality of the distributed services level as discussed in the current JMX specification.

  • Provide an interface for management applications to interact transparently with an agent and its JMX manageable resources through a connector

  • Exposes a management view of a JMX agent and its MBeans by mapping their semantic meaning into the constructs of a data-rich protocol (for example HTML or SNMP)

  • Distributes management information from high-level management platforms to numerous JMX agents

  • Consolidates management information coming from numerous JMX agents into logical views that are relevant to the end user's business operations

  • Provides security

It is intended that the distributed services level components will allow for cooperative management of networks of agents and their resources. These components can be expanded to provide a complete management application.

This section offers an overview of the instrumentation and agent level components. The instrumentation level components include the following:

  • MBeans (standard, dynamic, open, and model MBeans)

  • Notification model elements

  • MBean metadata classes

The agent level components include:

  • MBean server

  • Agent services

An MBean is a Java object that implements one of the standard MBean interfaces and follows the associated design patterns. The MBean for a resource exposes all necessary information and operations that a management application needs to control the resource.

The scope of the management interface of an MBean includes the following:

  • Attribute values that may be accessed by name

  • Operations or functions that may be invoked

  • Notifications or events that may be emitted

  • The constructors for the MBean's Java class

JMX defines four types of MBeans to support different instrumentation needs:

  • Standard MBeans : These use a simple JavaBean style naming convention and a statically defined management interface. This is the most common type of MBean used by JBoss.

  • Dynamic MBeans : These must implement the javax.management.DynamicMBean interface, and they expose their management interface at runtime when the component is instantiated for the greatest flexibility. JBoss makes use of Dynamic MBeans in circumstances where the components to be managed are not known until runtime.

  • Open MBeans : These are an extension of dynamic MBeans. Open MBeans rely on basic, self-describing, user-friendly data types for universal manageability.

  • Model MBeans : These are also an extension of dynamic MBeans. Model MBeans must implement the javax.management.modelmbean.ModelMBean interface. Model MBeans simplify the instrumentation of resources by providing default behavior. JBoss XMBeans are an implementation of Model MBeans.

We will present an example of a Standard and a Model MBean in the section that discusses extending JBoss with your own custom services.

A key component of the agent level is the managed bean server. Its functionality is exposed through an instance of the javax.management.MBeanServer. An MBean server is a registry for MBeans that makes the MBean management interface available for use by management applications. The MBean never directly exposes the MBean object itself; rather, its management interface is exposed through metadata and operations available in the MBean server interface. This provides a loose coupling between management applications and the MBeans they manage.

MBeans can be instantiated and registered with the MBeanServer by the following:

  • Another MBean

  • The agent itself

  • A remote management application (through the distributed services)

When you register an MBean, you must assign it a unique object name. The object name then becomes the unique handle by which management applications identify the object on which to perform management operations. The operations available on MBeans through the MBean server include the following:

  • Discovering the management interface of MBeans

  • Reading and writing attribute values

  • Invoking operations defined by MBeans

  • Registering for notifications events

  • Querying MBeans based on their object name or their attribute values

Protocol adaptors and connectors are required to access the MBeanServer from outside the agent's JVM. Each adaptor provides a view via its protocol of all MBeans registered in the MBean server the adaptor connects to. An example adaptor is an HTML adaptor that allows for the inspection and editing of MBeans using a Web browser. As was indicated in Figure 3.2, “The Relationship between the components of the JMX architecture”, there are no protocol adaptors defined by the current JMX specification. Later versions of the specification will address the need for remote access protocols in standard ways.

A connector is an interface used by management applications to provide a common API for accessing the MBean server in a manner that is independent of the underlying communication protocol. Each connector type provides the same remote interface over a different protocol. This allows a remote management application to connect to an agent transparently through the network, regardless of the protocol. The specification of the remote management interface will be addressed in a future version of the JMX specification.

Adaptors and connectors make all MBean server operations available to a remote management application. For an agent to be manageable from outside of its JVM, it must include at least one protocol adaptor or connector. JBoss currently includes a custom HTML adaptor implementation and a custom JBoss RMI adaptor.

Class loading is a fundamental part of all server architectures. Arbitrary services and their supporting classes must be loaded into the server framework. This can be problematic due to the strongly typed nature of Java. Most developers know that the type of a class in Java is a function of the fully qualified name of the class. However the type is also a function of the java.lang.ClassLoader that is used to define that class. This additional qualification of type is necessary to ensure that environments in which classes may be loaded from arbitrary locations would be type-safe.

However, in a dynamic environment like an application server, and especially JBoss with its support for hot deployment are that class cast exceptions, linkage errors and illegal access errors can show up in ways not seen in more static class loading contexts. Let's take a look at the meaning of each of these exceptions and how they can happen.

A java.lang.ClassCastException results whenever an attempt is made to cast an instance to an incompatible type. A simple example is trying to obtain a String from a List into which a URL was placed:

ArrayList array = new ArrayList();
array.add(new URL("file:/tmp"));
String url = (String) array.get(0);

java.lang.ClassCastException: java.net.URL
at org.jboss.book.jmx.ex0.ExCCEa.main(Ex1CCE.java:16)

The ClassCastException tells you that the attempt to cast the array element to a String failed because the actual type was URL. This trivial case is not what we are interested in however. Consider the case of a JAR being loaded by different class loaders. Although the classes loaded through each class loader are identical in terms of the bytecode, they are completely different types as viewed by the Java type system. An example of this is illustrated by the code shown in Example 3.1, “The ExCCEc class used to demonstrate ClassCastException due to duplicate class loaders”.

package org.jboss.book.jmx.ex0;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/**
 * An example of a ClassCastException that
 * results from classes loaded through
 * different class loaders.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExCCEc
{
    public static void main(String[] args) throws Exception
    {
        ChapterExRepository.init(ExCCEc.class);

        String chapDir = System.getProperty("j2eechapter.dir");
        Logger ucl0Log = Logger.getLogger("UCL0");
        File jar0 = new File(chapDir+"/j0.jar");
        ucl0Log.info("jar0 path: "+jar0.toString());
        URL[] cp0 = {jar0.toURL()};
        URLClassLoader ucl0 = new URLClassLoader(cp0);
        Thread.currentThread().setContextClassLoader(ucl0);
        Class objClass = ucl0.loadClass("org.jboss.book.jmx.ex0.ExObj");
        StringBuffer buffer = new
            StringBuffer("ExObj Info");
        Debug.displayClassInfo(objClass, buffer, false);
        ucl0Log.info(buffer.toString());
        Object value = objClass.newInstance();
        
        File jar1 = new File(chapDir+"/j0.jar");
        Logger ucl1Log = Logger.getLogger("UCL1");
        ucl1Log.info("jar1 path: "+jar1.toString());
        URL[] cp1 = {jar1.toURL()};
        URLClassLoader ucl1 = new URLClassLoader(cp1);
        Thread.currentThread().setContextClassLoader(ucl1);
        Class ctxClass2 = ucl1.loadClass("org.jboss.book.jmx.ex0.ExCtx");
        buffer.setLength(0);
        buffer.append("ExCtx Info");
        Debug.displayClassInfo(ctxClass2, buffer, false);
        ucl1Log.info(buffer.toString());
        Object ctx2 = ctxClass2.newInstance();
        
        try {
            Class[] types = {Object.class};
            Method useValue =
                ctxClass2.getMethod("useValue", types);
            Object[] margs = {value};
            useValue.invoke(ctx2, margs);
        } catch(Exception e) {
            ucl1Log.error("Failed to invoke ExCtx.useValue", e);
            throw e;
        }
    }
}

Example 3.1. The ExCCEc class used to demonstrate ClassCastException due to duplicate class loaders


package org.jboss.book.jmx.ex0;

import java.io.IOException;
import org.apache.log4j.Logger;
import org.jboss.util.Debug;

/**
 * A classes used to demonstrate various class
 * loading issues
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExCtx
{
    ExObj value;
    
    public ExCtx() 
        throws IOException
    {
        value = new ExObj();
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new StringBuffer("ctor.ExObj");
        Debug.displayClassInfo(value.getClass(), buffer, false);
        log.info(buffer.toString());
        ExObj2 obj2 = value.ivar;
        buffer.setLength(0);
        buffer = new StringBuffer("ctor.ExObj.ivar");
        Debug.displayClassInfo(obj2.getClass(), buffer, false);
        log.info(buffer.toString());
    }

    public Object getValue()
    {
        return value;
    }

    public void useValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new
            StringBuffer("useValue2.arg class");
        Debug.displayClassInfo(obj.getClass(), buffer, false);
        log.info(buffer.toString());
        buffer.setLength(0);
        buffer.append("useValue2.ExObj class");
        Debug.displayClassInfo(ExObj.class, buffer, false);
        log.info(buffer.toString());
        ExObj ex = (ExObj) obj;
    }

    void pkgUseValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        log.info("In pkgUseValue");
    }
}
package org.jboss.book.jmx.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExObj
    implements Serializable
{
    public ExObj2 ivar = new ExObj2();
}
package org.jboss.book.jmx.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExObj2 
    implements Serializable
{
} 

Example 3.2. The ExCtx, ExObj, and ExObj2 classes used by the examples


The ExCCEc.main method uses reflection to isolate the classes that are being loaded by the class loaders ucl0 and ucl1 from the application class loader. Both are setup to load classes from the output/jmx/j0.jar, the contents of which are:

[examples]$ jar -tf output/jmx/j0.jar
...
org/jboss/book/jmx/ex0/ExCtx.class
org/jboss/book/jmx/ex0/ExObj.class
org/jboss/book/jmx/ex0/ExObj2.class

We will run an example that demonstrates how a class cast exception can occur and then look at the specific issue with the example. See Appendix A, Book Example Installation for instructions on installing the examples accompanying the book, and then run the example from within the examples directory using the following command:

[examples]$ ant -Dchap=jmx -Dex=0c run-example
...
     [java] java.lang.reflect.InvocationTargetException
     [java]     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     [java]     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
     [java]     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
                .java:25)
     [java]     at java.lang.reflect.Method.invoke(Method.java:585)
     [java]     at org.jboss.book.jmx.ex0.ExCCEc.main(ExCCEc.java:58)
     [java] Caused by: java.lang.ClassCastException: org.jboss.book.jmx.ex0.ExObj
     [java]     at org.jboss.book.jmx.ex0.ExCtx.useValue(ExCtx.java:44)
     [java]     ... 5 more

Only the exception is shown here. The full output can be found in the logs/jmx-ex0c.log file. At line 55 of ExCCEc.java we are invoking ExcCCECtx.useValue(Object) on the instance loaded and created in lines 37-48 using ucl1. The ExObj passed in is the one loaded and created in lines 25-35 via ucl0. The exception results when the ExCtx.useValue code attempts to cast the argument passed in to a ExObj. To understand why this fails consider the debugging output from the jmx-ex0c.log file shown in Example 3.3, “The jmx-ex0c.log debugging output for the ExObj classes seen”.


The first output prefixed with [INFO,UCL0] shows that the ExObj class loaded at line ExCCEc.java:31 has a hash code of f8968f and an associated URLClassLoader instance with a hash code of 2611a7, which corresponds to ucl0. This is the class used to create the instance passed to the ExCtx.useValue method. The second output prefixed with [INFO,ExCtx] shows that the ExObj class as seen in the context of the ExCtx.useValue method has a hash code of bc8e1e and a URLClassLoader instance with an associated hash code of 6bd8ea, which corresponds to ucl1. So even though the ExObj classes are the same in terms of actual bytecode since it comes from the same j0.jar, the classes are different as seen by both the ExObj class hash codes, and the associated URLClassLoader instances. Hence, attempting to cast an instance of ExObj from one scope to the other results in the ClassCastException.

This type of error is common when redeploying an application to which other applications are holding references to classes from the redeployed application. For example, a standalone WAR accessing an EJB. If you are redeploying an application, all dependent applications must flush their class references. Typically this requires that the dependent applications themselves be redeployed.

An alternate means of allowing independent deployments to interact in the presence of redeployment would be to isolate the deployments by configuring the EJB layer to use the standard call-by-value semantics rather than the call-by-reference JBoss will default to for components collocated in the same VM. An example of how to enable call-by-value semantics is presented in Chapter 11, EJBs on JBoss

A java.lang.IllegalAccessException is thrown when one attempts to access a method or member that visibility qualifiers do not allow. Typical examples are attempting to access private or protected methods or instance variables. Another common example is accessing package protected methods or members from a class that appears to be in the correct package, but is really not due to caller and callee classes being loaded by different class loaders. An example of this is illustrated by the code shown in Example 3.4, “The ExIAEd class used to demonstrate IllegalAccessException due to duplicate class loaders”.

package org.jboss.book.jmx.ex0;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/**
 * An example of IllegalAccessExceptions due to
 * classes loaded by two class loaders.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExIAEd
{
    public static void main(String[] args) throws Exception
    {
	ChapterExRepository.init(ExIAEd.class);

	String chapDir = System.getProperty("j2eechapter.dir");
	Logger ucl0Log = Logger.getLogger("UCL0");
	File jar0 = new File(chapDir+"/j0.jar");
	ucl0Log.info("jar0 path: "+jar0.toString());
	URL[] cp0 = {jar0.toURL()};
	URLClassLoader ucl0 = new URLClassLoader(cp0);
	Thread.currentThread().setContextClassLoader(ucl0);
	
	StringBuffer buffer = new
	    StringBuffer("ExIAEd Info");
	Debug.displayClassInfo(ExIAEd.class, buffer, false);
	ucl0Log.info(buffer.toString());
	
	Class ctxClass1 = ucl0.loadClass("org.jboss.book.jmx.ex0.ExCtx");
	buffer.setLength(0);
	buffer.append("ExCtx Info");
	Debug.displayClassInfo(ctxClass1, buffer, false);
	ucl0Log.info(buffer.toString());
	Object ctx0 = ctxClass1.newInstance();

	try {
	    Class[] types = {Object.class};
	    Method useValue =
		ctxClass1.getDeclaredMethod("pkgUseValue", types);
	    Object[] margs = {null};
	    useValue.invoke(ctx0, margs);
	} catch(Exception e) {
	    ucl0Log.error("Failed to invoke ExCtx.pkgUseValue", e);
	}
    }
}

Example 3.4. The ExIAEd class used to demonstrate IllegalAccessException due to duplicate class loaders


The ExIAEd.main method uses reflection to load the ExCtx class via the ucl0 class loader while the ExIEAd class was loaded by the application class loader. We will run this example to demonstrate how the IllegalAccessException can occur and then look at the specific issue with the example. Run the example using the following command:

[examples]$ ant -Dchap=jmx -Dex=0d run-example
Buildfile: build.xml
...
[java] java.lang.IllegalAccessException: Class org.jboss.book.jmx.ex0.ExIAEd 
  can not access a member of class org.jboss.book.jmx.ex0.ExCtx with modifiers ""
[java]     at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
[java]     at java.lang.reflect.Method.invoke(Method.java:578)
[java]     at org.jboss.book.jmx.ex0.ExIAEd.main(ExIAEd.java:48)

The truncated output shown here illustrates the IllegalAccessException. The full output can be found in the logs/jmx-ex0d.log file. At line 48 of ExIAEd.java the ExCtx.pkgUseValue(Object) method is invoked via reflection. The pkgUseValue method has package protected access and even though both the invoking class ExIAEd and the ExCtx class whose method is being invoked reside in the org.jboss.book.jmx.ex0 package, the invocation is seen to be invalid due to the fact that the two classes are loaded by different class loaders. This can be seen by looking at the debugging output from the jmx-ex0d.log file.

[INFO,UCL0] ExIAEd Info
org.jboss.book.jmx.ex0.ExIAEd(7808b9).ClassLoader=sun.misc.Launcher$AppClassLoader@a9c85c
..sun.misc.Launcher$AppClassLoader@a9c85c
...
[INFO,UCL0] ExCtx Info
org.jboss.book.jmx.ex0.ExCtx(64c34e).ClassLoader=java.net.URLClassLoader@a9c85c
..java.net.URLClassLoader@5d88a
...

The ExIAEd class is seen to have been loaded via the default application class loader instance sun.misc.Launcher$AppClassLoader@a9c85c, while the ExCtx class was loaded by the java.net.URLClassLoader@a9c85c instance. Because the classes are loaded by different class loaders, access to the package protected method is seen to be a security violation. So, not only is type a function of both the fully qualified class name and class loader, the package scope is as well.

An example of how this can happen in practice is to include the same classes in two different SAR deployments. If classes in the deployment have a package protected relationship, users of the SAR service may end up loading one class from SAR class loading at one point, and then load another class from the second SAR at a later time. If the two classes in question have a protected access relationship an IllegalAccessError will result. The solution is to either include the classes in a separate jar that is referenced by the SARs, or to combine the SARs into a single deployment. This can either be a single SAR, or an EAR that includes both SARs.

Loading constraints validate type expectations in the context of class loader scopes to ensure that a class X is consistently the same class when multiple class loaders are involved. This is important because Java allows for user defined class loaders. Linkage errors are essentially an extension of the class cast exception that is enforced by the VM when classes are loaded and used.

To understand what loading constraints are and how they ensure type-safety we will first introduce the nomenclature of the Liang and Bracha paper along with an example from this paper. There are two type of class loaders, initiating and defining. An initiating class loader is one that a ClassLoader.loadClass method has been invoked on to initiate the loading of the named class. A defining class loader is the loader that calls one of the ClassLoader.defineClass methods to convert the class byte code into a Class instance. The most complete expression of a class is given by <C,Ld>Li , where C is the fully qualified class name, Ld is the defining class loader, and Li is the initiating class loader. In a context where the initiating class loader is not important the type may be represented by <C,Ld>, while when the defining class loader is not important, the type may be represented by CLi . In the latter case, there is still a defining class loader, it's just not important what the identity of the defining class loader is. Also, a type is completely defined by <C,Ld>. The only time the initiating loader is relevant is when a loading constraint is being validated. Now consider the classes shown in Example 3.5, “Classes demonstrating the need for loading constraints”.


The class C is defined by L1 and so L1 is used to initiate loading of the classes Spoofed and Delegated referenced in the C.f() method. The Spoofed class is defined by L1, but Delegated is defined by L2 because L1 delegates to L2. Since Delegated is defined by L2, L2 will be used to initiate loading of Spoofed in the context of the Delegated.g() method. In this example both L1 and L2 define different versions of Spoofed as indicated by the two versions shown at the end of Example 3.5, “Classes demonstrating the need for loading constraints”. Since C.f() believes x is an instance of <Spoofed,L1> it is able to access the private field secret_value of <Spoofed,L2> returned by Delegated.g() due to the 1.1 and earlier Java VM's failure to take into account that a class type is determined by both the fully qualified name of the class and the defining class loader.

Java addresses this problem by generating loader constraints to validate type consistency when the types being used are coming from different defining class loaders. For the Example 3.5, “Classes demonstrating the need for loading constraints” example, the VM generates a constraint SpoofedL1=SpoofedL2 when the first line of method C.f() is verified to indicate that the type Spoofed must be the same regardless of whether the load of Spoofed is initiated by L1 or L2. It does not matter if L1 or L2, or even some other class loader defines Spoofed. All that matters is that there is only one Spoofed class defined regardless of whether L1 or L2 was used to initiate the loading. If L1 or L2 have already defined separate versions of Spoofed when this check is made a LinkageError will be generated immediately. Otherwise, the constraint will be recorded and when Delegated.g() is executed, any attempt to load a duplicate version of Spoofed will result in a LinkageError.

Now let's take a look at how a LinkageError can occur with a concrete example. Example 3.6, “A concrete example of a LinkageError” gives the example main class along with the custom class loader used.

package org.jboss.book.jmx.ex0;
import java.io.File;
import java.net.URL;

import org.apache.log4j.Logger;
import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/** 
 * An example of a LinkageError due to classes being defined by more
 * than one class loader in a non-standard class loading environment.
 *
 * @author Scott.Stark@jboss.orgn
 * @version $Revision: 1.9 $
 */
public class ExLE
{
    public static void main(String[] args) 
	throws Exception
    {
        ChapterExRepository.init(ExLE.class);

        String chapDir = System.getProperty("j2eechapter.dir");
        Logger ucl0Log = Logger.getLogger("UCL0");
        File jar0 = new File(chapDir+"/j0.jar");
        ucl0Log.info("jar0 path: "+jar0.toString());
        URL[] cp0 = {jar0.toURL()};
        Ex0URLClassLoader ucl0 = new Ex0URLClassLoader(cp0);
        Thread.currentThread().setContextClassLoader(ucl0);
        
                                    Class ctxClass1 = ucl0.loadClass("org.jboss.book.jmx.ex0.ExCtx");
                                 
        
                                 
                                    Class obj2Class1 = ucl0.loadClass("org.jboss.book.jmx.ex0.ExObj2");
                                 
        StringBuffer buffer = new StringBuffer("ExCtx Info");
        Debug.displayClassInfo(ctxClass1, buffer, false);
        ucl0Log.info(buffer.toString());
        buffer.setLength(0);
        buffer.append("ExObj2 Info, UCL0");
        Debug.displayClassInfo(obj2Class1, buffer, false);
        ucl0Log.info(buffer.toString());
        
        File jar1 = new File(chapDir+"/j1.jar");
        Logger ucl1Log = Logger.getLogger("UCL1");
        ucl1Log.info("jar1 path: "+jar1.toString());
        URL[] cp1 = {jar1.toURL()};
        Ex0URLClassLoader ucl1 = new Ex0URLClassLoader(cp1);
        
                                    Class obj2Class2 = ucl1.loadClass("org.jboss.book.jmx.ex0.ExObj2");
                                 
        buffer.setLength(0);
        buffer.append("ExObj2 Info, UCL1");
        Debug.displayClassInfo(obj2Class2, buffer, false);
        ucl1Log.info(buffer.toString());
        
        
                                    ucl0.setDelegate(ucl1);
                                 
        try {
            ucl0Log.info("Try ExCtx.newInstance()");
            
                                    Object ctx0 = ctxClass1.newInstance();
                                 
            ucl0Log.info("ExCtx.ctor succeeded, ctx0: "+ctx0);
        } catch(Throwable e) {
            ucl0Log.error("ExCtx.ctor failed", e);
        }
    }
}
package org.jboss.book.jmx.ex0;

import java.net.URLClassLoader;
import java.net.URL;

import org.apache.log4j.Logger;

/** 
 * A custom class loader that overrides the standard parent delegation
 * model
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class Ex0URLClassLoader extends URLClassLoader
{
    private static Logger log = Logger.getLogger(Ex0URLClassLoader.class);
    private Ex0URLClassLoader delegate;

    public Ex0URLClassLoader(URL[] urls)
    {
        super(urls);
    }
    
    void setDelegate(Ex0URLClassLoader delegate)
    {
        this.delegate = delegate;
    }
    
    protected synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        Class clazz = null;
        if (delegate != null) {
            log.debug(Integer.toHexString(hashCode()) +
		      "; Asking delegate to loadClass: " + name);
            clazz = delegate.loadClass(name, resolve);
            log.debug(Integer.toHexString(hashCode()) +
		      "; Delegate returned: "+clazz);
        } else {
            log.debug(Integer.toHexString(hashCode()) + 
		      "; Asking super to loadClass: "+name);
            clazz = super.loadClass(name, resolve);
            log.debug(Integer.toHexString(hashCode()) + 
		      "; Super returned: "+clazz);
        }
        return clazz;
    }

    protected Class findClass(String name)
        throws ClassNotFoundException
    {
        Class clazz = null;
        log.debug(Integer.toHexString(hashCode()) + 
		  "; Asking super to findClass: "+name);
        clazz = super.findClass(name);
        log.debug(Integer.toHexString(hashCode()) + 
		  "; Super returned: "+clazz);
        return clazz;
    }
}

Example 3.6. A concrete example of a LinkageError


The key component in this example is the URLClassLoader subclass Ex0URLClassLoader. This class loader implementation overrides the default parent delegation model to allow the ucl0 and ucl1 instances to both load the ExObj2 class and then setup a delegation relationship from ucl0 to ucl1. At lines 30 and 31. the ucl0 Ex0URLClassLoader is used to load the ExCtx and ExObj2 classes. At line 45 of ExLE.main the ucl1 Ex0URLClassLoader is used to load the ExObj2 class again. At this point both the ucl0 and ucl1 class loaders have defined the ExObj2 class. A delegation relationship from ucl0 to ucl1 is then setup at line 51 via the ucl0.setDelegate(ucl1) method call. Finally, at line 54 of ExLE.main an instance of ExCtx is created using the class loaded via ucl0. The ExCtx class is the same as presented in Example 3.4, “The ExIAEd class used to demonstrate IllegalAccessException due to duplicate class loaders”, and the constructor was:

public ExCtx() 
    throws IOException
{
    value = new ExObj();
    Logger log = Logger.getLogger(ExCtx.class);
    StringBuffer buffer = new StringBuffer("ctor.ExObj");
    Debug.displayClassInfo(value.getClass(), buffer, false);
    log.info(buffer.toString());
    ExObj2 obj2 = value.ivar;
    buffer.setLength(0);
    buffer = new StringBuffer("ctor.ExObj.ivar");
    Debug.displayClassInfo(obj2.getClass(), buffer, false);
    log.info(buffer.toString());
}    

Now, since the ExCtx class was defined by the ucl0 class loader, and at the time the ExCtx constructor is executed, ucl0 delegates to ucl1, line 24 of the ExCtx constructor involves the following expression which has been rewritten in terms of the complete type expressions:

<ExObj2,ucl0>ucl0 obj2 = <ExObj,ucl1>ucl0 value * ivar

This generates a loading constraint of ExObj2ucl0 = ExObj2ucl1 since the ExObj2 type must be consistent across the ucl0 and ucl1 class loader instances. Because we have loaded ExObj2 using both ucl0 and ucl1 prior to setting up the delegation relationship, the constraint will be violated and should generate a LinkageError when run. Run the example using the following command:

[examples]$ ant -Dchap=jmx -Dex=0e run-example
Buildfile: build.xml
...
[java] java.lang.LinkageError: loader constraints violated when linking 
           org/jboss/book/jmx/ex0/ExObj2 class
[java]     at org.jboss.book.jmx.ex0.ExCtx.<init>(ExCtx.java:24)
[java]     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[java]     at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessor
           Impl.java:39)     
[java]     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructor
           AccessorImpl.java:27)
[java]     at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
[java]     at java.lang.Class.newInstance0(Class.java:350)
[java]     at java.lang.Class.newInstance(Class.java:303)
[java]     at org.jboss.book.jmx.ex0.ExLE.main(ExLE.java:53)                        

As expected, a LinkageError is thrown while validating the loader constraints required by line 24 of the ExCtx constructor.

Debugging class loading issues comes down to finding out where a class was loaded from. A useful tool for this is the code snippet shown in Example 3.7, “Obtaining debugging information for a Class” taken from the org.jboss.util.Debug class of the book examples.


The key items are shown in bold. The first is that every Class object knows its defining ClassLoader and this is available via the getClassLoader() method. The defines the scope in which the Class type is known as we have just seen in the previous sections on class cast exceptions, illegal access exceptions and linkage errors. From the ClassLoader you can view the hierarchy of class loaders that make up the parent delegation chain. If the class loader is a URLClassLoader you can also see the URLs used for class and resource loading.

The defining ClassLoader of a Class cannot tell you from what location that Class was loaded. To determine this you must obtain the java.security.ProtectionDomain and then the java.security.CodeSource. It is the CodeSource that has the URL p location from which the class originated. Note that not every Class has a CoPdeSource. If a class is loaded by the bootstrap class loader then its CodeSource will be null. This will be the case for all classes in the java.* and javax.* packages, for example.

Beyond that it may be useful to view the details of classes being loaded into the JBoss server. You can enable verbose logging of the JBoss class loading layer using a Log4j configuration fragment like that shown in Example 3.8, “An example log4j.xml configuration fragment for enabling verbose class loading logging”.


This places the output from the classes in the org.jboss.mx.loading package into the ucl.log file of the server configurations log directory. Although it may not be meaningful if you have not looked at the class loading code, it is vital information needed for submitting bug reports or questions regarding class loading problems.

Now that we have the role of class loaders in the Java type system defined, let's take a look at the JBoss class loading architecture. Figure 3.3, “The core JBoss class loading components”.


The central component is the org.jboss.mx.loading.UnifiedClassLoader3 (UCL) class loader. This is an extension of the standard java.net.URLClassLoader that overrides the standard parent delegation model to use a shared repository of classes and resources. This shared repository is the org.jboss.mx.loading.UnifiedLoaderRepository3. Every UCL is associated with a single UnifiedLoaderRepository3, and a UnifiedLoaderRepository3 typically has many UCLs. A UCL may have multiple URLs associated with it for class and resource loading. Deployers use the top-level deployment's UCL as a shared class loader and all deployment archives are assigned to this class loader. We will talk about the JBoss deployers and their interaction with the class loading system in more detail later in Section 3.4.2, “JBoss MBean Services”.

When a UCL is asked to load a class, it first looks to the repository cache it is associated with to see if the class has already been loaded. Only if the class does not exist in the repository will it be loaded into the repository by the UCL. By default, there is a single UnifiedLoaderRepository3 shared across all UCL instances. This means the UCLs form a single flat class loader namespace. The complete sequence of steps that occur when a UnfiedClassLoader3.loadClass(String, boolean) method is called is:

  1. Check the UnifiedLoaderRepository3 classes cache associated with the UnifiedClassLoader3. If the class is found in the cache it is returned.

  2. Else, ask the UnfiedClassLoader3 if it can load the class. This is essentially a call to the superclass URLClassLoader.loadClass(String, boolean) method to see if the class is among the URLs associated with the class loader, or visible to the parent class loader. If the class is found it is placed into the repository classes cache and returned.

  3. Else, the repository is queried for all UCLs that are capable of providing the class based on the repository package name to UCL map. When a UCL is added to a repository an association between the package names available in the URLs associated with the UCL is made, and a mapping from package names to the UCLs with classes in the package is updated. This allows for a quick determination of which UCLs are capable of loading the class. The UCLs are then queried for the requested class in the order in which the UCLs were added to the repository. If a UCL is found that can load the class it is returned, else a java.lang.ClassNotFoundException is thrown.

Another useful source of information on classes is the UnifiedLoaderRepository itself. This is an MBean that contains operations to display class and package information. The default repository is located under a standard JMX name of JMImplementation:name=Default,service=LoaderRepository, and its MBean can be accessed via the JMX console by following its link from the front page. The JMX console view of this MBean is shown in Figure 3.4, “The default class LoaderRepository MBean view in the JMX console”.


Two useful operations you will find here are getPackageClassLoaders(String) and displayClassInfo(String). The getPackageClassLoaders operation returns a set of class loaders that have been indexed to contain classes or resources for the given package name. The package name must have a trailing period. If you type in the package name org.jboss.ejb., the following information is displayed:

[org.jboss.mx.loading.UnifiedClassLoader3@e26ae7{
	url=file:/private/tmp/jboss-5.0.0/server/default/tmp/deploy/tmp11895jboss-service.xml,
  addedOrder=2}]

This is the string representation of the set. It shows one UnifiedClassLoader3 instance with a primary URL pointing to the jboss-service.xml descriptor. This is the second class loader added to the repository (shown by addedOrder=2). It is the class loader that owns all of the JARs in the lib directory of the server configuration (e.g., server/production/lib).

The view the information for a given class, use the displayClassInfo operation, passing in the fully qualified name of the class to view. For example, if we use org.jboss.jmx.adaptor.html.HtmlAdaptorServlet which is from the package we just looked at, the following description is displayed:

The information is a dump of the information for the Class instance in the loader repository if one has been loaded, followed by the class loaders that are seen to have the class file available. If a class is seen to have more than one class loader associated with it, then there is the potential for class loading related errors.

The previous discussion of the core class loading components introduced the custom UnifiedClassLoader3 and UnifiedLoaderRepository3 classes that form a shared class loading space. The complete class loading picture must also include the parent class loader used by UnifiedClassLoader3s as well as class loaders introduced for scoping and other specialty class loading purposes. Figure 3.5, “A complete class loader view” shows an outline of the class hierarchy that would exist for an EAR deployment containing EJBs and WARs.


The following points apply to this figure:

In its current form there are some advantages and disadvantages to the JBoss class loading architecture. Advantages include:

  • Classes do not need to be replicated across deployment units in order to have access to them.

  • Many future possibilities including novel partitioning of the repositories into domains, dependency and conflict detection, etc.

Disadvantages include:

  • Existing deployments may need to be repackaged to avoid duplicate classes. Duplication of classes in a loader repository can lead to class cast exceptions and linkage errors depending on how the classes are loaded.

  • Deployments that depend on different versions of a given class need to be isolated in separate EARs and a unique HeirarchicalLoaderRepository3 defined using a jboss-app.xml descriptor.

XMBeans are the JBoss JMX implementation version of the JMX model MBean. XMBeans have the richness of the dynamic MBean metadata without the tedious programming required by a direct implementation of the DynamicMBean interface. The JBoss model MBean implementation allows one to specify the management interface of a component through a XML descriptor, hence the X in XMBean. In addition to providing a simple mechanism for describing the metadata required for a dynamic MBean, XMBeans also allow for the specification of attribute persistence, caching behavior, and even advanced customizations like the MBean implementation interceptors. The high level elements of the jboss_xmbean_1_2.dtd for the XMBean descriptor is given in Figure 3.6, “The JBoss 1.0 XMBean DTD Overview (jboss_xmbean_1_2.dtd)”.


The mbean element is the root element of the document containing the required elements for describing the management interface of one MBean (constructors, attributes, operations and notifications). It also includes an optional description element, which can be used to describe the purpose of the MBean, as well as an optional descriptors element which allows for persistence policy specification, attribute caching, etc.

The descriptors element contains all the descriptors for a containing element, as subelements. The descriptors suggested in the JMX specification as well as those used by JBoss have predefined elements and attributes, whereas custom descriptors have a generic descriptor element with name and value attributes as show in Figure 3.7, “ The descriptors element content model”.


The key descriptors child elements include:

  • interceptors : The interceptors element specifies a customized stack of interceptors that will be used in place of the default stack. Currently this is only used when specified at the MBean level, but it could define a custom attribute or operation level interceptor stack in the future. The content of the interceptors element specifies a custom interceptor stack. If no interceptors element is specified the standard ModelMBean interceptors will be used. The standard interceptors are:

    • org.jboss.mx.interceptor.PersistenceInterceptor

    • org.jboss.mx.interceptor.MBeanAttributeInterceptor

    • org.jboss.mx.interceptor.ObjectReferenceInterceptor

    When specifying a custom interceptor stack you would typically include the standard interceptors along with your own unless you are replacing the corresponding standard interceptor.

    Each interceptor element content value specifies the fully qualified class name of the interceptor implementation. The class must implement the org.jboss.mx.interceptor.Interceptor interface. The interceptor class must also have either a no-arg constructor, or a constructor that accepts a javax.management.MBeanInfo.

    The interceptor elements may have any number of attributes that correspond to JavaBean style properties on the interceptor class implementation. For each interceptor element attribute specified, the interceptor class is queried for a matching setter method. The attribute value is converted to the true type of the interceptor class property using the java.beans.PropertyEditor associated with the type. It is an error to specify an attribute for which there is no setter or PropertyEditor.

  • persistence : The persistence element allows the specification of the persistPolicy, persistPeriod, persistLocation, and persistName persistence attributes suggested by the JMX specification. The persistence element attributes are:

    • persistPolicy : The persistPolicy attribute defines when attributes should be persisted and its value must be one of

      • Never : attribute values are transient values that are never persisted

      • OnUpdate : attribute values are persisted whenever they are updated

      • OnTimer : attribute values are persisted based on the time given by the persistPeriod.

      • NoMoreOftenThan : attribute values are persisted when updated but no more often than the persistPeriod.

    • persistPeriod : The persistPeriod attribute gives the update frequency in milliseconds if the perisitPolicy attribute is NoMoreOftenThan or OnTimer.

    • persistLocation : The persistLocation attribute specifies the location of the persistence store. Its form depends on the JMX persistence implementation. Currently this should refer to a directory into which the attributes will be serialized if using the default JBoss persistence manager.

    • persistName : The persistName attribute can be used in conjunction with the persistLocation attribute to further qualify the persistent store location. For a directory persistLocation the persistName specifies the file to which the attributes are stored within the directory.

  • currencyTimeLimit : The currencyTimeLimit element specifies the time in seconds that a cached value of an attribute remains valid. Its value attribute gives the time in seconds. A value of 0 indicates that an attribute value should always be retrieved from the MBean and never cached. A value of -1 indicates that a cache value is always valid.

  • display-name : The display-name element specifies the human friendly name of an item.

  • default : The default element specifies a default value to use when a field has not been set. Note that this value is not written to the MBean on startup as is the case with the jboss-service.xml attribute element content value. Rather, the default value is used only if there is no attribute accessor defined, and there is no value element defined.

  • value : The value element specifies a management attribute's current value. Unlike the default element, the value element is written through to the MBean on startup provided there is a setter method available.

  • persistence-manager : The persistence-manager element gives the name of a class to use as the persistence manager. The value attribute specifies the class name that supplies the org.jboss.mx.persistence.PersistenceManager interface implementation. The only implementation currently supplied by JBoss is the org.jboss.mx.persistence.ObjectStreamPersistenceManager which serializes the ModelMBeanInfo content to a file using Java serialization.

  • descriptor : The descriptor element specifies an arbitrary descriptor not known to JBoss. Its name attribute specifies the type of the descriptor and its value attribute specifies the descriptor value. The descriptor element allows for the attachment of arbitrary management metadata.

  • injection : The injection element describes an injection point for receiving information from the microkernel. Each injection point specifies the type and the set method to use to inject the information into the resource. The injection element supports type attributes:

    • id : The id attribute specifies the injection point type. The current injection point types are:

      • MBeanServerType : An MBeanServerType injection point receives a reference to the MBeanServer that the XMBean is registered with.

      • MBeanInfoType : An MBeanInfoType injection point receives a reference to the XMBean ModelMBeanInfo metadata.

      • ObjectNameType : The ObjectName injection point receives the ObjectName that the XMBean is registered under.

  • setMethod : The setMethod attribute gives the name of the method used to set the injection value on the resource. The set method should accept values of the type corresponding to the injection point type.

Note that any of the constructor, attribute, operation or notification elements may have a descriptors element to specify the specification defined descriptors as well as arbitrary extension descriptor settings.

JBoss includes adaptors that allow access to the JMX MBeanServer from outside of the JBoss server VM. The current adaptors include HTML, an RMI interface, and an EJB.

JBoss comes with its own implementation of a JMX HTML adaptor that allows one to view the server's MBeans using a standard web browser. The default URL for the console web application is http://localhost:8080/jmx-console/. If you browse this location you will see something similar to that presented in Figure 3.12, “The JBoss JMX console web application agent view”.


The top view is called the agent view and it provides a listing of all MBeans registered with the MBeanServer sorted by the domain portion of the MBean's ObjectName. Under each domain are the MBeans under that domain. When you select one of the MBeans you will be taken to the MBean view. This allows one to view and edit an MBean's attributes as well as invoke operations. As an example, Figure 3.13, “The MBean view for the "jboss.system:type=Server" MBean” shows the MBean view for the jboss.system:type=Server MBean.


The source code for the JMX console web application is located in the varia module under the src/main/org/jboss/jmx directory. Its web pages are located under varia/src/resources/jmx. The application is a simple MVC servlet with JSP views that utilize the MBeanServer.

Since the JMX console web application is just a standard servlet, it may be secured using standard J2EE role based security. The jmx-console.war that is deployed as an unpacked WAR that includes template settings for quickly enabling simple username and password based access restrictions. If you look at the jmx-console.war in the server/production/deploy directory you will find the web.xml and jboss-web.xml descriptors in the WEB-INF directory. The jmx-console-roles.properties and jmx-console-users.properties files are located in the server/production/conf/props directory.

By uncommenting the security sections of the web.xml and jboss-web.xml descriptors as shown in Example 3.10, “The jmx-console.war web.xml descriptors with the security elements uncommented.”, you enable HTTP basic authentication that restricts access to the JMX Console application to the user admin with password admin. The username and password are determined by the admin=admin line in the jmx-console-users.properties file.



Make these changes and then when you try to access the JMX Console URL. You will see a dialog similar to that shown in Figure 3.14, “The JMX Console basic HTTP login dialog.”.


You probably to use the properties files for securing access to the JMX console application. To see how to properly configure the security settings of web applications see Chapter 8, Security on JBoss .

JBoss supplies an RMI interface for connecting to the JMX MBeanServer. This interface is org.jboss.jmx.adaptor.rmi.RMIAdaptor. The RMIAdaptor interface is bound into JNDI in the default location of jmx/invoker/RMIAdaptor as well as jmx/rmi/RMIAdaptor for backwards compatibility with older clients.

Example 3.12, “ A JMX client that uses the RMIAdaptor” shows a client that makes use of the RMIAdaptor interface to query the MBeanInfo for the JNDIView MBean. It also invokes the MBean's list(boolean) method and displays the result.


To test the client access using the RMIAdaptor, run the following:

[examples]$ ant -Dchap=jmx -Dex=4 run-example
...
                
run-example4:
     [java] JNDIView Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIView Operations: 
     [java]  + java.lang.String list(boolean jboss:service=JNDIView)
     [java]  + java.lang.String listXML()
     [java]  + void create()
     [java]  + void start()
     [java]  + void stop()
     [java]  + void destroy()
     [java]  + void jbossInternalLifecycle(java.lang.String jboss:service=JNDIView)
     [java]  + java.lang.String getName()
     [java]  + int getState()
     [java]  + java.lang.String getStateString()
     [java] JNDIView.list(true) output:
     [java] <h1>java: Namespace</h1>
     [java] <pre>
     [java]   +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- DefaultDS (class: javax.sql.DataSource)
     [java]   +- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFactory)
     [java]   +- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
     [java]   +- comp (class: javax.naming.Context)
     [java]   +- JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl)
     [java]   +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- jaas (class: javax.naming.Context)
     [java]   |   +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   |   +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   |   +- HsqlDbRealm (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   +- timedCacheFactory (class: javax.naming.Context)
     [java] Failed to lookup: timedCacheFactory, errmsg=null
     [java]   +- TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPropag
ationContextFactory)
     [java]   +- StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory)
     [java]   +- Mail (class: javax.mail.Session)
     [java]   +- TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPropag
ationContextImporter)
     [java]   +- TransactionManager (class: org.jboss.tm.TxManager)
     [java] </pre>
     [java] <h1>Global JNDI Namespace</h1>
     [java] <pre>
     [java]   +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- UIL2ConnectionFactory[link -> ConnectionFactory] (class: javax.naming.Lin
kRef)
     [java]   +- UserTransactionSessionFactory (proxy: $Proxy11 implements interface org.jbos
s.tm.usertx.interfaces.UserTransactionSessionFactory)
     [java]   +- HTTPConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- console (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- PluginManager (proxy: $Proxy36 implements interface org.jboss.console.ma
nager.PluginManagerMBean)
     [java]   +- UIL2XAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming
.LinkRef)
     [java]   +- UUIDKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.uuid.UUID
KeyGeneratorFactory)
     [java]   +- HTTPXAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- topic (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- testDurableTopic (class: org.jboss.mq.SpyTopic)
     [java]   |   +- testTopic (class: org.jboss.mq.SpyTopic)
     [java]   |   +- securedTopic (class: org.jboss.mq.SpyTopic)
     [java]   +- queue (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- A (class: org.jboss.mq.SpyQueue)
     [java]   |   +- testQueue (class: org.jboss.mq.SpyQueue)
     [java]   |   +- ex (class: org.jboss.mq.SpyQueue)
     [java]   |   +- DLQ (class: org.jboss.mq.SpyQueue)
     [java]   |   +- D (class: org.jboss.mq.SpyQueue)
     [java]   |   +- C (class: org.jboss.mq.SpyQueue)
     [java]   |   +- B (class: org.jboss.mq.SpyQueue)
     [java]   +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- UserTransaction (class: org.jboss.tm.usertx.client.ClientUserTransaction)
     [java]   +- jmx (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- invoker (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- RMIAdaptor (proxy: $Proxy35 implements interface org.jboss.jmx.adapt
or.rmi.RMIAdaptor,interface org.jboss.jmx.adaptor.rmi.RMIAdaptorExt)
     [java]   |   +- rmi (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- RMIAdaptor[link -> jmx/invoker/RMIAdaptor] (class: javax.naming.L
inkRef)
     [java]   +- HiLoKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.hilo.HiLo
KeyGeneratorFactory)
     [java]   +- UILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.
LinkRef)
     [java]   +- UILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.Link
Ref)
     [java] </pre>

JBoss provides a simple command line tool that allows for interaction with a remote JMX server instance. This tool is called twiddle (for twiddling bits via JMX) and is located in the bin directory of the distribution. Twiddle is a command execution tool, not a general command shell. It is run using either the twiddle.sh or twiddle.bat scripts, and passing in a -h(--help) argument provides the basic syntax, and --help-commands shows what you can do with the tool:

[bin]$ ./twiddle.sh -h
A JMX client to 'twiddle' with a remote JBoss server.

usage: twiddle.sh [options] <command> [command_arguments]

options:
    -h, --help                Show this help message
        --help-commands       Show a list of commands
    -H=<command>              Show command specific help
    -c=command.properties     Specify the command.properties file to use
    -D<name>[=<value>]        Set a system property
    --                        Stop processing options
    -s, --server=<url>        The JNDI URL of the remote server
    -a, --adapter=<name>      The JNDI name of the RMI adapter to use
    -q, --quiet               Be somewhat more quiet

To access basic information about a server, use the serverinfo command. This currently supports:

[bin]$ ./twiddle.sh -H serverinfo
Get information about the MBean server

usage: serverinfo [options]

options:
    -d, --domain    Get the default domain
    -c, --count     Get the MBean count
    -l, --list      List the MBeans
    --              Stop processing options
[bin]$ ./twiddle.sh --server=toki serverinfo --count
460
[bin]$ ./twiddle.sh --server=toki serverinfo --domain
jboss

To query the server for the name of MBeans matching a pattern, use the query command. This currently supports:

[bin]$ ./twiddle.sh -H query
Query the server for a list of matching MBeans

usage: query [options] <query>
options:
    -c, --count    Display the matching MBean count
    --             Stop processing options
Examples:
 query all mbeans: query '*:*'
 query all mbeans in the jboss.j2ee domain: query 'jboss.j2ee:*'
[bin]$ ./twiddle.sh -s toki query 'jboss:service=invoker,*'
jboss:readonly=true,service=invoker,target=Naming,type=http
jboss:service=invoker,type=jrmp
jboss:service=invoker,type=local
jboss:service=invoker,type=pooled
jboss:service=invoker,type=http
jboss:service=invoker,target=Naming,type=http

To get the attributes of an MBean, use the get command:

[bin]$ ./twiddle.sh -H get
Get the values of one or more MBean attributes

usage: get [options] <name> [<attr>+]
  If no attribute names are given all readable attributes are retrieved
options:
    --noprefix    Do not display attribute name prefixes
    --            Stop processing options
[bin]$ ./twiddle.sh get jboss:service=invoker,type=jrmp RMIObjectPort StateString
RMIObjectPort=4444
StateString=Started
[bin]$ ./twiddle.sh get jboss:service=invoker,type=jrmp
ServerAddress=0.0.0.0
RMIClientSocketFactoryBean=null
StateString=Started
State=3
RMIServerSocketFactoryBean=org.jboss.net.sockets.DefaultSocketFactory@ad093076
EnableClassCaching=false
SecurityDomain=null
RMIServerSocketFactory=null
Backlog=200
RMIObjectPort=4444
Name=JRMPInvoker
RMIClientSocketFactory=null

To query the MBeanInfo for an MBean, use the info command:

[bin]$ ./twiddle.sh -H info
Get the metadata for an MBean

usage: info <mbean-name>
  Use '*' to query for all attributes
[bin]$ Description: Management Bean.
+++ Attributes:
 Name: ServerAddress
 Type: java.lang.String
 Access: rw
 Name: RMIClientSocketFactoryBean
 Type: java.rmi.server.RMIClientSocketFactory
 Access: rw
 Name: StateString
 Type: java.lang.String
 Access: r-
 Name: State
 Type: int
 Access: r-
 Name: RMIServerSocketFactoryBean
 Type: java.rmi.server.RMIServerSocketFactory
 Access: rw
 Name: EnableClassCaching
 Type: boolean
 Access: rw
 Name: SecurityDomain
 Type: java.lang.String
 Access: rw
 Name: RMIServerSocketFactory
 Type: java.lang.String
 Access: rw
 Name: Backlog
 Type: int
 Access: rw
 Name: RMIObjectPort
 Type: int
 Access: rw
 Name: Name
 Type: java.lang.String
 Access: r-
 Name: RMIClientSocketFactory
 Type: java.lang.String
 Access: rw
+++ Operations:
 void start()
 void jbossInternalLifecycle(java.lang.String java.lang.String)
 void create()
 void stop()
 void destroy()

To invoke an operation on an MBean, use the invoker command:

[bin]$ ./twiddle.sh -H invoke
Invoke an operation on an MBean

usage: invoke [options] <query> <operation> (<arg>)*

options:
    -q, --query-type[=<type>]    Treat object name as a query
    --                           Stop processing options

query type:
    f[irst]    Only invoke on the first matching name [default]
    a[ll]      Invoke on all matching names
[bin]$ ./twiddle.sh invoke jboss:service=JNDIView list true
<h1>java: Namespace</h1>
<pre>
  +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- DefaultDS (class: javax.sql.DataSource)
  +- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFactory)
  +- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
  +- comp (class: javax.naming.Context)
  +- JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl)
  +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- jaas (class: javax.naming.Context)
  |   +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext)
  |   +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext)
  |   +- HsqlDbRealm (class: org.jboss.security.plugins.SecurityDomainContext)
  +- timedCacheFactory (class: javax.naming.Context)
Failed to lookup: timedCacheFactory, errmsg=null
  +- TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPropagationContext
Factory)
  +- StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory)
  +- Mail (class: javax.mail.Session)
  +- TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPropagationContext
Importer)
  +- TransactionManager (class: org.jboss.tm.TxManager)
</pre>
<h1>Global JNDI Namespace</h1>
<pre>
  +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- UIL2ConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
  +- UserTransactionSessionFactory (proxy: $Proxy11 implements interface org.jboss.tm.usertx.
interfaces.UserTransactionSessionFactory)
  +- HTTPConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- console (class: org.jnp.interfaces.NamingContext)
  |   +- PluginManager (proxy: $Proxy36 implements interface org.jboss.console.manager.Plugin
ManagerMBean)
  +- UIL2XAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- UUIDKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.uuid.UUIDKeyGenerator
Factory)
  +- HTTPXAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- topic (class: org.jnp.interfaces.NamingContext)
  |   +- testDurableTopic (class: org.jboss.mq.SpyTopic)
  |   +- testTopic (class: org.jboss.mq.SpyTopic)
  |   +- securedTopic (class: org.jboss.mq.SpyTopic)
  +- queue (class: org.jnp.interfaces.NamingContext)
  |   +- A (class: org.jboss.mq.SpyQueue)
  |   +- testQueue (class: org.jboss.mq.SpyQueue)
  |   +- ex (class: org.jboss.mq.SpyQueue)
  |   +- DLQ (class: org.jboss.mq.SpyQueue)
  |   +- D (class: org.jboss.mq.SpyQueue)
  |   +- C (class: org.jboss.mq.SpyQueue)
  |   +- B (class: org.jboss.mq.SpyQueue)
  +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- UserTransaction (class: org.jboss.tm.usertx.client.ClientUserTransaction)
  +- jmx (class: org.jnp.interfaces.NamingContext)
  |   +- invoker (class: org.jnp.interfaces.NamingContext)
  |   |   +- RMIAdaptor (proxy: $Proxy35 implements interface org.jboss.jmx.adaptor.rmi.RMIAd
aptor,interface org.jboss.jmx.adaptor.rmi.RMIAdaptorExt)
  |   +- rmi (class: org.jnp.interfaces.NamingContext)
  |   |   +- RMIAdaptor[link -> jmx/invoker/RMIAdaptor] (class: javax.naming.LinkRef)
  +- HiLoKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.hilo.HiLoKeyGenerator
Factory)
  +- UILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- UILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
</pre>

When JBoss starts up, one of the first steps performed is to create an MBean server instance (javax.management.MBeanServer). The JMX MBean server in the JBoss architecture plays the role of a microkernel. All other manageable MBean components are plugged into JBoss by registering with the MBean server. The kernel in that sense is only an framework, and not a source of actual functionality. The functionality is provided by MBeans, and in fact all major JBoss components are manageable MBeans interconnected through the MBean server.

In this section we will describe the JBoss server startup process. A summary of the steps that occur during the JBoss server startup sequence is:

  1. The run start script initiates the boot sequence using the org.jboss.Main.main(String[]) method entry point.

  2. The Main.main method creates a thread group named jboss and then starts a thread belonging to this thread group. This thread invokes the Main.boot method.

  3. The Main.boot method processes the Main.main arguments and then creates an org.jboss.system.server.ServerLoader using the system properties along with any additional properties specified as arguments.

  4. The XML parser libraries, jboss-jmx.jar, concurrent.jar and extra libraries and classpaths given as arguments are registered with the ServerLoader .

  5. The JBoss server instance is created using the ServerLoader.load(ClassLoader) method with the current thread context class loader passed in as the ClassLoader argument. The returned server instance is an implementation of the org.jboss.system.server.Server interface. The creation of the server instance entails:

    • Creating a java.net.URLClassLoader with the URLs of the jars and directories registered with the ServerLoader . This URLClassLoader uses the ClassLoader passed in as its parent and it is pushed as the thread context class loader.

    • The class name of the implementation of the Server interface to use is determined by the jboss.server.type property. This defaults to org.jboss.system.server.ServerImpl.

    • The Server implementation class is loaded using the URLClassLoader created in step 6 and instantiated using its no-arg constructor. The thread context class loader present on entry into the ServerLoader.load method is restored and the server instance is returned.

  6. The server instance is initialized with the properties passed to the ServerLoader constructor using the Server.init(Properties) method.

  7. The server instance is then started using the Server.start() method. The default implementation performs the following steps:

The JBoss server starts out as nothing more than a container for the JMX MBean server, and then loads its personality based on the services defined in the jboss-service.xml MBean configuration file from the named configuration set passed to the server on the command line. Because MBeans define the functionality of a JBoss server instance, it is important to understand how the core JBoss MBeans are written, and how you should integrate your existing services into JBoss using MBeans. This is the topic of the next section.

As we have seen, JBoss relies on JMX to load in the MBean services that make up a given server instance's personality. All of the bundled functionality provided with the standard JBoss distribution is based on MBeans. The best way to add services to the JBoss server is to write your own JMX MBeans.

There are two classes of MBeans: those that are independent of JBoss services, and those that are dependent on JBoss services. MBeans that are independent of JBoss services are the trivial case. They can be written per the JMX specification and added to a JBoss server by adding an mbean tag to the deploy/user-service.xml file. Writing an MBean that relies on a JBoss service such as naming requires you to follow the JBoss service pattern. The JBoss MBean service pattern consists of a set of life cycle operations that provide state change notifications. The notifications inform an MBean service when it can create, start, stop, and destroy itself. The management of the MBean service life cycle is the responsibility of three JBoss MBeans: SARDeployer, ServiceConfigurator and ServiceController.

JBoss manages the deployment of its MBean services via a custom MBean that loads an XML variation of the standard JMX MLet configuration file. This custom MBean is implemented in the org.jboss.deployment.SARDeployer class. The SARDeployer MBean is loaded when JBoss starts up as part of the bootstrap process. The SAR acronym stands for service archive .

The SARDeployer handles services archives. A service archive can be either a jar that ends with a .sar suffix and contains a META-INF/jboss-service.xml descriptor, or a standalone XML descriptor with a naming pattern that matches *-service.xml. The DTD for the service descriptor is jboss-service_5.0.dtd and is shown in Figure 3.15, “The DTD for the MBean service descriptor parsed by the SARDeployer”.


The elements of the DTD are:

MBean attribute values don't need to be hardcoded literal strings. Service files may contain references to system properties using the ${name} notation, where name is the name of a Java system property. The value of this system property, as would be returned from the call System.getProperty("name"). Multiple properties can be specified separated by commas like ${name1,name2,name3}. If there is no system property named name1, name2 will be tried and then name3. This allows multiple levels of substitution to be used. Finally, a default value can be added using a colon separator. The substitution ${name:default value} would substitute the the text "default value" if the system property name didn't exist. If none of the listed properties exist and no default value is given, no substitution will occur.

When the SARDeployer is asked to deploy a service performs several steps. Figure 3.16, “A sequence diagram highlighting the main activities performed by the SARDeployer to start a JBoss MBean service” is a sequence diagram that shows the init through start phases of a service.


In Figure 3.16, “A sequence diagram highlighting the main activities performed by the SARDeployer to start a JBoss MBean service” the following is illustrated:

  • Methods prefixed with 1.1 correspond to the load and parse of the XML service descriptor.

  • Methods prefixed with 1.2 correspond to processing each classpath element in the service descriptor to create an independent deployment that makes the jar or directory available through a UnifiedClassLoader registered with the unified loader repository.

  • Methods prefixed with 1.3 correspond to processing each local-directory element in the service descriptor. This does a copy of the SAR elements specified in the path attribute to the server/<config>/db directory.

  • Method 1.4. Process each deployable unit nested in the service a child deployment is created and added to the service deployment info subdeployment list.

  • Method 2.1. The UnifiedClassLoader of the SAR deployment unit is registered with the MBean Server so that is can be used for loading of the SAR MBeans.

  • Method 2.2. For each MBean element in the descriptor, create an instance and initialize its attributes with the values given in the service descriptor. This is done by calling the ServiceController.install method.

  • Method 2.4.1. For each MBean instance created, obtain its JMX ObjectName and ask the ServiceController to handle the create step of the service life cycle. The ServiceController handles the dependencies of the MBean service. Only if the service's dependencies are satisfied is the service create method invoked.

  • Methods prefixed with 3.1 correspond to the start of each MBean service defined in the service descriptor. For each MBean instance created, obtain its JMX ObjectName and ask the ServiceController to handle the start step of the service life cycle. The ServiceController handles the dependencies of the MBean service. Only if the service's dependencies are satisfied is the service start method invoked.

JBoss manages dependencies between MBeans via the org.jboss.system.ServiceController custom MBean. The SARDeployer delegates to the ServiceController when initializing, creating, starting, stopping and destroying MBean services. Figure 3.17, “The interaction between the SARDeployer and ServiceController to start a service” shows a sequence diagram that highlights interaction between the SARDeployer and ServiceController.


The ServiceController MBean has four key methods for the management of the service life cycle: create, start, stop and destroy.

To specify that an MBean service depends on other MBean services you need to declare the dependencies in the mbean element of the service descriptor. This is done using the depends and depends-list elements. One difference between the two elements relates to the optional-attribute-name attribute usage. If you track the ObjectNames of dependencies using single valued attributes you should use the depends element. If you track the ObjectNames of dependencies using java.util.List compatible attributes you would use the depends-list element. If you only want to specify a dependency and don't care to have the associated service ObjectName bound to an attribute of your MBean then use whatever element is easiest. The following listing shows example service descriptor fragments that illustrate the usage of the dependency related elements.

<mbean code="org.jboss.mq.server.jmx.Topic"
       name="jms.topic:service=Topic,name=testTopic">
    <!-- Declare a dependency on the "jboss.mq:service=DestinationManager" and
         bind this name to the DestinationManager attribute -->
    <depends optional-attribute-name="DestinationManager">
        jboss.mq:service=DestinationManager 
    </depends>

    <!-- Declare a dependency on the "jboss.mq:service=SecurityManager" and
         bind this name to the SecurityManager attribute -->
    <depends optional-attribute-name="SecurityManager">
        jboss.mq:service=SecurityManager
    </depends>

    <!-- ... -->

    <!-- Declare a dependency on the
         "jboss.mq:service=CacheManager" without
         any binding of the name to an attribute-->
    <depends>jboss.mq:service=CacheManager</depends>
</mbean>

<mbean code="org.jboss.mq.server.jmx.TopicMgr" 
       name="jboss.mq.destination:service=TopicMgr">
    <!-- Declare a dependency on the given topic destination mbeans and
         bind these names to the Topics attribute -->
    <depends-list optional-attribute-name="Topics">
        <depends-list-element>jms.topic:service=Topic,name=A</depends-list-element>
        <depends-list-element>jms.topic:service=Topic,name=B</depends-list-element>
        <depends-list-element>jms.topic:service=Topic,name=C</depends-list-element>
    </depends-list>
</mbean>

Another difference between the depends and depends-list elements is that the value of the depends element may be a complete MBean service configuration rather than just the ObjectName of the service. Example 3.13, “An example of using the depends element to specify the complete configuration of a depended on service.” shows an example from the hsqldb-service.xml descriptor. In this listing the org.jboss.resource.connectionmanager.RARDeployment service configuration is defined using a nested mbean element as the depends element value. This indicates that the org.jboss.resource.connectionmanager.LocalTxConnectionManager MBean depends on this service. The jboss.jca:service=LocalTxDS,name=hsqldbDS ObjectName will be bound to the ManagedConnectionFactoryName attribute of the LocalTxConnectionManager class.


The URLDeploymentScanner MBean service provides the JBoss hot deployment capability. This service watches one or more URLs for deployable archives and deploys the archives as they appear or change. It also undeploys previously deployed applications if the archive from which the application was deployed is removed. The configurable attributes include:

  • URLs : A comma separated list of URL strings for the locations that should be watched for changes. Strings that do not correspond to valid URLs are treated as file paths. Relative file paths are resolved against the server home URL, for example, JBOSS_DIST/server/production for the production config file set. If a URL represents a file then the file is deployed and watched for subsequent updates or removal. If a URL ends in / to represent a directory, then the contents of the directory are treated as a collection of deployables and scanned for content that are to be watched for updates or removal. The requirement that a URL end in a / to identify a directory follows the RFC2518 convention and allows discrimination between collections and directories that are simply unpacked archives.

    The default value for the URLs attribute is deploy/ which means that any SARs, EARs, JARs, WARs, RARs, etc. dropped into the server/<name>/deploy directory will be automatically deployed and watched for updates.

    Example URLs include:

    • deploy/ scans ${jboss.server.url}/deploy/, which is local or remote depending on the URL used to boot the server

    • ${jboss.server.home.dir}/deploy/ scans ${jboss.server.home.dir)/deploy , which is always local

    • file:/var/opt/myapp.ear deploys myapp.ear from a local location

    • file:/var/opt/apps/ scans the specified directory

    • http://www.test.com/netboot/myapp.ear deploys myapp.ear from a remote location

    • http://www.test.com/netboot/apps/ scans the specified remote location using WebDAV. This will only work if the remote http server supports the WebDAV PROPFIND command.

  • ScanPeriod : The time in milliseconds between runs of the scanner thread. The default is 5000 (5 seconds).

  • URLComparator : The class name of a java.util.Comparator implementation used to specify a deployment ordering for deployments found in a scanned directory. The implementation must be able to compare two java.net.URL objects passed to its compare method. The default setting is the org.jboss.deployment.DeploymentSorter class which orders based on the deployment URL suffix. The ordering of suffixes is: deployer, deployer.xml, sar, rar, ds.xml, service.xml, har, jar, war, wsr, ear, zip, bsh, last.

    An alternate implementation is the org.jboss.deployment.scanner.PrefixDeploymentSorter class. This orders the URLs based on numeric prefixes. The prefix digits are converted to an int (ignoring leading zeroes), smaller prefixes are ordered ahead of larger numbers. Deployments that do not start with any digits will be deployed after all numbered deployments. Deployments with the same prefix value are further sorted by the DeploymentSorter logic.

  • Filter : The class name of a java.io.FileFilter implementation that is used to filter the contents of scanned directories. Any file not accepted by this filter will not be deployed. The default is org.jboss.deployment.scanner.DeploymentFilter which is an implementation that rejects the following patterns:

    "#*", "%*", ",*", ".*", "_$*", "*#", "*$", "*%", "*.BAK", "*.old", "*.orig", "*.rej", "*.bak", "*.sh", "*,v", "*~", ".make.state", ".nse_depinfo", "CVS", "CVS.admin", "RCS", "RCSLOG", "SCCS", "TAGS", "core", "tags"

  • RecursiveSearch : This property indicates whether or not deploy subdirectories are seen to be holding deployable content. If this is false, deploy subdirectories that do not contain a dot (.) in their name are seen to be unpackaged JARs with nested subdeployments. If true, then deploy subdirectories are just groupings of deployable content. The difference between the two views shows is related to the depth first deployment model JBoss supports. The false setting which treats directories as unpackaged JARs with nested content triggers the deployment of the nested content as soon as the JAR directory is deployed. The true setting simply ignores the directory and adds its content to the list of deployable packages and calculates the order based on the previous filter logic. The default is true.

  • Deployer : The JMX ObjectName string of the MBean that implements the org.jboss.deployment.Deployer interface operations. The default setting is to use the MainDeployer created by the bootstrap startup process.

Writing a custom MBean service that integrates into the JBoss server requires the use of the org.jboss.system.Service interface pattern if the custom service is dependent on other services. When a custom MBean depends on other MBean services you cannot perform any service dependent initialization in any of the javax.management.MBeanRegistration interface methods since JMX has no dependency notion. Instead, you must manage dependency state using the Service interface create and/or start methods. You can do this using any one of the following approaches:

  • Add any of the Service methods that you want called on your MBean to your MBean interface. This allows your MBean implementation to avoid dependencies on JBoss specific interfaces.

  • Have your MBean interface extend the org.jboss.system.Service interface.

  • Have your MBean interface extend the org.jboss.system.ServiceMBean interface. This is a subinterface of org.jboss.system.Service that adds getName(), getState(), getStateString() methods.

Which approach you choose depends on whether or not you want your code to be coupled to JBoss specific code. If you don't, then you would use the first approach. If you don't care about dependencies on JBoss classes, the simplest approach is to have your MBean interface extend from org.jboss.system.ServiceMBean and your MBean implementation class extend from the abstract org.jboss.system.ServiceMBeanSupport class. This class implements the org.jboss.system.ServiceMBean interface. ServiceMBeanSupport provides implementations of the create, start, stop, and destroy methods that integrate logging and JBoss service state management tracking. Each method delegates any subclass specific work to createService, startService, stopService, and destroyService methods respectively. When subclassing ServiceMBeanSupport, you would override one or more of the createService, startService, stopService, and destroyService methods as required

This section develops a simple MBean that binds a HashMap into the JBoss JNDI namespace at a location determined by its JndiName attribute to demonstrate what is required to create a custom MBean. Because the MBean uses JNDI, it depends on the JBoss naming service MBean and must use the JBoss MBean service pattern to be notified when the naming service is available.

Version one of the classes, shown in Example 3.14, “JNDIMapMBean interface and implementation based on the service interface method pattern”, is based on the service interface method pattern. This version of the interface declares the start and stop methods needed to start up correctly without using any JBoss-specific classes.

package org.jboss.book.jmx.ex1;
                
// The JNDIMap MBean interface
import javax.naming.NamingException;
                
public interface JNDIMapMBean
{
    public String getJndiName();
    public void setJndiName(String jndiName) throws NamingException;
    public void start() throws Exception;
    public void stop() throws Exception;
}
package org.jboss.book.jmx.ex1;

// The JNDIMap MBean implementation
import java.util.HashMap;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import org.jboss.naming.NonSerializableFactory;

public class JNDIMap implements JNDIMapMBean
{
    private String jndiName;
    private HashMap contextMap = new HashMap();
    private boolean started;
    
    public String getJndiName()
    {
        return jndiName;
    }
    public void setJndiName(String jndiName) throws NamingException
    {
        String oldName = this.jndiName;
        this.jndiName = jndiName;
        if (started) {
            unbind(oldName);
            try {
                rebind();
            } catch(Exception e) {
                NamingException ne = new NamingException("Failedto update jndiName");
                ne.setRootCause(e);
                throw ne;
            }
        }
    }

    public void start() throws Exception
    {
        started = true;
        rebind();
    }
                
    public void stop()
    {
        started = false;
        unbind(jndiName);
    }
                
    private void rebind() throws NamingException
    {
        InitialContext rootCtx = new InitialContext();
        Name fullName = rootCtx.getNameParser("").parse(jndiName);
        System.out.println("fullName="+fullName);
        NonSerializableFactory.rebind(fullName, contextMap, true);
    }

    private void unbind(String jndiName)
    {
        try {
            InitialContext rootCtx = new InitialContext();
            rootCtx.unbind(jndiName);
            NonSerializableFactory.unbind(jndiName);
        } catch(NamingException e) {
            e.printStackTrace();
        }
    }
}

Example 3.14. JNDIMapMBean interface and implementation based on the service interface method pattern


Version two of the classes, shown in Example 3.14, “JNDIMapMBean interface and implementation based on the service interface method pattern”, use the JBoss ServiceMBean interface and ServiceMBeanSupport class. In this version, the implementation class extends the ServiceMBeanSupport class and overrides the startService and stopService methods. JNDIMapMBean also implements the abstract getName method to return a descriptive name for the MBean. The JNDIMapMBean interface extends the org.jboss.system.ServiceMBean interface and only declares the setter and getter methods for the JndiName attribute because it inherits the service life cycle methods from ServiceMBean. This is the third approach mentioned at the start of the Section 3.4.2, “JBoss MBean Services”.

package org.jboss.book.jmx.ex2;

// The JNDIMap MBean interface
import javax.naming.NamingException;

public interface JNDIMapMBean extends org.jboss.system.ServiceMBean
{
    public String getJndiName();
    public void setJndiName(String jndiName) throws NamingException;
} 
package org.jboss.book.jmx.ex2;
// The JNDIMap MBean implementation
import java.util.HashMap;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import org.jboss.naming.NonSerializableFactory;

public class JNDIMap extends org.jboss.system.ServiceMBeanSupport
    implements JNDIMapMBean
{
    private String jndiName;
    private HashMap contextMap = new HashMap();
    
    public String getJndiName()
    {
        return jndiName;
    }

    public void setJndiName(String jndiName) 
        throws NamingException
    {
        String oldName = this.jndiName;
        this.jndiName = jndiName;
        if (super.getState() == STARTED) {
            unbind(oldName);
            try {
                rebind();
            } catch(Exception e) {
                NamingException ne = new NamingException("Failed to update jndiName");
                ne.setRootCause(e);
                throw ne;
            }
        }
    }
    
    public void startService() throws Exception
    {
        rebind();
    }

    public void stopService()
    {
        unbind(jndiName);
    }
    
    private void rebind() throws NamingException
    {
        InitialContext rootCtx = new InitialContext();
        Name fullName = rootCtx.getNameParser("").parse(jndiName);
        log.info("fullName="+fullName);
        NonSerializableFactory.rebind(fullName, contextMap, true);
    }

    private void unbind(String jndiName)
    {
        try {
            InitialContext rootCtx = new InitialContext();
            rootCtx.unbind(jndiName);
            NonSerializableFactory.unbind(jndiName);
        } catch(NamingException e) {
            log.error("Failed to unbind map", e);
        }
    }
}

Example 3.15. JNDIMap MBean interface and implementation based on the ServiceMBean interface and ServiceMBeanSupport class


The source code for these MBeans along with the service descriptors is located in the examples/src/main/org/jboss/book/jmx/{ex1,ex2} directories.

The jboss-service.xml descriptor for the first version is shown below.

<!-- The SAR META-INF/jboss-service.xml descriptor -->
<server>
    <mbean code="org.jboss.book.jmx.ex1.JNDIMap" 
           name="j2eechap2.ex1:service=JNDIMap">
        <attribute name="JndiName">inmemory/maps/MapTest</attribute>
        <depends>jboss:service=Naming</depends>
    </mbean>
</server> 

The JNDIMap MBean binds a HashMap object under the inmemory/maps/MapTest JNDI name and the client code fragment demonstrates retrieving the HashMap object from the inmemory/maps/MapTest location. The corresponding client code is shown below.

// Sample lookup code
InitialContext ctx = new InitialContext();
HashMap map = (HashMap) ctx.lookup("inmemory/maps/MapTest");

In this section we will develop a variation of the JNDIMap MBean introduced in the preceding section that exposes its management metadata using the JBoss XMBean framework. Our core managed component will be exactly the same core code from the JNDIMap class, but it will not implement any specific management related interface. We will illustrate the following capabilities not possible with a standard MBean:

  • The ability to add rich descriptions to attribute and operations

  • The ability to expose notification information

  • The ability to add persistence of attributes

  • The ability to add custom interceptors for security and remote access through a typed interface

Let's start with a simple XMBean variation of the standard MBean version of the JNDIMap that adds the descriptive information about the attributes and operations and their arguments. The following listing shows the jboss-service.xml descriptor and the jndimap-xmbean1.xml XMBean descriptor. The source can be found in the src/main/org/jboss/book/jmx/xmbean directory of the book examples.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE server PUBLIC    
                     "-//JBoss//DTD MBean Service 3.2//EN"
                     "http://www.jboss.org/j2ee/dtd/jboss-service_3_2.dtd">
<server>
    <mbean code="org.jboss.book.jmx.xmbean.JNDIMap"
           name="j2eechap2.xmbean:service=JNDIMap" 
           xmbean-dd="META-INF/jndimap-xmbean.xml">
        <attribute name="JndiName">inmemory/maps/MapTest</attribute>
        <depends>jboss:service=Naming</depends>
    </mbean>
</server>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean PUBLIC
          "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd">
<mbean>
    <description>The JNDIMap XMBean Example Version 1</description>
    <descriptors>
        <persistence persistPolicy="Never" persistPeriod="10"
            persistLocation="data/JNDIMap.data" persistName="JNDIMap"/>
        <currencyTimeLimit value="10"/>
        <state-action-on-update value="keep-running"/>
    </descriptors>
    <class>org.jboss.test.jmx.xmbean.JNDIMap</class>
    <constructor>
        <description>The default constructor</description>
        <name>JNDIMap</name>
    </constructor> 
    <!-- Attributes -->
    <attribute access="read-write" getMethod="getJndiName" setMethod="setJndiName">
        <description>
            The location in JNDI where the Map we manage will be bound
        </description>
        <name>JndiName</name>
        <type>java.lang.String</type>
        <descriptors>
            <default value="inmemory/maps/MapTest"/>
        </descriptors>
    </attribute>
    <attribute access="read-write" getMethod="getInitialValues"
               setMethod="setInitialValues">
        <description>The array of initial values that will be placed into the
            map associated with the service. The array is a collection of
            key,value pairs with elements[0,2,4,...2n] being the keys and
            elements [1,3,5,...,2n+1] the associated values. The
            "[Ljava.lang.String;" type signature is the VM representation of the
            java.lang.String[] type. </description>
        <name>InitialValues</name>
        <type>[Ljava.lang.String;</type>
        <descriptors>
            <default value="key0,value0"/>
        </descriptors>
    </attribute> 
    <!-- Operations -->
    <operation>
        <description>The start lifecycle operation</description>
        <name>start</name>
    </operation>
    <operation>
        <description>The stop lifecycle operation</description>
        <name>stop</name>
    </operation>
    <operation impact="ACTION">
        <description>Put a value into the map</description>
        <name>put</name>
        <parameter>
            <description>The key the value will be store under</description>
            <name>key</name>
            <type>java.lang.Object</type>
        </parameter>
        <parameter>
            <description>The value to place into the map</description>
            <name>value</name>
            <type>java.lang.Object</type>
        </parameter>
    </operation>
    <operation impact="INFO">
        <description>Get a value from the map</description>
        <name>get</name>
        <parameter>
            <description>The key to lookup in the map</description>
            <name>get</name>
            <type>java.lang.Object</type>
        </parameter>
        <return-type>java.lang.Object</return-type>
    </operation> 
    <!-- Notifications -->
    <notification>
        <description>The notification sent whenever a value is get into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.book.jmx.xmbean.JNDIMap.get</notification-type>
    </notification>
    <notification>
        <description>The notification sent whenever a value is put into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.book.jmx.xmbean.JNDIMap.put</notification-type>
    </notification>
</mbean>

You can build, deploy and test the XMBean as follows:

[examples]$ ant -Dchap=jmx -Dex=xmbean1 run-example
...
run-examplexmbean1:    
     [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIMap Operations: 
     [java]  + void start()
     [java]  + void stop()
     [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap,java.lang.Object 
                        chap2.xmbean:service=JNDIMap)
     [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)
     [java] name=chap2.xmbean:service=JNDIMap
     [java] listener=org.jboss.book.jmx.xmbean.TestXMBean1$Listener@f38cf0
     [java] key=key0, value=value0
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:
            service=JNDIMap][type=org.jboss.book.jmx.xmbean.JNDIMap.put][message=]
     [java] JNDIMap.put(key1, value1) successful
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:
            service=JNDIMap][type=org.jboss.book.jmx.xmbean.JNDIMap.get][message=]
     [java] JNDIMap.get(key0): null
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:
            service=JNDIMap][type=org.jboss.book.jmx.xmbean.JNDIMap.get][message=]
     [java] JNDIMap.get(key1): value1
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:
            service=JNDIMap][type=org.jboss.book.jmx.xmbean.JNDIMap.put][message=]
     [java] handleNotification, event: javax.management.AttributeChangeNotification[source
            =chap2.xmbean:service=JNDIMap][type=jmx.attribute.change][message=InitialValues 
            changed from javax.management.Attribute@82a72a to 
            javax.management.Attribute@acdb96]

The functionality is largely the same as the Standard MBean with the notable exception of the JMX notifications. A Standard MBean has no way of declaring that it will emit notifications. An XMBean may declare the notifications it emits using notification elements as is shown in the version 1 descriptor. We see the notifications from the get and put operations on the test client console output. Note that there is also an jmx.attribute.change notification emitted when the InitialValues attribute was changed. This is because the ModelMBean interface extends the ModelMBeanNotificationBroadcaster which supports AttributeChangeNotificationListeners.

The other major difference between the Standard and XMBean versions of JNDIMap is the descriptive metadata. Look at the chap2.xmbean:service=JNDIMap in the JMX Console, and you will see the attributes section as shown in Figure 3.18, “The Version 1 JNDIMapXMBean jmx-console view”.


Notice that the JMX Console now displays the full attribute description as specified in the XMBean descriptor rather than MBean Attribute text seen in standard MBean implementations. Scroll down to the operations and you will also see that these now also have nice descriptions of their function and parameters.

In version 2 of the XMBean we add support for persistence of the XMBean attributes. The updated XMBean deployment descriptor is given below.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean PUBLIC
          "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd">
<mbean>
    <description>The JNDIMap XMBean Example Version 2</description>
    <descriptors>
        <persistence persistPolicy="OnUpdate" persistPeriod="10"
            persistLocation="${jboss.server.data.dir}" persistName="JNDIMap.ser"/>
        <currencyTimeLimit value="10"/>
        <state-action-on-update value="keep-running"/>
        <persistence-manager value="org.jboss.mx.persistence.ObjectStreamPersistenceManager"/>
    </descriptors>  <class>org.jboss.test.jmx.xmbean.JNDIMap</class>
    <constructor>
        <description>The default constructor</description>
        <name>JNDIMap</name>
    </constructor> 
    <!-- Attributes -->
    <attribute access="read-write" getMethod="getJndiName" setMethod="setJndiName">
        <description>
            The location in JNDI where the Map we manage will be bound
        </description>
        <name>JndiName</name>
        <type>java.lang.String</type>
        <descriptors>
            <default value="inmemory/maps/MapTest"/>
        </descriptors>
    </attribute>
    <attribute access="read-write" getMethod="getInitialValues"
               setMethod="setInitialValues">
        <description>The array of initial values that will be placed into the
            map associated with the service. The array is a collection of
            key,value pairs with elements[0,2,4,...2n] being the keys and
            elements [1,3,5,...,2n+1] the associated values</description>
        <name>InitialValues</name>
        <type>[Ljava.lang.String;</type>
        <descriptors>
            <default value="key0,value0"/>
        </descriptors>
    </attribute> 
    <!-- Operations -->
    <operation>
        <description>The start lifecycle operation</description>
        <name>start</name>
    </operation>
    <operation>
        <description>The stop lifecycle operation</description>
        <name>stop</name>
    </operation>
    <operation impact="ACTION">
        <description>Put a value into the nap</description>
        <name>put</name>
        <parameter>
            <description>The key the value will be store under</description>
            <name>key</name>
            <type>java.lang.Object</type>
        </parameter>
        <parameter>
            <description>The value to place into the map</description>
            <name>value</name>
            <type>java.lang.Object</type>
        </parameter>
    </operation>
    <operation impact="INFO">
        <description>Get a value from the map</description>
        <name>get</name>
        <parameter>
            <description>The key to lookup in the map</description>
            <name>get</name>
            <type>java.lang.Object</type>
        </parameter>
        <return-type>java.lang.Object</return-type>
    </operation> 
    <!-- Notifications -->
    <notification>
        <description>The notification sent whenever a value is get into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.book.jmx.xmbean.JNDIMap.get</notification-type>
    </notification>
    <notification>
        <description>The notification sent whenever a value is put into the map
            managed by the service</description>
        <name>javax.management.Notification</name>
        <notification-type>org.jboss.book.jmx.xmbean.JNDIMap.put</notification-type>
    </notification>
</mbean>

Build, deploy and test the version 2 XMBean as follows:

[examples]$ ant -Dchap=jmx -Dex=xmbean2 -Djboss.deploy.conf=rmi-adaptor run-example
...
run-examplexmbean2:
     [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIMap Operations: 
     [java]  + void start()
     [java]  + void stop()
     [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap,java.lang.Object cha
p2.xmbean:service=JNDIMap)
     [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)
     [java]  + java.lang.String getJndiName()
     [java]  + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap)
     [java]  + [Ljava.lang.String; getInitialValues()
     [java]  + void setInitialValues([Ljava.lang.String; chap2.xmbean:service=JNDIMap)
     [java] handleNotification, event: null
     [java] key=key10, value=value10
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=7,timeStamp=10986326
93716,message=null,userData=null]
     [java] JNDIMap.put(key1, value1) successful
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=8,timeStamp=10986326
93857,message=null,userData=null]
     [java] JNDIMap.get(key0): null
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=9,timeStamp=10986326
93896,message=null,userData=null]
     [java] JNDIMap.get(key1): value1
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=10,timeStamp=1098632
693925,message=null,userData=null]

There is nothing manifestly different about this version of the XMBean at this point because we have done nothing to test that changes to attribute value are actually persisted. Perform this test by running example xmbean2a several times:

[examples] ant -Dchap=jmx -Dex=xmbean2a run-example
...
     [java] InitialValues.length=2
     [java] key=key10, value=value10

[examples] ant -Dchap=jmx -Dex=xmbean2a run-example
...
     [java] InitialValues.length=4
     [java] key=key10, value=value10
     [java] key=key2, value=value2

[examples] ant -Dchap=jmx -Dex=xmbean2a run-example
...
     [java] InitialValues.length=6
     [java] key=key10, value=value10
     [java] key=key2, value=value2
     [java] key=key3, value=value3

The org.jboss.book.jmx.xmbean.TestXMBeanRestart used in this example obtains the current InitialValues attribute setting, and then adds another key/value pair to it. The client code is shown below.

package org.jboss.book.jmx.xmbean;

import javax.management.Attribute;
import javax.management.ObjectName;
import javax.naming.InitialContext;

import org.jboss.jmx.adaptor.rmi.RMIAdaptor;

/**
 *  A client that demonstrates the persistence of the xmbean
 *  attributes. Every time it run it looks up the InitialValues
 *  attribute, prints it out and then adds a new key/value to the
 *  list.
 *  
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.9 $
 */
public class TestXMBeanRestart
{
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception
    {
	InitialContext ic = new InitialContext();
	RMIAdaptor server = (RMIAdaptor) ic.lookup("jmx/rmi/RMIAdaptor");
	
	// Get the InitialValues attribute
	ObjectName name = new ObjectName("j2eechap2.xmbean:service=JNDIMap");
	String[] initialValues = (String[])
	    server.getAttribute(name, "InitialValues");
	System.out.println("InitialValues.length="+initialValues.length);
	int length = initialValues.length;
	for (int n = 0; n < length; n += 2) {
	    String key = initialValues[n];
	    String value = initialValues[n+1];
	    
	    System.out.println("key="+key+", value="+value);
	}
	// Add a new key/value pair
	String[] newInitialValues = new String[length+2];
	System.arraycopy(initialValues, 0, newInitialValues,
			 0, length);
	newInitialValues[length] = "key"+(length/2+1);
	newInitialValues[length+1] = "value"+(length/2+1);
	
	Attribute ivalues = new
	    Attribute("InitialValues", newInitialValues);
	server.setAttribute(name, ivalues);
    }
}

At this point you may even shutdown the JBoss server, restart it and then rerun the initial example to see if the changes are persisted across server restarts:

[examples]$ ant -Dchap=jmx -Dex=xmbean2 run-example
...

run-examplexmbean2:
     [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIMap Operations: 
     [java]  + void start()
     [java]  + void stop()
     [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap,java.lang.Object cha
p2.xmbean:service=JNDIMap)
     [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)
     [java]  + java.lang.String getJndiName()
     [java]  + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap)
     [java]  + [Ljava.lang.String; getInitialValues()
     [java]  + void setInitialValues([Ljava.lang.String; chap2.xmbean:service=JNDIMap)
     [java] handleNotification, event: null
     [java] 
                                 key=key10, value=value10
                              
     [java] 
                                 key=key2, value=value2
                              
     [java] 
                                 key=key3, value=value3
                              
     [java] 
                                 key=key4, value=value4
                              
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.book.jmx.xmbean.JNDIMap.put,sequenceNumber=3,timeStamp=10986
33664712,message=null,userData=null]
     [java] JNDIMap.put(key1, value1) successful
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.book.jmx.xmbean.JNDIMap.get,sequenceNumber=4,timeStamp=10986
33664821,message=null,userData=null]
     [java] JNDIMap.get(key0): null
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.book.jmx.xmbean.JNDIMap.get,sequenceNumber=5,timeStamp=10986
33664860,message=null,userData=null]
     [java] JNDIMap.get(key1): value1
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.book.jmx.xmbean.JNDIMap.put,sequenceNumber=6,timeStamp=10986
33664877,message=null,userData=null]
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.book.jmx.xmbean.JNDIMap.put,sequenceNumber=7,timeStamp=10986
33664895,message=null,userData=null]
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.book.jmx.xmbean.JNDIMap.put,sequenceNumber=8,timeStamp=10986
33664899,message=null,userData=null]
     [java] handleNotification, event: javax.management.Notification[source=chap2.xmbean:s
ervice=JNDIMap,type=org.jboss.book.jmx.xmbean.JNDIMap.put,sequenceNumber=9,timeStamp=10986
33665614,message=null,userData=null]   

You see that the last InitialValues attribute setting is in fact visible.

We have seen how to manage dependencies using the service descriptor depends and depends-list tags. The deployment ordering supported by the deployment scanners provides a coarse-grained dependency management in that there is an order to deployments. If dependencies are consistent with the deployment packages then this is a simpler mechanism than having to enumerate the explicit MBean-MBean dependencies. By writing your own filters you can change the coarse grained ordering performed by the deployment scanner.

When a component archive is deployed, its nested deployment units are processed in a depth first ordering. Structuring of components into an archive hierarchy is yet another way to manage deployment ordering.You will need to explicitly state your MBean dependencies if your packaging structure does not happen to resolve the dependencies. Let's consider an example component deployment that consists of an MBean that uses an EJB. Here is the structure of the example EAR.

                        
                           output/jmx/jmx-ex3.ear
                        
+- META-INF/MANIFEST.MF
+- META-INF/jboss-app.xml
+- jmx-ex3.jar (archive) [EJB jar]
| +- META-INF/MANIFEST.MF
| +- META-INF/ejb-jar.xml
| +- org/jboss/book/jmx/ex3/EchoBean.class
| +- org/jboss/book/jmx/ex3/EchoLocal.class
| +- org/jboss/book/jmx/ex3/EchoLocalHome.class
+- jmx-ex3.sar (archive) [MBean sar]
| +- META-INF/MANIFEST.MF
| +- META-INF/jboss-service.xml
| +- org/jboss/book/jmx/ex3/EjbMBeanAdaptor.class
+- META-INF/application.xml

The EAR contains a jmx-ex3.jar and jmx-ex3.sar. The jmx-ex3.jar is the EJB archive and the jmx-ex3.sar is the MBean service archive. We have implemented the service as a Dynamic MBean to provide an illustration of their use.

package org.jboss.book.jmx.ex3;
            
import java.lang.reflect.Method;
import javax.ejb.CreateException;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.JMRuntimeException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.jboss.system.ServiceMBeanSupport;

/** 
 *  An example of a DynamicMBean that exposes select attributes and
 *  operations of an EJB as an MBean.
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.9 $
 */
public class EjbMBeanAdaptor extends ServiceMBeanSupport
    implements DynamicMBean
{
    private String helloPrefix;
    private String ejbJndiName;
    private EchoLocalHome home;
    
    /** These are the mbean attributes we expose
     */
    private MBeanAttributeInfo[] attributes = {
        new MBeanAttributeInfo("HelloPrefix", "java.lang.String",
                               "The prefix message to append to the session echo reply",
                               true, // isReadable
                               true, // isWritable
                               false), // isIs
        new MBeanAttributeInfo("EjbJndiName", "java.lang.String",
                               "The JNDI name of the session bean local home",
                               true, // isReadable
                               true, // isWritable
                               false) // isIs
    };

    /** 
     * These are the mbean operations we expose
     */
    private MBeanOperationInfo[] operations;
    
    /** 
     * We override this method to setup our echo operation info. It
     * could also be done in a ctor.
     */
    public ObjectName preRegister(MBeanServer server,
                                  ObjectName name)
        throws Exception
    {
        log.info("preRegister notification seen");
        
        operations = new MBeanOperationInfo[5];
        
        Class thisClass = getClass();
        Class[] parameterTypes = {String.class};
        Method echoMethod =
            thisClass.getMethod("echo", parameterTypes);
        String desc = "The echo op invokes the session bean echo method and"
            + " returns its value prefixed with the helloPrefix attribute value";
        operations[0] = new MBeanOperationInfo(desc, echoMethod);
            
        // Add the Service interface operations from our super class
        parameterTypes = new Class[0];
        Method createMethod =
            thisClass.getMethod("create", parameterTypes);
        operations[1] = new MBeanOperationInfo("The
                JBoss Service.create", createMethod);
        Method startMethod =
            thisClass.getMethod("start", parameterTypes);
        operations[2] = new MBeanOperationInfo("The
                JBoss Service.start", startMethod);
        Method stopMethod =
            thisClass.getMethod("stop", parameterTypes);
        operations[3] = new MBeanOperationInfo("The
                JBoss Service.stop", startMethod);
        Method destroyMethod =
            thisClass.getMethod("destroy", parameterTypes);
        operations[4] = new MBeanOperationInfo("The
                JBoss Service.destroy", startMethod);
        return name;
    }
    
    
    // --- Begin ServiceMBeanSupport overides
    protected void createService() throws Exception
    {
        log.info("Notified of create state");
    }

    protected void startService() throws Exception
    {
        log.info("Notified of start state");
        InitialContext ctx = new InitialContext();
        home = (EchoLocalHome) ctx.lookup(ejbJndiName);
    }

    protected void stopService()
    {
        log.info("Notified of stop state");
    }

    // --- End ServiceMBeanSupport overides
            
    public String getHelloPrefix()
    {
        return helloPrefix;
    }
    public void setHelloPrefix(String helloPrefix)
    {
        this.helloPrefix = helloPrefix;
    }
    
    public String getEjbJndiName()
    {
        return ejbJndiName;
    }
    public void setEjbJndiName(String ejbJndiName)
    {
        this.ejbJndiName = ejbJndiName;
    }
    
    public String echo(String arg)
        throws CreateException, NamingException
    {
        log.debug("Lookup EchoLocalHome@"+ejbJndiName);
        EchoLocal bean = home.create();
        String echo = helloPrefix + bean.echo(arg);
        return echo;
    }
    
    // --- Begin DynamicMBean interface methods
    /** 
     *  Returns the management interface that describes this dynamic
     *  resource.  It is the responsibility of the implementation to
     *  make sure the description is accurate.
     *
     * @return the management interface descriptor.
     */
    public MBeanInfo getMBeanInfo()
    {
        String classname = getClass().getName();
        String description = "This is an MBean that uses a session bean in the"
            + " implementation of its echo operation.";
        MBeanInfo[] constructors = null;
        MBeanNotificationInfo[] notifications = null;
        MBeanInfo mbeanInfo = new MBeanInfo(classname,
                                            description, attributes,
                                            constructors, operations,
                                            notifications);
        // Log when this is called so we know when in the
        lifecycle this is used
            Throwable trace = new Throwable("getMBeanInfo trace");
        log.info("Don't panic, just a stack
                trace", trace);
        return mbeanInfo;
    }
    
    /** 
     *  Returns the value of the attribute with the name matching the
     *  passed string.
     *
     * @param attribute the name of the attribute.
     * @return the value of the attribute.
     * @exception AttributeNotFoundException when there is no such
     * attribute.
     * @exception MBeanException wraps any error thrown by the
     * resource when
     * getting the attribute.
     * @exception ReflectionException wraps any error invoking the
     * resource.
     */
    public Object getAttribute(String attribute)
        throws AttributeNotFoundException, 
               MBeanException, 
               ReflectionException
    {
        Object value = null;
        if (attribute.equals("HelloPrefix")) {
            value = getHelloPrefix();
        } else if(attribute.equals("EjbJndiName")) {
            value = getEjbJndiName();
        } else {
            throw new AttributeNotFoundException("Unknown
                attribute("+attribute+") requested");
        }
        return value;
    }
            
    /** 
     * Returns the values of the attributes with names matching the
     * passed string array.
     *
     * @param attributes the names of the attribute.
     * @return an {@link AttributeList AttributeList} of name
     * and value pairs.
     */
    public AttributeList getAttributes(String[] attributes)
    {
        AttributeList values = new AttributeList();
        for (int a = 0; a < attributes.length; a++) {
            String name = attributes[a];
            try {
                Object value = getAttribute(name);
                Attribute attr = new Attribute(name, value);
                values.add(attr);
            } catch(Exception e) {
                log.error("Failed to find attribute: "+name, e);
            }
        }
        return values;
    }
            
    /**
     *  Sets the value of an attribute. The attribute and new value
     *  are passed in the name value pair {@link Attribute
     *  Attribute}.
     *
     * @see javax.management.Attribute
     *
     * @param attribute the name and new value of the attribute.
     * @exception AttributeNotFoundException when there is no such
     * attribute.
     * @exception InvalidAttributeValueException when the new value
     * cannot be converted to the type of the attribute.
     * @exception MBeanException wraps any error thrown by the
     * resource when setting the new value.
     * @exception ReflectionException wraps any error invoking the
     * resource.
     */
    public void setAttribute(Attribute attribute)
        throws AttributeNotFoundException, 
               InvalidAttributeValueException,
               MBeanException, 
               ReflectionException
    {
        String name = attribute.getName();
        if (name.equals("HelloPrefix")) { 
            String value = attribute.getValue().toString();
            setHelloPrefix(value);
        } else if(name.equals("EjbJndiName")) {
            String value = attribute.getValue().toString();
            setEjbJndiName(value);
        } else {
            throw new AttributeNotFoundException("Unknown attribute("+name+") requested");
        }
    }
            
    /**
     * Sets the values of the attributes passed as an
     * {@link AttributeList AttributeList} of name and new
     * value pairs.
     *
     * @param attributes the name an new value pairs.
     * @return an {@link AttributeList AttributeList} of name and
     * value pairs that were actually set.
     */
    public AttributeList setAttributes(AttributeList attributes)
    {
        AttributeList setAttributes = new AttributeList();
        for(int a = 0; a < attributes.size(); a++) {
            Attribute attr = (Attribute) attributes.get(a);
            try {
                setAttribute(attr);
                setAttributes.add(attr);
            } catch(Exception ignore) {
            }
        }
        return setAttributes;
    }
    
    /**
     *  Invokes a resource operation.
     *
     *  @param actionName the name of the operation to perform.
     *  @param params the parameters to pass to the operation.
     *  @param signature the signartures of the parameters.
     *  @return the result of the operation.
     *  @exception MBeanException wraps any error thrown by the
     *  resource when performing the operation.
     *  @exception ReflectionException wraps any error invoking the
     *  resource.
     */
    public Object invoke(String actionName, Object[] params,
                         String[] signature)
        throws MBeanException,
               ReflectionException
    {
        Object rtnValue = null;
        log.debug("Begin invoke, actionName="+actionName);
        try {
            if (actionName.equals("echo")) {
                String arg = (String) params[0];
                rtnValue = echo(arg);
                log.debug("Result: "+rtnValue);
            } else if (actionName.equals("create")) {
                super.create();
            } else if (actionName.equals("start")) {
                super.start();
            } else if (actionName.equals("stop")) {
                super.stop();
            } else if (actionName.equals("destroy")) {
                super.destroy();
            } else {
                throw new JMRuntimeException("Invalid state,
                don't know about op="+actionName);
            }
        } catch(Exception e) {
            throw new ReflectionException(e, "echo failed");
        }


        log.debug("End invoke, actionName="+actionName);
        return rtnValue;
    }
    
    // --- End DynamicMBean interface methods
    
}

Believe it or not, this is a very trivial MBean. The vast majority of the code is there to provide the MBean metadata and handle the callbacks from the MBean Server. This is required because a Dynamic MBean is free to expose whatever management interface it wants. A Dynamic MBean can in fact change its management interface at runtime simply by returning different metadata from the getMBeanInfo method. Of course, some clients may not be happy with such a dynamic object, but the MBean Server will do nothing to prevent a Dynamic MBean from changing its interface.

There are two points to this example. First, demonstrate how an MBean can depend on an EJB for some of its functionality and second, how to create MBeans with dynamic management interfaces. If we were to write a standard MBean with a static interface for this example it would look like the following.

public interface EjbMBeanAdaptorMBean
{
    public String getHelloPrefix();
    public void setHelloPrefix(String prefix);
    public String getEjbJndiName();
    public void setEjbJndiName(String jndiName);
    public String echo(String arg) throws CreateException, NamingException;
    public void create() throws Exception;
    public void start() throws Exception;
    public void stop();
    public void destroy();
} 

Moving to lines 67-83, this is where the MBean operation metadata is constructed. The echo(String), create(), start(), stop() and destroy() operations are defined by obtaining the corresponding java.lang.reflect.Method object and adding a description. Let's go through the code and discuss where this interface implementation exists and how the MBean uses the EJB. Beginning with lines 40-51, the two MBeanAttributeInfo instances created define the attributes of the MBean. These attributes correspond to the getHelloPrefix/setHelloPrefix and getEjbJndiName/setEjbJndiName of the static interface. One thing to note in terms of why one might want to use a Dynamic MBean is that you have the ability to associate descriptive text with the attribute metadata. This is not something you can do with a static interface.

Lines 88-103 correspond to the JBoss service life cycle callbacks. Since we are subclassing the ServiceMBeanSupport utility class, we override the createService, startService, and stopService template callbacks rather than the create, start, and stop methods of the service interface. Note that we cannot attempt to lookup the EchoLocalHome interface of the EJB we make use of until the startService method. Any attempt to access the home interface in an earlier life cycle method would result in the name not being found in JNDI because the EJB container had not gotten to the point of binding the home interfaces. Because of this dependency we will need to specify that the MBean service depends on the EchoLocal EJB container to ensure that the service is not started before the EJB container is started. We will see this dependency specification when we look at the service descriptor.

Lines 105-121 are the HelloPrefix and EjbJndiName attribute accessors implementations. These are invoked in response to getAttribute/setAttribute invocations made through the MBean Server.

Lines 123-130 correspond to the echo(String) operation implementation. This method invokes the EchoLocal.echo(String) EJB method. The local bean interface is created using the EchoLocalHome that was obtained in the startService method.

The remainder of the class makes up the Dynamic MBean interface implementation. Lines 133-152 correspond to the MBean metadata accessor callback. This method returns a description of the MBean management interface in the form of the javax.management.MBeanInfo object. This is made up of a description, the MBeanAttributeInfo and MBeanOperationInfo metadata created earlier, as well as constructor and notification information. This MBean does not need any special constructors or notifications so this information is null.

Lines 154-258 handle the attribute access requests. This is rather tedious and error prone code so a toolkit or infrastructure that helps generate these methods should be used. A Model MBean framework based on XML called XBeans is currently being investigated in JBoss. Other than this, no other Dynamic MBean frameworks currently exist.

Lines 260-310 correspond to the operation invocation dispatch entry point. Here the request operation action name is checked against those the MBean handles and the appropriate method is invoked.

The jboss-service.xml descriptor for the MBean is given below. The dependency on the EJB container MBean is highlighted in bold. The format of the EJB container MBean ObjectName is: "jboss.j2ee:service=EJB,jndiName=" + <home-jndi-name> where the <home-jndi-name> is the EJB home interface JNDI name.

<server>
    <mbean code="org.jboss.book.jmx.ex3.EjbMBeanAdaptor"
           name="jboss.book:service=EjbMBeanAdaptor">
        <attribute name="HelloPrefix">AdaptorPrefix</attribute>
        <attribute name="EjbJndiName">local/j2ee_chap2.EchoBean</attribute>
        <depends>jboss.j2ee:service=EJB,jndiName=local/j2ee_chap2.EchoBean</depends>
    </mbean>
</server>    

Deploy the example ear by running:

[examples]$ ant -Dchap=jmx -Dex=3 run-example

On the server console there will be messages similar to the following:

14:57:12,906 INFO  [EARDeployer] Init J2EE application: file:/private/tmp/jboss-5.0.0/server/
	production/deploy/j2ee_chap2-ex3.ear
14:57:13,044 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
...
14:57:13,088 INFO  [EjbMBeanAdaptor] preRegister notification seen
14:57:13,093 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
...
14:57:13,117 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
...        
14:57:13,140 WARN  [EjbMBeanAdaptor] Unexcepted error accessing MBeanInfo for null
java.lang.NullPointerException
  at org.jboss.system.ServiceMBeanSupport.postRegister(ServiceMBeanSupport.java:418)
...
14:57:13,203 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
... 
14:57:13,232 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
...
14:57:13,420 INFO  [EjbModule] Deploying Chap2EchoInfoBean
14:57:13,443 INFO  [EjbModule] Deploying chap2.EchoBean
14:57:13,488 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
...
14:57:13,542 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
...
14:57:13,558 INFO  [EjbMBeanAdaptor] Begin invoke, actionName=create
14:57:13,560 INFO  [EjbMBeanAdaptor] Notified of create state
14:57:13,562 INFO  [EjbMBeanAdaptor] End invoke, actionName=create
14:57:13,604 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
... 
14:57:13,621 INFO  [EjbMBeanAdaptor] Don't panic, just a stack trace
java.lang.Throwable: getMBeanInfo trace
  at org.jboss.book.jmx.ex3.EjbMBeanAdaptor.getMBeanInfo(EjbMBeanAdaptor.java:153)
14:57:13,641 INFO  [EjbMBeanAdaptor] Begin invoke, actionName=getState
14:57:13,942 INFO  [EjbMBeanAdaptor] Begin invoke, actionName=start
14:57:13,944 INFO  [EjbMBeanAdaptor] Notified of start state
14:57:13,951 INFO  [EjbMBeanAdaptor] Testing Echo
14:57:13,983 INFO  [EchoBean] echo, info=echo info, arg=, arg=startService
14:57:13,986 INFO  [EjbMBeanAdaptor] echo(startService) = startService
14:57:13,988 INFO  [EjbMBeanAdaptor] End invoke, actionName=start
14:57:13,991 INFO  [EJBDeployer] Deployed: file:/tmp/jboss-5.0.0.GA/server/default/tmp/deploy
/tmp60550jmx-ex3.ear-contents/jmx-ex3.jar                    
14:57:14,075 INFO  [EARDeployer] Started J2EE application: ...

The stack traces are not exceptions. They are traces coming from the EjbMBeanAdaptor code to demonstrate that clients ask for the MBean interface when they want to discover the MBean's capabilities. Notice that the EJB container (lines with [EjbModule]) is started before the example MBean (lines with [EjbMBeanAdaptor]).

Now, let's invoke the echo method using the JMX console web application. Go to the JMX Console (http://localhost:8080/jmx-console) and find the service=EjbMBeanAdaptor in the jboss.book domain. Click on the link and scroll down to the echo operation section. The view should be like that shown in Figure 3.19, “The EjbMBeanAdaptor MBean operations JMX console view”.


As shown, we have already entered an argument string of -echo-arg into the ParamValue text field. Press the Invoke button and a result string of AdaptorPrefix-echo-arg is displayed on the results page. The server console will show several stack traces from the various metadata queries issues by the JMX console and the MBean invoke method debugging lines:

10:51:48,671 INFO [EjbMBeanAdaptor] Begin invoke, actionName=echo
10:51:48,671 INFO [EjbMBeanAdaptor] Lookup EchoLocalHome@local/j2ee_chap2.EchoBean
10:51:48,687 INFO [EchoBean] echo, info=echo info, arg=, arg=-echo-arg
10:51:48,687 INFO [EjbMBeanAdaptor] Result: AdaptorPrefix-echo-arg
10:51:48,687 INFO [EjbMBeanAdaptor] End invoke, actionName=echo

JBoss has an extensible deployment architecture that allows one to incorporate components into the bare JBoss JMX microkernel. The MainDeployer is the deployment entry point. Requests to deploy a component are sent to the MainDeployer and it determines if there is a subdeployer capable of handling the deployment, and if there is, it delegates the deployment to the subdeployer. We saw an example of this when we looked at how the MainDeployer used the SARDeployer to deploy MBean services. Among the deployers provided with JBoss are:

  • AbstractWebDeployer : This subdeployer handles web application archives (WARs). It accepts deployment archives and directories whose name ends with a war suffix. WARs must have a WEB-INF/web.xml descriptor and may have a WEB-INF/jboss-web.xml descriptor.

  • EARDeployer : This subdeployer handles enterprise application archives (EARs). It accepts deployment archives and directories whose name ends with an ear suffix. EARs must have a META-INF/application.xml descriptor and may have a META-INF/jboss-app.xml descriptor.

  • EJBDeployer : This subdeployer handles enterprise bean jars. It accepts deployment archives and directories whose name ends with a jar suffix. EJB jars must have a META-INF/ejb-jar.xml descriptor and may have a META-INF/jboss.xml descriptor.

  • JARDeployer : This subdeployer handles library JAR archives. The only restriction it places on an archive is that it cannot contain a WEB-INF directory.

  • RARDeployer : This subdeployer handles JCA resource archives (RARs). It accepts deployment archives and directories whose name ends with a rar suffix. RARs must have a META-INF/ra.xml descriptor.

  • SARDeployer : This subdeployer handles JBoss MBean service archives (SARs). It accepts deployment archives and directories whose name ends with a sar suffix, as well as standalone XML files that end with service.xml. SARs that are jars must have a META-INF/jboss-service.xml descriptor.

  • XSLSubDeployer : This subdeployer deploys arbitrary XML files. JBoss uses the XSLSubDeployer to deploy ds.xml files and transform them into service.xml files for the SARDeployer. However, it is not limited to just this task.

  • HARDeployer : This subdeployer deploys hibernate archives (HARs). It accepts deployment archives and directories whose name ends with a har suffix. HARs must have a META-INF/hibernate-service.xml descriptor.

  • AspectDeployer : This subdeployer deploys AOP archives. It accepts deployment archives and directories whose name ends with an aop suffix as well as aop.xml files. AOP archives must have a META-INF/jboss-aop.xml descriptor.

  • ClientDeployer : This subdeployer deploys J2EE application clients. It accepts deployment archives and directories whose name ends with a jar suffix. J2EE clients must have a META-INF/application-client.xml descriptor and may have a META-INF/jboss-client.xml descriptor.

  • BeanShellSubDeployer : This subdeployer deploys bean shell scripts as MBeans. It accepts files whose name ends with a bsh suffix.

The MainDeployer, JARDeployer and SARDeployer are hard coded deployers in the JBoss server core. All other deployers are MBean services that register themselves as deployers with the MainDeployer using the addDeployer(SubDeployer) operation.

The MainDeployer communicates information about the component to be deployed the SubDeployer using a DeploymentInfo object. The DeploymentInfo object is a data structure that encapsulates the complete state of a deployable component.

When the MainDeployer receives a deployment request, it iterates through its registered subdeployers and invokes the accepts(DeploymentInfo) method on the subdeployer. The first subdeployer to return true is chosen. The MainDeployer will delegate the init, create, start, stop and destroy deployment life cycle operations to the subdeployer.

Deployers are the mechanism by which components are brought into a JBoss server. Deployers are also the creators of the majority of UCL instances, and the primary creator is the MainDeployer. The MainDeployer creates the UCL for a deployment early on during its init method. The UCL is created by calling the DeploymentInfo.createClassLoaders() method. Only the topmost DeploymentInfo will actually create a UCL. All subdeployments will add their class paths to their parent DeploymentInfo UCL. Every deployment does have a standalone URLClassLoader that uses the deployment URL as its path. This is used to localize the loading of resources such as deployment descriptors. Figure 3.20, “An illustration of the class loaders involved with an EAR deployment” provides an illustration of the interaction between Deployers, DeploymentInfos and class loaders.


The figure illustrates an EAR deployment with EJB and WAR subdeployments. The EJB deployment references the lib/util.jar utility jar via its manifest. The WAR includes classes in its WEB-INF/classes directory as well as the WEB-INF/lib/jbosstest-web-util.jar. Each deployment has a DeploymentInfo instance that has a URLClassLoader pointing to the deployment archive. The DeploymentInfo associated with some.ear is the only one to have a UCL created. The ejbs.jar and web.war DeploymentInfos add their deployment archive to the some.ear UCL classpath, and share this UCL as their deployment UCL. The EJBDeployer also adds any manifest jars to the EAR UCL.

The WARDeployer behaves differently than other deployers in that it only adds its WAR archive to the DeploymentInfo UCL classpath. The loading of classes from the WAR WEB-INF/classes and WEB-INF/lib locations is handled by the servlet container class loader. The servlet container class loaders delegate to the WAR DeploymentInfo UCL as their parent class loader, but the server container class loader is not part of the JBoss class loader repository. Therefore, classes inside of a WAR are not visible to other components. Classes that need to be shared between web application components and other components such as EJBs, and MBeans need to be loaded into the shared class loader repository either by including the classes into a SAR or EJB deployment, or by referencing a jar containing the shared classes through a manifest Class-Path entry. In the case of a SAR, the SAR classpath element in the service deployment serves the same purpose as a JAR manifest Class-Path.

In addition to the MBean services notion that allows for the ability to integrate arbitrary functionality, JBoss also has a detached invoker concept that allows MBean services to expose functional interfaces via arbitrary protocols for remote access by clients. The notion of a detached invoker is that remoting and the protocol by which a service is accessed is a functional aspect or service independent of the component. Thus, one can make a naming service available for use via RMI/JRMP, RMI/HTTP, RMI/SOAP, or any arbitrary custom transport.

Let's begin our discussion of the detached invoker architecture with an overview of the components involved. The main components in the detached invoker architecture are shown in Figure 3.21, “The main components in the detached invoker architecture”.


On the client side, there exists a client proxy which exposes the interface(s) of the MBean service. This is the same smart, compile-less dynamic proxy that we use for EJB home and remote interfaces. The only difference between the proxy for an arbitrary service and the EJB is the set of interfaces exposed as well as the client side interceptors found inside the proxy. The client interceptors are represented by the rectangles found inside of the client proxy. An interceptor is an assembly line type of pattern that allows for transformation of a method invocation and/or return values. A client obtains a proxy through some lookup mechanism, typically JNDI. Although RMI is indicated in Figure 3.21, “The main components in the detached invoker architecture”, the only real requirement on the exposed interface and its types is that they are serializable between the client server over JNDI as well as the transport layer.

The choice of the transport layer is determined by the last interceptor in the client proxy, which is referred to as the Invoker Interceptor in Figure 3.21, “The main components in the detached invoker architecture”. The invoker interceptor contains a reference to the transport specific stub of the server side Detached Invoker MBean service. The invoker interceptor also handles the optimization of calls that occur within the same VM as the target MBean. When the invoker interceptor detects that this is the case the call is passed to a call-by-reference invoker that simply passes the invocation along to the target MBean.

The detached invoker service is responsible for making a generic invoke operation available via the transport the detached invoker handles. The Invoker interface illustrates the generic invoke operation.

package org.jboss.invocation;
            
import java.rmi.Remote;
import org.jboss.proxy.Interceptor;
import org.jboss.util.id.GUID;
            
            
public interface Invoker
    extends Remote
{
    GUID ID = new GUID();

    String getServerHostName() throws Exception;

    Object invoke(Invocation invocation) throws Exception;
}

The Invoker interface extends Remote to be compatible with RMI, but this does not mean that an invoker must expose an RMI service stub. The detached invoker service simply acts as a transport gateway that accepts invocations represented as the org.jboss.invocation.Invocation object over its specific transport, unmarshalls the invocation, forwards the invocation onto the destination MBean service, represented by the Target MBean in Figure 3.21, “The main components in the detached invoker architecture”, and marshalls the return value or exception resulting from the forwarded call back to the client.

The Invocation object is just a representation of a method invocation context. This includes the target MBean name, the method, the method arguments, a context of information associated with the proxy by the proxy factory, and an arbitrary map of data associated with the invocation by the client proxy interceptors.

The configuration of the client proxy is done by the server side proxy factory MBean service, indicated by the Proxy Factory component in Figure 3.21, “The main components in the detached invoker architecture”. The proxy factory performs the following tasks:

  • Create a dynamic proxy that implements the interface the target MBean wishes to expose.

  • Associate the client proxy interceptors with the dynamic proxy handler.

  • Associate the invocation context with the dynamic proxy. This includes the target MBean, detached invoker stub and the proxy JNDI name.

  • Make the proxy available to clients by binding the proxy into JNDI.

The last component in Figure 3.21, “The main components in the detached invoker architecture” is the Target MBean service that wishes to expose an interface for invocations to remote clients. The steps required for an MBean service to be accessible through a given interface are:

  • Define a JMX operation matching the signature: public Object invoke(org.jboss.invocation.Invocation) throws Exception

  • Create a HashMap<Long, Method> mapping from the exposed interface java.lang.reflect.Methods to the long hash representation using the org.jboss.invocation.MarshalledInvocation.calculateHash method.

  • Implement the invoke(Invocation) JMX operation and use the interface method hash mapping to transform from the long hash representation of the invoked method to the java.lang.reflect.Method of the exposed interface. Reflection is used to perform the actual invocation on the object associated with the MBean service that actually implements the exposed interface.

In the section on connecting to the JMX server we mentioned that there was a service that allows one to access the javax.management.MBeanServer via any protocol using an invoker service. In this section we present the org.jboss.jmx.connector.invoker.InvokerAdaptorService and its configuration for access via RMI/JRMP as an example of the steps required to provide remote access to an MBean service.

The InvokerAdaptorService is a simple MBean service that only exists to fulfill the target MBean role in the detached invoker pattern.

package org.jboss.jmx.connector.invoker;
public interface InvokerAdaptorServiceMBean
    extends org.jboss.system.ServiceMBean
{
    Class getExportedInterface();
    void setExportedInterface(Class exportedInterface);

    Object invoke(org.jboss.invocation.Invocation invocation)
        throws Exception;
}

package org.jboss.jmx.connector.invoker;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.jboss.invocation.Invocation;
import org.jboss.invocation.MarshalledInvocation;
import org.jboss.mx.server.ServerConstants;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.system.Registry;

public class InvokerAdaptorService
    extends ServiceMBeanSupport
    implements InvokerAdaptorServiceMBean, ServerConstants
{
    private static ObjectName mbeanRegistry;
    
    static {
        try {
            mbeanRegistry = new ObjectName(MBEAN_REGISTRY);
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
    }

    private Map marshalledInvocationMapping = new HashMap();
    private Class exportedInterface;

    public Class getExportedInterface()
    {
        return exportedInterface;
    }

    public void setExportedInterface(Class exportedInterface)
    {
        this.exportedInterface = exportedInterface;
    }

    protected void startService()
        throws Exception
    {
        // Build the interface method map
        Method[] methods = exportedInterface.getMethods();
        HashMap tmpMap = new HashMap(methods.length);
        for (int m = 0; m < methods.length; m ++) {
            Method method = methods[m];
            Long hash = new Long(MarshalledInvocation.calculateHash(method));
            tmpMap.put(hash, method);
        }

        marshalledInvocationMapping = Collections.unmodifiableMap(tmpMap);
        // Place our ObjectName hash into the Registry so invokers can
        // resolve it
        Registry.bind(new Integer(serviceName.hashCode()), serviceName);
    }

    protected void stopService()
        throws Exception
    {
        Registry.unbind(new Integer(serviceName.hashCode()));
    }


    public Object invoke(Invocation invocation)
        throws Exception
    {
        // Make sure we have the correct classloader before unmarshalling
        Thread thread = Thread.currentThread();
        ClassLoader oldCL = thread.getContextClassLoader();

        // Get the MBean this operation applies to
        ClassLoader newCL = null;
        ObjectName objectName = (ObjectName) 
            invocation.getValue("JMX_OBJECT_NAME");
        if (objectName != null) {
            // Obtain the ClassLoader associated with the MBean deployment
            newCL = (ClassLoader) 
                server.invoke(mbeanRegistry, "getValue",
                              new Object[] { objectName, CLASSLOADER },
                              new String[] { ObjectName.class.getName(),
                                             "java.lang.String" });
        }
        
        if (newCL != null && newCL != oldCL) {
            thread.setContextClassLoader(newCL);
        }

        try {
            // Set the method hash to Method mapping
            if (invocation instanceof MarshalledInvocation) {
                MarshalledInvocation mi = (MarshalledInvocation) invocation;
                mi.setMethodMap(marshalledInvocationMapping);
            }

            // Invoke the MBeanServer method via reflection
            Method method = invocation.getMethod();
            Object[] args = invocation.getArguments();
            Object value = null;
            try {
                String name = method.getName();
                Class[] sig = method.getParameterTypes();
                Method mbeanServerMethod =
                    MBeanServer.class.getMethod(name, sig);
                value = mbeanServerMethod.invoke(server, args);
            } catch(InvocationTargetException e) {
                Throwable t = e.getTargetException();
                if (t instanceof Exception) {
                    throw (Exception) t;
                } else {
                    throw new UndeclaredThrowableException(t, method.toString());
                }
            }

            return value;
        } finally {
            if (newCL != null && newCL != oldCL) {
                thread.setContextClassLoader(oldCL);
            }
        }
    }
}    

Example 3.16. The InvokerAdaptorService MBean


Let's go through the key details of this service. The InvokerAdaptorServiceMBean Standard MBean interface of the InvokerAdaptorService has a single ExportedInterface attribute and a single invoke(Invocation) operation. The ExportedInterface attribute allows customization of the type of interface the service exposes to clients. This has to be compatible with the MBeanServer class in terms of method name and signature. The invoke(Invocation) operation is the required entry point that target MBean services must expose to participate in the detached invoker pattern. This operation is invoked by the detached invoker services that have been configured to provide access to the InvokerAdaptorService.

Lines 54-64 of the InvokerAdaptorService build the HashMap<Long, Method> of the ExportedInterface Class using the org.jboss.invocation.MarshalledInvocation.calculateHash(Method) utility method. Because java.lang.reflect.Method instances are not serializable, a MarshalledInvocation version of the non-serializable Invocation class is used to marshall the invocation between the client and server. The MarshalledInvocation replaces the Method instances with their corresponding hash representation. On the server side, the MarshalledInvocation must be told what the hash to Method mapping is.

Line 64 creates a mapping between the InvokerAdaptorService service name and its hash code representation. This is used by detached invokers to determine what the target MBean ObjectName of an Invocation is. When the target MBean name is store in the Invocation, its store as its hashCode because ObjectNames are relatively expensive objects to create. The org.jboss.system.Registry is a global map like construct that invokers use to store the hash code to ObjectName mappings in.

Lines 77-93 obtain the name of the MBean on which the MBeanServer operation is being performed and lookup the class loader associated with the MBean's SAR deployment. This information is available via the org.jboss.mx.server.registry.BasicMBeanRegistry, a JBoss JMX implementation specific class. It is generally necessary for an MBean to establish the correct class loading context because the detached invoker protocol layer may not have access to the class loaders needed to unmarshall the types associated with an invocation.

Lines 101-105 install the ExposedInterface class method hash to method mapping if the invocation argument is of type MarshalledInvocation. The method mapping calculated previously at lines 54-62 is used here.

Lines 107-114 perform a second mapping from the ExposedInterface Method to the matching method of the MBeanServer class. The InvokerServiceAdaptor decouples the ExposedInterface from the MBeanServer class in that it allows an arbitrary interface. This is needed on one hand because the standard java.lang.reflect.Proxy class can only proxy interfaces. It also allows one to only expose a subset of the MBeanServer methods and add transport specific exceptions like java.rmi.RemoteException to the ExposedInterface method signatures.

Line 115 dispatches the MBeanServer method invocation to the MBeanServer instance to which the InvokerAdaptorService was deployed. The server instance variable is inherited from the ServiceMBeanSupport superclass.

Lines 117-124 handle any exceptions coming from the reflective invocation including the unwrapping of any declared exception thrown by the invocation.

Line 126 is the return of the successful MBeanServer method invocation result.

Note that the InvokerAdaptorService MBean does not deal directly with any transport specific details. There is the calculation of the method hash to Method mapping, but this is a transport independent detail.

Now let's take a look at how the InvokerAdaptorService may be used to expose the same org.jboss.jmx.adaptor.rmi.RMIAdaptor interface via RMI/JRMP as seen in Connecting to JMX Using RMI. We will start by presenting the proxy factory and InvokerAdaptorService configurations found in the default setup in the jmx-invoker-adaptor-service.sar deployment. Example 3.17, “The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor” shows the jboss-service.xml descriptor for this deployment.

<server>
    <!-- The JRMP invoker proxy configuration for the InvokerAdaptorService -->
    <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory"
           name="jboss.jmx:type=adaptor,name=Invoker,protocol=jrmp,service=proxyFactory">
        <!-- Use the standard JRMPInvoker from conf/jboss-service.xml -->
        <attribute name="InvokerName">jboss:service=invoker,type=jrmp</attribute>
        <!-- The target MBean is the InvokerAdaptorService configured below -->
        <attribute name="TargetName">jboss.jmx:type=adaptor,name=Invoker</attribute>
        <!-- Where to bind the RMIAdaptor proxy -->
        <attribute name="JndiName">jmx/invoker/RMIAdaptor</attribute>
        <!-- The RMI compabitle MBeanServer interface -->
        <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor</attribute>
        <attribute name="ClientInterceptors">
            <iterceptors>
                <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
                <interceptor>
                    org.jboss.jmx.connector.invoker.client.InvokerAdaptorClientInterceptor 
                </interceptor>
                <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </iterceptors>
        </attribute>
        <depends>jboss:service=invoker,type=jrmp</depends>
    </mbean> 
    <!-- This is the service that handles the RMIAdaptor invocations by routing
         them to the MBeanServer the service is deployed under. -->
    <mbean code="org.jboss.jmx.connector.invoker.InvokerAdaptorService" 
           name="jboss.jmx:type=adaptor,name=Invoker">
        <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor</attribute>
    </mbean>
</server>

Example 3.17. The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor


The first MBean, org.jboss.invocation.jrmp.server.JRMPProxyFactory, is the proxy factory MBean service that creates proxies for the RMI/JRMP protocol. The configuration of this service as shown in Example 3.17, “The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor” states that the JRMPInvoker will be used as the detached invoker, the InvokerAdaptorService is the target mbean to which requests will be forwarded, that the proxy will expose the RMIAdaptor interface, the proxy will be bound into JNDI under the name jmx/invoker/RMIAdaptor, and the proxy will contain 3 interceptors: ClientMethodInterceptor, InvokerAdaptorClientInterceptor, InvokerInterceptor. The configuration of the InvokerAdaptorService simply sets the RMIAdaptor interface that the service is exposing.

The last piece of the configuration for exposing the InvokerAdaptorService via RMI/JRMP is the detached invoker. The detached invoker we will use is the standard RMI/JRMP invoker used by the EJB containers for home and remote invocations, and this is the org.jboss.invocation.jrmp.server.JRMPInvoker MBean service configured in the conf/jboss-service.xml descriptor. That we can use the same service instance emphasizes the detached nature of the invokers. The JRMPInvoker simply acts as the RMI/JRMP endpoint for all RMI/JRMP proxies regardless of the interface(s) the proxies expose or the service the proxies utilize.

The org.jboss.invocation.jrmp.server.JRMPInvoker class is an MBean service that provides the RMI/JRMP implementation of the Invoker interface. The JRMPInvoker exports itself as an RMI server so that when it is used as the Invoker in a remote client, the JRMPInvoker stub is sent to the client instead and invocations use the RMI/JRMP protocol.

The JRMPInvoker MBean supports a number of attribute to configure the RMI/JRMP transport layer. Its configurable attributes are:

  • RMIObjectPort : sets the RMI server socket listening port number. This is the port RMI clients will connect to when communicating through the proxy interface. The default setting in the jboss-service.xml descriptor is 4444, and if not specified, the attribute defaults to 0 to indicate an anonymous port should be used.

  • RMIClientSocketFactory : specifies a fully qualified class name for the java.rmi.server.RMIClientSocketFactory interface to use during export of the proxy interface.

  • RMIServerSocketFactory : specifies a fully qualified class name for the java.rmi.server.RMIServerSocketFactory interface to use during export of the proxy interface.

  • ServerAddress : specifies the interface address that will be used for the RMI server socket listening port. This can be either a DNS hostname or a dot-decimal Internet address. Since the RMIServerSocketFactory does not support a method that accepts an InetAddress object, this value is passed to the RMIServerSocketFactory implementation class using reflection. A check for the existence of a public void setBindAddress(java.net.InetAddress addr) method is made, and if one exists the RMIServerSocketAddr value is passed to the RMIServerSocketFactory implementation. If the RMIServerSocketFactory implementation does not support such a method, the ServerAddress value will be ignored.

  • SecurityDomain : specifies the JNDI name of an org.jboss.security.SecurityDomain interface implementation to associate with the RMIServerSocketFactory implementation. The value will be passed to the RMIServerSocketFactory using reflection to locate a method with a signature of public void setSecurityDomain(org.jboss.security.SecurityDomain d). If no such method exists the SecurityDomain value will be ignored.

The org.jboss.invocation.pooled.server.PooledInvoker is an MBean service that provides RMI over a custom socket transport implementation of the Invoker interface. The PooledInvoker exports itself as an RMI server so that when it is used as the Invoker in a remote client, the PooledInvoker stub is sent to the client instead and invocations use the custom socket protocol.

The PooledInvoker MBean supports a number of attribute to configure the socket transport layer. Its configurable attributes are:

  • NumAcceptThreads : The number of threads that exist for accepting client connections. The default is 1.

  • MaxPoolSize : The number of server threads for processing client. The default is 300.

  • SocketTimeout : The socket timeout value passed to the Socket.setSoTimeout() method. The default is 60000.

  • ServerBindPort : The port used for the server socket. A value of 0 indicates that an anonymous port should be chosen.

  • ClientConnectAddress : The address that the client passes to the Socket(addr, port) constructor. This defaults to the server InetAddress.getLocalHost() value.

  • ClientConnectPort : The port that the client passes to the Socket(addr, port) constructor. The default is the port of the server listening socket.

  • ClientMaxPoolSize : The client side maximum number of threads. The default is 300.

  • Backlog : The backlog associated with the server accept socket. The default is 200.

  • EnableTcpNoDelay : A boolean flag indicating if client sockets will enable the TcpNoDelay flag on the socket. The default is false.

  • ServerBindAddress : The address on which the server binds its listening socket. The default is an empty value which indicates the server should be bound on all interfaces.

  • TransactionManagerService : The JMX ObjectName of the JTA transaction manager service.

The org.jboss.invocation.http.server.HttpInvoker MBean service provides support for making invocations into the JMX bus over HTTP. Unlike the JRMPInvoker, the HttpInvoker is not an implementation of Invoker, but it does implement the Invoker.invoke method. The HttpInvoker is accessed indirectly by issuing an HTTP POST against the org.jboss.invocation.http.servlet.InvokerServlet. The HttpInvoker exports a client side proxy in the form of the org.jboss.invocation.http.interfaces.HttpInvokerProxy class, which is an implementation of Invoker, and is serializable. The HttpInvoker is a drop in replacement for the JRMPInvoker as the target of the bean-invoker and home-invoker EJB configuration elements. The HttpInvoker and InvokerServlet are deployed in the http-invoker.sar discussed in the JNDI chapter in the section entitled Accessing JNDI over HTTP

The HttpInvoker supports the following attributes:

  • InvokerURL : This is either the http URL to the InvokerServlet mapping, or the name of a system property that will be resolved inside the client VM to obtain the http URL to the InvokerServlet.

  • InvokerURLPrefix : If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. The default prefix is http://.

  • InvokerURLSuffix : If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. The default suffix is :8080/invoker/JMXInvokerServlet.

  • UseHostName : A boolean flag if the InetAddress.getHostName() or getHostAddress() method should be used as the host component of invokerURLPrefix + host + invokerURLSuffix. If true getHostName() is used, otherwise getHostAddress() is used.

The org.jboss.invocation.http.server.HttpProxyFactory MBean service is a proxy factory that can expose any interface with RMI compatible semantics for access to remote clients using HTTP as the transport.

The HttpProxyFactory supports the following attributes:

  • InvokerName : The server side MBean that exposes the invoke operation for the exported interface. The name is embedded into the HttpInvokerProxy context as the target to which the invocation should be forwarded by the HttpInvoker.

  • JndiName : The JNDI name under which the HttpInvokerProxy will be bound. This is the name clients lookup to obtain the dynamic proxy that exposes the service interfaces and marshalls invocations over HTTP. This may be specified as an empty value to indicate that the proxy should not be bound into JNDI.

  • InvokerURL : This is either the http URL to the InvokerServlet mapping, or the name of a system property that will be resolved inside the client VM to obtain the http URL to the InvokerServlet.

  • InvokerURLPrefix : If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. The default prefix is http://.

  • InvokerURLSuffix : If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. The default suffix is :8080/invoker/JMXInvokerServlet.

  • UseHostName : A boolean flag indicating if the InetAddress.getHostName() or getHostAddress() method should be used as the host component of invokerURLPrefix + host + invokerURLSuffix. If true getHostName() is used, otherwise getHostAddress() is used.

  • ExportedInterface : The name of the RMI compatible interface that the HttpInvokerProxy implements.

Using the HttpProxyFactory MBean and JMX, you can expose any interface for access using HTTP as the transport. The interface to expose does not have to be an RMI interface, but it does have to be compatible with RMI in that all method parameters and return values are serializable. There is also no support for converting RMI interfaces used as method parameters or return values into their stubs.

The three steps to making your object invocable via HTTP are:

  • Create a mapping of longs to the RMI interface methods using the MarshalledInvocation.calculateHash method. Here for example, is the procedure for an RMI SRPRemoteServerInterface interface:

    import java.lang.reflect.Method;
    import java.util.HashMap;
    import org.jboss.invocation.MarshalledInvocation;
    
    HashMap marshalledInvocationMapping = new HashMap();
    
    // Build the Naming interface method map
    Method[] methods = SRPRemoteServerInterface.class.getMethods();
    for(int m = 0; m < methods.length; m ++) {
        Method method = methods[m];
        Long hash = new Long(MarshalledInvocation.calculateHash(method));
        marshalledInvocationMapping.put(hash, method);
    }
    
  • Either create or extend an existing MBean to support an invoke operation. Its signature is Object invoke(Invocation invocation) throws Exception, and the steps it performs are as shown here for the SRPRemoteServerInterface interface. Note that this uses the marshalledInvocationMapping from step 1 to map from the Long method hashes in the MarshalledInvocation to the Method for the interface.

    import org.jboss.invocation.Invocation;
    import org.jboss.invocation.MarshalledInvocation;
    
    public Object invoke(Invocation invocation)
        throws Exception
    {
        SRPRemoteServerInterface theServer = <the_actual_rmi_server_object>;
        // Set the method hash to Method mapping
        if (invocation instanceof MarshalledInvocation) {
            MarshalledInvocation mi = (MarshalledInvocation) invocation;
            mi.setMethodMap(marshalledInvocationMapping);
        }
    
        // Invoke the Naming method via reflection
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        Object value = null;
        try {
            value = method.invoke(theServer, args);
        } catch(InvocationTargetException e) {
            Throwable t = e.getTargetException();    
            if (t instanceof Exception) {
                throw (Exception) e;
            } else {
                throw new UndeclaredThrowableException(t, method.toString());
            }
        }
    
        return value;
    }
    

  • Create a configuration of the HttpProxyFactory MBean to make the RMI/HTTP proxy available through JNDI. For example:

    <!-- Expose the SRP service interface via HTTP -->
    <mbean code="org.jboss.invocation.http.server.HttpProxyFactory"
           name="jboss.security.tests:service=SRP/HTTP">
        <attribute name="InvokerURL">http://localhost:8080/invoker/JMXInvokerServlet</attribute>
        <attribute name="InvokerName">jboss.security.tests:service=SRPService</attribute>
        <attribute name="ExportedInterface">
            org.jboss.security.srp.SRPRemoteServerInterface
        </attribute>
        <attribute name="JndiName">srp-test-http/SRPServerInterface</attribute>
    </mbean>
    

Any client may now lookup the RMI interface from JNDI using the name specified in the HttpProxyFactory (e.g., srp-test-http/SRPServerInterface) and use the obtain proxy in exactly the same manner as the RMI/JRMP version.

The naming service plays a key role in enterprise Java applications, providing the core infrastructure that is used to locate objects or services in an application server. It is also the mechanism that clients external to the application server use to locate services inside the application server. Application code, whether it is internal or external to the JBoss instance, need only know that it needs to talk to the a message queue named queue/IncomingOrders and would not need to worry about any of the details of how the queue is configured. In a clustered environment, naming services are even more valuable. A client of a service would desire to look up the ProductCatalog session bean from the cluster without worrying which machine the service is residing. Whether it is a big clustered service, a local resource or just a simple application component that is needed, the JNDI naming service provides the glue that lets code find the objects in the system by name.

JNDI is a standard Java API that is bundled with JDK1.3 and higher. JNDI provides a common interface to a variety of existing naming services: DNS, LDAP, Active Directory, RMI registry, COS registry, NIS, and file systems. The JNDI API is divided logically into a client API that is used to access naming services, and a service provider interface (SPI) that allows the user to create JNDI implementations for naming services.

The SPI layer is an abstraction that naming service providers must implement to enable the core JNDI classes to expose the naming service using the common JNDI client interface. An implementation of JNDI for a naming service is referred to as a JNDI provider. JBoss naming is an example JNDI implementation, based on the SPI classes. Note that the JNDI SPI is not needed by J2EE component developers.

For a thorough introduction and tutorial on JNDI, which covers both the client and service provider APIs, see the Sun tutorial at http://java.sun.com/products/jndi/tutorial/.

The main JNDI API package is the javax.naming package. It contains five interfaces, 10 classes, and several exceptions. There is one key class, InitialContext, and two key interfaces, Context and Name

The notion of a name is of fundamental importance in JNDI. The naming system determines the syntax that the name must follow. The syntax of the naming system allows the user to parse string representations of names into its components. A name is used with a naming system to locate objects. In the simplest sense, a naming system is just a collection of objects with unique names. To locate an object in a naming system you provide a name to the naming system, and the naming system returns the object store under the name.

As an example, consider the Unix file system's naming convention. Each file is named from its path relative to the root of the file system, with each component in the path separated by the forward slash character ("/"). The file's path is ordered from left to right. The pathname/usr/jboss/readme.txt, for example, names a file readme.txt in the directory jboss, under the directory usr, located in the root of the file system. JBoss naming uses a UNIX-style namespace as its naming convention.

The javax.naming.Name interface represents a generic name as an ordered sequence of components. It can be a composite name (one that spans multiple namespaces), or a compound name (one that is used within a single hierarchical naming system). The components of a name are numbered. The indexes of a name with N components range from 0 up to, but not including, N. The most significant component is at index 0. An empty name has no components.

A composite name is a sequence of component names that span multiple namespaces. An example of a composite name would be the hostname and file combination commonly used with UNIX commands like scp. For example, the following command copies localfile.txt to the file remotefile.txt in the tmp directory on host ahost.someorg.org:

scp localfile.txt ahost.someorg.org:/tmp/remotefile.txt

A compound name is derived from a hierarchical namespace. Each component in a compound name is an atomic name, meaning a string that cannot be parsed into smaller components. A file pathname in the UNIX file system is an example of a compound name. ahost.someorg.org:/tmp/remotefile.txt is a composite name that spans the DNS and UNIX file system namespaces. The components of the composite name are ahost.someorg.org and /tmp/remotefile.txt. A component is a string name from the namespace of a naming system. If the component comes from a hierarchical namespace, that component can be further parsed into its atomic parts by using the javax.naming.CompoundName class. The JNDI API provides the javax.naming.CompositeName class as the implementation of the Name interface for composite names.

The javax.naming.Context interface is the primary interface for interacting with a naming service. The Context interface represents a set of name-to-object bindings. Every context has an associated naming convention that determines how the context parses string names into javax.naming.Name instances. To create a name to object binding you invoke the bind method of a Context and specify a name and an object as arguments. The object can later be retrieved using its name using the Context lookup method. A Context will typically provide operations for binding a name to an object, unbinding a name, and obtaining a listing of all name-to-object bindings. The object you bind into a Context can itself be of type Context . The Context object that is bound is referred to as a subcontext of the Context on which the bind method was invoked.

As an example, consider a file directory with a pathname /usr, which is a context in the UNIX file system. A file directory named relative to another file directory is a subcontext (commonly referred to as a subdirectory). A file directory with a pathname /usr/jboss names a jboss context that is a subcontext of usr. In another example, a DNS domain, such as org, is a context. A DNS domain named relative to another DNS domain is another example of a subcontext. In the DNS domain jboss.org, the DNS domain jboss is a subcontext of org because DNS names are parsed right to left.

All naming service operations are performed on some implementation of the Context interface. Therefore, you need a way to obtain a Context for the naming service you are interested in using. The javax.naming.IntialContext class implements the Context interface, and provides the starting point for interacting with a naming service.

When you create an InitialContext, it is initialized with properties from the environment. JNDI determines each property's value by merging the values from the following two sources, in order.

  • The first occurrence of the property from the constructor's environment parameter and (for appropriate properties) the applet parameters and system properties.

  • All jndi.properties resource files found on the classpath.

For each property found in both of these two sources, the property's value is determined as follows. If the property is one of the standard JNDI properties that specify a list of JNDI factories, all of the values are concatenated into a single colon-separated list. For other properties, only the first value found is used. The preferred method of specifying the JNDI environment properties is through a jndi.properties file, which allows your code to externalize the JNDI provider specific information so that changing JNDI providers will not require changes to your code or recompilation.

The Context implementation used internally by the InitialContext class is determined at runtime. The default policy uses the environment property java.naming.factory.initial, which contains the class name of the javax.naming.spi.InitialContextFactory implementation. You obtain the name of the InitialContextFactory class from the naming service provider you are using.

Example 4.1, “A sample jndi.properties file” gives a sample jndi.properties file a client application would use to connect to a JBossNS service running on the local host at port 1099. The client application would need to have the jndi.properties file available on the application classpath. These are the properties that the JBossNS JNDI implementation requires. Other JNDI providers will have different properties and values.


The JBossNS architecture is a Java socket/RMI based implementation of the javax.naming.Context interface. It is a client/server implementation that can be accessed remotely. The implementation is optimized so that access from within the same VM in which the JBossNS server is running does not involve sockets. Same VM access occurs through an object reference available as a global singleton. Figure 4.1, “Key components in the JBossNS architecture.” illustrates some of the key classes in the JBossNS implementation and their relationships.


We will start with the NamingService MBean. The NamingService MBean provides the JNDI naming service. This is a key service used pervasively by the J2EE technology components. The configurable attributes for the NamingService are as follows.

  • Port : The jnp protocol listening port for the NamingService. If not specified default is 1099, the same as the RMI registry default port.

  • RmiPort : The RMI port on which the RMI Naming implementation will be exported. If not specified the default is 0 which means use any available port.

  • BindAddress : The specific address the NamingService listens on. This can be used on a multi-homed host for a java.net.ServerSocket that will only accept connect requests on one of its addresses.

  • RmiBindAddress : The specific address the RMI server portion of the NamingService listens on. This can be used on a multi-homed host for a java.net.ServerSocket that will only accept connect requests on one of its addresses. If this is not specified and the BindAddress is, the RmiBindAddress defaults to the BindAddress value.

  • Backlog : The maximum queue length for incoming connection indications (a request to connect) is set to the backlog parameter. If a connection indication arrives when the queue is full, the connection is refused.

  • ClientSocketFactory : An optional custom java.rmi.server.RMIClientSocketFactory implementation class name. If not specified the default RMIClientSocketFactory is used.

  • ServerSocketFactory : An optional custom java.rmi.server.RMIServerSocketFactory implementation class name. If not specified the default RMIServerSocketFactory is used.

  • JNPServerSocketFactory : An optional custom javax.net.ServerSocketFactory implementation class name. This is the factory for the ServerSocket used to bootstrap the download of the JBossNS Naming interface. If not specified the javax.net.ServerSocketFactory.getDefault() method value is used.

The NamingService also creates the java:comp context such that access to this context is isolated based on the context class loader of the thread that accesses the java:comp context. This provides the application component private ENC that is required by the J2EE specs. This segregation is accomplished by binding a javax.naming.Reference to a context that uses the org.jboss.naming.ENCFactory as its javax.naming.ObjectFactory. When a client performs a lookup of java:comp, or any subcontext, the ENCFactory checks the thread context ClassLoader, and performs a lookup into a map using the ClassLoader as the key.

If a context instance does not exist for the class loader instance, one is created and associated with that class loader in the ENCFactory map. Thus, correct isolation of an application component's ENC relies on each component receiving a unique ClassLoader that is associated with the component threads of execution.

The NamingService delegates its functionality to an org.jnp.server.Main MBean. The reason for the duplicate MBeans is because JBossNS started out as a stand-alone JNDI implementation, and can still be run as such. The NamingService MBean embeds the Main instance into the JBoss server so that usage of JNDI with the same VM as the JBoss server does not incur any socket overhead. The configurable attributes of the NamingService are really the configurable attributes of the JBossNS Main MBean. The setting of any attributes on the NamingService MBean simply set the corresponding attributes on the Main MBean the NamingService contains. When the NamingService is started, it starts the contained Main MBean to activate the JNDI naming service.

In addition, the NamingService exposes the Naming interface operations through a JMX detyped invoke operation. This allows the naming service to be accessed via JMX adaptors for arbitrary protocols. We will look at an example of how HTTP can be used to access the naming service using the invoke operation later in this chapter.

The details of threads and the thread context class loader won't be explored here, but the JNDI tutorial provides a concise discussion that is applicable. See http://java.sun.com/products/jndi/tutorial/beyond/misc/classloader.html for the details.

When the Main MBean is started, it performs the following tasks:

  • Instantiates an org.jnp.naming.NamingService instance and sets this as the local VM server instance. This is used by any org.jnp.interfaces.NamingContext instances that are created within the JBoss server VM to avoid RMI calls over TCP/IP.

  • Exports the NamingServer instance's org.jnp.naming.interfaces.Naming RMI interface using the configured RmiPort, ClientSocketFactory, ServerSocketFactoryattributes.

  • Creates a socket that listens on the interface given by the BindAddress and Port attributes.

  • Spawns a thread to accept connections on the socket.

The JBoss JNDI provider currently supports several different InitialContext factory implementations.

The most commonly used factory is the org.jnp.interfaces.NamingContextFactory implementation. Its properties include:

  • java.naming.factory.initial : The name of the environment property for specifying the initial context factory to use. The value of the property should be the fully qualified class name of the factory class that will create an initial context. If it is not specified, a javax.naming.NoInitialContextException will be thrown when an InitialContext object is created.

  • java.naming.provider.url : The name of the environment property for specifying the location of the JBoss JNDI service provider the client will use. The NamingContextFactory class uses this information to know which JBossNS server to connect to. The value of the property should be a URL string. For JBossNS the URL format is jnp://host:port/[jndi_path]. The jnp: portion of the URL is the protocol and refers to the socket/RMI based protocol used by JBoss. The jndi_path portion of the URL is an optional JNDI name relative to the root context, for example, apps or apps/tmp. Everything but the host component is optional. The following examples are equivalent because the default port value is 1099.

    • jnp://www.jboss.org:1099/

    • www.jboss.org:1099

    • www.jboss.org

  • java.naming.factory.url.pkgs : The name of the environment property for specifying the list of package prefixes to use when loading in URL context factories. The value of the property should be a colon-separated list of package prefixes for the class name of the factory class that will create a URL context factory. For the JBoss JNDI provider this must be org.jboss.naming:org.jnp.interfaces. This property is essential for locating the jnp: and java: URL context factories of the JBoss JNDI provider.

  • jnp.socketFactory : The fully qualified class name of the javax.net.SocketFactory implementation to use to create the bootstrap socket. The default value is org.jnp.interfaces.TimedSocketFactory. The TimedSocketFactory is a simple SocketFactory implementation that supports the specification of a connection and read timeout. These two properties are specified by:

  • jnp.timeout : The connection timeout in milliseconds. The default value is 0 which means the connection will block until the VM TCP/IP layer times out.

  • jnp.sotimeout : The connected socket read timeout in milliseconds. The default value is 0 which means reads will block. This is the value passed to the Socket.setSoTimeout on the newly connected socket.

When a client creates an InitialContext with these JBossNS properties available, the org.jnp.interfaces.NamingContextFactory object is used to create the Context instance that will be used in subsequent operations. The NamingContextFactory is the JBossNS implementation of the javax.naming.spi.InitialContextFactory interface. When the NamingContextFactory class is asked to create a Context, it creates an org.jnp.interfaces.NamingContext instance with the InitialContext environment and name of the context in the global JNDI namespace. It is the NamingContext instance that actually performs the task of connecting to the JBossNS server, and implements the Context interface. The Context.PROVIDER_URL information from the environment indicates from which server to obtain a NamingServer RMI reference.

The association of the NamingContext instance to a NamingServer instance is done in a lazy fashion on the first Context operation that is performed. When a Context operation is performed and the NamingContext has no NamingServer associated with it, it looks to see if its environment properties define a Context.PROVIDER_URL. A Context.PROVIDER_URL defines the host and port of the JBossNS server the Context is to use. If there is a provider URL, the NamingContext first checks to see if a Naming instance keyed by the host and port pair has already been created by checking a NamingContext class static map. It simply uses the existing Naming instance if one for the host port pair has already been obtained. If no Naming instance has been created for the given host and port, the NamingContext connects to the host and port using a java.net.Socket, and retrieves a Naming RMI stub from the server by reading a java.rmi.MarshalledObject from the socket and invoking its get method. The newly obtained Naming instance is cached in the NamingContext server map under the host and port pair. If no provider URL was specified in the JNDI environment associated with the context, the NamingContext simply uses the in VM Naming instance set by the Main MBean.

The NamingContext implementation of the Context interface delegates all operations to the Naming instance associated with the NamingContext. The NamingServer class that implements the Naming interface uses a java.util.Hashtable as the Context store. There is one unique NamingServer instance for each distinct JNDI Name for a given JBossNS server. There are zero or more transient NamingContext instances active at any given moment that refers to a NamingServer instance. The purpose of the NamingContext is to act as a Context to the Naming interface adaptor that manages translation of the JNDI names passed to the NamingContext . Because a JNDI name can be relative or a URL, it needs to be converted into an absolute name in the context of the JBossNS server to which it refers. This translation is a key function of the NamingContext.

When running in a clustered JBoss environment, you can choose not to specify a Context.PROVIDER_URL value and let the client query the network for available naming services. This only works with JBoss servers running with the all configuration, or an equivalent configuration that has org.jboss.ha.framework.server.ClusterPartition and org.jboss.ha.jndi.HANamingService services deployed. The discovery process consists of sending a multicast request packet to the discovery address/port and waiting for any node to respond. The response is a HA-RMI version of the Naming interface. The following InitialContext properties affect the discovery configuration:

  • jnp.partitionName : The cluster partition name discovery should be restricted to. If you are running in an environment with multiple clusters, you may want to restrict the naming discovery to a particular cluster. There is no default value, meaning that any cluster response will be accepted.

  • jnp.discoveryGroup : The multicast IP/address to which the discovery query is sent. The default is 230.0.0.4.

  • jnp.discoveryPort : The port to which the discovery query is sent. The default is 1102.

  • jnp.discoveryTimeout : The time in milliseconds to wait for a discovery query response. The default value is 5000 (5 seconds).

  • jnp.disableDiscovery : A flag indicating if the discovery process should be avoided. Discovery occurs when either no Context.PROVIDER_URL is specified, or no valid naming service could be located among the URLs specified. If the jnp.disableDiscovery flag is true, then discovery will not be attempted.

The JNDI naming service can be accessed over HTTP. From a JNDI client's perspective this is a transparent change as they continue to use the JNDI Context interface. Operations through the Context interface are translated into HTTP posts to a servlet that passes the request to the NamingService using its JMX invoke operation. Advantages of using HTTP as the access protocol include better access through firewalls and proxies setup to allow HTTP, as well as the ability to secure access to the JNDI service using standard servlet role based security.

To access JNDI over HTTP you use the org.jboss.naming.HttpNamingContextFactory as the factory implementation. The complete set of support InitialContext environment properties for this factory are:

  • java.naming.factory.initial : The name of the environment property for specifying the initial context factory, which must be org.jboss.naming.HttpNamingContextFactory.

  • java.naming.provider.url (or Context.PROVIDER_URL): This must be set to the HTTP URL of the JNDI factory. The full HTTP URL would be the public URL of the JBoss servlet container plus /invoker/JNDIFactory. Examples include:

    • http://www.jboss.org:8080/invoker/JNDIFactory

    • http://www.jboss.org/invoker/JNDIFactory

    • https://www.jboss.org/invoker/JNDIFactory

    The first example accesses the servlet using the port 8080. The second uses the standard HTTP port 80, and the third uses an SSL encrypted connection to the standard HTTPS port 443.

  • java.naming.factory.url.pkgs : For all JBoss JNDI provider this must be org.jboss.naming:org.jnp.interfaces. This property is essential for locating the jnp: and java: URL context factories of the JBoss JNDI provider.

The JNDI Context implementation returned by the HttpNamingContextFactory is a proxy that delegates invocations made on it to a bridge servlet. The bridge sevlet forwards the invocation to the NamingService through the JMX bus and marshalls the reply back over HTTP. The proxy needs to know what the URL of the bridge servlet is in order to operate. This value may have been bound on the server side if the JBoss web server has a well known public interface. If the JBoss web server is sitting behind one or more firewalls or proxies, the proxy cannot know what URL is required. In this case, the proxy will be associated with a system property value that must be set in the client VM. For more information on the operation of JNDI over HTTP see Section 4.4.1, “Accessing JNDI over HTTP”.

JAAS is the preferred method for authenticating a remote client to JBoss. However, for simplicity and to ease the migration from other application server environment that do not use JAAS, JBoss allows you the security credentials to be passed through the InitialContext. JAAS is still used under the covers, but there is no manifest use of the JAAS interfaces in the client application.

The factory class that provides this capability is the org.jboss.security.jndi.LoginInitialContextFactory. The complete set of support InitialContext environment properties for this factory are:

  • java.naming.factory.initial : The name of the environment property for specifying the initial context factory, which must be org.jboss.security.jndi.LoginInitialContextFactory.

  • java.naming.provider.url : This must be set to a NamingContextFactory provider URL. The LoginIntialContext is really just a wrapper around the NamingContextFactory that adds a JAAS login to the existing NamingContextFactory behavior.

  • java.naming.factory.url.pkgs : For all JBoss JNDI provider this must be org.jboss.naming:org.jnp.interfaces. This property is essential for locating the jnp: and java: URL context factories of the JBoss JNDI provider.

  • java.naming.security.principal (or Context.SECURITY_PRINCIPAL): The principal to authenticate. This may be either a java.security.Principal implementation or a string representing the name of a principal.

  • java.naming.security.credentials (or Context.SECURITY_CREDENTIALS), The credentials that should be used to authenticate the principal, e.g., password, session key, etc.

  • java.naming.security.protocol : (Context.SECURITY_PROTOCOL) This gives the name of the JAAS login module to use for the authentication of the principal and credentials.

In addition to the legacy RMI/JRMP with a socket bootstrap protocol, JBoss provides support for accessing its JNDI naming service over HTTP.

This capability is provided by http-invoker.sar. The structure of the http-invoker.sar is:

                        
                           http-invoker.sar
                        
+- META-INF/jboss-service.xml
+- invoker.war
| +- WEB-INF/jboss-web.xml
| +- WEB-INF/classes/org/jboss/invocation/http/servlet/InvokerServlet.class
| +- WEB-INF/classes/org/jboss/invocation/http/servlet/NamingFactoryServlet.class
| +- WEB-INF/classes/org/jboss/invocation/http/servlet/ReadOnlyAccessFilter.class
| +- WEB-INF/web.xml
| +- META-INF/MANIFEST.MF
+- META-INF/MANIFEST.MF

The jboss-service.xml descriptor defines the HttpInvoker and HttpInvokerHA MBeans. These services handle the routing of methods invocations that are sent via HTTP to the appropriate target MBean on the JMX bus.

The http-invoker.war web application contains servlets that handle the details of the HTTP transport. The NamingFactoryServlet handles creation requests for the JBoss JNDI naming service javax.naming.Context implementation. The InvokerServlet handles invocations made by RMI/HTTP clients. The ReadOnlyAccessFilter allows one to secure the JNDI naming service while making a single JNDI context available for read-only access by unauthenticated clients.


Before looking at the configurations let's look at the operation of the http-invoker services. Figure 4.2, “The HTTP invoker proxy/server structure for a JNDI Context” shows a logical view of the structure of a JBoss JNDI proxy and its relationship to the JBoss server side components of the http-invoker. The proxy is obtained from the NamingFactoryServlet using an InitialContext with the Context.INITIAL_CONTEXT_FACTORY property set to org.jboss.naming.HttpNamingContextFactory, and the Context.PROVIDER_URL property set to the HTTP URL of the NamingFactoryServlet. The resulting proxy is embedded in an org.jnp.interfaces.NamingContext instance that provides the Context interface implementation.

The proxy is an instance of org.jboss.invocation.http.interfaces.HttpInvokerProxy, and implements the org.jnp.interfaces.Naming interface. Internally the HttpInvokerProxy contains an invoker that marshalls the Naming interface method invocations to the InvokerServlet via HTTP posts. The InvokerServlet translates these posts into JMX invocations to the NamingService, and returns the invocation response back to the proxy in the HTTP post response.

There are several configuration values that need to be set to tie all of these components together and Figure 4.3, “The relationship between configuration files and JNDI/HTTP component” illustrates the relationship between configuration files and the corresponding components.


The http-invoker.sar/META-INF/jboss-service.xml descriptor defines the HttpProxyFactory that creates the HttpInvokerProxy for the NamingService. The attributes that need to be configured for the HttpProxyFactory include:

  • InvokerName : The JMX ObjectName of the NamingService defined in the conf/jboss-service.xml descriptor. The standard setting used in the JBoss distributions is jboss:service=Naming.

  • InvokerURL or InvokerURLPrefix + InvokerURLSuffix + UseHostName . You can specify the full HTTP URL to the InvokerServlet using the InvokerURL attribute, or you can specify the hostname independent parts of the URL and have the HttpProxyFactory fill them in. An example InvokerURL value would be http://jbosshost1.dot.com:8080/invoker/JMXInvokerServlet. This can be broken down into:

    • InvokerURLPrefix : the URL prefix prior to the hostname. Typically this will be http:// or https:// if SSL is to be used.

    • InvokerURLSuffix : the URL suffix after the hostname. This will include the port number of the web server as well as the deployed path to the InvokerServlet . For the example InvokerURL value the InvokerURLSuffix would be :8080/invoker/JMXInvokerServlet without the quotes. The port number is determined by the web container service settings. The path to the InvokerServlet is specified in the http-invoker.sar/invoker.war/WEB-INF/web.xml descriptor.

    • UseHostName : a flag indicating if the hostname should be used in place of the host IP address when building the hostname portion of the full InvokerURL. If true, InetAddress.getLocalHost().getHostName method will be used. Otherwise, the InetAddress.getLocalHost().getHostAddress() method is used.

  • ExportedInterface : The org.jnp.interfaces.Naming interface the proxy will expose to clients. The actual client of this proxy is the JBoss JNDI implementation NamingContext class, which JNDI client obtain from InitialContext lookups when using the JBoss JNDI provider.

  • JndiName : The name in JNDI under which the proxy is bound. This needs to be set to a blank/empty string to indicate the interface should not be bound into JNDI. We can't use the JNDI to bootstrap itself. This is the role of the NamingFactoryServlet.

The http-invoker.sar/invoker.war/WEB-INF/web.xml descriptor defines the mappings of the NamingFactoryServlet and InvokerServlet along with their initialization parameters. The configuration of the NamingFactoryServlet relevant to JNDI/HTTP is the JNDIFactory entry which defines:

  • A namingProxyMBean initialization parameter that maps to the HttpProxyFactory MBean name. This is used by the NamingFactoryServlet to obtain the Naming proxy which it will return in response to HTTP posts. For the default http-invoker.sar/META-INF/jboss-service.xml settings the name jboss:service=invoker,type=http,target=Naming is used.

  • A proxy initialization parameter that defines the name of the namingProxyMBean attribute to query for the Naming proxy value. This defaults to an attribute name of Proxy.

  • The servlet mapping for the JNDIFactory configuration. The default setting for the unsecured mapping is /JNDIFactory/*. This is relative to the context root of the http-invoker.sar/invoker.war, which by default is the WAR name minus the .war suffix.

The configuration of the InvokerServlet relevant to JNDI/HTTP is the JMXInvokerServlet which defines:

  • The servlet mapping of the InvokerServlet. The default setting for the unsecured mapping is /JMXInvokerServlet/*. This is relative to the context root of the http-invoker.sar/invoker.war, which by default is the WAR name minus the .war suffix.

To be able to access JNDI over HTTP/SSL you need to enable an SSL connector on the web container. The details of this are covered in the Integrating Servlet Containers for Tomcat. We will demonstrate the use of HTTPS with a simple example client that uses an HTTPS URL as the JNDI provider URL. We will provide an SSL connector configuration for the example, so unless you are interested in the details of the SSL connector setup, the example is self contained.

We also provide a configuration of the HttpProxyFactory setup to use an HTTPS URL. The following example shows the section of the http-invoker.sar jboss-service.xml descriptor that the example installs to provide this configuration. All that has changed relative to the standard HTTP configuration are the InvokerURLPrefix and InvokerURLSuffix attributes, which setup an HTTPS URL using the 8443 port.

<!-- Expose the Naming service interface via HTTPS -->
<mbean code="org.jboss.invocation.http.server.HttpProxyFactory" 
       name="jboss:service=invoker,type=https,target=Naming">
    <!-- The Naming service we are proxying -->
    <attribute name="InvokerName">jboss:service=Naming</attribute>
    <!-- Compose the invoker URL from the cluster node address -->
    <attribute name="InvokerURLPrefix">https://</attribute>
    <attribute name="InvokerURLSuffix">:8443/invoker/JMXInvokerServlet 
</attribute>
    <attribute name="UseHostName">true</attribute>
    <attribute name="ExportedInterface">org.jnp.interfaces.Naming 
</attribute>
    <attribute name="JndiName"/>
    <attribute name="ClientInterceptors">
        <interceptors>
            <interceptor>org.jboss.proxy.ClientMethodInterceptor 
</interceptor>
            <interceptor>org.jboss.proxy.SecurityInterceptor
</interceptor>
            <interceptor>org.jboss.naming.interceptors.ExceptionInterceptor 
</interceptor>
            <interceptor>org.jboss.invocation.InvokerInterceptor 
</interceptor>
        </interceptors>
    </attribute>
</mbean>

At a minimum, a JNDI client using HTTPS requires setting up a HTTPS URL protocol handler. We will be using the Java Secure Socket Extension (JSSE) for HTTPS. The JSSE documentation does a good job of describing what is necessary to use HTTPS, and the following steps were needed to configure the example client shown in Example 4.2, “A JNDI client that uses HTTPS as the transport”:

  • A protocol handler for HTTPS URLs must be made available to Java. The JSSE release includes an HTTPS handler in the com.sun.net.ssl.internal.www.protocol package. To enable the use of HTTPS URLs you include this package in the standard URL protocol handler search property, java.protocol.handler.pkgs. We set the java.protocol.handler.pkgs property in the Ant script.

  • The JSSE security provider must be installed in order for SSL to work. This can be done either by installing the JSSE jars as an extension package, or programatically. We use the programmatic approach in the example since this is less intrusive. Line 18 of the ExClient code demonstrates how this is done.

  • The JNDI provider URL must use HTTPS as the protocol. Lines 24-25 of the ExClient code specify an HTTP/SSL connection to the localhost on port 8443. The hostname and port are defined by the web container SSL connector.

  • The validation of the HTTPS URL hostname against the server certificate must be disabled. By default, the JSSE HTTPS protocol handler employs a strict validation of the hostname portion of the HTTPS URL against the common name of the server certificate. This is the same check done by web browsers when you connect to secured web site. We are using a self-signed server certificate that uses a common name of "Chapter 8 SSL Example" rather than a particular hostname, and this is likely to be common in development environments or intranets. The JBoss HttpInvokerProxy will override the default hostname checking if a org.jboss.security.ignoreHttpsHost system property exists and has a value of true. We set the org.jboss.security.ignoreHttpsHost property to true in the Ant script.


To test the client, first build the chapter 3 example to create the chap3 configuration fileset.

[examples]$ ant -Dchap=naming config

Next, start the JBoss server using the naming configuration fileset:

[bin]$ sh run.sh -c naming

And finally, run the ExClient using:

[examples]$ ant -Dchap=naming -Dex=1 run-example
...
run-example1:

[java] Created InitialContext, env={java.naming. \ 
provider.url=https://localhost:8443/invoker/JNDIFactorySSL, java.naming. \
factory.initial=org.jboss.naming.HttpNamingContextFactory}
     [java] lookup(jmx/invoker/RMIAdaptor): org.jboss.invocation.jrmp. \
     interfaces.JRMPInvokerP
roxy@cac3fa

One benefit to accessing JNDI over HTTP is that it is easy to secure access to the JNDI InitialContext factory as well as the naming operations using standard web declarative security. This is possible because the server side handling of the JNDI/HTTP transport is implemented with two servlets. These servlets are included in the http-invoker.sar/invoker.war directory found in the default and all configuration deploy directories as shown previously. To enable secured access to JNDI you need to edit the invoker.war/WEB-INF/web.xml descriptor and remove all unsecured servlet mappings. For example, the web.xml descriptor shown in Example 4.3, “An example web.xml descriptor for secured access to the JNDI servlets” only allows access to the invoker.war servlets if the user has been authenticated and has a role of HttpInvoker.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
          "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
          "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <!-- ### Servlets -->
    <servlet>
        <servlet-name>JMXInvokerServlet</servlet-name>
        <servlet-class>
            org.jboss.invocation.http.servlet.InvokerServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>   <servlet>
        <servlet-name>JNDIFactory</servlet-name>
        <servlet-class>
            org.jboss.invocation.http.servlet.NamingFactoryServlet
        </servlet-class>
        <init-param>
            <param-name>namingProxyMBean</param-name>
            <param-value>jboss:service=invoker,type=http,target=Naming</param-value>
        </init-param>
        <init-param>
            <param-name>proxyAttribute</param-name>
            <param-value>Proxy</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>  
    <!-- ### Servlet Mappings -->
    <servlet-mapping>
        <servlet-name>JNDIFactory</servlet-name>
        <url-pattern>/restricted/JNDIFactory/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>JMXInvokerServlet</servlet-name>
        <url-pattern>/restricted/JMXInvokerServlet/*</url-pattern>
    </servlet-mapping>   <security-constraint>
        <web-resource-collection>
            <web-resource-name>HttpInvokers</web-resource-name>
            <description>An example security config that only allows users with
                the role HttpInvoker to access the HTTP invoker servlets </description>
            <url-pattern>/restricted/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>HttpInvoker</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>JBoss HTTP Invoker</realm-name>
    </login-config>   <security-role>
        <role-name>HttpInvoker</role-name>
    </security-role>
</web-app>

Example 4.3. An example web.xml descriptor for secured access to the JNDI servlets


The web.xml descriptor only defines which sevlets are secured, and which roles are allowed to access the secured servlets. You must additionally define the security domain that will handle the authentication and authorization for the war. This is done through the jboss-web.xml descriptor, and an example that uses the http-invoker security domain is given below.

<jboss-web>
    <security-domain>java:/jaas/http-invoker</security-domain>
</jboss-web>

The security-domain element defines the name of the security domain that will be used for the JAAS login module configuration used for authentication and authorization. See Section 8.1.6, “Enabling Declarative Security in JBoss” for additional details on the meaning and configuration of the security domain name.

Another feature available for the JNDI/HTTP naming service is the ability to define a context that can be accessed by unauthenticated users in read-only mode. This can be important for services used by the authentication layer. For example, the SRPLoginModule needs to lookup the SRP server interface used to perform authentication. We'll now walk through how read-only JNDI works in JBoss.

First, the ReadOnlyJNDIFactory is declared in invoker.sar/WEB-INF/web.xml. It will be mapped to /invoker/ReadOnlyJNDIFactory.

<servlet>
    <servlet-name>ReadOnlyJNDIFactory</servlet-name>
    <description>A servlet that exposes the JBoss JNDI Naming service stub
          through http, but only for a single read-only context. The return content 
          is serialized MarshalledValue containing the org.jnp.interfaces.Naming 
          stub.
    </description>
    <servlet-class>org.jboss.invocation.http.servlet.NamingFactoryServlet</servlet-class>
    <init-param>
        <param-name>namingProxyMBean</param-name>
        <param-value>
                           jboss:service=invoker,type=http,target=Naming,readonly=true
                        </param-value>
    </init-param>
    <init-param>
        <param-name>proxyAttribute</param-name>
        <param-value>Proxy</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
</servlet>

<!-- ... -->
                        
<servlet-mapping>
    <servlet-name>ReadOnlyJNDIFactory</servlet-name>
    <url-pattern>/ReadOnlyJNDIFactory/*</url-pattern>
</servlet-mapping>

The factory only provides a JNDI stub which needs to be connected to an invoker. Here the invoker is jboss:service=invoker,type=http,target=Naming,readonly=true. This invoker is declared in the http-invoker.sar/META-INF/jboss-service.xml file.

   <mbean code="org.jboss.invocation.http.server.HttpProxyFactory"
      name="jboss:service=invoker,type=http,target=Naming,readonly=true">
      <attribute name="InvokerName">jboss:service=Naming</attribute>
      <attribute name="InvokerURLPrefix">http://</attribute>
      <attribute name="InvokerURLSuffix">:8080
                           /invoker/readonly/JMXInvokerServlet
                        </attribute>
      <attribute name="UseHostName">true</attribute>
      <attribute name="ExportedInterface">org.jnp.interfaces.Naming</attribute>
      <attribute name="JndiName"></attribute>
      <attribute name="ClientInterceptors">
          <interceptors>
             <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
             <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
             <interceptor>org.jboss.naming.interceptors.ExceptionInterceptor</interceptor>
             <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
          </interceptors>
      </attribute>
   </mbean>

The proxy on the client side needs to talk back to a specific invoker servlet on the server side. The configuration here has the actual invocations going to /invoker/readonly/JMXInvokerServlet. This is actually the standard JMXInvokerServlet with a read-only filter attached.

    <filter>
        <filter-name>ReadOnlyAccessFilter</filter-name>
        <filter-class>org.jboss.invocation.http.servlet.ReadOnlyAccessFilter</filter-class>
        <init-param>
            <param-name>readOnlyContext</param-name>
            <param-value>
                           readonly
                        </param-value>
            <description>The top level JNDI context the filter will enforce
                read-only access on. If specified only Context.lookup operations
                will be allowed on this context. Another other operations or
                lookups on any other context will fail. Do not associate this
                filter with the JMXInvokerServlets if you want unrestricted
                access. </description>
        </init-param>
        <init-param>
            <param-name>invokerName</param-name>
            <param-value>jboss:service=Naming</param-value>
            <description>The JMX ObjectName of the naming service mbean </description>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>ReadOnlyAccessFilter</filter-name>
        <url-pattern>/readonly/*</url-pattern>
    </filter-mapping>

    <!-- ... -->
    <!-- A mapping for the JMXInvokerServlet that only allows invocations 
            of lookups under a read-only context. This is enforced by the
            ReadOnlyAccessFilter 
            -->
    <servlet-mapping>
        <servlet-name>JMXInvokerServlet</servlet-name>
        <url-pattern>/readonly/JMXInvokerServlet/*</url-pattern>
    </servlet-mapping>

The readOnlyContext parameter is set to readonly which means that when you access JBoss through the ReadOnlyJNDIFactory, you will only be able to access data in the readonly context. Here is a code fragment that illustrates the usage:

Properties env = new Properties();
env.setProperty(Context.INITIAL_CONTEXT_FACTORY, 
                "org.jboss.naming.HttpNamingContextFactory");
env.setProperty(Context.PROVIDER_URL, 
                "http://localhost:8080/invoker/ReadOnlyJNDIFactory");

Context ctx2 = new InitialContext(env);
Object data = ctx2.lookup("readonly/data");

Attempts to look up any objects outside of the readonly context will fail. Note that JBoss doesn't ship with any data in the readonly context, so the readonly context won't be bound usable unless you create it.

In addition to the NamingService MBean that configures an embedded JBossNS server within JBoss, there are several additional MBean services related to naming that ship with JBoss. They are JndiBindingServiceMgr, NamingAlias, ExternalContext, and JNDIView.

The JNDI binding manager service allows you to quickly bind objects into JNDI for use by application code. The MBean class for the binding service is org.jboss.naming.JNDIBindingServiceMgr. It has a single attribute, BindingsConfig, which accepts an XML document that conforms to the jndi-binding-service_1_0.xsd schema. The content of the BindingsConfig attribute is unmarshalled using the JBossXB framework. The following is an MBean definition that shows the most basic form usage of the JNDI binding manager service.

<mbean code="org.jboss.naming.JNDIBindingServiceMgr" 
       name="jboss.tests:name=example1">
    <attribute name="BindingsConfig" serialDataType="jbxb">
        <jndi:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
                       xmlns:jndi="urn:jboss:jndi-binding-service"
                       xs:schemaLocation="urn:jboss:jndi-binding-service  \
		       resource:jndi-binding-service_1_0.xsd"> 
            <jndi:binding name="bindexample/message">
                <jndi:value trim="true">
                    Hello, JNDI!
                </jndi:value>
            </jndi:binding>
        </jndi:bindings>
    </attribute>
</mbean>

This binds the text string "Hello, JNDI!" under the JNDI name bindexample/message. An application would look up the value just as it would for any other JNDI value. The trim attribute specifies that leading and trailing whitespaces should be ignored. The use of the attribute here is purely for illustrative purposes as the default value is true.

InitialContext ctx  = new InitialContext();
String         text = (String) ctx.lookup("bindexample/message");

String values themselves are not that interesting. If a JavaBeans property editor is available, the desired class name can be specified using the type attribute

<jndi:binding name="urls/jboss-home">
    <jndi:value type="java.net.URL">http://www.jboss.org</jndi:value>
</jndi:binding>

The editor attribute can be used to specify a particular property editor to use.

<jndi:binding name="hosts/localhost">
    <jndi:value editor="org.jboss.util.propertyeditor.InetAddressEditor"> 
        127.0.0.1 
    </jndi:value>
</jndi:binding>

For more complicated structures, any JBossXB-ready schema may be used. The following example shows how a java.util.Properties object would be mapped.

<jndi:binding name="maps/testProps">
    <java:properties xmlns:java="urn:jboss:java-properties" 
                     xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
                     xs:schemaLocation="urn:jboss:java-properties \
		     resource:java-properties_1_0.xsd">
        <java:property>
            <java:key>key1</java:key>
            <java:value>value1</java:value>
        </java:property>
        <java:property>
            <java:key>key2</java:key>
            <java:value>value2</java:value>
        </java:property>
    </java:properties>
</jndi:binding>

For more information on JBossXB, see the JBossXB wiki page.

The ExternalContext MBean allows you to federate external JNDI contexts into the JBoss server JNDI namespace. The term external refers to any naming service external to the JBossNS naming service running inside of the JBoss server VM. You can incorporate LDAP servers, file systems, DNS servers, and so on, even if the JNDI provider root context is not serializable. The federation can be made available to remote clients if the naming service supports remote access.

To incorporate an external JNDI naming service, you have to add a configuration of the ExternalContext MBean service to the jboss-service.xml configuration file. The configurable attributes of the ExternalContext service are as follows:

  • JndiName : The JNDI name under which the external context is to be bound.

  • RemoteAccess : A boolean flag indicating if the external InitialContext should be bound using a Serializable form that allows a remote client to create the external InitialContext . When a remote client looks up the external context via the JBoss JNDI InitialContext, they effectively create an instance of the external InitialContext using the same env properties passed to the ExternalContext MBean. This will only work if the client can do a new InitialContext(env) remotely. This requires that the Context.PROVIDER_URL value of env is resolvable in the remote VM that is accessing the context. This should work for the LDAP example. For the file system example this most likely won't work unless the file system path refers to a common network path. If this property is not given it defaults to false.

  • CacheContext : The cacheContext flag. When set to true, the external Context is only created when the MBean is started and then stored as an in memory object until the MBean is stopped. If cacheContext is set to false, the external Context is created on each lookup using the MBean properties and InitialContext class. When the uncached Context is looked up by a client, the client should invoke close() on the Context to prevent resource leaks.

  • InitialContext : The fully qualified class name of the InitialContext implementation to use. Must be one of: javax.naming.InitialContext, javax.naming.directory.InitialDirContext or javax.naming.ldap.InitialLdapContext. In the case of the InitialLdapContext a null Controls array is used. The default is javax.naming.InitialContex.

  • Properties : The Properties attribute contains the JNDI properties for the external InitialContext. The input should be the text equivalent to what would go into a jndi.properties file.

  • PropertiesURL : This set the jndi.properties information for the external InitialContext from an external properties file. This is either a URL, string or a classpath resource name. Examples are as follows:

    • file:///config/myldap.properties

    • http://config.mycompany.com/myldap.properties

    • /conf/myldap.properties

    • myldap.properties

The MBean definition below shows a binding to an external LDAP context into the JBoss JNDI namespace under the name external/ldap/jboss.

<!-- Bind a remote LDAP server -->
<mbean code="org.jboss.naming.ExternalContext" 
       name="jboss.jndi:service=ExternalContext,jndiName=external/ldap/jboss">
    <attribute name="JndiName">external/ldap/jboss</attribute>
    <attribute name="Properties">
        java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
        java.naming.provider.url=ldap://ldaphost.jboss.org:389/o=jboss.org
        java.naming.security.principal=cn=Directory Manager
        java.naming.security.authentication=simple
        java.naming.security.credentials=secret
    </attribute>
    <attribute name="InitialContext"> javax.naming.ldap.InitialLdapContext </attribute>
    <attribute name="RemoteAccess">true</attribute>
</mbean>

With this configuration, you can access the external LDAP context located at ldap://ldaphost.jboss.org:389/o=jboss.org from within the JBoss VM using the following code fragment:

InitialContext iniCtx = new InitialContext();
LdapContext ldapCtx = iniCtx.lookup("external/ldap/jboss");

Using the same code fragment outside of the JBoss server VM will work in this case because the RemoteAccess property was set to true. If it were set to false, it would not work because the remote client would receive a Reference object with an ObjectFactory that would not be able to recreate the external InitialContext

<!-- Bind the /usr/local file system directory  -->
<mbean code="org.jboss.naming.ExternalContext" 
       name="jboss.jndi:service=ExternalContext,jndiName=external/fs/usr/local">
    <attribute name="JndiName">external/fs/usr/local</attribute>
    <attribute name="Properties">
        java.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory
        java.naming.provider.url=file:///usr/local
    </attribute>
    <attribute name="InitialContext">javax.naming.IntialContext</attribute>
</mbean>

This configuration describes binding a local file system directory /usr/local into the JBoss JNDI namespace under the name external/fs/usr/local.

With this configuration, you can access the external file system context located at file:///usr/local from within the JBoss VM using the following code fragment:

InitialContext iniCtx = new InitialContext();
                Context ldapCtx = iniCtx.lookup("external/fs/usr/local");

Note that you need to use the Sun JNDI service providers, which must be downloaded from http://java.sun.com/products/jndi/serviceproviders.html. The provider JARs should be placed in the server configuration lib directory.

JNDI is a fundamental aspect of the J2EE specifications. One key usage is the isolation of J2EE component code from the environment in which the code is deployed. Use of the application component's environment allows the application component to be customized without the need to access or change the application component's source code. The application component environment is referred to as the ENC, the enterprise naming context. It is the responsibility of the application component container to make an ENC available to the container components in the form of JNDI Context. The ENC is utilized by the participants involved in the life cycle of a J2EE component in the following ways.

  • Application component business logic should be coded to access information from its ENC. The component provider uses the standard deployment descriptor for the component to specify the required ENC entries. The entries are declarations of the information and resources the component requires at runtime.

  • The container provides tools that allow a deployer of a component to map the ENC references made by the component developer to the deployment environment entity that satisfies the reference.

  • The component deployer utilizes the container tools to ready a component for final deployment.

  • The component container uses the deployment package information to build the complete component ENC at runtime

The complete specification regarding the use of JNDI in the J2EE platform can be found in section 5 of the J2EE 1.4 specification. The J2EE specification is available at http://java.sun.com/j2ee/download.html.

An application component instance locates the ENC using the JNDI API. An application component instance creates a javax.naming.InitialContext object by using the no argument constructor and then looks up the naming environment under the name java:comp/env. The application component's environment entries are stored directly in the ENC, or in its subcontexts. Example 4.4, “ENC access sample code” illustrates the prototypical lines of code a component uses to access its ENC.


An application component environment is a local environment that is accessible only by the component when the application server container thread of control is interacting with the application component. This means that an EJB Bean1 cannot access the ENC elements of EJB Bean2, and vice versa. Similarly, Web application Web1 cannot access the ENC elements of Web application Web2 or Bean1 or Bean2 for that matter. Also, arbitrary client code, whether it is executing inside of the application server VM or externally cannot access a component's java:comp JNDI context. The purpose of the ENC is to provide an isolated, read-only namespace that the application component can rely on regardless of the type of environment in which the component is deployed. The ENC must be isolated from other components because each component defines its own ENC content. Components A and B, for example, may define the same name to refer to different objects. For example, EJB Bean1 may define an environment entry java:comp/env/red to refer to the hexadecimal value for the RGB color for red, while Web application Web1 may bind the same name to the deployment environment language locale representation of red.

There are three commonly used levels of naming scope in JBoss: names under java:comp, names under java:, and any other name. As discussed, the java:comp context and its subcontexts are only available to the application component associated with that particular context. Subcontexts and object bindings directly under java: are only visible within the JBoss server virtual machine and not to remote clients. Any other context or object binding is available to remote clients, provided the context or object supports serialization. You'll see how the isolation of these naming scopes is achieved in the Section 4.2, “The JBossNS Architecture”.

An example of where the restricting a binding to the java: context is useful would be a javax.sql.DataSource connection factory that can only be used inside of the JBoss server where the associated database pool resides. On the other hand, an EJB home interface would be bound to a globally visible name that should accessible by remote client.

JNDI is used as the API for externalizing a great deal of information from an application component. The JNDI name that the application component uses to access the information is declared in the standard ejb-jar.xml deployment descriptor for EJB components, and the standard web.xml deployment descriptor for Web components. Several different types of information may be stored in and retrieved from JNDI including:

  • Environment entries as declared by the env-entry elements

  • EJB references as declared by ejb-ref and ejb-local-ref elements.

  • Resource manager connection factory references as declared by the resource-ref elements

  • Resource environment references as declared by the resource-env-ref elements

Each type of deployment descriptor element has a JNDI usage convention with regard to the name of the JNDI context under which the information is bound. Also, in addition to the standard deploymentdescriptor element, there is a JBoss server specific deployment descriptor element that maps the JNDI name as used by the application component to the deployment environment JNDI name.

Environment entries are the simplest form of information stored in a component ENC, and are similar to operating system environment variables like those found on UNIX or Windows. Environment entries are a name-to-value binding that allows a component to externalize a value and refer to the value using a name.

An environment entry is declared using an env-entry element in the standard deployment descriptors. The env-entry element contains the following child elements:

  • An optional description element that provides a description of the entry

  • An env-entry-name element giving the name of the entry relative to java:comp/env

  • An env-entry-type element giving the Java type of the entry value that must be one of:

    • java.lang.Byte

    • java.lang.Boolean

    • java.lang.Character

    • java.lang.Double

    • java.lang.Float

    • java.lang.Integer

    • java.lang.Long

    • java.lang.Short

    • java.lang.String

  • An env-entry-value element giving the value of entry as a string

An example of an env-entry fragment from an ejb-jar.xml deployment descriptor is given in Example 4.5, “An example ejb-jar.xml env-entry fragment”. There is no JBoss specific deployment descriptor element because an env-entry is a complete name and value specification. Example 4.6, “ENC env-entry access code fragment” shows a sample code fragment for accessing the maxExemptions and taxRate env-entry values declared in the deployment descriptor.



It is common for EJBs and Web components to interact with other EJBs. Because the JNDI name under which an EJB home interface is bound is a deployment time decision, there needs to be a way for a component developer to declare a reference to an EJB that will be linked by the deployer. EJB references satisfy this requirement.

An EJB reference is a link in an application component naming environment that points to a deployed EJB home interface. The name used by the application component is a logical link that isolates the component from the actual name of the EJB home in the deployment environment. The J2EE specification recommends that all references to enterprise beans be organized in the java:comp/env/ejb context of the application component's environment.

An EJB reference is declared using an ejb-ref element in the deployment descriptor. Each ejb-ref element describes the interface requirements that the referencing application component has for the referenced enterprise bean. The ejb-ref element contains the following child elements:

  • An optional description element that provides the purpose of the reference.

  • An ejb-ref-name element that specifies the name of the reference relative to the java:comp/env context. To place the reference under the recommended java:comp/env/ejb context, use an ejb/link-name form for the ejb-ref-name value.

  • An ejb-ref-type element that specifies the type of the EJB. This must be either Entity or Session.

  • A home element that gives the fully qualified class name of the EJB home interface.

  • A remote element that gives the fully qualified class name of the EJB remote interface.

  • An optional ejb-link element that links the reference to another enterprise bean in the same EJB JAR or in the same J2EE application unit. The ejb-link value is the ejb-name of the referenced bean. If there are multiple enterprise beans with the same ejb-name, the value uses the path name specifying the location of the ejb-jar file that contains the referenced component. The path name is relative to the referencing ejb-jar file. The Application Assembler appends the ejb-name of the referenced bean to the path name separated by #. This allows multiple beans with the same name to be uniquely identified.

An EJB reference is scoped to the application component whose declaration contains the ejb-ref element. This means that the EJB reference is not accessible from other application components at runtime, and that other application components may define ejb-ref elements with the same ejb-ref-name without causing a name conflict. Example 4.7, “An example ejb-jar.xml ejb-ref descriptor fragment” provides an ejb-jar.xml fragment that illustrates the use of the ejb-ref element. A code sample that illustrates accessing the ShoppingCartHome reference declared in Example 4.7, “An example ejb-jar.xml ejb-ref descriptor fragment” is given in Example 4.8, “ENC ejb-ref access code fragment”.



The JBoss specific jboss.xml EJB deployment descriptor affects EJB references in two ways. First, the jndi-name child element of the session and entity elements allows the user to specify the deployment JNDI name for the EJB home interface. In the absence of a jboss.xml specification of the jndi-name for an EJB, the home interface is bound under the ejb-jar.xml ejb-name value. For example, the session EJB with the ejb-name of ShoppingCartBean in Example 4.7, “An example ejb-jar.xml ejb-ref descriptor fragment” would have its home interface bound under the JNDI name ShoppingCartBean in the absence of a jboss.xml jndi-name specification.

The second use of the jboss.xml descriptor with respect to ejb-refs is the setting of the destination to which a component's ENC ejb-ref refers. The ejb-link element cannot be used to refer to EJBs in another enterprise application. If your ejb-ref needs to access an external EJB, you can specify the JNDI name of the deployed EJB home using the jboss.xml ejb-ref/jndi-name element.

The jboss-web.xml descriptor is used only to set the destination to which a Web application ENC ejb-ref refers. The content model for the JBoss ejb-ref is as follows:

  • An ejb-ref-name element that corresponds to the ejb-ref-name element in the ejb-jar.xml or web.xml standard descriptor

  • A jndi-name element that specifies the JNDI name of the EJB home interface in the deployment environment

Example 4.9, “An example jboss.xml ejb-ref fragment” provides an example jboss.xml descriptor fragment that illustrates the following usage points:

  • The ProductBeanUser ejb-ref link destination is set to the deployment name of jboss/store/ProductHome

  • The deployment JNDI name of the ProductBean is set to jboss/store/ProductHome


EJB 2.0 added local interfaces that do not use RMI call by value semantics. These interfaces use a call by reference semantic and therefore do not incur any RMI serialization overhead. An EJB local reference is a link in an application component naming environment that points to a deployed EJB local home interface. The name used by the application component is a logical link that isolates the component from the actual name of the EJB local home in the deployment environment. The J2EE specification recommends that all references to enterprise beans be organized in the java:comp/env/ejb context of the application component's environment.

An EJB local reference is declared using an ejb-local-ref element in the deployment descriptor. Each ejb-local-ref element describes the interface requirements that the referencing application component has for the referenced enterprise bean. The ejb-local-ref element contains the following child elements:

  • An optional description element that provides the purpose of the reference.

  • An ejb-ref-name element that specifies the name of the reference relative to the java:comp/env context. To place the reference under the recommended java:comp/env/ejb context, use an ejb/link-name form for the ejb-ref-name value.

  • An ejb-ref-type element that specifies the type of the EJB. This must be either Entity or Session.

  • A local-home element that gives the fully qualified class name of the EJB local home interface.

  • A local element that gives the fully qualified class name of the EJB local interface.

  • An ejb-link element that links the reference to another enterprise bean in the ejb-jar file or in the same J2EE application unit. The ejb-link value is the ejb-name of the referenced bean. If there are multiple enterprise beans with the same ejb-name, the value uses the path name specifying the location of the ejb-jar file that contains the referenced component. The path name is relative to the referencing ejb-jar file. The Application Assembler appends the ejb-name of the referenced bean to the path name separated by #. This allows multiple beans with the same name to be uniquely identified. An ejb-link element must be specified in JBoss to match the local reference to the corresponding EJB.

An EJB local reference is scoped to the application component whose declaration contains the ejb-local-ref element. This means that the EJB local reference is not accessible from other application components at runtime, and that other application components may define ejb-local-ref elements with the same ejb-ref-name without causing a name conflict. Example 4.10, “An example ejb-jar.xml ejb-local-ref descriptor fragment” provides an ejb-jar.xml fragment that illustrates the use of the ejb-local-ref element. A code sample that illustrates accessing the ProbeLocalHome reference declared in Example 4.10, “An example ejb-jar.xml ejb-local-ref descriptor fragment” is given in Example 4.11, “ENC ejb-local-ref access code fragment”.



Resource manager connection factory references allow application component code to refer to resource factories using logical names called resource manager connection factory references. Resource manager connection factory references are defined by the resource-ref elements in the standard deployment descriptors. The Deployer binds the resource manager connection factory references to the actual resource manager connection factories that exist in the target operational environment using the jboss.xml and jboss-web.xml descriptors.

Each resource-ref element describes a single resource manager connection factory reference. The resource-ref element consists of the following child elements:

  • An optional description element that provides the purpose of the reference.

  • A res-ref-name element that specifies the name of the reference relative to the java:comp/env context. The resource type based naming convention for which subcontext to place the res-ref-name into is discussed in the next paragraph.

  • A res-type element that specifies the fully qualified class name of the resource manager connection factory.

  • A res-auth element that indicates whether the application component code performs resource signon programmatically, or whether the container signs on to the resource based on the principal mapping information supplied by the Deployer. It must be one of Application or Container.

  • An optional res-sharing-scope element. This currently is not supported by JBoss.

The J2EE specification recommends that all resource manager connection factory references be organized in the subcontexts of the application component's environment, using a different subcontext for each resource manager type. The recommended resource manager type to subcontext name is as follows:

  • JDBC DataSource references should be declared in the java:comp/env/jdbc subcontext.

  • JMS connection factories should be declared in the java:comp/env/jms subcontext.

  • JavaMail connection factories should be declared in the java:comp/env/mail subcontext.

  • URL connection factories should be declared in the java:comp/env/url subcontext.

Example 4.12, “A web.xml resource-ref descriptor fragment” shows an example web.xml descriptor fragment that illustrates the resource-ref element usage. Example 4.13, “ENC resource-ref access sample code fragment” provides a code fragment that an application component would use to access the DefaultMail resource declared by the resource-ref.



The purpose of the JBoss jboss.xml EJB deployment descriptor and jboss-web.xml Web application deployment descriptor is to provide the link from the logical name defined by the res-ref-name element to the JNDI name of the resource factory as deployed in JBoss. This is accomplished by providing a resource-ref element in the jboss.xml or jboss-web.xml descriptor. The JBoss resource-ref element consists of the following child elements:

  • A res-ref-name element that must match the res-ref-name of a corresponding resource-ref element from the ejb-jar.xml or web.xml standard descriptors

  • An optional res-type element that specifies the fully qualified class name of the resource manager connection factory

  • A jndi-name element that specifies the JNDI name of the resource factory as deployed in JBoss

  • A res-url element that specifies the URL string in the case of a resource-ref of type java.net.URL

Example 4.14, “A sample jboss-web.xml resource-ref descriptor fragment” provides a sample jboss-web.xml descriptor fragment that shows sample mappings of the resource-ref elements given in Example 4.12, “A web.xml resource-ref descriptor fragment”.


Resource environment references are elements that refer to administered objects that are associated with a resource (for example, JMS destinations) using logical names. Resource environment references are defined by the resource-env-ref elements in the standard deployment descriptors. The Deployer binds the resource environment references to the actual administered objects location in the target operational environment using the jboss.xml and jboss-web.xml descriptors.

Each resource-env-ref element describes the requirements that the referencing application component has for the referenced administered object. The resource-env-ref element consists of the following child elements:

  • An optional description element that provides the purpose of the reference.

  • A resource-env-ref-name element that specifies the name of the reference relative to the java:comp/env context. Convention places the name in a subcontext that corresponds to the associated resource factory type. For example, a JMS queue reference named MyQueue should have a resource-env-ref-name of jms/MyQueue.

  • A resource-env-ref-type element that specifies the fully qualified class name of the referenced object. For example, in the case of a JMS queue, the value would be javax.jms.Queue.

Example 4.15, “An example ejb-jar.xml resource-env-ref fragment” provides an example resource-ref-env element declaration by a session bean. Example 4.16, “ENC resource-env-ref access code fragment” gives a code fragment that illustrates how to look up the StockInfo queue declared by the resource-env-ref.



This chapter discusses the JBoss server implementation of the J2EE Connector Architecture (JCA). JCA is a resource manager integration API whose goal is to standardize access to non-relational resources in the same way the JDBC API standardized access to relational data. The purpose of this chapter is to introduce the utility of the JCA APIs and then describe the architecture of JCA in JBoss

J2EE 1.4 contains a connector architecture (JCA) specification that allows for the integration of transacted and secure resource adaptors into a J2EE application server environment. The JCA specification describes the notion of such resource managers as Enterprise Information Systems (EIS). Examples of EIS systems include enterprise resource planning packages, mainframe transaction processing, non-Java legacy applications, etc.

The reason for focusing on EIS is primarily because the notions of transactions, security, and scalability are requirements in enterprise software systems. However, the JCA is applicable to any resource that needs to integrate into JBoss in a secure, scalable and transacted manner. In this introduction we will focus on resource adapters as a generic notion rather than something specific to the EIS environment.

The connector architecture defines a standard SPI (Service Provider Interface) for integrating the transaction, security and connection management facilities of an application server with those of a resource manager. The SPI defines the system level contract between the resource adaptor and the application server.

The connector architecture also defines a Common Client Interface (CCI) for accessing resources. The CCI is targeted at EIS development tools and other sophisticated users of integrated resources. The CCI provides a way to minimize the EIS specific code required by such tools. Typically J2EE developers will access a resource using such a tool, or a resource specific interface rather than using CCI directly. The reason is that the CCI is not a type specific API. To be used effectively it must be used in conjunction with metadata that describes how to map from the generic CCI API to the resource manager specific data types used internally by the resource manager.

The purpose of the connector architecture is to enable a resource vendor to provide a standard adaptor for its product. A resource adaptor is a system-level software driver that is used by a Java application to connect to resource. The resource adaptor plugs into an application server and provides connectivity between the resource manager, the application server, and the enterprise application. A resource vendor need only implement a JCA compliant adaptor once to allow use of the resource manager in any JCA capable application server.

An application server vendor extends its architecture once to support the connector architecture and is then assured of seamless connectivity to multiple resource managers. Likewise, a resource manager vendor provides one standard resource adaptor and it has the capability to plug in to any application server that supports the connector architecture.


Figure 5.1, “The relationship between a J2EE application server and a JCA resource adaptor” illustrates that the application server is extended to provide support for the JCA SPI to allow a resource adaptor to integrate with the server connection pooling, transaction management and security management facilities. This integration API defines a three-part system contract.

  • Connection management : a contract that allows the application server to pool resource connections. The purpose of the pool management is to allow for scalability. Resource connections are typically expense objects to create and pooling them allows for more effective reuse and management.

  • Transaction Management : a contract that allows the application server transaction manager to manage transactions that engage resource managers.

  • Security Management : a contract that enables secured access to resource managers.

The resource adaptor implements the resource manager side of the system contract. This entails using the application server connection pooling, providing transaction resource information and using the security integration information. The resource adaptor also exposes the resource manager to the application server components. This can be done using the CCI and/or a resource adaptor specific API.

The application component integrates into the application server using a standard J2EE container to component contract. For an EJB component this contract is defined by the EJB specification. The application component interacts with the resource adaptor in the same way as it would with any other standard resource factory, for example, a javax.sql.DataSource JDBC resource factory. The only difference with a JCA resource adaptor is that the client has the option of using the resource adaptor independent CCI API if the resource adaptor supports this.

Figure 5.2, “The JCA 1.0 specification class diagram for the connection management architecture.” (from the JCA 1.5 specification) illustrates the relationship between the JCA architecture participants in terms of how they relate to the JCA SPI, CCI and JTA packages.


The JBossCX architecture provides the implementation of the application server specific classes. Figure 5.2, “The JCA 1.0 specification class diagram for the connection management architecture.” shows that this comes down to the implementation of the javax.resource.spi.ConnectionManager and javax.resource.spi.ConnectionEventListener interfaces. The key aspects of this implementation are discussed in the following section on the JBossCX architecture.

The JBossCX framework provides the application server architecture extension required for the use of JCA resource adaptors. This is primarily a connection pooling and management extension along with a number of MBeans for loading resource adaptors into the JBoss server.

There are three coupled MBeans that make up a RAR deployment. These are the org.jboss.resource.deployment.RARDeployment, org.jboss.resource.connectionmanager.RARDeployment, and org.jboss.resource.connectionmanager.BaseConnectionManager2. The org.jboss.resource.deployment.RARDeployment is simply an encapsulation of the metadata of a RAR META-INF/ra.xml descriptor. It exposes this information as a DynamicMBean simply to make it available to the org.jboss.resource.connectionmanager.RARDeployment MBean.

The RARDeployer service handles the deployment of archives files containing resource adaptors (RARs). It creates the org.jboss.resource.deployment.RARDeployment MBeans when a RAR file is deployed. Deploying the RAR file is the first step in making the resource adaptor available to application components. For each deployed RAR, one or more connection factories must be configured and bound into JNDI. This task performed using a JBoss service descriptor that sets up a org.jboss.resource.connectionmanager.BaseConnectionManager2 MBean implementation with a org.jboss.resource.connectionmgr.RARDeployment dependent.

The org.jboss.resource.connectionmanager.BaseConnectionManager2 MBean is a base class for the various types of connection managers required by the JCA spec. Subclasses include NoTxConnectionManager, LocalTxConnectionManager and XATxConnectionManager. These correspond to resource adaptors that support no transactions, local transaction and XA transaction respectively. You choose which subclass to use based on the type of transaction semantics you want, provided the JCA resource adaptor supports the corresponding transaction capability.

The common attributes supported by the BaseConnectionManager2 MBean are:

  • ManagedConnectionPool : This specifies the ObjectName of the MBean representing the pool for this connection manager. The MBean must have an ManagedConnectionPool attribute that is an implementation of the org.jboss.resource.connectionmanager.ManagedConnectionPool interface. Normally it will be an embedded MBean in a depends tag rather than an ObjectName reference to an existing MBean. The default MBean for use is the org.jboss.resource.connectionmanager.JBossManagedConnectionPool. Its configurable attributes are discussed below.

  • CachedConnectionManager : This specifies the ObjectName of the CachedConnectionManager MBean implementation used by the connection manager. Normally this is specified using a depends tag with the ObjectName of the unique CachedConnectionManager for the server. The name jboss.jca:service=CachedConnectionManager is the standard setting to use.

  • SecurityDomainJndiName : This specifies the JNDI name of the security domain to use for authentication and authorization of resource connections. This is typically of the form java:/jaas/<domain> where the <domain> value is the name of an entry in the conf/login-config.xml JAAS login module configuration file. This defines which JAAS login modules execute to perform authentication.

  • JaasSecurityManagerService : This is the ObjectName of the security manager service. This should be set to the security manager MBean name as defined in the conf/jboss-service.xml descriptor, and currently this is jboss.security:service=JaasSecurityManager. This attribute will likely be removed in the future.

The org.jboss.resource.connectionmanager.RARDeployment MBean manages configuration and instantiation ManagedConnectionFactory instance. It does this using the resource adaptor metadata settings from the RAR META-INF/ra.xml descriptor along with the RARDeployment attributes. The configurable attributes are:

  • OldRarDeployment : This is the ObjectName of the org.jboss.resource.RarDeployment MBean that contains the resource adaptor metadata. The form of this name is jboss.jca:service=RARDeployment,name=<ra-display-name> where the <ra-display-name> is the ra.xml descriptor display-name attribute value. The RARDeployer creates this when it deploys a RAR file. This attribute will likely be removed in the future.

  • ManagedConnectionFactoryProperties : This is a collection of (name, type, value) triples that define attributes of the ManagedConnectionFactory instance. Therefore, the names of the attributes depend on the resource adaptor ManagedConnectionFactory instance. The following example shows the structure of the content of this attribute.

    <properties>
        <config-property>
            <config-property-name>Attr0Name</config-property-name>
            <config-property-type>Attr0Type</config-property-type>
            <config-property-value>Attr0Value</config-property-value>
        </config-property>
        <config-property>
            <config-property-name>Attr1Name</config-property-name>
            <config-property-type>Attr2Type</config-property-type>
            <config-property-value>Attr2Value</config-property-value>
        </config-property> 
        ...
    </properties>  
    

    AttrXName is the Xth attribute name, AttrXType is the fully qualified Java type of the attribute, and AttrXValue is the string representation of the value. The conversion from string to AttrXType is done using the java.beans.PropertyEditor class for the AttrXType.

  • JndiName : This is the JNDI name under which the resource adaptor will be made available. Clients of the resource adaptor use this name to obtain either the javax.resource.cci.ConnectionFactory or resource adaptor specific connection factory. The full JNDI name will be java:/<JndiName> meaning that the JndiName attribute value will be prefixed with java:/. This prevents use of the connection factory outside of the JBoss server VM. In the future this restriction may be configurable.

The org.jboss.resource.connectionmanager.JBossManagedConnectionPool MBean is a connection pooling MBean. It is typically used as the embedded MBean value of the BaseConnectionManager2 ManagedConnectionPool attribute. When you setup a connection manager MBean you typically embed the pool configuration in the connection manager descriptor. The configurable attributes of the JBossManagedConnectionPool are:

  • ManagedConnectionFactoryName : This specifies the ObjectName of the MBean that creates javax.resource.spi.ManagedConnectionFactory instances. Normally this is configured as an embedded MBean in a depends element rather than a separate MBean reference using the RARDeployment MBean. The MBean must provide an appropriate startManagedConnectionFactory operation.

  • MinSize : This attribute indicates the minimum number of connections this pool should hold. These are not created until a Subject is known from a request for a connection. MinSize connections will be created for each sub-pool.

  • MaxSize : This attribute indicates the maximum number of connections for a pool. No more than MaxSize connections will be created in each sub-pool.

  • BlockingTimeoutMillis : This attribute indicates the maximum time to block while waiting for a connection before throwing an exception. Note that this blocks only while waiting for a permit for a connection, and will never throw an exception if creating a new connection takes an inordinately long time.

  • IdleTimeoutMinutes : This attribute indicates the maximum time a connection may be idle before being closed. The actual maximum time depends also on the idle remover thread scan time, which is 1/2 the smallest idle timeout of any pool.

  • NoTxSeparatePools : Setting this to true doubles the available pools. One pool is for connections used outside a transaction the other inside a transaction. The actual pools are lazily constructed on first use. This is only relevant when setting the pool parameters associated with the LocalTxConnectionManager and XATxConnectionManager. Its use case is for Oracle (and possibly other vendors) XA implementations that don't like using an XA connection with and without a JTA transaction.

  • Criteria : This attribute indicates if the JAAS javax.security.auth.Subject from security domain associated with the connection, or app supplied parameters (such as from getConnection(user, pw)) are used to distinguish connections in the pool. The allowed values are:

    • ByContainer : use Subject

    • ByApplication : use application supplied parameters only

    • ByContainerAndApplication : use both

    • ByNothing : all connections are equivalent, usually if adapter supports reauthentication

To conclude our discussion of the JBoss JCA framework we will create and deploy a single non-transacted resource adaptor that simply provides a skeleton implementation that stubs out the required interfaces and logs all method calls. We will not discuss the details of the requirements of a resource adaptor provider as these are discussed in detail in the JCA specification. The purpose of the adaptor is to demonstrate the steps required to create and deploy a RAR in JBoss, and to see how JBoss interacts with the adaptor.

The adaptor we will create could be used as the starting point for a non-transacted file system adaptor. The source to the example adaptor can be found in the src/main/org/jboss/book/jca/ex1 directory of the book examples. A class diagram that shows the mapping from the required javax.resource.spi interfaces to the resource adaptor implementation is given in Figure 5.3, “The file system RAR class diagram”.


We will build the adaptor, deploy it to the JBoss server and then run an example client against an EJB that uses the resource adaptor to demonstrate the basic steps in a complete context. We'll then take a look at the JBoss server log to see how the JBoss JCA framework interacts with the resource adaptor to help you better understand the components in the JCA system level contract.

To build the example and deploy the RAR to the JBoss server deploy/lib directory, execute the following Ant command in the book examples directory.

[examples]$ ant -Dchap=jca build-chap

The deployed files include a jca-ex1.sar and a notxfs-service.xml service descriptor. The example resource adaptor deployment descriptor is shown in Example 5.1, “The nontransactional file system resource adaptor deployment descriptor.”.

<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/"Whats_new_in_JBoss_4-J2EE_Certification_and_Standards_Compliance"
    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/connector_1_5.xsd" version="1.5">
    <display-name>File System Adapter</display-name>
    <vendor-name>JBoss</vendor-name>
    <eis-type>FileSystem</eis-type>
    <resourceadapter-version>1.0</resourceadapter-version>
    <license>
        <description>LGPL</description>
        <license-required>false</license-required>
    </license>
    <resourceadapter>
        <resourceadapter-class>
            org.jboss.resource.deployment.DummyResourceAdapter
        </resourceadapter-class>
        <outbound-resourceadapter>
            <connection-definition>
                
                                 <managedconnectionfactory-class> org.jboss.book.jca.ex1.ra.FSManagedConnectionFactory </managedconnectionfactory-class>
                              
                <config-property>
                    <config-property-name>FileSystemRootDir</config-property-name>
                    <config-property-type>java.lang.String</config-property-type>
                    <config-property-value>/tmp/db/fs_store</config-property-value>
                </config-property>
                <config-property>
                    <config-property-name>UserName</config-property-name>
                    <config-property-type>java.lang.String</config-property-type>
                    <config-property-value/>
                </config-property>
                <config-property>
                    <config-property-name>Password</config-property-name>
                    <config-property-type>java.lang.String</config-property-type>
                    <config-property-value/>
                </config-property>
                
                                 <connectionfactory-interface> org.jboss.book.jca.ex1.ra.DirContextFactory </connectionfactory-interface> <connectionfactory-impl-class> org.jboss.book.jca.ex1.ra.DirContextFactoryImpl </connectionfactory-impl-class> <connection-interface> javax.naming.directory.DirContext </connection-interface> <connection-impl-class> org.jboss.book.jca.ex1.ra.FSDirContext </connection-impl-class>
                              
            </connection-definition>
            <transaction-support>NoTransaction</transaction-support>
            <authentication-mechanism>
                <authentication-mechanism-type>BasicPassword</authentication-mechanism-type>
                <credential-interface>
                    javax.resource.spi.security.PasswordCredential
                </credential-interface>
            </authentication-mechanism>
            
                                 <reauthentication-support>true</reauthentication-support>
                              
        </outbound-resourceadapter>
        <security-permission>
            <description> Read/Write access is required to the contents of the
                FileSystemRootDir </description>
            <security-permission-spec> permission java.io.FilePermission
                "/tmp/db/fs_store/*", "read,write"; 
            </security-permission-spec>
        </security-permission>
    </resourceadapter>
</connector>

Example 5.1. The nontransactional file system resource adaptor deployment descriptor.


The key items in the resource adaptor deployment descriptor are highlighted in bold. These define the classes of the resource adaptor, and the elements are:

  • managedconnectionfactory-class : The implementation of the ManagedConnectionFactory interface, org.jboss.book.jca.ex1.ra.FSManagedConnectionFactory

  • connectionfactory-interface : This is the interface that clients will obtain when they lookup the connection factory instance from JNDI, here a proprietary resource adaptor value, org.jboss.book.jca.ex1.ra.DirContextFactory. This value will be needed when we create the JBoss ds.xml to use the resource.

  • connectionfactory-impl-class : This is the class that provides the implementation of the connectionfactory-interface, org.jboss.book.jca.ex1.ra.DirContextFactoryImpl.

  • connection-interface : This is the interface for the connections returned by the resource adaptor connection factory, here the JNDI javax.naming.directory.DirContext interface.

  • connection-impl-class : This is he class that provides the connection-interface implementation, org.jboss.book.jca.ex1.ra.FSDirContext.

  • transaction-support : The level of transaction support, here defined as NoTransaction, meaning the file system resource adaptor does not do transactional work.

The RAR classes and deployment descriptor only define a resource adaptor. To use the resource adaptor it must be integrated into the JBoss application server using a ds.xml descriptor file. An example of this for the file system adaptor is shown in Example 5.2, “The notxfs-ds.xml resource adaptor MBeans service descriptor.”.


The main attributes are:

  • jndi-name : This specifies where the connection factory will be bound into JNDI. For this deployment that binding will be java:/NoTransFS.

  • rar-name : This is the name of the RAR file that contains the definition for the resource we want to provide. For nested RAR files, the name would look like myapplication.ear#my.rar. In this example, it is simply jca-ex1.rar.

  • connection-definition : This is the connection factory interface class. It should match the connectionfactory-interface in the ra.xml file. Here our connection factory interface is org.jboss.book.jca.ex1.ra.DirContextFactory.

  • config-property : This can be used to provide non-default settings to the resource adaptor connection factory. Here the FileSystemRootDir is being set to /tmp/db/fs_store. This overrides the default value in the ra.xml file.

To deploy the RAR and connection manager configuration to the JBoss server, run the following:

[examples]$ ant -Dchap=jca config

The server console will display some logging output indicating that the resource adaptor has been deployed.

Now we want to test access of the resource adaptor by a J2EE component. To do this we have created a trivial stateless session bean that has a single method called echo. Inside of the echo method the EJB accesses the resource adaptor connection factory, creates a connection, and then immediately closes the connection. The echo method code is shown below.


The EJB is not using the CCI interface to access the resource adaptor. Rather, it is using the resource adaptor specific API based on the proprietary DirContextFactory interface that returns a JNDI DirContext object as the connection object. The example EJB is simply exercising the system contract layer by looking up the resource adaptor connection factory, creating a connection to the resource and closing the connection. The EJB does not actually do anything with the connection, as this would only exercise the resource adaptor implementation since this is a non-transactional resource.

Run the test client which calls the EchoBean.echo method by running Ant as follows from the examples directory:

[examples]$ ant -Dchap=jca -Dex=1 run-example

You'll see some output from the bean in the system console, but much more detailed logging output can be found in the server/production/log/server.log file. Don't worry if you see exceptions. They are just stack traces to highlight the call path into parts of the adaptor. To help understand the interaction between the adaptor and the JBoss JCA layer, we'll summarize the events seen in the log using a sequence diagram. Figure 5.4, “A sequence diagram illustrating the key interactions between the JBossCX framework and the example resource adaptor that result when the EchoBean accesses the resource adaptor connection factory.” is a sequence diagram that summarizes the events that occur when the EchoBean accesses the resource adaptor connection factory from JNDI and creates a connection.


The starting point is the client's invocation of the EchoBean.echo method. For the sake of conciseness of the diagram, the client is shown directly invoking the EchoBean.echo method when in reality the JBoss EJB container handles the invocation. There are three distinct interactions between the EchoBean and the resource adaptor; the lookup of the connection factory, the creation of a connection, and the close of the connection.

The lookup of the resource adaptor connection factory is illustrated by the 1.1 sequences of events. The events are:

  • 1, the echo method invokes the getConnection method on the resource adaptor connection factory obtained from the JNDI lookup on the java:comp/env/ra/DirContextFactory name which is a link to the java:/NoTransFS location.

  • 1.1, the DirContextFactoryImpl class asks its associated ConnectionManager to allocate a connection. It passes in the ManagedConnectionFactory and FSRequestInfo that were associated with the DirContextFactoryImpl during its construction.

  • 1.1.1, the ConnectionManager invokes its getManagedConnection method with the current Subject and FSRequestInfo.

  • 1.1.1.1, the ConnectionManager asks its object pool for a connection object. The JBossManagedConnectionPool$BasePool is get the key for the connection and then asks the matching InternalPool for a connection.

  • 1.1.1.1.1, Since no connections have been created the pool must create a new connection. This is done by requesting a new managed connection from the ManagedConnectionFactory. The Subject associated with the pool as well as the FSRequestInfo data are passed as arguments to the createManagedConnection method invocation.

  • 1.1.1.1.1.1, the ConnectionFactory creates a new FSManagedConnection instance and passes in the Subject and FSRequestInfo data.

  • 1.1.1.2, a javax.resource.spi.ConnectionListener instance is created. The type of listener created is based on the type of ConnectionManager. In this case it is an org.jboss.resource.connectionmgr.BaseConnectionManager2$NoTransactionListener instance.

  • 1.1.1.2.1, the listener registers as a javax.resource.spi.ConnectionEventListener with the ManagedConnection instance created in 1.2.1.1.

  • 1.1.2, the ManagedConnection is asked for the underlying resource manager connection. The Subject and FSRequestInfo data are passed as arguments to the getConnection method invocation.

  • The resulting connection object is cast to a javax.naming.directory.DirContext instance since this is the public interface defined by the resource adaptor.

  • After the EchoBean has obtained the DirContext for the resource adaptor, it simply closes the connection to indicate its interaction with the resource manager is complete.

This concludes the resource adaptor example. Our investigation into the interaction between the JBossCX layer and a trivial resource adaptor should give you sufficient understanding of the steps required to configure any resource adaptor. The example adaptor can also serve as a starting point for the creation of your own custom resource adaptors if you need to integrate non-JDBC resources into the JBoss server environment.

Rather than configuring the connection manager factory related MBeans discussed in the previous section via a mbean services deployment descriptor, JBoss provides a simplified datasource centric descriptor. This is transformed into the standard jboss-service.xml MBean services deployment descriptor using a XSL transform applied by the org.jboss.deployment.XSLSubDeployer included in the jboss-jca.sar deployment. The simplified configuration descriptor is deployed the same as other deployable components. The descriptor must be named using a *-ds.xml pattern in order to be recognized by the XSLSubDeployer.

The schema for the top-level datasource elements of the *-ds.xml configuration deployment file is shown in Figure 5.5, “The simplified JCA DataSource configuration descriptor top-level schema elements”.


Multiple datasource configurations may be specified in a configuration deployment file. The child elements of the datasources root are:






Elements that are common to all datasources include:

  • jndi-name : The JNDI name under which the DataSource wrapper will be bound. Note that this name is relative to the java:/ context, unless use-java-context is set to false. DataSource wrappers are not usable outside of the server VM, so they are normally bound under the java:/, which isn't shared outside the local VM.

  • use-java-context : If this is set to false the the datasource will be bound in the global JNDI context rather than the java: context.

  • user-name : This element specifies the default username used when creating a new connection. The actual username may be overridden by the application code getConnection parameters or the connection creation context JAAS Subject.

  • password : This element specifies the default password used when creating a new connection. The actual password may be overridden by the application code getConnection parameters or the connection creation context JAAS Subject.

  • application-managed-security : Specifying this element indicates that connections in the pool should be distinguished by application code supplied parameters, such as from getConnection(user, pw).

  • security-domain : Specifying this element indicates that connections in the pool should be distinguished by JAAS Subject based information. The content of the security-domain is the name of the JAAS security manager that will handle authentication. This name correlates to the JAAS login-config.xml descriptor application-policy/name attribute.

  • security-domain-and-application : Specifying this element indicates that connections in the pool should be distinguished both by application code supplied parameters and JAAS Subject based information. The content of the security-domain is the name of the JAAS security manager that will handle authentication. This name correlates to the JAAS login-config.xml descriptor application-policy/name attribute.

  • min-pool-size : This element specifies the minimum number of connections a pool should hold. These pool instances are not created until an initial request for a connection is made. This default to 0.

  • max-pool-size : This element specifies the maximum number of connections for a pool. No more than the max-pool-size number of connections will be created in a pool. This defaults to 20.

  • blocking-timeout-millis : This element specifies the maximum time in milliseconds to block while waiting for a connection before throwing an exception. Note that this blocks only while waiting for a permit for a connection, and will never throw an exception if creating a new connection takes an inordinately long time. The default is 5000.

  • idle-timeout-minutes : This element specifies the maximum time in minutes a connection may be idle before being closed. The actual maximum time depends also on the IdleRemover scan time, which is 1/2 the smallest idle-timeout-minutes of any pool.

  • new-connection-sql : This is a SQL statement that should be executed when a new connection is created. This can be used to configure a connection with database specific settings not configurable via connection properties.

  • check-valid-connection-sql : This is a SQL statement that should be run on a connection before it is returned from the pool to test its validity to test for stale pool connections. An example statement could be: select count(*) from x.

  • exception-sorter-class-name : This specifies a class that implements the org.jboss.resource.adapter.jdbc.ExceptionSorter interface to examine database exceptions to determine whether or not the exception indicates a connection error. Current implementations include:

    • org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter

    • org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter

    • org.jboss.resource.adapter.jdbc.vendor.SybaseExceptionSorter

    • org.jboss.resource.adapter.jdbc.vendor.InformixExceptionSorte

  • valid-connection-checker-class-name : This specifies a class that implements the org.jboss.resource.adapter.jdbc.ValidConnectionChecker interface to provide a SQLException isValidConnection(Connection e) method that is called with a connection that is to be returned from the pool to test its validity. This overrides the check-valid-connection-sql when present. The only provided implementation is org.jboss.resource.adapter.jdbc.vendor.OracleValidConnectionChecker.

  • track-statements : This boolean element specifies whether to check for unclosed statements when a connection is returned to the pool. If true, a warning message is issued for each unclosed statement. If the log4j category org.jboss.resource.adapter.jdbc.WrappedConnection has trace level enabled, a stack trace of the connection close call is logged as well. This is a debug feature that can be turned off in production.

  • prepared-statement-cache-size : This element specifies the number of prepared statements per connection in an LRU cache, which is keyed by the SQL query. Setting this to zero disables the cache.

  • depends : The depends element specifies the JMX ObjectName string of a service that the connection manager services depend on. The connection manager service will not be started until the dependent services have been started.

  • type-mapping : This element declares a default type mapping for this datasource. The type mapping should match a type-mapping/name element from standardjbosscmp-jdbc.xml.

Additional common child elements for both no-tx-datasource and local-tx-datasource include:

  • connection-url : This is the JDBC driver connection URL string, for example, jdbc:hsqldb:hsql://localhost:1701.

  • driver-class : This is the fully qualified name of the JDBC driver class, for example, org.hsqldb.jdbcDriver.

  • connection-property : The connection-property element allows you to pass in arbitrary connection properties to the java.sql.Driver.connect(url, props) method. Each connection-property specifies a string name/value pair with the property name coming from the name attribute and the value coming from the element content.

Elements in common to the local-tx-datasource and xa-datasource are:

  • transaction-isolation : This element specifies the java.sql.Connection transaction isolation level to use. The constants defined in the Connection interface are the possible element content values and include:

    • TRANSACTION_READ_UNCOMMITTED

    • TRANSACTION_READ_COMMITTED

    • TRANSACTION_REPEATABLE_READ

    • TRANSACTION_SERIALIZABLE

    • TRANSACTION_NONE

  • no-tx-separate-pools : The presence of this element indicates that two connection pools are required to isolate connections used with JTA transaction from those used without a JTA transaction. The pools are lazily constructed on first use. Its use case is for Oracle (and possibly other vendors) XA implementations that don't like using an XA connection with and without a JTA transaction.

The unique xa-datasource child elements are:

  • track-connection-by-tx : Specifying a true value for this element makes the connection manager keep an xid to connection map and only put the connection back in the pool when the transaction completes and all the connection handles are closed or disassociated (by the method calls returning). As a side effect, we never suspend and resume the xid on the connection's XAResource. This is the same connection tracking behavior used for local transactions.

    The XA spec implies that any connection may be enrolled in any transaction using any xid for that transaction at any time from any thread (suspending other transactions if necessary). The original JCA implementation assumed this and aggressively delisted connections and put them back in the pool as soon as control left the EJB they were used in or handles were closed. Since some other transaction could be using the connection the next time work needed to be done on the original transaction, there is no way to get the original connection back. It turns out that most XADataSource driver vendors do not support this, and require that all work done under a particular xid go through the same connection.

  • xa-datasource-class : The fully qualified name of the javax.sql.XADataSource implementation class, for example, com.informix.jdbcx.IfxXADataSource.

  • xa-datasource-property : The xa-datasource-property element allows for specification of the properties to assign to the XADataSource implementation class. Each property is identified by the name attribute and the property value is given by the xa-datasource-property element content. The property is mapped onto the XADataSource implementation by looking for a JavaBeans style getter method for the property name. If found, the value of the property is set using the JavaBeans setter with the element text translated to the true property type using the java.beans.PropertyEditor for the type.

  • isSameRM-override-value : A boolean flag that allows one to override the behavior of the javax.transaction.xa.XAResource.isSameRM(XAResource xaRes) method behavior on the XA managed connection. If specified, this value is used unconditionally as the isSameRM(xaRes) return value regardless of the xaRes parameter.

The failover options common to ha-xa-datasource and ha-local-tx-datasource are:

  • url-delimeter : This element specifies a character used to separate multiple JDBC URLs.

  • url-property : In the case of XA datasources, this property specifies the name of the xa-datasource-property that contains the list of JDBC URLs to use.

Example configurations for many third-party JDBC drivers are included in the JBOSS_DIST/docs/examples/jca directory. Current example configurations include:

  • asapxcess-jb3.2-ds.xml

  • cicsr9s-service.xml

  • db2-ds.xml

  • db2-xa-ds.xml

  • facets-ds.xml

  • fast-objects-jboss32-ds.xml

  • firebird-ds.xml

  • firstsql-ds.xml

  • firstsql-xa-ds.xml

  • generic-ds.xml

  • hsqldb-ds.xml

  • informix-ds.xml

  • informix-xa-ds.xml

  • jdatastore-ds.xml

  • jms-ds.xml

  • jsql-ds.xml

  • lido-versant-service.xml

  • mimer-ds.xml

  • mimer-xa-ds.xml

  • msaccess-ds.xml

  • mssql-ds.xml

  • mssql-xa-ds.xml

  • mysql-ds.xml

  • oracle-ds.xml

  • oracle-xa-ds.xml

  • postgres-ds.xml

  • sapdb-ds.xml

  • sapr3-ds.xml

  • solid-ds.xml

  • sybase-ds.xml

The XSLSubDeployer also supports the deployment of arbitrary non-JDBC JCA resource adaptors. The schema for the top-level connection factory elements of the *-ds.xml configuration deployment file is shown in Figure 5.11, “The simplified JCA adaptor connection factory configuration descriptor top-level schema elements”.


Multiple connection factory configurations may be specified in a configuration deployment file. The child elements of the connection-factories root are:



The majority of the elements are the same as those of the datasources configuration. The element unique to the connection factory configuration include:

  • adaptor-display-name : A human readable display name to assign to the connection manager MBean.

  • local-transaction : This element specifies that the tx-connection-factory supports local transactions.

  • xa-transaction : This element specifies that the tx-connection-factory supports XA transactions.

  • track-connection-by-tx : This element specifies that a connection should be used only on a single transaction and that a transaction should only be associated with one connection.

  • rar-name : This is the name of the RAR file that contains the definition for the resource we want to provide. For nested RAR files, the name would look like myapplication.ear#my.rar.

  • connection-definition : This is the connection factory interface class. It should match the connectionfactory-interface in the ra.xml file.

  • config-property : Any number of properties to supply to the ManagedConnectionFactory (MCF) MBean service configuration. Each config-property element specifies the value of a MCF property. The config-property element has two required attributes:

    • name : The name of the property

    • type : The fully qualified type of the property

    The content of the config-property element provides the string representation of the property value. This will be converted to the true property type using the associated type PropertyEditor.

This chapter discusses transaction management in JBoss and the JBossTX architecture. The JBossTX architecture allows for any Java Transaction API (JTA) transaction manager implementation to be used. JBossTX includes a fast in-VM implementation of a JTA compatible transaction manager which is now deprecated. JBoss Transactions (JBoss TS) is the new default transaction manager for JBoss. JBoss TS is founded on industry proven technology and 18 year history as a leader in distributed transactions. The JTA version of JBoss Transactions included with the server provides for fully recoverable transactions. For distributed transaction support the JTS version of JBoss Transactions will need to be used.

We will first provide an overview of the key transaction concepts and notions in the JTA to provide sufficient background for the JBossTX architecture discussion. We will then discuss the interfaces that make up the JBossTX architecture and conclude with a discussion of the MBeans available for integration of alternate transaction managers.

For the purpose of this discussion, we can define a transaction as a unit of work containing one or more operations involving one or more shared resources having ACID properties. ACID is an acronym for atomicity, consistency, isolation and durability, the four important properties of transactions. The meanings of these terms is:

  • Atomicity : A transaction must be atomic. This means that either all the work done in the transaction must be performed, or none of it must be performed. Doing part of a transaction is not allowed.

  • Consistency : When a transaction is completed, the system must be in a stable and consistent condition.

  • Isolation : Different transactions must be isolated from each other. This means that the partial work done in one transaction is not visible to other transactions until the transaction is committed, and that each process in a multi-user system can be programmed as if it was the only process accessing the system.

  • Durability : The changes made during a transaction are made persistent when it is committed. When a transaction is committed, its changes will not be lost, even if the server crashes afterwards.

To illustrate these concepts, consider a simple banking account application. The banking application has a database with a number of accounts. The sum of the amounts of all accounts must always be 0. An amount of money M is moved from account A to account B by subtracting M from account A and adding M to account B. This operation must be done in a transaction, and all four ACID properties are important.

The atomicity property means that both the withdrawal and deposit is performed as an indivisible unit. If, for some reason, both cannot be done nothing will be done.

The consistency property means that after the transaction, the sum of the amounts of all accounts must still be 0.

The isolation property is important when more than one bank clerk uses the system at the same time. A withdrawal or deposit could be implemented as a three-step process: First the amount of the account is read from the database; then something is subtracted from or added to the amount read from the database; and at last the new amount is written to the database. Without transaction isolation several bad things could happen. For example, if two processes read the amount of account A at the same time, and each independently added or subtracted something before writing the new amount to the database, the first change would be incorrectly overwritten by the last.

The durability property is also important. If a money transfer transaction is committed, the bank must trust that some subsequent failure cannot undo the money transfer.

Transactional isolation is usually implemented by locking whatever is accessed in a transaction. There are two different approaches to transactional locking: Pessimistic locking and optimistic locking.

The disadvantage of pessimistic locking is that a resource is locked from the time it is first accessed in a transaction until the transaction is finished, making it inaccessible to other transactions during that time. If most transactions simply look at the resource and never change it, an exclusive lock may be overkill as it may cause lock contention, and optimistic locking may be a better approach. With pessimistic locking, locks are applied in a fail-safe way. In the banking application example, an account is locked as soon as it is accessed in a transaction. Attempts to use the account in other transactions while it is locked will either result in the other process being delayed until the account lock is released, or that the process transaction will be rolled back. The lock exists until the transaction has either been committed or rolled back.

With optimistic locking, a resource is not actually locked when it is first is accessed by a transaction. Instead, the state of the resource at the time when it would have been locked with the pessimistic locking approach is saved. Other transactions are able to concurrently access to the resource and the possibility of conflicting changes is possible. At commit time, when the resource is about to be updated in persistent storage, the state of the resource is read from storage again and compared to the state that was saved when the resource was first accessed in the transaction. If the two states differ, a conflicting update was made, and the transaction will be rolled back.

In the banking application example, the amount of an account is saved when the account is first accessed in a transaction. If the transaction changes the account amount, the amount is read from the store again just before the amount is about to be updated. If the amount has changed since the transaction began, the transaction will fail itself, otherwise the new amount is written to persistent storage.

There are a number of participants in a distributed transaction. These include:

  • Transaction Manager : This component is distributed across the transactional system. It manages and coordinates the work involved in the transaction. The transaction manager is exposed by the javax.transaction.TransactionManager interface in JTA.

  • Transaction Context : A transaction context identifies a particular transaction. In JTA the corresponding interface is javax.transaction.Transaction.

  • Transactional Client : A transactional client can invoke operations on one or more transactional objects in a single transaction. The transactional client that started the transaction is called the transaction originator. A transaction client is either an explicit or implicit user of JTA interfaces and has no interface representation in the JTA.

  • Transactional Object : A transactional object is an object whose behavior is affected by operations performed on it within a transactional context. A transactional object can also be a transactional client. Most Enterprise Java Beans are transactional objects.

  • Recoverable Resource : A recoverable resource is a transactional object whose state is saved to stable storage if the transaction is committed, and whose state can be reset to what it was at the beginning of the transaction if the transaction is rolled back. At commit time, the transaction manager uses the two-phase XA protocol when communicating with the recoverable resource to ensure transactional integrity when more than one recoverable resource is involved in the transaction being committed. Transactional databases and message brokers like JBossMQ are examples of recoverable resources. A recoverable resource is represented using the javax.transaction.xa.XAResource interface in JTA.

When a transaction is about to be committed, it is the responsibility of the transaction manager to ensure that either all of it is committed, or that all of is rolled back. If only a single recoverable resource is involved in the transaction, the task of the transaction manager is simple: It just has to tell the resource to commit the changes to stable storage.

When more than one recoverable resource is involved in the transaction, management of the commit gets more complicated. Simply asking each of the recoverable resources to commit changes to stable storage is not enough to maintain the atomic property of the transaction. The reason for this is that if one recoverable resource has committed and another fails to commit, part of the transaction would be committed and the other part rolled back.

To get around this problem, the two-phase XA protocol is used. The XA protocol involves an extra prepare phase before the actual commit phase. Before asking any of the recoverable resources to commit the changes, the transaction manager asks all the recoverable resources to prepare to commit. When a recoverable resource indicates it is prepared to commit the transaction, it has ensured that it can commit the transaction. The resource is still able to rollback the transaction if necessary as well.

So the first phase consists of the transaction manager asking all the recoverable resources to prepare to commit. If any of the recoverable resources fails to prepare, the transaction will be rolled back. But if all recoverable resources indicate they were able to prepare to commit, the second phase of the XA protocol begins. This consists of the transaction manager asking all the recoverable resources to commit the transaction. Because all the recoverable resources have indicated they are prepared, this step cannot fail.

In a distributed environment communications failures can happen. If communication between the transaction manager and a recoverable resource is not possible for an extended period of time, the recoverable resource may decide to unilaterally commit or rollback changes done in the context of a transaction. Such a decision is called a heuristic decision. It is one of the worst errors that may happen in a transaction system, as it can lead to parts of the transaction being committed while other parts are rolled back, thus violating the atomicity property of transaction and possibly leading to data integrity corruption.

Because of the dangers of heuristic exceptions, a recoverable resource that makes a heuristic decision is required to maintain all information about the decision in stable storage until the transaction manager tells it to forget about the heuristic decision. The actual data about the heuristic decision that is saved in stable storage depends on the type of recoverable resource and is not standardized. The idea is that a system manager can look at the data, and possibly edit the resource to correct any data integrity problems.

There are several different kinds of heuristic exceptions defined by the JTA. The javax.transaction.HeuristicCommitException is thrown when a recoverable resource is asked to rollback to report that a heuristic decision was made and that all relevant updates have been committed. On the opposite end is the javax.transaction.HeuristicRollbackException, which is thrown by a recoverable resource when it is asked to commit to indicate that a heuristic decision was made and that all relevant updates have been rolled back.

The javax.transaction.HeuristicMixedException is the worst heuristic exception. It is thrown to indicate that parts of the transaction were committed, while other parts were rolled back. The transaction manager throws this exception when some recoverable resources did a heuristic commit, while other recoverable resources did a heuristic rollback.

In traditional ACID transaction systems, transactions are short lived, resources (such as databases) are locked for the duration of the transaction and participants have a high degree of trust with each other. With the advent of the Internet and Web services, the scenario that is now emerging requires involvement of participants unknown to each other in distributed transactions. These transactions have the following characteristics:

  • Transactions may be of a long duration, sometimes lasting hours, days, or more.

  • Participants may not allow their resources to be locked for long durations.

  • The communication infrastructure between participants may not be reliable.

  • Some of the ACID properties of traditional transactions are not mandatory.

  • A transaction may succeed even if only some of the participants choose to confirm and others cancel.

  • All participants may choose to have their own coordinator (Transaction Manager), because of lack of trust.

  • All activities are logged.

  • Transactions that have to be rolled back have the concept of compensation.

JBoss Transactions adds native support for Web services transactions by providing all of the components necessary to build interoperable, reliable, multi-party, Web services-based applications with the minimum of effort. The programming interfaces are based on the Java API for XML Transactioning (JAXTX) and the product includes protocol support for the WS-AtomicTransaction and WS-BusinessActivity specifications. JBossTS 4.2 is designed to support multiple coordination protocols and therefore helps to future-proof transactional applications.

The JMS API stands for Java Message Service Application Programming Interface, and it is used by applications to send asynchronous business-quality messages to other applications. In the messaging world, messages are not sent directly to other applications. Instead, messages are sent to destinations, known as queues or topics. Applications sending messages do not need to worry if the receiving applications are up and running, and conversely, receiving applications do not need to worry about the sending application's status. Both senders, and receivers only interact with the destinations.

The JMS API is the standardized interface to a JMS provider, sometimes called a Message Oriented Middleware (MOM) system. JBoss comes with a JMS 1.1 compliant JMS provider called JBoss Messaging or JBossMQ. When you use the JMS API with JBoss, you are using the JBoss Messaging engine transparently. JBoss Messaging fully implements the JMS specification; therefore, the best JBoss Messaging user guide is the JMS specification. For more information about the JMS API please visit the JMS Tutorial or JMS Downloads & Specifications.

This chapter focuses on the JBoss specific aspects of using JMS and message driven beans as well as the JBoss Messaging configuration and MBeans.

In this section we discuss the basics needed to use the JBoss JMS implementation. JMS leaves the details of accessing JMS connection factories and destinations as provider specific details. What you need to know to use the JBoss Messaging layer is:

  • The location of the queue and topic connect factories: In JBoss both connection factory implementations are located under the JNDI name ConnectionFactory.

  • How to lookup JMS destinations (queues and topics): Destinations are configured via MBeans as we will see when we discuss the messaging MBeans. JBoss comes with a few queues and topics preconfigured. You can find them under the jboss.mq.destination domain in the JMX Console..

  • Which JARS JMS requires: These include concurrent.jar, jbossmq-client.jar, jboss-common-client.jar, jboss-system-client.jar, jnp-client.jar and log4j.jar.

In the following sections we will look at examples of the various JMS messaging models and message driven beans. The chapter example source is located under the src/main/org/jboss/book/jms directory of the book examples.

Let's start out with a point-to-point (P2P) example. In the P2P model, a sender delivers messages to a queue and a single receiver pulls the message off of the queue. The receiver does not need to be listening to the queue at the time the message is sent. Example 7.1, “A P2P JMS client example” shows a complete P2P example that sends a javax.jms.TextMessage to the queue queue/testQueue and asynchronously receives the message from the same queue.

package org.jboss.book.jms.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import EDU.oswego.cs.dl.util.concurrent.CountDown;
import org.apache.log4j.Logger;
import org.jboss.util.ChapterExRepository;

/** 
 * A complete JMS client example program that sends a
 * TextMessage to a Queue and asynchronously receives the
 * message from the same Queue.
 * 
 * @author  Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class SendRecvClient
{
    static Logger log;
    static CountDown done = new CountDown(1);
    
    QueueConnection conn;
    QueueSession session;
    Queue que;
    
    public static class ExListener 
        implements MessageListener
    {
        public void onMessage(Message msg)
        {
            done.release();
            TextMessage tm = (TextMessage) msg;
            try {
                log.info("onMessage, recv text=" + tm.getText());
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }
    
    public void setupPTP()
        throws JMSException, 
               NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        conn = qcf.createQueueConnection();
        que = (Queue) iniCtx.lookup("queue/testQueue");
        session = conn.createQueueSession(false,
                                          QueueSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendRecvAsync(String text)
        throws JMSException, 
               NamingException
    {
        log.info("Begin sendRecvAsync");
        // Setup the PTP connection, session
        setupPTP();

        // Set the async listener
        QueueReceiver recv = session.createReceiver(que);
        recv.setMessageListener(new ExListener());

        // Send a text msg
        QueueSender send = session.createSender(que);
        TextMessage tm = session.createTextMessage(text);
        send.send(tm);
        log.info("sendRecvAsync, sent text=" + tm.getText());
        send.close();
        log.info("End sendRecvAsync");
    }
    
    public void stop()
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        ChapterExRepository.init(SendRecvClient.class);
        log = Logger.getLogger("SendRecvClient");
        
        log.info("Begin SendRecvClient, now=" + System.currentTimeMillis());
        SendRecvClient client = new SendRecvClient();
        client.sendRecvAsync("A text msg");
        client.done.acquire();
        client.stop();
        log.info("End SendRecvClient");
        System.exit(0);
    }
}

Example 7.1. A P2P JMS client example


The client may be run using the following command line:

[examples]$ ant -Dchap=jms -Dex=1p2p run-example
...
run-example1p2p:
     [java] [INFO,SendRecvClient] Begin SendRecvClient, now=1102808673386
     [java] [INFO,SendRecvClient] Begin sendRecvAsync
     [java] [INFO,SendRecvClient] onMessage, recv text=A text msg
     [java] [INFO,SendRecvClient] sendRecvAsync, sent text=A text msg
     [java] [INFO,SendRecvClient] End sendRecvAsync
     [java] [INFO,SendRecvClient] End SendRecvClient

The JMS publish/subscribe (Pub-Sub) message model is a one-to-many model. A publisher sends a message to a topic and all active subscribers of the topic receive the message. Subscribers that are not actively listening to the topic will miss the published message. shows a complete JMS client that sends a javax.jms.TextMessage to a topic and asynchronously receives the message from the same topic.

package org.jboss.book.jms.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import EDU.oswego.cs.dl.util.concurrent.CountDown;

/**
 *  A complete JMS client example program that sends a TextMessage to
 *  a Topic and asynchronously receives the message from the same
 *  Topic.
 * 
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.9 $
 */

public class TopicSendRecvClient
{
    static CountDown done = new CountDown(1);
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public static class ExListener implements MessageListener
    {
        public void onMessage(Message msg)
        {
            done.release();
            TextMessage tm = (TextMessage) msg;
            try {
                System.out.println("onMessage, recv text=" + tm.getText());
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection();
        topic = (Topic) iniCtx.lookup("topic/testTopic");
        session = conn.createTopicSession(false,
                                          TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendRecvAsync(String text)
        throws JMSException, NamingException
    {
        System.out.println("Begin sendRecvAsync");
        // Setup the PubSub connection, session
        setupPubSub();
        // Set the async listener
        
        TopicSubscriber recv = session.createSubscriber(topic);
        recv.setMessageListener(new ExListener());
        // Send a text msg
        TopicPublisher send = session.createPublisher(topic);
        TextMessage tm = session.createTextMessage(text);
        send.publish(tm);
        System.out.println("sendRecvAsync, sent text=" + tm.getText());
        send.close();
        System.out.println("End sendRecvAsync");
    }
    
    public void stop() throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) throws Exception
    {
        System.out.println("Begin TopicSendRecvClient, now=" + 
                           System.currentTimeMillis());
        TopicSendRecvClient client = new TopicSendRecvClient();
        client.sendRecvAsync("A text msg, now="+System.currentTimeMillis());
        client.done.acquire();
        client.stop();
        System.out.println("End TopicSendRecvClient");
        System.exit(0);
    }
    
}

Example 7.2. A Pub-Sub JMS client example


The client may be run using the following command line:

[examples]$ ant -Dchap=jms -Dex=1ps run-example
...
run-example1ps:
     [java] Begin TopicSendRecvClient, now=1102809427043
     [java] Begin sendRecvAsync
     [java] onMessage, recv text=A text msg, now=1102809427071
     [java] sendRecvAsync, sent text=A text msg, now=1102809427071
     [java] End sendRecvAsync
     [java] End TopicSendRecvClient

Now let's break the publisher and subscribers into separate programs to demonstrate that subscribers only receive messages while they are listening to a topic. Example 7.3, “A JMS publisher client” shows a variation of the previous pub-sub client that only publishes messages to the topic/testTopic topic. The subscriber only client is shown in Example 7.4, “A JMS subscriber client”.

package org.jboss.book.jms.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSlistubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/** 
 *  A JMS client example program that sends a TextMessage to a Topic
 *    
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.9 $
 */
public class TopicSendClient
{
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection();
        topic = (Topic) iniCtx.lookup("topic/testTopic");
        session = conn.createTopicSession(false,
                                          TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendAsync(String text)
        throws JMSException, NamingException
    {
        System.out.println("Begin sendAsync");
        // Setup the pub/sub connection, session
        setupPubSub();
        // Send a text msg
        TopicPublisher send = session.createPublisher(topic);
        TextMessage tm = session.createTextMessage(text);
        send.publish(tm);
        System.out.println("sendAsync, sent text=" +  tm.getText());
        send.close();
        System.out.println("End sendAsync");
    }
    
    public void stop() 
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin TopicSendClient, now=" + 
		                   System.currentTimeMillis());
        TopicSendClient client = new TopicSendClient();
	    client.sendAsync("A text msg, now="+System.currentTimeMillis());
        client.stop();
        System.out.println("End TopicSendClient");
        System.exit(0);
    }
    
}

Example 7.3. A JMS publisher client


package org.jboss.book.jms.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 * A JMS client example program that synchronously receives a message a Topic
 *  
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class TopicRecvClient
{
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection();
        topic = (Topic) iniCtx.lookup("topic/testTopic");
        session = conn.createTopicSession(false,
                                          TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void recvSync()
        throws JMSException, NamingException
    {
        System.out.println("Begin recvSync");
        // Setup the pub/sub connection, session
        setupPubSub();

        // Wait upto 5 seconds for the message
        TopicSubscriber recv = session.createSubscriber(topic);
        Message msg = recv.receive(5000);
        if (msg == null) {
            System.out.println("Timed out waiting for msg");
        } else {
            System.out.println("TopicSubscriber.recv, msgt="+msg);
        }
    }
    
    public void stop()
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin TopicRecvClient, now=" +
                           System.currentTimeMillis());
        TopicRecvClient client = new TopicRecvClient();
        client.recvSync();
        client.stop();
        System.out.println("End TopicRecvClient");
        System.exit(0);
    }
    
}

Example 7.4. A JMS subscriber client


Run the TopicSendClient followed by the TopicRecvClient as follows:

[examples]$ ant -Dchap=jms -Dex=1ps2 run-example
...
run-example1ps2:
     [java] Begin TopicSendClient, now=1102810007899
     [java] Begin sendAsync
     [java] sendAsync, sent text=A text msg, now=1102810007909
     [java] End sendAsync
     [java] End TopicSendClient
     [java] Begin TopicRecvClient, now=1102810011524
     [java] Begin recvSync
     [java] Timed out waiting for msg
     [java] End TopicRecvClient

The output shows that the topic subscriber client (TopicRecvClient) fails to receive the message sent by the publisher due to a timeout.

JMS supports a messaging model that is a cross between the P2P and pub-sub models. When a pub-sub client wants to receive all messages posted to the topic it subscribes to even when it is not actively listening to the topic, the client may achieve this behavior using a durable topic. Let's look at a variation of the preceding subscriber client that uses a durable topic to ensure that it receives all messages, include those published when the client is not listening to the topic. Example 7.5, “A durable topic JMS client example” shows the durable topic client with the key differences between the Example 7.4, “A JMS subscriber client” client highlighted in bold.

package org.jboss.book.jms.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 *  A JMS client example program that synchronously receives a message a Topic
 *     
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.9 $
 */
public class DurableTopicRecvClient
{
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");

        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection("john", "needle");
        topic = (Topic) iniCtx.lookup("topic/testTopic");

        session = conn.createTopicSession(false,
                                          TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void recvSync()
        throws JMSException, NamingException
    {
        System.out.println("Begin recvSync");
        // Setup the pub/sub connection, session
        setupPubSub();
        // Wait upto 5 seconds for the message
        TopicSubscriber recv = session.createDurableSubscriber(topic, "jms-ex1dtps");
        Message msg = recv.receive(5000);
        if (msg == null) {
            System.out.println("Timed out waiting for msg");
        } else {
            System.out.println("DurableTopicRecvClient.recv, msgt=" + msg);
        } 
    }
    
    public void stop() 
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin DurableTopicRecvClient, now=" + 
                           System.currentTimeMillis());
        DurableTopicRecvClient client = new DurableTopicRecvClient();
        client.recvSync();
        client.stop();
        System.out.println("End DurableTopicRecvClient");
        System.exit(0);
    }
    
}

Example 7.5. A durable topic JMS client example


Now run the previous topic publisher with the durable topic subscriber as follows:

[examples]$ ant -Dchap=jms -Dex=1psdt run-example
...                
run-example1psdt:
     [java] Begin DurableTopicSetup
     [java] End DurableTopicSetup
     [java] Begin TopicSendClient, now=1102899834273
     [java] Begin sendAsync
     [java] sendAsync, sent text=A text msg, now=1102899834345
     [java] End sendAsync
     [java] End TopicSendClient
     [java] Begin DurableTopicRecvClient, now=1102899840043
     [java] Begin recvSync
     [java] DurableTopicRecvClient.recv, msgt=SpyTextMessage {
     [java] Header { 
     [java]    jmsDestination  : TOPIC.testTopic.DurableSubscription[
               clientId=DurableSubscriberExample name=jms-ex1dtps selector=null]
     [java]    jmsDeliveryMode : 2
     [java]    jmsExpiration   : 0
     [java]    jmsPriority     : 4
     [java]    jmsMessageID    : ID:3-11028998375501
     [java]    jmsTimeStamp    : 1102899837550
     [java]    jmsCorrelationID: null
     [java]    jmsReplyTo      : null
     [java]    jmsType         : null
     [java]    jmsRedelivered  : false
     [java]    jmsProperties   : {}
     [java]    jmsPropReadWrite: false
     [java]    msgReadOnly     : true
     [java]    producerClientId: ID:3
     [java] }
     [java] Body {
     [java]    text            :A text msg, now=1102899834345
     [java] }
     [java] }
     [java] End DurableTopicRecvClient

Items of note for the durable topic example include:

  • The TopicConnectionFactory creation in the durable topic client used a username and password, and the TopicSubscriber creation was done using the createDurableSubscriber(Topic, String) method. This is a requirement of durable topic subscribers. The messaging server needs to know what client is requesting the durable topic and what the name of the durable topic subscription is. We will discuss the details of durable topic setup in the configuration section.

  • An org.jboss.book.jms.DurableTopicSetup client was run prior to the TopicSendClient. The reason for this is a durable topic subscriber must have registered a subscription at some point in the past in order for the messaging server to save messages. JBoss supports dynamic durable topic subscribers and the DurableTopicSetup client simply creates a durable subscription receiver and the exits. This leaves an active durable topic subscriber on the topic/testTopic and the messaging server knows that any messages posted to this topic must be saved for latter delivery.

  • The TopicSendClient does not change for the durable topic. The notion of a durable topic is a subscriber only notion.

  • The DurableTopicRecvClient sees the message published to the topic/testTopic even though it was not listening to the topic at the time the message was published.

Example 7.6, “A TextMessage processing MDB” shows an message driven bean (MDB) that transforms the TextMessages it receives and sends the transformed messages to the queue found in the incoming message JMSReplyTo header.

package org.jboss.book.jms.ex2;
                
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.EJBException;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/** 
 * An MDB that transforms the TextMessages it receives and send the
 * transformed messages to the Queue found in the incoming message
 * JMSReplyTo header.
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class TextMDB 
    implements MessageDrivenBean, MessageListener
{
    private MessageDrivenContext ctx = null;
    private QueueConnection conn;
    private QueueSession session;
    
    public TextMDB()
    {
        System.out.println("TextMDB.ctor, this="+hashCode());
    }
    
    public void setMessageDrivenContext(MessageDrivenContext ctx)
    {
        this.ctx = ctx;
        System.out.println("TextMDB.setMessageDrivenContext, this=" + 
                           hashCode());
    }
    
    public void ejbCreate()
    {
        System.out.println("TextMDB.ejbCreate, this="+hashCode());
        try {
            setupPTP();
        } catch (Exception e) {
            throw new EJBException("Failed to init TextMDB", e);
        }
    }

    public void ejbRemove()
    {
        System.out.println("TextMDB.ejbRemove, this="+hashCode());
        ctx = null;
        try {
            if (session != null) {
                session.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch(JMSException e) {
            e.printStackTrace();
        }
    }
                
    public void onMessage(Message msg)
    {
        System.out.println("TextMDB.onMessage, this="+hashCode());
        try {
            TextMessage tm = (TextMessage) msg;
            String text = tm.getText() + "processed by: "+hashCode();
            Queue dest = (Queue) msg.getJMSReplyTo();
            sendReply(text, dest);
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }
                
    private void setupPTP()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("java:comp/env/jms/QCF");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        conn = qcf.createQueueConnection();
        session = conn.createQueueSession(false,
                                          QueueSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }

    private void sendReply(String text, Queue dest)
        throws JMSException
    {
        System.out.println("TextMDB.sendReply, this=" + 
                           hashCode() + ", dest="+dest);
        QueueSender sender = session.createSender(dest);
        TextMessage tm = session.createTextMessage(text);
        sender.send(tm);
        sender.close();
    }
}

Example 7.6. A TextMessage processing MDB


The MDB ejb-jar.xml and jboss.xml deployment descriptors are shown in Example 7.7, “The MDB ejb-jar.xml descriptor” and Example 7.8, “The MDB jboss.xml descriptor”.



Example 7.9, “A JMS client that interacts with the TextMDB” shows a variation of the P2P client that sends several messages to the queue/B destination and asynchronously receives the messages as modified by TextMDB from queue A.

package org.jboss.book.jms.ex2;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import EDU.oswego.cs.dl.util.concurrent.CountDown;

/**
 *  A complete JMS client example program that sends N TextMessages to
 *  a Queue B and asynchronously receives the messages as modified by
 *  TextMDB from Queue A.
 *
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.9 $
 */
public class SendRecvClient
{
    static final int N = 10;
    static CountDown done = new CountDown(N);

    QueueConnection conn;
    QueueSession session;
    Queue queA;
    Queue queB;
    
    public static class ExListener 
        implements MessageListener
    {
        public void onMessage(Message msg)
        {
            done.release();
            TextMessage tm = (TextMessage) msg;
            try {
                System.out.println("onMessage, recv text="+tm.getText());
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }
    
    public void setupPTP()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        conn = qcf.createQueueConnection();
        queA = (Queue) iniCtx.lookup("queue/A");
        queB = (Queue) iniCtx.lookup("queue/B");
        session = conn.createQueueSession(false,
                                          QueueSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendRecvAsync(String textBase)
        throws JMSException, NamingException, InterruptedException
    {
        System.out.println("Begin sendRecvAsync");

        // Setup the PTP connection, session
        setupPTP();

        // Set the async listener for queA
        QueueReceiver recv = session.createReceiver(queA);
        recv.setMessageListener(new ExListener());

        // Send a few text msgs to queB
        QueueSender send = session.createSender(queB);

        for(int m = 0; m < 10; m ++) {
            TextMessage tm = session.createTextMessage(textBase+"#"+m);
            tm.setJMSReplyTo(queA);
            send.send(tm);
            System.out.println("sendRecvAsync, sent text=" + tm.getText());
        }
        System.out.println("End sendRecvAsync");
    }
    
    public void stop() 
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin SendRecvClient,now=" + 
                           System.currentTimeMillis());
        SendRecvClient client = new SendRecvClient();
        client.sendRecvAsync("A text msg");
        client.done.acquire();
        client.stop();
        System.exit(0);
        System.out.println("End SendRecvClient");
    }
    
}

Example 7.9. A JMS client that interacts with the TextMDB


Run the client as follows:

[examples]$ ant -Dchap=jms -Dex=2 run-example
...
run-example2:
...
     [java] Begin SendRecvClient, now=1102900541558
     [java] Begin sendRecvAsync
     [java] sendRecvAsync, sent text=A text msg#0
     [java] sendRecvAsync, sent text=A text msg#1
     [java] sendRecvAsync, sent text=A text msg#2
     [java] sendRecvAsync, sent text=A text msg#3
     [java] sendRecvAsync, sent text=A text msg#4
     [java] sendRecvAsync, sent text=A text msg#5
     [java] sendRecvAsync, sent text=A text msg#6
     [java] sendRecvAsync, sent text=A text msg#7
     [java] sendRecvAsync, sent text=A text msg#8
     [java] sendRecvAsync, sent text=A text msg#9
     [java] End sendRecvAsync
     [java] onMessage, recv text=A text msg#0processed by: 12855623
     [java] onMessage, recv text=A text msg#5processed by: 9399816
     [java] onMessage, recv text=A text msg#9processed by: 6598158
     [java] onMessage, recv text=A text msg#3processed by: 8153998
     [java] onMessage, recv text=A text msg#4processed by: 10118602
     [java] onMessage, recv text=A text msg#2processed by: 1792333
     [java] onMessage, recv text=A text msg#7processed by: 14251014
     [java] onMessage, recv text=A text msg#1processed by: 10775981
     [java] onMessage, recv text=A text msg#8processed by: 6056676
     [java] onMessage, recv text=A text msg#6processed by: 15679078

The corresponding JBoss server console output is:

19:15:40,232 INFO  [EjbModule] Deploying TextMDB
	19:15:41,498 INFO  [EJBDeployer] Deployed: file:/jboss-4.2.0.GA/server/production/deploy/
  jms-ex2.jar
19:15:45,606 INFO  [TextMDB] TextMDB.ctor, this=10775981
19:15:45,620 INFO  [TextMDB] TextMDB.ctor, this=1792333
19:15:45,627 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=10775981
19:15:45,638 INFO  [TextMDB] TextMDB.ejbCreate, this=10775981
19:15:45,640 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=1792333
19:15:45,640 INFO  [TextMDB] TextMDB.ejbCreate, this=1792333
19:15:45,649 INFO  [TextMDB] TextMDB.ctor, this=12855623
19:15:45,658 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=12855623
19:15:45,661 INFO  [TextMDB] TextMDB.ejbCreate, this=12855623
19:15:45,742 INFO  [TextMDB] TextMDB.ctor, this=8153998
19:15:45,744 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=8153998
19:15:45,744 INFO  [TextMDB] TextMDB.ejbCreate, this=8153998
19:15:45,763 INFO  [TextMDB] TextMDB.ctor, this=10118602
19:15:45,764 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=10118602
19:15:45,764 INFO  [TextMDB] TextMDB.ejbCreate, this=10118602
19:15:45,777 INFO  [TextMDB] TextMDB.ctor, this=9399816
19:15:45,779 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=9399816
19:15:45,779 INFO  [TextMDB] TextMDB.ejbCreate, this=9399816
19:15:45,792 INFO  [TextMDB] TextMDB.ctor, this=15679078
19:15:45,798 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=15679078
19:15:45,799 INFO  [TextMDB] TextMDB.ejbCreate, this=15679078
19:15:45,815 INFO  [TextMDB] TextMDB.ctor, this=14251014
19:15:45,816 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=14251014
19:15:45,817 INFO  [TextMDB] TextMDB.ejbCreate, this=14251014
19:15:45,829 INFO  [TextMDB] TextMDB.ctor, this=6056676
19:15:45,831 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=6056676
19:15:45,864 INFO  [TextMDB] TextMDB.ctor, this=6598158
19:15:45,903 INFO  [TextMDB] TextMDB.ejbCreate, this=6056676
19:15:45,906 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=6598158
19:15:45,906 INFO  [TextMDB] TextMDB.ejbCreate, this=6598158
19:15:46,236 INFO  [TextMDB] TextMDB.onMessage, this=12855623
19:15:46,238 INFO  [TextMDB] TextMDB.sendReply, this=12855623, dest=QUEUE.A
19:15:46,734 INFO  [TextMDB] TextMDB.onMessage, this=9399816
19:15:46,736 INFO  [TextMDB] TextMDB.onMessage, this=8153998
19:15:46,737 INFO  [TextMDB] TextMDB.onMessage, this=6598158
19:15:46,768 INFO  [TextMDB] TextMDB.sendReply, this=9399816, dest=QUEUE.A
19:15:46,768 INFO  [TextMDB] TextMDB.sendReply, this=6598158, dest=QUEUE.A
19:15:46,774 INFO  [TextMDB] TextMDB.sendReply, this=8153998, dest=QUEUE.A
19:15:46,903 INFO  [TextMDB] TextMDB.onMessage, this=10118602
19:15:46,904 INFO  [TextMDB] TextMDB.sendReply, this=10118602, dest=QUEUE.A
19:15:46,927 INFO  [TextMDB] TextMDB.onMessage, this=1792333
19:15:46,928 INFO  [TextMDB] TextMDB.sendReply, this=1792333, dest=QUEUE.A
19:15:47,002 INFO  [TextMDB] TextMDB.onMessage, this=14251014
19:15:47,007 INFO  [TextMDB] TextMDB.sendReply, this=14251014, dest=QUEUE.A
19:15:47,051 INFO  [TextMDB] TextMDB.onMessage, this=10775981
19:15:47,051 INFO  [TextMDB] TextMDB.sendReply, this=10775981, dest=QUEUE.A
19:15:47,060 INFO  [TextMDB] TextMDB.onMessage, this=6056676
19:15:47,061 INFO  [TextMDB] TextMDB.sendReply, this=6056676, dest=QUEUE.A
19:15:47,064 INFO  [TextMDB] TextMDB.onMessage, this=15679078
19:15:47,065 INFO  [TextMDB] TextMDB.sendReply, this=15679078, dest=QUEUE.A

Items of note in this example include:

  • The JMS client has no explicit knowledge that it is dealing with an MDB. The client simply uses the standard JMS APIs to send messages to a queue and receive messages from another queue.

  • The MDB declares whether it will listen to a queue or topic in the ejb-jar.xml descriptor. The name of the queue or topic must be specified using a jboss.xml descriptor. In this example the MDB also sends messages to a JMS queue. MDBs may act as queue senders or topic publishers within their onMessage callback.

  • The messages received by the client include a "processed by: NNN" suffix, where NNN is the hashCode value of the MDB instance that processed the message. This shows that many MDBs may actively process messages posted to a destination. Concurrent processing is one of the benefits of MDBs.

JBossMQ is composed of several services working together to provide JMS API level services to client applications. The services that make up the JBossMQ JMS implementation are introduced in this section.

The Invocation Layer (IL) services are responsible for handling the communication protocols that clients use to send and receive messages. JBossMQ can support running different types of Invocation Layers concurrently. All Invocation Layers support bidirectional communication which allows clients to send and receive messages concurrently. ILs only handle the transport details of messaging. They delegate messages to the JMS server JMX gateway service known as the invoker. This is similar to how the detached invokers expose the EJB container via different transports.

Each IL service binds a JMS connection factory to a specific location in the JNDI tree. Clients choose the protocol they wish to use by the JNDI location used to obtain the JMS connection factory. JBossMQ currently has several different invocation layers.

  • UIL2 IL : The Unified Invocation Layer version 2(UIL2) is the preferred invocation layer for remote messaging. A multiplexing layer is used to provide bidirectional communication. The multiplexing layer creates two virtual sockets over one physical socket. This allows communication with clients that cannot have a connection created from the server back to the client due to firewall or other restrictions. Unlike the older UIL invocation layer which used a blocking round-trip message at the socket level, the UIL2 protocol uses true asynchronous send and receive messaging at the transport level, providing for improved throughput and utilization.

  • JVM IL : The Java Virtual Machine (JVM) Invocation Layer was developed to cut out the TCP/IP overhead when the JMS client is running in the same JVM as the server. This IL uses direct method calls for the server to service the client requests. This increases efficiency since no sockets are created and there is no need for the associated worker threads. This is the IL that should be used by Message Driven Beans (MDB) or any other component that runs in the same virtual machine as the server such as servlets, MBeans, or EJBs.

  • HTTP IL : The HTTP Invocation Layer (HTTPIL) allows for accessing the JBossMQ service over the HTTP or HTTPS protocols. This IL relies on the servlet deployed in the deploy/jms/jbossmq-httpil.sar to handle the http traffic. This IL is useful for access to JMS through a firewall when the only port allowed requires HTTP.

This section defines the MBean services that correspond to the components introduced in the previous section along with their MBean attributes. The configuration and service files that make up the JBossMQ system include:

  • deploy/hsqldb-jdbc-state-service.xml : This configures the JDBC state service for storing state in the embedded Hypersonic database.

  • deploy/jms/hsqldb-jdbc2-service.xml : This service descriptor configures the DestinationManager, MessageCache, and jdbc2 PersistenceManager for the embedded Hypersonic database.

  • deploy/jms/jbossmq-destinations-service.xml : This service describes defines default JMS queue and topic destination configurations used by the testsuite unit tests. You can add/remove destinations to this file, or deploy another *-service.xml descriptor with the destination configurations.

  • jbossmq-httpil.sar : This SAR file configures the HTTP invocation layer.

  • deploy/jms/jbossmq-service.xml : This service descriptor configures the core JBossMQ MBeans like the Invoker, SecurityManager, DynamicStateManager, and core interceptor stack. It also defines the MDB default dead letter queue DLQ.

  • deploy/jms/jms-ds.xml : This is a JCA connection factory and JMS provider MDB integration services configuration which sets JBossMQ as the JMS provider.

  • deploy/jms/jms-ra.rar : This is a JCA resource adaptor for JMS providers.

  • deploy/jms/jvm-il-service.xml : This service descriptor configures the JVMServerILService which provides the JVM IL transport.

  • deploy/jms/rmi-il-service.xml : This service descriptor configures the RMIServerILService which provides the RMI IL. The queue and topic connection factory for this IL is bound under the name RMIConnectionFactory.

  • deploy/jms/uil2-service.xml : This service descriptor configures the UILServerILService which provides the UIL2 transport. The queue and topic connection factory for this IL is bound under the name UIL2ConnectionFactory as well as UILConnectionFactory to replace the deprecated version 1 UIL service.

We will discuss the associated MBeans in the following subsections.

The org.jboss.mq.il.uil2.UILServerILService is used to configure the UIL2 IL. The configurable attributes are as follows:

  • Invoker : This attribute specifies JMX ObjectName of the JMS entry point service that is used to pass incoming requests to the JMS server. This is not something you would typically change from the jboss.mq:service=Invoker setting unless you change the entry point service.

  • ConnectionFactoryJNDIRef : The JNDI location that this IL will bind a ConnectionFactory setup to use this IL.

  • XAConnectionFactoryJNDIRef : The JNDI location that this IL will bind a XAConnectionFactory setup to use this IL.

  • PingPeriod : How often, in milliseconds, the client should send a ping message to the server to validate that the connection is still valid. If this is set to zero, then no ping message will be sent.

  • ReadTimeout : The period in milliseconds is passed onto as the SoTimeout value of the UIL2 socket. This allows detection of dead sockets that are not responsive and are not capable of receiving ping messages. Note that this setting should be longer in duration than the PingPeriod setting.

  • BufferSize : The size in bytes used as the buffer over the basic socket streams. This corresponds to the java.io.BufferedOutputStream buffer size.

  • ChunkSize : The size in bytes between stream listener notifications. The UIL2 layer uses the org.jboss.util.stream.NotifyingBufferedOutputStream and NotifyingBufferedInputStream implementations that support the notion of a heartbeat that is triggered based on data read/written to the stream. Whenever ChunkSize bytes are read/written to a stream. This allows serves as a ping or keepalive notification when large reads or writes require a duration greater than the PingPeriod.

  • ServerBindPort : The protocol listening port for this IL. If not specified default is 0, which means that a random port will be chosen.

  • BindAddress : The specific address this IL listens on. This can be used on a multi-homed host for a java.net.ServerSocket that will only accept connection requests on one of its addresses.

  • EnableTcpNoDelay : TcpNoDelay causes TCP/IP packets to be sent as soon as the request is flushed. This may improve request response times. Otherwise request packets may be buffered by the operating system to create larger IP packets.

  • ServerSocketFactory : The javax.net.ServerSocketFactory implementation class name to use to create the service java.net.ServerSocket. If not specified the default factory will be obtained from javax.net.ServerSocketFactory.getDefault().

  • ClientAddress : The address passed to the client as the address that should be used to connect to the server.

  • ClientSocketFactory : The javax.net.SocketFactory implementation class name to use on the client. If not specified the default factory will be obtained from javax.net.SocketFactory.getDefault().

  • SecurityDomain : Specify the security domain name to use with JBoss SSL aware socket factories. This is the JNDI name of the security manager implementation as described for the security-domain element of the jboss.xml and jboss-web.xml descriptors.

The UIL2 service support the use of SSL through custom socket factories that integrate JSSE using the security domain associated with the IL service. An example UIL2 service descriptor fragment that illustrates the use of the custom JBoss SSL socket factories is shown in Example 7.10, “An example UIL2 config fragment for using SSL”.