Jul 12, 2002 1:00 AM PT

Serve clients' specific protocol requirements with Brazil, Part 6

Plug Jini, BeanShell, and JAXM into Brazil

Brazil is a third-generation HTTP server that provides an extensible, understandable framework for building small, application-specific HTTP servers. In Parts 1 through 5 of this series, I discussed how to use Brazil technology to process content from various nontraditional sources, how to add to that content, and how to deliver that content to users via different delivery mechanisms and networks, such as applets, Java Reliable Multicast Service (JRMS), wireless clients, and plain HTML. In this final article of the series, I illustrate how to use the following technologies with Brazil:

  • Jini
  • BeanShell
  • JAXM (Java API for XML Messaging)

I also walk you through the first steps involved in integrating Brazil with Xalan-Java, servlets, Velocity, and LDAP (lightweight directory access protocol).

In some ways, this article resembles a shopping trip to Home Depot, where you purchase tools and gadgets not because you need them now, but because you might need them later. Many of you will not need the technologies listed above all at once or ever; however, you should know about them. So consider this article a short stroll down a short aisle in the open source world.

Read the whole series on Brazil technology:

In this article's examples, we will employ the Brazil server as a robust back plane to plug in various technologies using a combination of filters, handlers, and templates. Handlers provide basic functionality for accepting HTTP requests and dispatching to methods handling those requests; filters capture handlers' output and optionally modify the input for other filters, templates, or handlers to continue processing; and templates send HTML content through an HTML/XML parser to a set of templates. Each HTML/XML tag can dynamically invoke a Java method present in the templates. The dynamically generated content from the HTML/XML tag evaluation returns to the caller.

Use Jini with Brazil

I frequently run 10 to 20 instances of Brazil on my servers and often lose track of them. The last thing I want to do is remember where they are and what they do. I looked for a solution that provided basic services for locating and determining the servers' status and capabilities, as well as services for administering many Brazil servers. I wanted a solution that didn't require special bash scripts and monitor daemons, as traditional Unix/Linux/Windows approaches often do. Those solutions don't scale well when you have thousands of servers and you forget what they do.

Jini's lookup and discovery features combined with leasing can provide more dynamic and accurate solutions. The basic idea is to use the Jini lookup services provided via reggie, so interested users can find and interact with various Web servers without knowing the servers' URLs and services. reggie is Sun Microsystems' implementation of the Jini lookup service and is an activatable service that rmid (Java RMI Activation System Daemon) controls. The example I provide in this article deals only with a simple case and does not describe the services in great detail. Interested readers might consider expanding the example to support UDDI (Universal Description, Discovery, and Integration) and WSDL (Web Services Description Language). SUBHEAD2: Develop the application components

To register with the Brazil lookup service, we need a Jini lookup service and some client code. I provided some code to look up the registered services. In that example, which appears later in this section, I developed some additional scripts that allow you to start and stop reggie and rmid. You could also create a facility in Brazil that uses a reggie as a handler (an area requiring further research), allowing you, via a script or an ExecHandler interface, to start and manage the service without going to the operating system. Because the ExecHandler allows the services to automatically start up, it ensures fewer errors in the deployment of many servers providing reggie services (see the Brazil documentation for more information).

I wrote the reggie and rmid services in BeanShell. Before we move on, let's take a look at this tool in more detail.

BeanShell

Written in Java, BeanShell is a small, free, embeddable Java source interpreter with object-scripting language features. It executes standard Java statements and expressions in addition to obvious scripting commands and syntax. BeanShell supports scripted objects as simple method closures like those in Perl and JavaScript (see Resources for more information).

In prior JavaWorld articles, I discussed how to use Python and Tcl with Brazil technology. I like to program in one language everywhere; BeanShell allows me to use Java in yet another application space—in this case, scripting content on a Webpage—formerly occupied by languages like Tcl, Python, Perl, PHP (hypertext preprocessor), and Velocity. (I will discuss Velocity later in this article.)

