Novell Home

Multi-tiered Service-Oriented Systems with JBoss

Novell Cool Solutions: Feature
By J. Jeffrey Hanson

Digg This - Slashdot This

Posted: 21 Jun 2005
 

J. Jeffrey Hanson
Chief Architect
eReinsure.com, Inc.
jeff@jeffhanson.com

This is the first in a series of articles to explore building multi-tiered service-oriented systems on the JBoss 4.0 platform. This article outlines JBoss service-implementation fundamentals as well as techniques for instrumenting services using JBoss and JMX.

JOverview of the JBoss 4.0 Platform Architecture
The JBoss Microkernel Layer
The JBoss Service Layer
The JBoss Aspect Layer
The JBoss Application Layer
JBoss Service-Implementation Fundamentals
The JBoss Service Lifecycle
JBoss and JMX
Instrumenting Services using JBoss and JMX
Defining the Management Interface for the Service
Defining the JMXManageable Interface
Creating the Service Implementation
Creating the Service Deployment Descriptor
Creating the Ancillary Components for the Service
Creating the FrontController Servlet for the Service
Creating the ViewHelper Class
Creating the BusinessDelegate Class
Creating the ServiceLocator Class
Deploying the Service to the JBoss Application Server
Creating the SAR for the Service
Creating the index.html File
Creating the web.xml File
Testing the Service
Viewing the Initial Attribute Values for the Service
Monitoring and Modifying the Service Using the JBoss JMX Console
Viewing the Updated Attribute Values for the Service
Conclusion
For Additional Information

Overview of the JBoss 4.0 Platform Architecture

JBoss Application Server version 4.0 provides a comprehensive platform for developing multi-tiered, service-oriented enterprise systems. JBoss 4.0 is J2EE 1.4 compliant and uses a Java Management Extensions (JMX) -based microkernel as its services foundation. Services in the JBoss 4.0 platform are implemented as plain-old Java Objects (POJOs) and can be instrumented easily using the JBoss JMX framework.

The JBoss 4.0 platform is comprised of four main architectural layers, as discussed next.

The JBoss Microkernel Layer

The JBoss foundation is a JMX-based microkernel-based layer. This microkernel facilitates a hot-deployable component model with dynamic class-loading features and lifecycle management.

The JBoss Service Layer

The next layer is the service layer. This layer enables custom services and provides pre-built JBoss services, such as transaction services, messaging services, security services, etc. Each service is packaged as a hot-deployable component known as a "Service ARchive' (SAR).

The JBoss Aspect Layer

The next layer is the aspect layer. In this layer, components are embodied in interceptors which enable the system to add service behavior transparently into any object. In JBoss 4.0, aspect-oriented programming (AOP) techniques are employed along with bytecode engineering in order to facilitate custom behavior within this layer.

The JBoss Application Layer

The next layer is the application layer. This layer is the where end-user applications and systems are deployed. The following diagram illustrates the relationships between each of the JBoss 4.0 layers.


Figure 1: The JBoss 4.0 Platform Architecture

JBoss Service-Implementation Fundamentals

A service in JBoss 4.0 is implemented as a plain-old Java Object (POJO). This means that you, the service developer, are not required to learn any new programming concepts or a proprietary syntax.

The minimal requirement for a JBoss service is a simple POJO, which is deployed as a JMX-compliant managed resource, known as an MBean. You can create an MBean for any POJO using XMBean, which is the JBoss version of a JMX ModelMBean. XMBeans allow you to specify the management interface of a component declaratively, using an XML descriptor.

JBoss services are deployed in one of three ways:

  1. -service.xml files
  2. .sar files or directories with a META-INF/jboss-service of the -service.xml format
  3. programmatically

The key elements and attributes defined within a -service.xml file are:

  • code – the class that implements the service
  • name – the JMX ObjectName that uniquely identifies the service
  • attribute – this defines the attributes of the service
  • depends – this defines dependencies on other services

