Novell Home

Multi-tiered Service-Oriented Systems with JBoss, Part 2

Novell Cool Solutions: Feature
By J. Jeffrey Hanson

Digg This - Slashdot This

Posted: 21 Jul 2005
 

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

This is the latest article in a series of articles discussing service-oriented systems development on JBoss 4.0. In the previous article, I discussed JBoss' 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.

In this article, I will begin a discussion of how JBoss 4.0 can be used to enhance a service-oriented system with access controls, service security, service persistence, service discovery, etc.

Review of the JBoss 4.0 Platform Architecture
The JBoss Microkernel Layer
The JBoss Service Layer
The JBoss Aspect Layer
The JBoss Application Layer
Access Controls and Services
Access Controls and Java
The Application Framework Architecture
The Components for the Application Framework
The ACL Utilities Class
Modifying the Management Interface for the Service
Defining the ParamValidateable Interface
Modifying the Service Implementation
The UserProfile Class
Adding Authentication to the FrontController Servlet
Modifying the ViewHelper Class
Modifying the BusinessDelegate Class
Deploying the Service to the JBoss Application Server
Modifying the SAR for the Service
Modifying the index.html File
Testing the Service
A "Create" Attempt without Permissions
A "Create" Attempt with Permissions
Conclusion
For Additional Information

Review of the JBoss 4.0 Platform Architecture

For review, the 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:

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

  • The JBoss 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: 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: 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

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

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.

Access Controls and Services

The loosely-coupled character of service-oriented systems and the high-degree of security threats, imply the need for extensive efforts to protect service invocations and ancillary resources. Access control lists (ACLs) can be used to protect service invocations from unauthorized access attempts. For a typical service, requests from malevolent users at the application level can be rejected using ACL-group membership and access control permissions.

Access Controls and Java

Access controls and permission in J2SE are comprised of a set of interfaces, classes, and APIs used to guard access to resources. An ACL in J2SE can be thought of as a container for multiple ACL entries. Each ACL entry contains a set of permissions associated with a particular entity, known as a "principal." Each ACL entry is specified as being either positive or negative, depending on whether or not the permissions are to be granted or denied respectively to the associated principal.

ACL Entries abide by the following rules:

  • Each principal can have at most one positive ACL entry and one negative entry. Each entry specifies the set of permissions that are to be granted or denied.

  • If there is no entry for a particular principal, then the principal is considered to have a null (empty) permission set.

  • If there is a positive entry and a negative entry for a principal for the same permission, the two cancel each other.

  • Individual permissions always override permissions of the group(s) to which the individual permissions belong.

The java.security.acl package provides the interfaces, classes, and APIs for constructing ACLs and related data objects such as ACL entries, groups, permissions, etc. The sun.security.acl classes provide a default implementation of the interfaces. For example, java.security.acl.Acl provides the interface to an ACL and the sun.security.acl.AclImpl class provides the default implementation of the interface.

The Application Framework Architecture

The application framework used for this article is built upon the JBoss platform in order to take advantage of the JBoss service infrastructure and the JMX management foundation. The following diagram illustrates these relationships:


Figure 2: The Web Application Framework

Next, the components used to embody the infrastructure of the Web application framework are discussed.

The Components for the Application Framework

The ancillary framework 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 response to the user.

The components of the Web application framework loosely follow the J2EE blueprints published by Sun. The following illustrates the portions of the blueprint defining the ancillary classes used in the framework:


Figure 3: Framework Components

The ACL Utilities Class

The ACL and permission functionality is encapsulated in a utilities class named ACLUtils, as follows:

package com.jeffhanson.businesstier;

import sun.security.acl.AclEntryImpl;
import sun.security.acl.PrincipalImpl;
import sun.security.acl.PermissionImpl;
import sun.security.acl.AclImpl;

import java.security.*;
import java.security.acl.*;
import java.security.acl.Permission;
import java.util.HashMap;