The following documentation from the Brazil toolkit explains how BeanShell interfaces with Brazil as well as some of the configuration options available:

The BeanShellServerTemplate looks for one of the starting tags <server language="beanshell">, <beanshell>, or <bsh> in an HTML page and treats the following data up to the corresponding ending tag (</server>, </beanshell>, or </bsh>) as a BeanShell script to evaluate.

The reason that BeanShell scripts are included in an HTML page is usually to generate dynamic, server-side content. After running this template, everything between and including the starting tag and the ending tag is replaced by all output written to the BeanShell output stream (if any).

All BeanShell fragments within a given page are evaluated in the same BeanShell interpreter. The BeanShell interpreter actually lives for the entire duration of this Template object, so the user can implement persistence across requests.

Later in this article, I will introduce two additional methods for transforming and/or scripting content on a Webpage: using XSLT (Extensible Stylesheet Language Transformations) and XML; and, separately, Velocity.

The two BeanShell scripts below demonstrate how to register and look up a service. The services we use are written for Brazil Web servers so they can register themselves with a Jini lookup and provide a service for facilitating management and control as well as distributed computing environments that offer Web server features and Jini abilities.

To register the Jini Brazil service in our example, we use BeanShell to script a lookup discovery and subsequent registration on a Webpage. In other words, the Brazil server interprets the Webpage as containing BeanShell code, first processing the BeanShell code and then returning the page to the browser. This offers tremendous value for advanced developers and provides a powerful scripting language, powerful enough to script Jini directly.

The BeanShell code that follows registers a service with a running Jini lookup service such as reggie. Let's go over the code in detail.

The <bsh> tag informs the server that BeanShell code follows. You code your import statements as you would with Java. I have not used any wild cards, so you can see the classes that we're using:

 <bsh>
    import java.io.IOException;
    import java.rmi.RemoteException;
    import net.jini.core.lookup .ServiceRegistration;
    import net.jini.core.entry.Entry;
    import net.jini.core.lookup.ServiceID;
    import net.jini.core.lookup.ServiceItem;
    import net.jini.core.lookup.ServiceTemplate;
    import net.jini.discovery.DiscoveryManagement;
    import net.jini.lease.LeaseRenewalManager;
    import net.jini.lookup.ServiceItemFilter;
    import net.jini.lookup.ServiceDiscoveryManager;
    import net.jini.core.lookup.ServiceRegistrar;

Set some parameters for the lease duration and other attributes:

     long LEASE_DUR = 60*1000;
    String PRODUCT = "Brazil Registration Service";
    String MANUFACTURER = "Rinaldo DiGiorgio";
    String VENDOR = MANUFACTURER;
    String VERSION = "Jini 1.2";

BrazilAttribute.java contains the code for the Brazil attribute, which the lookup service uses. The example is simple. BrazilAttribute.java allows applications, other services, and users to look up services by requesting specific attributes.

The code below tells the lookup service that one of the attributes of the entity currently being registered is a location attribute:

     Entry[] serviceAttrs = new Entry[] {
        new BrazilAttribute("location"),
    };

This silly little print method prints some diagnostics:

     msg(String s) {
        System.err.println(s);
    }
    msg("Creating ServiceDiscoveryManager ...");
    DiscoveryManagement discoveryMgr = null;
    LeaseRenewalManager leaseMgr = null;
    ServiceDiscoveryManager sdm = null;

As of Jini 1.2, the preferred mechanism for discovering services is the ServiceDiscoveryManager:

     try {
        sdm = new ServiceDiscoveryManager(discoveryMgr, leaseMgr);
    } catch (IOException ioe) {
        msg("Trouble creating ServiceDiscoveryManager: " + ioe);
        return;
    }
    discoveryMgr = sdm.getDiscoveryManager();
    msg ( discoveryMgr.toString() );

