.
.
developers.sun.com java.sun.com
   View this issue as simple text November 23, 2004    

In this Issue

Welcome to the Enterprise Java Technologies Tech Tips for November 23, 2004. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE).

This issue covers:

.J2EE Connector Architecture 1.5
.Custom Components With JavaServer Faces Technology

These tips were developed using the Java 2, Enterprise Edition, v 1.4 SDK. You can download the SDK at http://java.sun.com/j2ee/1.4/download.html.

This issue of the Tech Tips is written by Robert Eckstein, a Java technology developer, editor, and author. Robert is owner of Nexes Consulting, a Java consulting firm based in Austin, Texas.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

You can download the sample archive for these tips. Any use of this code and/or information below is subject to the license terms.

For more Java technology content, visit these sites:

java.sun.com - The latest Java platform releases, tutorials, and newsletters.

java.net - A web forum for collaborating and building solutions together.

java.com - The marketplace for Java technology, applications and services.

.

J2EE CONNECTOR ARCHITECTURE 1.5

If you've ever tried to integrate legacy systems with a J2EE application server, you know that it can be quite a challenge. Managing connections, converting between different data types, and even sending messages between an application server and an Enterprise Information System (EIS) can leave an enterprise Java programmer with quite a headache. Thankfully, there's the J2EE Connector Architecture, now in version 1.5 in J2EE 1.4, which defines a standard architecture for connecting the J2EE platform to heterogeneous EISs.

Using the J2EE Connector Architecture, EIS vendors do not have to create custom connection software for multiple application servers. Neither do application server vendors have to customize their code to support various EIS packages. Instead, these vendors can package "in-between" code that conforms to the J2EE Connector Architecture specifications. The code implements or extends a set of standardized contract interfaces and classes that adapt data and messages for communication between the application server and the EIS resource. This in-between code plugs into the application server, and is called a resource adapter.

A resource adapter module, typically called a Resource Adapter Archive (RAR), is simply a JAR file. A RAR typically includes the following:

  • Java classes and interfaces that implement the resource adapter contracts and client APIs.

  • Any native libraries that are required to communicate with the EIS.

  • A deployment descriptor file, ra.xml, that the application server uses to configure the resource adapter. The file is located in the META-INF directory.

Here is an illustration that shows where the resource adapter fits in with an application server and an EIS.

Note how each element communicates with one other.

There are three important communication bridges that comprise the J2EE Connector Architecture. Let's look at each of them.

Client APIs and the Common Client Interface

Application components that plug into the application server must have a way of communicating with the resource adapter. The Common Client Interface (CCI) is the recommended API to do this. The CCI is an API that is implemented in the javax.resource.cci package. The API is recommended because it's easy to use, but it's not mandatory. Resource adapter implementers are free to create their own specialized client APIs to communicate with application components if the CCI does not satisfy their needs.

Container-Component Contracts

This is a standardized communication method between the application server and the application components. There's not much here in terms of JCA. For example, if a Web application uses Enterprise JavaBeans (EJB) components, then the EJB specification is the container-component contract that governs how each EJB component communicates with the application server.

System Contracts

This is a major part of the J2EE Connector Architecture. The architecture specifies several system contracts that a resource adapter must conform to in order to communicate clearly with the application server. The APIs for the system contracts are implemented in the javax.resource.spi package. The 1.0 version of the J2EE Connector Architecture introduced three such contracts:
  • Connection Management Contract: Provides interfaces and classes that allow for connections between the EIS and the application server. It also allows the application server to pool these connections as it deems necessary.

  • Transaction Management Contract: Brings atomic transaction capabilities to the EIS. The EIS can then be managed either by the resource adapter or a transaction manager. This only works one way, however. Transactions must be initiated outside the EIS and passed to it, not the reverse.

  • Security Contract: Ensures that there is proper authentication between the application server and the EIS.

With version 1.5 of the architecture, four new contracts were created that allow greater functionality inside of a resource adapter. The new contracts are:

  • Lifecycle Management Contract: Provides a way for the application server to cleanly start and shutdown the resource adapter.

  • Work Management Contract: Provides interfaces and classes that allow resource adapters to submit work for execution by the application server.

  • Transaction Inflow Contract: Allows transactions to be passed from the EIS to the application server (the reverse of the Transaction Management Contract). It also assists with transaction recovery in the event that the EIS crashes.

  • Message Inflow Contract: Allows the resource adapter to send synchronous or asynchronous messages to endpoints within the application server.