The following is an example of a simple -service.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<server>
   <mbean code="com.jeffhanson.businesstier.DateService"
          name="jeffhanson.com:service=DateService">
      <attribute name="CurrentDate">5.22.2005</attribute>
   </mbean>
</server>

A JBoss SAR is either a service archive (packaged as a JAR file) or a directory with a name ending in .sar. A SAR file or directory contains a JBoss service definition file (META-INF/jboss-service.xml) and other resources needed by the service.

The JBoss Service Lifecycle

JBoss manages four optional lifecycle operations for a service's management interface:

  1. create() – this is called when the service is created. Any needed setup steps are taken here
  2. start() – this is called when the service is activated. Reference other services can be performed here
  3. stop() – this is called when the service is being deactivated. A service should perform temporary cleanup steps here, such as removing links to other services
  4. destroy() – this is called when the service is being removed. Permanent cleanup steps are taken here

JBoss and JMX

JMX is a specification and provider framework for managing and monitoring software and hardware resources from Java. JMX consists of three levels of management functionality:

  • Instrumentation – this level deals with the resources to manage. Components on this level include:
    • Managed Beans (MBeans)
    • Notification components
    • MBean metadata
  • Agents – this level deals with the controllers of the instrumentation-level resources. Components on this level include:
    • MBean server
    • Agent services
  • Distributed Services – this level deals with the mechanisms by which administration applications interact with agents and their managed resources

The JBoss management architecture consists of a JMX MBean server instance (the microkernel) and a set of pluggable MBean services. These MBean services can be removed and/or modified as needed. The different server configurations provided by JBoss 4.0 are constructed using different configuration of these MBean services. These server configurations are as follows:

  • default: The default configuration contains everything you need to run a stand-alone J2EE server.
  • minimal: The bare minimum required to start JBoss. It consists of a logging service, a JNDI server, and a URL-based deployment scanner enabling hot-deployment of services and Web applications. There are no additional services such as a Web container, an EJB container or a JMS framework.
  • all: This configuration loads all of the available JBoss/J2EE services, including RMI/IIOP, clustering services, and a Web services deployer.

Each server configuration is embodied within a file-system directory aptly named "default", "minimal", or "all." Each server configuration contains a deployment directory where services and Web applications are deployed. The specific contents of each server-configuration's directory structure are as follows:

  • conf: contains the jboss-service.xml file specifying the core services for the server
  • data: contains an embedded database used by the JBoss implementation of JMS to store messages
  • deploy: contains services and Web applications. The directory is periodically scanned for new and modified components which are re-deployed automatically
  • lib: contains jar files needed by the server configuration
  • log: contains log files for the server configuration
  • tmp: contains temporary files, such as unpacked applications
  • work: contains compiled JavaServer Pages (JSPs)

Instrumenting Services using JBoss and JMX

JBoss MX is the JBoss implementation of the JMX technology. JBoss MX shipped with JBoss 4.0 supports version 1.2 of the JMX specification. JBoss MX is used to manage services deployed in JBoss 4.0. Each service can be instrumented as an MBean and configured using the JMX console shipped with JBoss 4.0. This allows services to be dynamically configured at runtime within the JBoss 4.0 environment.

To illustrate the management capabilities of JBoss 4.0, I will show you how to create a simple user-profile service and how to define the management interface and deployment-descriptor used by JBoss 4.0. First, I will discuss the management interface for the user-profile service.

Defining the Management Interface for the Service

The user-profile service consists of a user's name, address, city, state, zip, and social security number. The important things to note are how the interface extends the org.jboss.system.Service interface (found in \lib\jboss-system.jar) and how the name of the management interface is the same as the service-implementation class with "MBean" appended.

package com.jeffhanson.businesstier;

import org.jboss.system.Service;

public interface UserProfileServiceMBean extends Service
{
   public String getName();

   public void setName(String name);

   public String getAddress();

   public void setAddress(String address);