public class ACLUtils
{
   public static final Permission CREATE_PERMISSION =
      new PermissionImpl("CREATE");
   public static final Permission READ_PERMISSION =
      new PermissionImpl("READ");
   public static final Permission UPDATE_PERMISSION =
      new PermissionImpl("UPDATE");
   public static final Permission DELETE_PERMISSION =
      new PermissionImpl("DELETE");
   private static final Principal aclOwner =
      new PrincipalImpl("owner");
   private static Acl serviceAcl =
      new AclImpl(aclOwner, "serviceAcl");
   private static HashMap aclEntries = new HashMap();

   public static boolean checkPermission(String userName,
                                         Permission permission)
   {
      if (aclEntries.get(userName) == null)
      {
         return false;
      }

      AclEntry aclEntry = (AclEntry)aclEntries.get(userName);

      return serviceAcl.checkPermission(aclEntry.getPrincipal(),
                                        permission);
   }

   public static void setPermissions(String userName,
                                     Permission[] permissions)
      throws Exception
   {
      if (permissions == null || permissions.length == 0)
      {
         aclEntries.remove(userName);
         return;
      }

      Principal userPrincipal = new PrincipalImpl(userName);

      // Make an entry for the principal
      AclEntry aclEntry = new AclEntryImpl(userPrincipal);

      for (int i = 0; i < permissions.length; i++)
      {
         aclEntry.addPermission(permissions[i]);
      }

      serviceAcl.addEntry(aclOwner, aclEntry);

      aclEntries.put(userName, aclEntry);
   }
}

Modifying the Interface for the Service

The user-profile service is modified to manage separate user-profile objects consisting of a user's name, address, city, state, zip, and social security number. The user-profile service is used to specify the method-permissions for the user-profile class. The management interface for the user-profile service simply exposes a method for retrieving a list of user-profile names, as follows:

package com.jeffhanson.businesstier;

import org.jboss.system.Service;

public interface UserProfileServiceMBean extends Service
{
   public String[] getUserNames();
}

Defining the Securable Interface

The service needs an interface that can be used by a caller to identify the service as being securable. This interface is embodied within the Securable interface, as illustrated in the following listing:

package com.jeffhanson.businesstier;

import javax.management.MBeanInfo;

public interface Securable
{
   public boolean isSecuredCreateMethod(String methodName,
                                        Class[] paramTypes);

   public boolean isSecuredReadMethod(String methodName,
                                      Class[] paramTypes);

   public boolean isSecuredUpdateMethod(String methodName,
                                        Class[] paramTypes);

   public boolean isSecuredDeleteMethod(String methodName,
                                        Class[] paramTypes);
}

Defining the ParamValidateable Interface

The service also needs an interface that can be used to identify the service as an object that can validate and convert String params to applicable Object params for each method of the user-profile class. This interface is embodied within the ParamValidateable interface:

package com.jeffhanson.businesstier;

import javax.management.MBeanInfo;

public interface ParamValidateable
{
  public static final String PARAM_PREFIX = "PARAM_";
  
  public Object[] validateStringParams(String methodName, String[] params);
}

Modifying the Service Implementation