Let's examine these four new contracts in more detail.

Lifecycle Management and Work Management Contracts

The ResourceAdapter interface in the javax.resource.spi package represents a resource adapter. There are two methods in the ResourceAdapter interface that allow for lifecycle management: start() and stop(). The start() method is called when an application server wants to start a resource adapter (for example, to deploy it). The stop() method is called when the application server wants to release a resource adapter (for example, to undeploy it).

The Work Management contract allows the resource adapter to submit work to the application server. It does this by creating an object that extends the Work interface in the javax.resource.spi.work package. The Work interface is an extension of the Runnable interface. In addition to the run() method that it inherits from Runnable and which executes in its own thread, the Work interface contains a release() method. The application server can use release() to request that the thread complete, and release its resources as soon as possible.

Here is part of an example implementation of the ResourceAdapter interface that illustrates the lifecycle management and work management contracts:

   public class NexesResourceAdapterImpl implements 
           ResourceAdapter {

      // Ten seconds
      public static final long WORK_START_TIMEOUT = 10000L;
      
      public NexesResourceAdapterImpl() {    }
      public void start(BootstrapContext ctx)
      throws ResourceAdapterInternalException
      {
 
          WorkManager workManager = ctx.getWorkManager();
          Work nexesWorkJob = new NexesWorkImpl();
          WorkListener workListener = 
                  new NexesWorkListenerImpl();

          try {

              // Unlike scheduleWork() or doWork(), this call 
              // blocks until the work has started. If it takes 
              // longer than 10 seconds for the work to start, 
              // the call throws a WorkRejectedException.

              workManager.startWork
                      (nexesWorkJob, WORK_START_TIMEOUT, 
                      new ExecutionContext(), workListener);

          } catch (WorkException e) {
              //  Handle the exception
          }
        }

       public void stop()
       {
          //  Do whatever you need to do here to close down the
          //  resource adapter.
       }  
       
          // Transaction Inflow contract methods omitted. 
          // See the section "Message Inflow and Transaction 
          // Inflow Contracts"

      }

Notice that there is an object passed in with the start() method that implements the BootstrapContext interface. This is an important object that allows the EIS to pass transaction information to the application server, as well as the ability to pass work to the application server. See the section "Message Inflow and Transaction Inflow Contracts" for more details about the BootstrapContext interface.

Also notice the reference to the WorkListener interface:

   WorkListener workListener = 
           new NexesWorkListenerImpl();

and the startWork method:

   workManager.startWork
           (nexesWorkJob, WORK_START_TIMEOUT, 
           new ExecutionContext(), workListener);

If you want the application server to notify you about the progress of any work that is submitted, you can create an object that implements the javax.resource.spi.WorkListener interface. You can then register this object using the startWork() method of the WorkManager object on the application server. The WorkManager interface provides a facility to submit Work instances for execution. Registering the object allows the server to notify the resource adapter if the work was rejected or accepted, and if accepted, when the work was started and completed. You can also extend the WorkAdapter class, which implements the WorkListener interface and provides empty methods for each of these. Here is skeleton code for a class that implements WorkListener:

   public class NexesWorkListenerImpl implements WorkListener {

      public void workAccepted(WorkEvent e) {
          //  myAppServerLog.log("Work instance " + e + 
          //      " has been accepted."); 
      }

      public void workRejected(WorkEvent e) {
          //  myAppServerLog.log("Work instance " + e + 
          //      " has been rejected."); 
      }

      public void workStarted(WorkEvent e) {
          //  myAppServerLog.log("Work instance " + e + 
          //      " has been started."); 
      }

      public void workCompleted(WorkEvent e) {
          //  myAppServerLog.log("Work instance " + e + 
          //      " has been completed."); 
      }

}

Message Inflow and Transaction Inflow Contracts