   public String getCityStateZip();

   public void setCityStateZip(String cityStateZip);

   public String getSocialSecurityNumber();

   public void setSocialSecurityNumber(String socialSecurityNumber);
}

Defining the JMXManageable Interface

The service needs an interface that can be used to identify the service as being manageable by a JMX-enabled entity. This interface is embodied within the JMXManageable interface:

package com.jeffhanson.businesstier;

import javax.management.MBeanInfo;

public interface JMXManageable
{
   public String getObjectNameString();

   public MBeanInfo getMBeanInfo()
      throws Exception;
}

Creating the Service Implementation

Now that the service's management interface and the JMX-manageable interface are defined, the service's implementation class can be defined. The implementation class implements java.io.Serializable so its state can be persisted and restored.

package com.jeffhanson.businesstier;

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

import javax.naming.InitialContext;
import javax.management.ObjectName;
import javax.management.MBeanInfo;
import java.io.Serializable;

public class UserProfileService
   implements UserProfileServiceMBean,
              JMXManageable,
              Serializable
{
   private static final String MBEAN_OBJ_NAME = "jeffhanson.com:service=UserProfile";
   private static UserProfileService instance = null;

   public static UserProfileService getInstance()
   {
      if (instance == null)
      {
         // TODO: retrieve user info from directory service
         instance = new UserProfileService();
      }

      return instance;
   }

   private String name;
   private String address;
   private String cityStateZip;
   private String socialSecurityNumber;

   public UserProfileService()
   {
      System.out.println("UserProfileService default constructor");
   }

   public String getName()
   {
      System.out.println("UserProfileService.getName");
      return name;
   }

   public void setName(String name)
   {
      System.out.println("UserProfileService.setName");
      this.name = name;
   }

   public String getAddress()
   {
      System.out.println("UserProfileService.getAddress");
      return address;
   }

   public void setAddress(String address)
   {
      System.out.println("UserProfileService.setAddress");
      this.address = address;
   }

   public String getCityStateZip()
   {
      System.out.println("UserProfileService.getCityStateZip");
      return cityStateZip;
   }

   public void setCityStateZip(String cityStateZip)
   {
      System.out.println("UserProfileService.setCityStateZip");
      this.cityStateZip = cityStateZip;
   }

   public String getSocialSecurityNumber()
   {
     System.out.println("UserProfileService.getSocialSecurityNumber");
     return socialSecurityNumber;
   }

   public void setSocialSecurityNumber(String socialSecurityNumber)
   {
      System.out.println("UserProfileService.setSocialSecurityNumber");
      this.socialSecurityNumber = socialSecurityNumber;
   }

   public String toString()
   {
      String retStr = "Name: " + name + "\n"
                    + "Address: " +  address + "\n"
                    + "City, State Zip: " +  cityStateZip + "\n"
                    + "Social Security Number: " +  socialSecurityNumber;
      return retStr;
   }

   // JMXManageable interface implementation

   public String getObjectNameString()
   {
      return MBEAN_OBJ_NAME;
   }

   public MBeanInfo getMBeanInfo()
      throws Exception
   {
      InitialContext ic = new InitialContext();
      RMIAdaptor server = (RMIAdaptor) ic.lookup("jmx/rmi/RMIAdaptor");

      ObjectName name = new ObjectName(MBEAN_OBJ_NAME);

      // Get the MBeanInfo for this MBean
      MBeanInfo info = server.getMBeanInfo(name);
      return info;
   }

   // JBoss service lifecycle methods

   public void create()
      throws Exception
   {
      System.out.println("UserProfileService.create");
   }

   public void start()
      throws Exception
   {
      System.out.println("UserProfileService.start");
   }

   public void stop()
   {
      System.out.println("UserProfileService.stop");
   }

   public void destroy()
   {
      System.out.println("UserProfileService.destroy");
   }
}

Creating the Service Deployment Descriptor