Now, the service's implementation class can be modified as needed. The invocations for the user-profile service will take the form of create-read-update-delete (CRUD) in order to present a uniform invocation model that can be transposed across most any service. The implementation class implements each interface of the application framework and the java.io.Serializable interface, as illustrated below:

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,
              Securable,
              ParamValidateable,
              Serializable
{
   ...

   // CRUD methods

   public UserProfile create(String name,
                             String address,
                             String cityStateZip,
                             String socialSecurityNumber)
   {
      UserProfile userProfile = new UserProfile(name,
                                                address,
                                                cityStateZip,
                                                socialSecurityNumber);
      userProfiles.put(socialSecurityNumber, userProfile);
      return userProfile;
   }

   public UserProfile read(String socialSecurityNumber)
   {
      if (userProfiles.get(socialSecurityNumber) == null)
      {
         return null;
      }

      return (UserProfile)userProfiles.get(socialSecurityNumber);
   }

   public UserProfile update(String name,
                             String address,
                             String cityStateZip,
                             String socialSecurityNumber)
   {
      if (userProfiles.get(socialSecurityNumber) == null)
      {
         return create(name, address, cityStateZip, socialSecurityNumber);
      }
      else
      {
         UserProfile userProfile =
            (UserProfile)userProfiles.get(socialSecurityNumber);
         if (name != null && name.length() > 0)
            userProfile.setName(name);
         if (address != null && address.length() > 0)
            userProfile.setAddress(address);
         if (cityStateZip != null && cityStateZip.length() > 0)
            userProfile.setCityStateZip(cityStateZip);
         if (socialSecurityNumber != null && socialSecurityNumber.length() > 0)
            userProfile.setSocialSecurityNumber(socialSecurityNumber);
         return userProfile;
      }
   }

   public void delete(String socialSecurityNumber)
   {
      userProfiles.remove(socialSecurityNumber);
   }

   // 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");
   }
}
 

The UserProfile Class

The UserProfile class is a simple value-object class consisting of a user's name, address, city, state, zip, and social security number:

package com.jeffhanson.businesstier;

import java.io.Serializable;

public class UserProfile
   implements Serializable
{
   private String name;
   private String address;
   private String cityStateZip;
   private String socialSecurityNumber;

   public UserProfile(String name,
                      String address,
                      String cityStateZip,
                      String socialSecurityNumber)
   {
      this.name = name;
      this.address = address;
      this.cityStateZip = cityStateZip;
      this.socialSecurityNumber = socialSecurityNumber;
   }

   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 + "<br/>\n"
                    + "Address: " +  address + "<br/>\n"
                    + "City, State Zip: " +  cityStateZip + "<br/>\n"
                    + "Social Security Number: " +  socialSecurityNumber;
      return retStr;
   }
}

Adding Authentication to the FrontController Servlet

Each HTTP request for the Web application is received initially by the ServiceServlet which embodies the FrontController pattern. The ServiceServlet class uses BASIC authentication (username and password) to authenticate a client and then simply and passes subsequent authenticated requests to the ViewHelper class. The following illustrates the modified ServiceServlet class:

package com.jeffhanson.applicationtier;

import com.jeffhanson.businesstier.ACLUtils;
import com.jeffhanson.businesstier.BusinessDelegate;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletException;
import javax.servlet.ServletConfig;
import java.io.IOException;
import java.security.acl.Permission;