The Message Inflow contract allows the resource adapter to react to calls made by the application server to activate and deactivate message endpoints. The endpointActivation() method in the ResourceAdapter interface is called during endpoint activation. This causes the resource adapter to do the necessary setup for message delivery to the message endpoint. The endpointDeactivation() method of ResourceAdapter is called when a message endpoint is deactivated. This stops the resource adapter from delivering messages to the message endpoint. A MessageEndpointFactory object in the javax.resource.spi.endpoint package is passed in to the endpointActivation method. The object is used by the resource adapter to create a number of message endpoints. Any information about these endpoints should be removed from the resource adapter when the endpointDeactivation() method is called. Finally, the getXAResources() method of ResourceAdapter can be used to retrieve transaction resources in the event of a system crash. The endpointActivation(), endpointDeactivation(), and getXAResources() methods are mandated by the ResourceAdapter interface.

   public class NexesResourceAdapterImpl implements 
          ResourceAdapter {

      //  Lifecycle Contract methods from earlier omitted.

      public XAResource[] getXAResources(ActivationSpec[] specs)
      throws ResourceException
      {
          // This method should either return an array of 
          // XAResource objects that uniquely correspond to the 
          // resource manager given the ActivationSpecs passed 
          // in, or null if it does  not support this par 
          // of the Message Inflow contract.
        
          return null;
      }

      public void endpointActivation(MessageEndpointFactory mef, 
          ActivationSpec as) 
      throws NotSupportedException
      {
          // This is also part of the Message Inflow contract.
          // The idea here is to create a message endpoint 
          // using the MEF's createEndpoint() method, which is 
          // then stored in the ActivationSpec class. This binds 
          // the EIS and the application server together so that 
          // the two can communicate independently of the 
          // messaging style of the EIS.
      }

      public void endpointDeactivation(MessageEndpointFactory mef, 
          ActivationSpec as)
      {
          //  This removes any resources that were created by the 
          //  endpointActivation() method above for the specified
          //  messaging endpoint. The resource adapter must notify
          //  any message providers that the endpoint is no longer
          //  valid
      }

   }

The ActivationSpec class that is passed in to the ResourceAdapter methods is a JavaBean that implements a number of get and set methods for various properties. In addition to providing these get and set methods, an implementation must also provide a validate() method to ensure that all of the properties have been legally set. If a property has not been set properly, the method must throw an InvalidPropertyException. Note that an ActivationSpec object cannot override equals().

   public class MyActivationSpec implements ActivationSpec, 
           Serializable {

      public void setMyProperty(MyProperty s) { }
      public MyProperty getMyProperty() { }

      public void validate() throws InvalidPropertyException { }

   }

In version 1.0 of the J2EE Connector Architecture, a resource adapter could only pass transaction information to the EIS, either from itself or from an external transaction manager. However with the Transaction Inflow contract in version 1.5 of the architecture, the resource adapter can pass EIS transaction requests to the application server as well as use the BootstrapContext object that is passed in with the start() method of the Lifecycle Contract. The BootStrapContext interface was mentioned briefly in the discussion of the Lifecycle Management contract. Here are the methods in the BootstrapContext interface:

   public class NexesBootstrapContextImpl implements 
           BootstrapContext {

      public WorkManager getWorkManager() {
          //  Get the work manager from the application server
      }

      public XATerminator getXATerminator() {
          return new NexesXATerminatorImpl();
      }

      public Timer createTimer() {
          return new Timer();
      }

}

Let's also take a closer look at the XATerminator interface. Notice that it's the return type of the getXATerminator() method in the BootStrapContext interface. The XATerminator interface contains five simple methods that handle transactions:

   public class NexesXATerminatorImpl implements XATerminator {

      public void commit(Xid xid, boolean onePhase)
              throws XAException { }
      public void forget(Xid xid) throws XAException { }
      public int prepare(Xid xid) throws XAException { }
      public Xid[] recover(int flag) throws XAException { }
      public void rollback(Xid xid) throws XAException { }

   }

The Resource Adapter File