The service is deployed to the JBoss 4.0 server configuration as a SAR which includes a jboss-service.xml file containing deployment instructions, as follows:

<?xml version="1.0" encoding="UTF-8"?>
<server>
   <mbean code="com.jeffhanson.businesstier.UserProfileService"
          name="jeffhanson.com:service=UserProfile">
      <attribute name="Name">Unknown</attribute>
      <attribute name="Address">Unknown</attribute>
      <attribute name="CityStateZip">Unknown</attribute>
      <attribute name="SocialSecurityNumber">Unknown</attribute>
   </mbean>
</server>

Notice in the jboss-service.xml file how each attribute corresponds to the properties of the service's management interface. The attributes are initialized with the string "Unknown" and will be modified using the JBoss 4.0 JMX console.

Now, the components used to makeup the infrastructure of the Web application are defined.

Creating the Ancillary Components for the Service

The components of the Web application that I will use to demonstrate the user-profile service loosely follow the J2EE blueprints published by Sun. The following illustrates the portions of the blueprint defining the ancillary classes for the service:

The ancillary classes facilitate a simple HTTP-based Web application which receives a service request from a user, dispatches the request to the service, and returns an HTTP-based request to the user. Each ancillary class is discussed in the following sections.

Creating the FrontController Servlet for the Service

Each HTTP request for the Web application in this demonstration will be initially received by a servlet which embodies the FrontController pattern. The FrontController servlet simply receives each HTTP request and passes the request to the ViewHelper class. The following illustrates the FrontController servlet:

package com.jeffhanson.applicationtier;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class ServiceServlet
   extends HttpServlet
{
   protected void doGet(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException
   {
      doPost(req, res);
   }

   protected void doPost(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException
   {
      ViewHelper.getInstance().dispatchToView(req, res);
   }
}



The ancillary classes facilitate a simple HTTP-based Web application which receives a service request from a user, dispatches the request to the service, and returns an HTTP-based request to the user. Each ancillary class is discussed in the following sections.

Creating the FrontController Servlet for the Service

Each HTTP request for the Web application in this demonstration will be initially received by a servlet which embodies the FrontController pattern. The FrontController servlet simply receives each HTTP request and passes the request to the ViewHelper class. The following illustrates the FrontController servlet:

package com.jeffhanson.applicationtier;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

public class ServiceServlet
   extends HttpServlet
{
   protected void doGet(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException
   {
      doPost(req, res);
   }

   protected void doPost(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException
   {
      ViewHelper.getInstance().dispatchToView(req, res);
   }
}

Creating the ViewHelper Class

Each HTTP request passed from the FrontController servlet to the ViewHelper class is then dissected and dispatched to the appropriate service. The ViewHelper class enlists the help of the BusinessDelegate class to find the service. The ViewHelper class then makes the desired method call on the service, formats the response, and passes the response back to the HTTP client. The following illustrates the ViewHelper class:

package com.jeffhanson.applicationtier;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.naming.InitialContext;
import javax.management.*;
import java.io.PrintWriter;
import java.io.IOException;

import com.jeffhanson.businesstier.BusinessDelegate;
import com.jeffhanson.businesstier.JMXManageable;

public class ViewHelper
{
   private static ViewHelper instance = null;

   public static ViewHelper getInstance()
   {
      if (instance == null)
      {
         instance = new ViewHelper();
      }
      return instance;
   }

   private ViewHelper()
   {
   }

   public void dispatchToView(HttpServletRequest req,
                              HttpServletResponse res)
      throws IOException
   {
      PrintWriter out = res.getWriter();
      try
      {
         out.println("<h2>Welcome to SOA with JBoss</h2>");
         // TODO: modify the next line to accommodate dynamic service names
         String serviceName = "com.jeffhanson.businesstier.UserProfileService";
         JMXManageable service =
            BusinessDelegate.getInstance().locateService(serviceName);
         MBeanInfo mBeanInfo = service.getMBeanInfo();
         showServiceInfo(out, service, mBeanInfo);
      }
      catch (Exception e)
      {
         out.println("<p><pre>");
         e.printStackTrace(out);
         out.println("</pre></p>");
      }
      out.flush();
      out.close();
   }

   private void showServiceInfo(PrintWriter out,
                                JMXManageable service,
                                MBeanInfo info)
      throws Exception
   {
      InitialContext ic = new InitialContext();
      RMIAdaptor server = (RMIAdaptor) ic.lookup("jmx/rmi/RMIAdaptor");

      out.println("<br/>UserProfile Class: " + info.getClassName());

      MBeanAttributeInfo[] attrInfo = info.getAttributes();
      out.println("<br/><br/>UserProfile Attributes: ");

      ObjectName name = new ObjectName(service.getObjectNameString());

      String attrValue = (String)server.getAttribute(name, "Name");
      out.print("<br/>    Name = " + attrValue);

      attrValue = (String)server.getAttribute(name, "Address");
      out.print("<br/>    Address = " + attrValue);

      attrValue = (String)server.getAttribute(name, "CityStateZip");
      out.print("<br/>    CityStateZip = " + attrValue);

      attrValue = (String)server.getAttribute(name, "SocialSecurityNumber");
      out.print("<br/>    SocialSecurityNumber = "
                + attrValue);
   }
}

Creating the BusinessDelegate Class

The BusinessDelegate class simply uses the ServiceLocator class to retrieve the appropriate service. The service is then returned to the ViewHelper class. The following illustrates the BusinessDelegate class:

package com.jeffhanson.businesstier;

public class BusinessDelegate
{
   private static BusinessDelegate instance = null;

   public static BusinessDelegate getInstance()
   {
      if (instance == null)
      {
         instance = new BusinessDelegate();
      }
      return instance;
   }

   private BusinessDelegate()
   {
   }

   public JMXManageable locateService(String serviceName)
      throws Exception
   {
      JMXManageable service =
         ServiceLocator.getInstance().locateService(serviceName);
      return service;
   }
}

Creating the ServiceLocator Class

The function of the ServiceLocator class is to provide a transparent means to find a service no matter where or how it is deployed. This can include services deployed in a separate JVM, across a network, etc. For purposes of this article, the ServiceLocator class simply assumes the name of the service to be the Java package and class name of the service. The package and class name of the service are used to instantiate the service using standard Java instantiation mechanisms. The following illustrates the ServiceLocator class:

package com.jeffhanson.businesstier;

public class ServiceLocator
{
   private static ServiceLocator instance = null;

   public static ServiceLocator getInstance()
   {
      if (instance == null)
      {
         instance = new ServiceLocator();
      }
      return instance;
   }

   private ServiceLocator()
   {
   }

   public JMXManageable locateService(String serviceName)
      throws Exception
   {
      Class serviceCls = Class.forName(serviceName);
      Object obj = serviceCls.newInstance();
      JMXManageable service = (JMXManageable)obj;
      return service;
   }
}

Deploying the Service to the JBoss Application Server

The service classes, configuration files, and deployment descriptor are packaged as a SAR directory. The classes declared in the deployment descriptor must be packaged in a .jar file at the root of the SAR directory structure. The server configuration, "all", will be used for this application, so the SAR directory will be created as a child of the <JBOSS_HOME>/server/all/deploy directory.

Creating the SAR for the Service

To create the SAR for the service, a directory structure named "soawithjboss.sar" is defined and files are deployed within this structure as follows:

oawithjboss.sar/META-INF/jboss-service.xml
soawithjboss.sar/com/jeffhanson/businesstier/BusinessDelegate.class
soawithjboss.sar/com/jeffhanson/businesstier/JMXManageable.class
soawithjboss.sar/com/jeffhanson/businesstier/ServiceLocator.class
soawithjboss.sar/com/jeffhanson/businesstier/UserProfileService.class
soawithjboss.sar/com/jeffhanson/businesstier/UserProfileServiceMBean.class
soawithjboss.sar/soawithjboss.war/index.html
soawithjboss.sar/soawithjboss.war/WEB-INF/jboss-web.xml
soawithjboss.sar/soawithjboss.war/WEB-INF/web.xml
soawithjboss.sar/soawithjboss.war/WEB-INF/classes/com/jeffhanson/applicationtier/ServiceServlet.class
soawithjboss.sar/soawithjboss.war/WEB-INF/classes/com/jeffhanson/applicationtier/ViewHelper.class

Creating the index.html File

The Web application consists of one HTML file – index.html. The index.html file consists of one link to the FrontController servlet, as follows:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>
<head>
<title>SOA with JBoss</title>
</head>
<body>
<h2>SOA with JBoss</h2>
<br/>
Select to list UserProfileService attributes:
<a href="/soawithjboss/services">UserProfileService</a>
</body>
</html>

Creating the web.xml File

The web.xml file consists of one servlet definition for the FrontController servlet, as follows:

<?xml version="1.0"?>
<!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>
   <description>SOA with JBoss</description>

   <servlet>
      <servlet-name>ServiceServlet</servlet-name>
      <servlet-class>com.jeffhanson.applicationtier.ServiceServlet</servlet-class>
   </servlet>

   <servlet-mapping>
      <servlet-name>ServiceServlet</servlet-name>
      <url-pattern>/services/*</url-pattern>
   </servlet-mapping>
</web-app>


Testing the Service

Now that the directory structure is in place and the files are deployed, start the JBoss application server by executing the run.bat or run.sh file (depending on your operating system environment) with the command-line options "-c all" as follows:

run.bat -c all

This will execute the server configuration defined at <JBOSS_HOME>/server/all.

Once the server has completed the startup sequence, go to http://localhost:8080/soawithjboss and you will see the following response page:

Viewing the Initial Attribute Values for the Service

Now, select the "UserProfileService" link and you will see the following response page:

Notice how each attribute of the user-profile service reflects the values ("Unknown") specified in the jboss-service.xml file.

Monitoring and Modifying the Service Using the JBoss JMX Console

Now, locate the JMX console by going to http://localhost:8080/jmx-console. Notice that if you have configured the servlet container to service a different port then you need to supply another port number in the URL.

The JMX console provides a view into the microkernel of the JBoss application server. It lists all registered services (MBeans) that are active in the application server. Since the user-profile service defines an MBean interface and the jboss-service.xml file declares the user-profile service, the JBoss JMX console can now be used to configure the values for the user-profile service's properties at runtime.

http://localhost:8080/jmx-console/HtmlAdaptor?action=inspectMBean&name=jeffhanson.com%3Aservice%3DUserProfile returns the following response page showing the current values for the user-profile service:

Apply changes to the MBean-attributes by typing new values in the "Value" boxes, as follows:


Now, press the "Apply Changes" button; go back to http://localhost:8080/soawithjboss/services; refresh the page; and, you will see the updated MBean values in the response page, as follows:

Viewing the Updated Attribute Values for the Service

Conclusion

JBoss 4.0 provides a sophisticated platform for developing multi-tiered, service-oriented, enterprise systems. Its use of plain-old Java Objects and JMX-based management techniques enables service developers to define and implement services that can be deployed and configured in many different configurations to meet business demands. Using the tips discussed in this article, the JBoss 4.0 service-oriented platform architecture, allows you to build dynamic and sophisticated services, applications, and systems.

This is the first of a series of articles discussing service-oriented systems development on JBoss 4.0. In subsequent articles, I will discuss how JBoss 4.0 can be used to enhance a service-oriented system with service security, persistence, discovery, etc.

For Additional Information

For more information about the technologies discussed in this Cool Solution for Developers, refer to the following resources:


Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

© 2014 Novell