public class ServiceServlet
   extends HttpServlet
{
   public static final String USER_NAME = "USER_NAME";

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

      // TODO: read users and permissions from config
      
      BusinessDelegate.addUser("jdoe",
                               "doej",
                               new Permission[] {
                                 ACLUtils.READ_PERMISSION });
      BusinessDelegate.addUser("bbunny",
                               "bunnyb",
                               new Permission[] {
                                 ACLUtils.CREATE_PERMISSION,
                                 ACLUtils.READ_PERMISSION,
                                 ACLUtils.UPDATE_PERMISSION,
                                 ACLUtils.DELETE_PERMISSION });
      BusinessDelegate.addUser("dduck",
                               "duckd",
                               new Permission[] {
                                 ACLUtils.CREATE_PERMISSION,
                                 ACLUtils.READ_PERMISSION,
                                 ACLUtils.UPDATE_PERMISSION,
                                 ACLUtils.DELETE_PERMISSION });
   }

   protected void doGet(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException
   {
      doPost(req, res);
   }

   protected void doPost(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException
   {
      // Get Authorization header
      String authHeader = req.getHeader("Authorization");
      String userName = null;
      if ((userName = checkUser(authHeader)) == null)
      {
         // Not allowed, so report as unauthorized
         res.setHeader("WWW-Authenticate",
                       "BASIC realm=\"users\"");
         res.sendError(res.SC_UNAUTHORIZED);
      }
      else
      {
         HttpSession session = req.getSession(true);
         if (session.getAttribute(USER_NAME) == null)
         {
            session.setAttribute(USER_NAME, userName);
         }
         ViewHelper.getInstance().dispatchToView(req, res);
      }
   }

   /**
    * Checks the user data from the authorization header against
    * a preset table of user credentials.
    *
    * @param authHeader    authorization header
    * @return <code>String</String> the userName or null
    * @throws IOException  on error
    */
   protected String checkUser(String authHeader)
      throws IOException
   {
      if (authHeader == null)
      {
         return null;
      }

      if (!authHeader.toUpperCase().startsWith("BASIC "))
      {
         return null;
      }

      // Get encoded user and password after "BASIC "
      String userpassEncoded = authHeader.substring("BASIC ".length());

      // Decode it, using a base-64 decoder
      sun.misc.BASE64Decoder dec = new sun.misc.BASE64Decoder();
      String userpassDecoded = new String(dec.decodeBuffer(userpassEncoded));

      int idx = userpassDecoded.indexOf(":");
      if (idx < 0 || idx >= (userpassDecoded.length() - 1))
      {
         return null;
      }

      String userName = userpassDecoded.substring(0, idx);
      String password = userpassDecoded.substring(idx + 1);

      if (BusinessDelegate.isUserValid(userName, password))
      {
         return userName;
      }
      else
      {
         return null;
      }
   }
}

Notice in the preceding listing how each user and associated permissions are hard-coded in the servlet's "init" method. A production implementation should move these definitions to secondary storage in order to facilitate a more declarative configuration.

Modifying the ViewHelper Class

Each HTTP request is passed from the FrontController servlet to the ViewHelper class and then dissected and dispatched to the appropriate service. The ViewHelper class enlists the help of the BusinessDelegate class to find the service and to propagate service invocations. The ViewHelper class uses the BusinessDelegate class to make the desired method call on the service. The ViewHelper class then formats the response, and passes the response back to the HTTP client. The following illustrates the noteworthy content of the modified 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
   {
      try
      {
         String serviceName = req.getParameter("ServiceName");
         String methodName = req.getParameter("MethodName");
         String[] params = null;
         Vector paramList = new Vector();
         Enumeration paramNames = req.getParameterNames();
         if (paramNames.hasMoreElements())
         {
           while (paramNames.hasMoreElements())
           {
             String paramName = paramNames.nextElement().toString();
             if (paramName.toUpperCase().startsWith(ParamValidateable.PARAM_PREFIX))
             {
               String[] paramVals = req.getParameterValues(paramName);
               if (paramVals != null && paramVals.length > 0)
               {
                  String paramValStr = "";
                  for (int i = 0; i < paramVals.length; i++)
                  {
                    if (i > 0)
                    {
                      paramValStr += ",";
                    }
                    paramValStr += paramVals[i];
                  }
                  paramList.addElement(paramName +"=" + paramValStr);
               }
             }
           }
           params = new String[paramList.size()];
           paramList.copyInto(params);
         }


         if (serviceName == null || serviceName.length() == 0)
         {
            showErrorPage(res, "Service name was not specified.");
            return;
         }

         JMXManageable service =
            BusinessDelegate.getInstance().locateService(serviceName);

         if (service == null)
         {
            showErrorPage(res, "Unable to find service: " + serviceName);
         }
         else if (methodName == null || methodName.length() == 0)
         {
            showErrorPage(res, "Method name was not specified.");
         }
         else if (methodName.equals("showInfo"))
         {
            showServiceInfoPage(res, service);
         }
         else if (methodName.equals("showSecurity"))
         {
            showSecurityPage(req, res);
         }
         else
         {
            String userName =
               (String)req.getSession().getAttribute(ServiceServlet.USER_NAME);
            Object retObj =
               BusinessDelegate.invokeServiceMethod(userName,
                                                    (ParamValidateable)service,
                                                    methodName,
                                                    params);
            showSuccessPage(res, serviceName, retObj);
         }
      }
      catch (Exception e)
      {
         showErrorPage(res, "Exception encountered: " + e.toString());
      }
   }
}