The resource adapter descriptor file, ra.xml, is fairly easy to create. You simply need to point in the file to the class that implements the ResourceAdapter interface. The application server will then access that class. See the J2EE Connector Architecture 1.5 specifications for more information on resource adapter deployment descriptors, including how to tie incoming message classes to ActivationSpec classes. Like all deployment descriptors, the ra.xml file needs to be in the WEB-INF directory of the WAR file.

   <?xml version="1.0" encoding="UTF-8"?>
   <connector xmlns="http://java.sun.com/xml/ns/j2ee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
     http://java.sun.com/xml/ns/j2ee/connector_1_5.xsd" 
     version="1.5">

     <display-name>Skeleton Resource Adapter</display-name>
     <vendor-name>Sun Microsystems, Inc.</vendor-name>
     <eis-type>Unknown</eis-type>
     <resourceadapter-version>1.0</resourceadapter-version>

     <resourceadapter>
       <resourceadapter-class>
         com.nexes.ra.NexesResourceAdapterImpl
       </resourceadapter-class>
     </resourceadapter>

   </connector>

For more information about the J2EE Connector Architecture, see the J2EE Connector Architecture page.

.
.

CUSTOM COMPONENTS WITH JAVASERVER FACES TECHNOLOGY

The March 24, 2004 Tech Tip Improving Designs With the MVC Design Pattern introduced the architectural pattern known as Model, View, Controller (MVC or Model2). MVC is a pervasive pattern throughout the world of computer science, and is fundamental to understanding JavaServer Faces (JSF) technology. The pattern separates the data and business logic of an application from its visual representation. The data and business logic is stored in an object called the Model. The visual representation is stored in a separate object called the View. The two objects are linked together with a third object called the Controller. The controller reacts to input from the view and updates the model data accordingly. The advantage of using this design is that any changes to the business logic or data can be isolated to the model without affecting the view. You can create multiple views without affecting the model.

A second tip in the March 24, 2004, titled Introducing JavaServer Faces Technology showed how to create a JSF application that includes GUI components that are modeled by the JSF framework. In this tip, you'll learn how to create custom components using JSF technology. More specifically, you'll learn how to create a custom JSF technology component that represents a simple stock display. Through an accompanying JavaServer Pages (JSP) page, a user can enter a stock symbol into a input text field and then press the Submit button. In response, the custom component displays a table below the text field. The table contains the stock's symbol, the current price of the stock, and the daily change in the stock price.

This tip assumes that you are familiar with the basics of JSF technology, and you know how to create JSP technology custom tag libraries. For information on the basics of JSF technology, see An Introduction to JavaServer Faces. For information on creating your own JSP custom tag libraries, see Using Custom Tags in the J2EE 1.4 Tutorial.

To create the custom component, you need to:

  • Create a JavaBean Model class that can be used to retrieve the stock data

  • Create a custom JSF View output component class that extends javax.faces.component.UIComponent.

  • Create a custom JSF View class that extends javax.faces.render.Renderer.

  • Integrate the custom component into the JSF framework using a custom tag.

  • Create a JSP page, including an input text field, and a backing bean for the input text field to assist the custom component.

Here is a visual representaion of how these objects fit in the MVC architecture.

Create a JavaBean Model

The model for the stock component is simple JavaBean class that has get and set accessor methods to store and retrieve data. The bean also has a method, retrieveStock(), that the UI component calls when the user enters the stock symbol. This method takes the entered stock symbol and updates the model information accordingly.

The sample archive that accompanies this tip includes all the source code for the stock component. In it you'll find the following source code for the class StockModel, the Model component:
   public class StockModel {
   
       private String selectedStock = "UNKNOWN";
       private String currentValue = "0";
       private String dailyChange = "0";
   
       public String getSelectedStock() { return selectedStock; }
       public String getCurrentValue() { return currentValue; }
       public String getDailyChange() { return dailyChange; }
   
       public void retrieveStock(String stockSubmitted) {
           selectedStock = stockSubmitted;
           currentValue = "23.5";  
           dailyChange = "+2.5";   
       }
   }

Typically a method such as retrieveStock() would query a database to obtain the data, but for simplicity, the method in this class updates the rest of the model with the constant strings specified in the class.

Create a UI Component