Once we have a valid DiscoveryManager instance, register a Brazil service with the attributes supplied above in serviceAttrs:

     try {
        Thread.sleep(1000*5);
    } catch ( Exception e ) {
    if ( discoveryMgr != null ) {
        ServiceRegistrar[] regs = discoveryMgr.getRegistrars();
        for ( int k = 0; k < regs.length; k++) {
            msg("ServiceRegistrar found:" + regs[k]);
        }
        //
        // Register the service with the lookup service
        //
        BrazilService bs = new BrazilService("hostname:port");

The Jini lookup service returns a reference to a service. Our goal is to use Jini to look up Brazil instances; we do that by registering services with specific attributes and then looking for these services by providing attributes to the reggie lookup service. The BrazilService provides an example of some rudimentary interactions with a single instance of a Brazil server (there could be thousands of servers running). Register the Brazil service with the lookup:

        ServiceItem srvcItem = new ServiceItem(null,bs,serviceAttrs);
        for ( int h=0; h < regs.length; h++) {
            try {
                ServiceRegistration srvcRegistration = regs[h].register(srvcItem, LEASE_DUR);
                msg("Registered ServiceID:  "+(srvcRegistration.getServiceID()).toString());
            } catch (RemoteException e) {
                msg.println("RemoteException while registering the "+"Service: +service+"\n"+e.toString());
                e.printStackTrace();
            }
        }
    }
    sdm.terminate();
</bsh>

At the end of the above code, we register the BrazilService and terminate the ServiceDiscoveryManager. In our example, the BrazilService is associated with a running instance of a Brazil server. When the Brazil server starts, it registers itself with a Jini lookup service so that the other servers can find it using Jini lookup services. You can work with the leases to ensure that the registered servers are still up and operational, and enhance the BrazilService to include additional functionality.

Now that we have registered a service, how do we find it? To find a service matching the attributes we specified above, we can run our next example anywhere we wish. In this example, we look up and store the properties into the Brazil server's namespace, thereby making those properties available on a request, session, or server scope. When the user selects the URL for the code below, that code is processed along with the BSL (Brazil Scripting Language). Afterwards, Brazil's filter generates output.

Use BeanShell to look up the BrazilService:

 <bsh>
import java.io.IOException;
import BrazilService;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.discovery.DiscoveryManagement;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.ServiceItemFilter;
import net.jini.lookup.ServiceDiscoveryManager;
msg(String s) {
   System.out.println(s);
}

Create ServiceDiscoveryManager with null arguments for default behavior:

   msg("Creating ServiceDiscoveryManager ...");
   DiscoveryManagement discoveryMgr = null;
   LeaseRenewalManager leaseMgr = null;
   ServiceDiscoveryManager sdm = null;
   try {
      sdm = new ServiceDiscoveryManager(discoveryMgr, leaseMgr);
   } catch (IOException ioe) {
      err("Trouble creating ServiceDiscoveryManager: " + ioe);
   }
   discoveryMgr = sdm.getDiscoveryManager();
   Create ServiceTemplate for an anything based lookup
   ServiceID serviceID = null;
   Class[] serviceTypes = { null };
   Entry[] attrSetTemplates = null;
   ServiceTemplate tmpl = new ServiceTemplate(null, null, null);

Perform nonblocking lookup with no local filtering; sleep just before lookup to give the discovery process time to find any available lookup services:

   try {
   Thread.sleep(5000);
   } catch (InterruptedException ie) {  }
   ServiceItem[] item = null;
   ServiceItemFilter filter = null;
   item = sdm.lookup(tmpl, 50, filter);

Display results, if any:

   System.out.println("Length:" + item.length );
   request.props.put("jini.lookup.number" , Integer.toString(item.length));
   String ids = "";
   String key = "jini.lookup.srvcs.";
   for ( int k = 0; k < item.length; k++) {
      String keyid = key + item[k].serviceID;
      ids += item[k].serviceID + " ";
      msg("Service type found, id=" + item[k].serviceID);
      msg("Service type found, service=" + item[k].service);
      if ( item[k].service instanceof BrazilService ) {
         BrazilService bs = item[k].service;
         try {
            String s = keyid +  ".url";
            request.props.put(s,bs.getURL());
         } catch ( Exception e ) {
            e.printStackTrace();
         }
      }
      Entry[] entry = item[k].attributeSets;
      for ( int i = 0; i < entry.length; i++) {
         msg("Service type found, attributes=" + entry[k] + 
"\n");
      }
      request.props.put(keyid , (item[k].service).toString());
      msg("\n\n");
   }
   request.props.put("jini.lookup.ids" , ids);

Tidy up:

   sdm.terminate();</bsh>

Once the BeanShell code processes, the following BSL generates some output for a user. We could also have generated XML or taken other actions. Here's the generated code:

 <!--   Display results-->
<center>
<if not name=jini.lookup.number value="0">
    <h1>Found <get jini.lookup.number> services</h1>
        <table>
            <tr>
                <th>Service ID</th>
                <th>Name</th>
                <th>Location</th>
            </tr>
            <foreach name=j property=jini.lookup.ids>
                <tr>
                    <td><get name=j> </td>
                    <td><get name=jini.lookup.srvcs.${j}>  </td>
                    <if name=jini.lookup.srvcs.${j}.url>
                        <td>
                        <tag>a href=<get name=jini.lookup.srvcs.${j}.url></tag>
                        <get 
name=jini.lookup.srvcs.${j}.url></a>
                        </td>
                    </if>
                </tr>
             </foreach>
        </table>
<else>
        <h1>No service found, activation system or reggie is 
down</H1>
</if>
</center>

Brazil instances can register themselves with a Jini lookup service by scripting the code required for registration with BeanShell and using the two classes BrazilService.java and BrazilAttribute.java for the required Jini attribute class and the actual service. In our example, the BrazilService returns only a URL to get to this service. Serious applications can expand upon this approach to retrieve more complex objects.

Use JAXM with Brazil

JAXM, the Java API for XML Messaging, provides developers with the ability to send XML documents using SOAP (Simple Object Access Protocol) 1.1, allowing developers to concentrate on their applications and not on low-level messaging technology. For more details, see Resources. In prior articles, you learned how to retrieve weather data from our 1-wire weather station using HTTP, applets, JRMS, and JMS (Java Message Service). The JAXM example expands upon these delivery options.

The code below uses JAXM to make the weather data available; the handler runs in a Brazil instance and provides weather data with JAXM. This small JAXM client queries the Web server for the data and simply dumps the XML document to standard out, as follows:

 import sunlabs.brazil.server.Handler;
import sunlabs.brazil.server.Request;
import sunlabs.brazil.server.Server;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Enumeration;
import java.io.IOException;
import javax.xml.messaging.*;
import javax.xml.soap.*;
import javax.xml.transform.*;
import java.io.ByteArrayOutputStream;

The example is written as a Brazil handler, so we subclass sunlabs.brazil.server.Handler (see Resources for a quick review of writing handlers). Handler developers can overwrite two methods: init() and request(). init() is called the first time the handler runs; request() is called for each user request. In this case, our users request the server by sending the form URL http://somehost:someport//someprefix/WeatherSample.

Once this URL is received, we grab the current weather data and create an appropriate response:

 /**
 * Class declaration
 *
 *
 * @see
 *
 * @author
 * @version   %I%, %G%
 */
public class JAXMServiceHandler implements Handler {
    public static final String PREFIX = "prefix";
    /**
     * One time initialization
     */
    private String prefix;
    private String propsPrefix;
    private Hashtable commands;
    // The commands we support are
    private static Hashtable defaultCommands;
    static {
        defaultCommands = new Hashtable();
        defaultCommands.put("Ping", "");
        defaultCommands.put("WeatherSample", "");
    }

Our init() method initializes the prefix set in the configuration file. Brazil uses a configuration file to determine what handlers, filters, ports, log levels, and so on to use:

              /**
     * Method declaration
     *
     *
     * @param server
     * @param prefix
     *
     * @return
     *
     * @see
     */
    public boolean init(Server server, String prefix) {
        this.propsPrefix = prefix;
        return (true);
    }

The respond() method does the real work. It puts out some diagnostics checks to see if the request is for JAXMServiceHandler, since Brazil presents every user request to every handler.

If the request is for JAXMServiceHandler, we process it. Here are the first few lines of the respond() method:

   /**
   * Method declaration
   *
   *
   * @param request
   *
   * @return
   *
   * @exception IOException
   *
   * @see
   */
   public boolean respond(Request request) throws IOException {
      Properties props = request.props;
      String prefix = props.getProperty(propsPrefix + PREFIX, "/jaxm/");
      request.log(Server.LOG_DIAGNOSTIC, "prefix=" + prefix);
      request.log(Server.LOG_DIAGNOSTIC, "request.url=" + request.url);
      if (request.url.startsWith(prefix) == false) {
      return false;
      }
      System.out.println(new String(request.postData));
      // Get the command name from the URL
      // http:.../SecureTokenServices/<command>?name=value...
      String command = request.url.substring(prefix.length());
      Hashtable args = request.getQueryData();
      request.log(Server.LOG_DIAGNOSTIC, prefix + " " + request.url + ":
      " + args);
      Properties r = new Properties();

At this point, we know the request is for JAXMServiceHandler; so, let's see which specific function the user is requesting. We understand two types of user requests: http://thisserver:thisport/Ping and http://thisserver:thisport/WeatherSample. If the request is either of these URLs, we process it by setting some values in a properties object, then use a method to get those properties, and, with the JAXM APIs, create a SOAP message that carries the properties to the requestor:

      try {
         if (command.startsWith("Ping")) {
            r.put("Ping", "TIME");
            } else if (command.startsWith("WeatherSample")) {
               String x = "remotews";
                r.put("Temperature", props.getProperty(x + "Temperature", "N/A"));
                r.put("WindSpeed", props.getProperty(x + "WindSpeed", "N/A"));
                r.put("WindDirection", props.getProperty(x + "WindDirection", "N/A"));
                r.put("Precipitation", props.getProperty(x + "BucketCount", "N/A"));
                r.put("Time", props.getProperty(x + "Time", "N/A"));
            } else {
                r.put("ErrorNumber", "404");
                r.put("ErrorDescription", "Not Found");
                for (Enumeration e = defaultCommands.keys();
                e.hasMoreElements(); ) {
                   r.put("Try", (String) e.nextElement());
                }
             }
           } catch (Exception e) {
                r.put("Number", "500");
                r.put("Description", e.getMessage());
           }
           finally {
             try {
                String s = makeSoapMessage(r);
                request.sendResponse(s, "text/xml");
            } catch (Exception soap) {
                request.sendError(500, "Server Error : " + soap.getMessage());
                return (true);
            }
      }
      return (true);
   }

The makeSoapMessage() method takes the Properties object passed by reference and creates a SOAP message that returns as a string.

The Brazil server then returns that string as type text/XML:

   /**
   * Method declaration
   *
   *
   * @param p
   *
   * @return
   *
   * @exception Exception
   *
   * @see
   */
   public String makeSoapMessage(Properties p) throws Exception {
      MessageFactory mf = MessageFactory.newInstance();
      SOAPMessage msg = mf.createMessage();
      SOAPEnvelope env = msg.getSOAPPart().getEnvelope(true);
      for (Enumeration e = p.propertyNames(); e.hasMoreElements(); ) {
         String tag = (String) e.nextElement();
         String text = new String( '"' + p.getProperty(tag) + '"');
         System.out.println(tag + " " + text);
         env.getBody().addChildElement(env.createName(tag))
            .addTextNode(text);
     }
     ByteArrayOutputStream b = new ByteArrayOutputStream();
     msg.writeTo(b);
     return (new String(b.toByteArray()));
   }

Use Brazil with even more technologies

In addition to JAXM, Jini, and BeanShell, you can integrate Brazil technology with Xalan-Java, servlets, Velocity, or LDAP. In the following sections, I show you how get started with each.

Transform XSLT documents into HTML

XSLT and XPath provide useful abilities for XML document processing. XPath uses a simple path-based language to address parts of XML documents. XSLT applications often use XPath; both technologies are W3C (World Wide Web Consortium) recommendations. Going forward, Web servers that don't support XLST/XPath won't stand up to their competition.

Xalan-Java is an XSLT processor that can transform XML documents into HTML. You can use it from the command line, from an applet, from a servlet as a module in another program, or as a Brazil handler. Thanks to Morten Jorgensen, who wrote Transformhander.java, Brazil server technology supports XSLT/XPath; the file is part of the Xalan distribution available at xml.apache.org. It contains the handler distributed with the Xalan-Java 2.3.1 release. Transformhandler.java lets a user request that a compiled XSLT stylesheet and XML document process, with the output directed to the client.

You can refine this approach slightly by creating a filter that allows you to bracket parts of a document containing XML with directives for applying XSL stylesheets to these specific sections. You can also use BSL to generate dynamic XML and XSLT stylesheets for processing by downstream filters. Interested developers can contact me.

Use servlets with Brazil

The Brazil toolkit servlet adapter allows you to run applications built with the Brazil toolkit in any Web server that provides a servlet container implementing the Servlet 2.2 API. This configuration features two steps:

  1. Configure and develop a Brazil toolkit application
  2. Deploy the developed application

Consult the Javadocs for detailed instructions on how to construct additional configurations for the application developed with the Brazil toolkit and to deploy the toolkit to your favorite Web server.

Velocity support

Velocity is a Java-based template engine that lets Webpage designers reference Java code methods (it doesn't really care what the object model is). Velocity allows Web developers to create Websites that feature dynamic content and comply with the Model-View-Controller (MVC) architecture, which helps separate Java code from Website design. The Resources section points you to references that can help you configure the Brazil toolkit so you can code a Velocity template, which you can then merge with a BeanShell script to produce output.

Brazil support for LDAP

Web developers now use LDAP quite often as the source of frequently accessed information stored as attributes using Schemas organized as directory information trees (DITs). Developers who use LDAP can more securely and inexpensively manage their respective user communities. The Apache Software Foundation has some Java Server Tag Libraries (JSTLs) that support LDAP.

The Brazil toolkit's LDAPHandler provides some easy-to-use LDAP tag support. It supports authentication through the user's LDAP user ID and password; it can also search for entries, and delete, add, and modify entries with one simple tag.

Enjoy Brazil's benefits

You can use Brazil with many other technologies, such as JSPs (JavaServer Pages) and, in the Microsoft world, ASPs (Active Server Pages). My goal with this series has not been to promote one API over another, but to demonstrate how to use multiple APIs. Some developers run in horror if someone doesn't tell them exactly what to use; other developers, like me, prefer choice. When developing Websites, I try to write code so I can rewrite it with an alternative technology in case one leapfrogs the other. Most of these languages are pretty simple, so many developers can pick them up in a day or two, if not in just a few hours. I use Java everywhere; since I often develop the content and the Java application logic, I can afford to use BeanShell on the page. Some consider this a disadvantage since Java might prove harder to learn than, for instance, Velocity.

The purpose of this series has been to demonstrate how to quickly respond to a particular application's requirements by using Brazil technology to support diverse client requirements. Over the course of the series, I have shown how to support many different client types. Declarative XML Transformation and Templating is an interesting technology to keep your eye on, one that you could also integrate with Brazil. It takes the two best features of XSLT—declarative syntax and document control—and XPath-based selection and adds Velocity as the template language. Three additional application examples I developed but did not include with this article: support for Jxta, Gnutella, and Xindice. For more information on these applications, please contact me.

Rinaldo Di Giorgio writes the Java Developer column for JavaWorld.

Learn more about this topic