Modifying the BusinessDelegate Class

The BusinessDelegate class uses the ServiceLocator class to retrieve the appropriate service, which is then invoked as needed. Before invoking methods on the service, the BusinessDelegate class checks each request for the appropriate permissions and continues or aborts as applicable. The following illustrates the important subject matter of the BusinessDelegate class:

package com.jeffhanson.businesstier;

public class BusinessDelegate
{
   ...

   private static HashMap users = new HashMap();
   
   static class UserCredentials
   {
      private String password = null;
      private Permission[] permissions = null;

      private UserCredentials(String password,
                              Permission[] permissions)
      {
         this.password = password;
         this.permissions = new Permission[permissions.length];
         System.arraycopy(permissions, 0,
                          this.permissions, 0,
                          permissions.length);
      }
   }

   public static void addUser(String userName,
                              String password,
                              Permission[] permissions)
   {
      try
      {
         ACLUtils.setPermissions(userName, permissions);
      }
      catch (Exception e)
      {
         System.err.println(e);
      }

      users.put(userName, new UserCredentials(password,
                                              permissions));
   }
   
   public static boolean isUserValid(String userName,
                                     String password)
   {
      if (users.get(userName) == null)
      {
         return false;
      }

      UserCredentials credentials = (UserCredentials)users.get(userName);
      if (credentials.password.equals(password))
      {
         return true;
      }

      return false;
   }

   public static Object invokeServiceMethodSecured(String userName,
                                                   Object service,
                                                   String methodName,
                                                   Object[] paramObjs)
      throws Exception
   {
      if (!(service instanceof Securable))
      {
         throw new Exception("Service is not securable.");
      }

      Class[] paramTypes = null;
      if (paramObjs != null && paramObjs.length > 0)
      {
         paramTypes = new Class[paramObjs.length];
         for (int i = 0; i < paramObjs.length; i++)
         {
            paramTypes[i] = paramObjs[i].getClass();
         }
      }

      try
      {
         if (!((Securable)service).isSecuredCreateMethod(methodName, paramTypes) &&
             !((Securable)service).isSecuredReadMethod(methodName, paramTypes) &&
             !((Securable)service).isSecuredUpdateMethod(methodName, paramTypes) &&
             !((Securable)service).isSecuredDeleteMethod(methodName, paramTypes))
         {
            throw new Exception("Method: " + methodName
                                + " is not a secured method.");
         }

         if (((Securable)service).isSecuredCreateMethod(methodName, paramTypes) &&
             ACLUtils.checkPermission(userName, ACLUtils.CREATE_PERMISSION) == false)
         {
            throw new Exception("User: " + username
                                + " does not have CREATE permission");
         }
         if (((Securable)service).isSecuredReadMethod(methodName, paramTypes) &&
             ACLUtils.checkPermission(userName, ACLUtils.READ_PERMISSION) == false)
         {
            throw new Exception("User: " + username
                                + " does not have READ permission");
         }
         if (((Securable)service).isSecuredUpdateMethod(methodName, paramTypes) &&
             ACLUtils.checkPermission(userName, ACLUtils.UPDATE_PERMISSION) == false)
         {
            throw new Exception("User: " + username
                                + " does not have UPDATE permission");
         }
         if (((Securable)service).isSecuredDeleteMethod(methodName, paramTypes) &&
             ACLUtils.checkPermission(userName, ACLUtils.DELETE_PERMISSION) == false)
         {
            throw new Exception("User: " + username
                                + " does not have DELETE permission");
         }

         Class serviceCls = service.getClass();
         Method method = serviceCls.getMethod(methodName,
                                              paramTypes);

         Object retObj = null;
         try
         {
            retObj = method.invoke(service, paramObjs);
         }
         catch (IllegalAccessException e)
         {
            throw new Exception("Illegal access for service method: "
                                + methodName
                                + ", with the specified param types.");
         }
         catch (IllegalArgumentException e)
         {
            throw new Exception("Illegal argument list for service method: "
                                + methodName);
         }
         catch (InvocationTargetException e)
         {
            throw new Exception("Invocation target exception "
                                + "for service method: "
                                + methodName);
         }

         return retObj;
      }
      catch (NoSuchMethodException e)
      {
         throw new Exception("Service does not expose method: "
                             + methodName
                             + ", with the specified param types.");
      }
   }
}


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 /server/all/deploy directory.