The primary purpose of the UI Component, which is part of the view, is to take data from the model and either display it itself, or relay it to a custom renderer to display the data as necessary. A custom UI component extends javax.faces.component.UIComponent. This class extends javax.faces.component.UIOutput, where the UIOutput class is a subclass of the UIComponent class. With UIOutput, most of the view functionality that is needed by the custom component is already provided. The only addition that this class needs is the getFamily() method, which returns a String that describes the component family (this must match an entry in the resource descriptor later). Here is the source code for the class UIStockChoiceOutput:
   public class UIStockChoiceOutput extends UIOutput {

       public static final String STOCK_FAMILY = "StockFamily";

       public UIStockChoiceOutput() {
           super();
       }

       public String getFamily() {
           return STOCK_FAMILY;
       }

   }

Create a Custom Renderer

The next step is to create a renderer for how this JSF component should appear in the client browser. This is a custom View class that extends the javax.faces.render.Renderer class. The purpose of this renderer is to create the output HTML table for the stock information display. Although it is not the case here, renderers can also be responsible for rendering input. Hence, a renderer can perform two types of translation services: decoding any input data into a format that can be passed on to the controller, and encoding any data from the controller object and rendering it to the browser. Decoding is done by the decode() method in the renderer. Encoding can be done by a number of encoding methods. This example uses the encodeEnd() method. Here is an extract of the source code for SimpleStockRenderer, the renderer (and custom View component):

   public class SimpleStockRenderer extends Renderer {
       
       public void encodeEnd(FacesContext context, 
           UIComponent component) throws IOException
       {
           if (!component.isRendered())
               return;
   
           String selectedStock =
              (String)component.getAttributes().get("selectedStock");
           String currentValue =
              (String)component.getAttributes().get("currentValue");
           String dailyChange =
              (String)component.getAttributes().get("dailyChange");
           String id = (String)component.getClientId(context);
   
           ResponseWriter out = context.getResponseWriter();
           
           out.startElement("table", component);  
           out.writeAttribute("border", "0", "border");   
   
           out.startElement("tr", component);
           out.writeAttribute("bgcolor", "#000077", "bgcolor");                      
          
           writeTableHeader(out, component, "Stock Symbol");
           writeTableHeader(out, component, "Current Value");
           writeTableHeader(out, component, "Daily Change");
   
           out.endElement("tr");
    
           out.startElement("tr", component);
           out.writeAttribute("bgcolor", "#FFFFFF", "bgcolor");                      
   
           writeTableEntry(out, component, selectedStock);
           writeTableEntry(out, component, currentValue);
           writeTableEntry(out, component, dailyChange);
         
           out.endElement("tr");
                                                     
           out.endElement("table");                            
       }
   
       //  Methods writeTableEntry() and writeTableHeader() omitted
       
   }

Notice that a FacesContext object and a UIComponent object are passed in to the encodeEnd() method. The FacesContext object contains information about the JSF environment. The UIComponent is a reference to the Controller object. The encodeEnd() method retrieves the model data through the UIComponent controller and creates a ResponseWriter object. The method uses this object to write the HTML elements and their attributes for the HTML table, including its headers. There are a number of other encode methods in the Renderer class. See the JSF specification for information about these methods.

Create the Backing Bean

The next step is to create a backing bean for the input component. The backing bean provides a String-based property accessor for the input stock, as well as a boolean (rendered) indicating whether the output table should appear. Note that a validator has been added to the source code as well. If the input is not valid, the rendered boolean is set to false, and the renderer will not display the output table. The source code for the backing bean is shown here:

public class InputBackingBean {

    private String inputStock = "";
    private boolean rendered = false;
    
    public void setStockOutputRendered(boolean r) 
       { rendered = r; }
    public boolean getStockOutputRendered() 
       { return rendered; }
        
    public String getInputStock() { return inputStock; }
    public void setInputStock(String stock) 
       { inputStock = stock; }
        
    public void validateStock
       (FacesContext ctx, UIComponent component, Object value)
    throws ValidatorException
    {
        
    public void validateStock(FacesContext ctx, 
        UIComponent component, Object value)
    throws ValidatorException
    {
        
        String string = (String)value;
        if ((value.equals("AAA")) || (value.equals("BBB")) ||        
            (value.equals("CCC")) || (value.equals("DDD")) ||        
            (value.equals("EEE")) || (value.equals("FFF")) ||        
            (value.equals("GGG")) || (value.equals("HHH"))) {
            
            rendered = true;
        } else {
            rendered = false;
            throw new ValidatorException(
                new FacesMessage
                ("Invalid Stock Entry: (Enter AAA,BBB, etc.)"));
        }
    }
    
    public String inputStockSubmitted() {

        FacesContext context = FacesContext.getCurrentInstance();
        StockModel model = (StockModel)
          context.getApplication().getVariableResolver().resolveVariable(
                context, "StockModel");
        model.retrieveStock(inputStock);
        
        return "Input Stock Accepted";
    }

}

The inputStockSubmitted() method is very important. This method is tied to an action attribute of a <h:commandButton> (which is a Submit button in the JSP page). When invoked, the method retrieves the StockModel by resolving it through the current faces context. The retrieveStock() method is then called on the model to update the stock information in the output table.

Final Steps

There are a few other things that need to be done to use the custom component. The component requires a custom tag library class that the JSP framework uses to communicate data to and from a JSP file. The class SimpleStockInputTag.java is the custom tag library class used in this tip. The custom tag library for this tip is called "stock".

The JSF framework also needs to way to tie the Model bean to the custom tag. It uses a standard JSP tag library descriptor file to do this. You can find the JSP tag library descriptor file, stock.tld, in the WEB-INF/lib directory of the sample archive.

The custom component objects must be registered with the JSF architecture. This is done through a faces-config.xml file in the WEB-INF directory.

Finally, you need a JSP page to display the component, provide a Submit button, and display the component. Here is most of the JSP page, Stock.jsp, that displays the component:

   ...
   <%@taglib prefix="stock" 
       uri="http://nexes.org/example/stock"%> 
    
   <html>
     <head>
     <title>Stock Choice Example</title>
     </head>
     <body>
     <h2>Please Enter a Stock Symbol</h2>
     <f:view>
        <p>
        <h:messages id="messageList" showSummary="true"/>
        </p>
        <h:form>
        <h:inputText value="#{InputBackingBean.inputStock}" 
            validator="#{InputBackingBean.validateStock}" />
        <h:commandButton value="Submit"
            action="#{InputBackingBean.inputStockSubmitted}" />
        <stock:stockOutput id="symbol"
            rendered="#{InputBackingBean.stockOutputRendered}" 
            selectedStock="#{StockModel.selectedStock}"
            currentValue="#{StockModel.currentValue}"
            dailyChange="#{StockModel.dailyChange}" />
        </h:form>
     </f:view>
     </body>
   </html>

Running the Sample Code

Download the sample archive for these tips. The application's context root is ttnov2004. The downloaded war file also contains the complete source code for the sample.

You can deploy the application archive (ttnov2004.war) on the J2EE 1.4 Application Server using the deploytool program or the admin console. You can also deploy it by issuing the asadmin command as follows:

   asadmin deploy install_dir/ttnov2004.war

Replace install_dir with the directory in which you installed the war file.

You can access the application at http://localhost:8000/ttnov2004

For a J2EE 1.4-compliant implementation other than the J2EE 1.4 Application Server, use your J2EE product's deployment tools to deploy the application on your platform.

When you start the application, you should see a page that looks like this (not all of the page is shown):

Click on the custom component link. You should see a page that prompts you for a stock symbol. Enter a stock symbol, such as AAA or BBB into the data field. In response you should see the appropriate stock information displayed in a table.

If you enter an incorrect stock symbol, as judged by the validator, the component creates a FacesMessage that is displayed with the <h:messages> tag in the JSP page. For example, if you enter the symbol ABC, you will see the message: "Invalid Stock Entry ...".

.
.
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.
.
.

IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html

Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.

Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to other Java developer Tech Tips:

- Core Java Technologies Tech Tips. Get tips on using core Java technologies and APIs, such as those in the Java 2 Platform, Standard Edition (J2SE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the Sun Developer Network - Subscriptions page, choose the newsletters you want to subscribe to and click "Submit".
- To unsubscribe, go to the Subscriptions page, uncheck the appropriate checkbox, and click "Submit".


ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://java.sun.com/developer/EJTechTips/index.html


Copyright 1994-2004 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by Copyright 1994-2004 Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems,
Inc.
.
.