Modifying the SAR for the Service

The SAR for the service is contained in a directory structure named "soawithjboss.sar". The modified "SAR" directory is deployed within a structure as follows:


Figure 4: The Modified SAR Directory Structure

Modifying the index.html File

The Web application consists of one HTML file – index.html. The index.html file consists of four discrete HTML forms for posting CRUD requests to the ServiceServlet class, 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/>

<h2>Create a New User</h2>
<form name="CreateUser" method="post" action="/soawithjboss/services">
   Name: <input type="text" name="PARAM_Name"/><br/>
   Address: <input type="text" name="PARAM_Address"/><br/>
   City, State Zip: <input type="text" name="PARAM_CityStateZip"/><br/>
   Social Security Number: <input type="text" name="PARAM_SocialSecurityNumber"/><br/>
   <input type="hidden" name="ServiceName" value="com.jeffhanson.businesstier.UserProfileService" />
   <input type="submit" name="MethodName" value="create">
</form>

<br/>

<h2>Update an Existing User</h2>
<form name="UpdateUser" method="post" action="/soawithjboss/services">
   Name: <input type="text" name="PARAM_Name"/><br/>
   Address: <input type="text" name="PARAM_Address"/><br/>
   City, State Zip: <input type="text" name="PARAM_CityStateZip"/><br/>
   Social Security Number: <input type="text" name="PARAM_SocialSecurityNumber"/><br/>
   <input type="hidden" name="ServiceName" value="com.jeffhanson.businesstier.UserProfileService" />
   <input type="submit" name="MethodName" value="update">
</form>

<br/>

<h2>Read an Existing User</h2>
<form name="ReadUser" method="post" action="/soawithjboss/services">
   Social Security Number: <input type="text" name="PARAM_SocialSecurityNumber"/><br/>
   <input type="hidden" name="ServiceName" value="com.jeffhanson.businesstier.UserProfileService" />
   <input type="submit" name="MethodName" value="read">
</form>

<br/>

<h2>Delete an Existing User</h2>
<form name="DeleteUser" method="post" action="/soawithjboss/services">
   Social Security Number: <input type="text" name="PARAM_SocialSecurityNumber"/><br/>
   <input type="hidden" name="ServiceName" value="com.jeffhanson.businesstier.UserProfileService" />
   <input type="submit" name="MethodName" value="delete">
</form>

</body>
</html>

Testing the Service

Now that the modifications are made and the directory structure is 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:


Figure 5: Initial Application View

A "Create" Attempt without Permissions

Now, fill-out the "Create a New User" form and submit it and you will be presented with a username/password authentication dialog. Type in jdoe as the username and doej as the password and you will see the following response page:


Figure 6: Failed "Create" Attempt Result Page

Since the jdoe user only has "read" permissions, attempts to create, update, or delete will fail.

A "Create" Attempt with Permissions

Now, close the browser window and open a new one. Next, fill-out the "Create a New User" form and submit it and you will be presented with a username/password authentication dialog. Type in bbunny as the username and bunnyb as the password and you will see the following response page:


Figure 7: Successful "Create" Attempt Result Page

Since the bbunny user has "create", "read", "update", and "delete" permissions, attempts to create, read, update, and delete will all succeed.

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 and the J2SE access-control-list framework, allow you to build dynamic, sophisticated, and secure services, applications, and systems.

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

You can download the files associated with this article [HERE].

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