WebSphere Programming Model Extensions

This section discusses facilities that are provided as part of the Programming Model Extensions in WebSphere Application Server:

The exception-chaining and command packages are available as part of WebSphere Application Server Advanced Edition and Enterprise Edition; the localizable-text package is available as part of WebSphere Application Server Advanced Edition. All three packages are general-purpose utilities, designed to provide common functions in a reusable way. Although these facilities are described in the context of enterprise beans, they are available to any WebSphere Application Server Java application. They are not restricted to use with enterprise beans.


The distributed-exception package

Distributed applications require a strategy for exception handling. As applications become more complex and are used by more participants, handling exceptions becomes problematic. To capture the information contained in every exception, methods have to rethrow every exception they catch. If every method adopts this approach, the number of exceptions can become unmanageable, and the code itself becomes less maintainable. Furthermore, if a new method introduces a new exception, all existing methods that call the new method have to be modified to handle the new exception. Trying to explicitly manage every possible exception in a complex application quickly becomes intractable.

In order to keep the number of exceptions manageable, some programmers adopt a strategy in which methods catch all exceptions in a single clause and throw one exception in response. This reduces the number of exceptions each method must recognize, but it also means that the information about the originating exception is lost. This loss of information can be desirable, for example, when you wish to hide implementation details from end users. However, this strategy can make applications much more difficult to debug.

The distributed-exception package provides a facility that allows you to build chains of exceptions. An exception chain encapsulates the stack of previous exceptions. With an exception chain, you can throw one exception in response to another without discarding the previous exceptions, so you can manage the number of exceptions without losing the information they carry. Exceptions that support chaining are called distributed exceptions.

Distributed exceptions are packaged in the ras.jar file, which must be included in the application's CLASSPATH variable.

Overview
Support for chaining distributed exceptions is provided by the com.ibm.websphere.exception Java package. The following classes and interfaces make up this package:

This section provides a general description of the interfaces and classes in the exception-chaining package.

The DistributedException class
The DistributedException class provides the root exception for exception hierarchies defined by applications. With this class, you build chains of exceptions by saving a caught exception and bundling it into the new exception to be thrown. This way, the information about the old exception is forwarded along with the new exception. The class declares six constructors; Figure 55 shows the signatures for these constructors. When your exception is a subclass of the DistributedException class, you must provide corresponding constructors in your exception class.

Figure 55. Code example: Constructors for the DistributedException class

...
public class DistributedException extends Exception
implements DistributedExceptionEnabled
{
    // Constructors
    public DistributedException() {...}
    public DistributedException(String message) {...}
    public DistributedException(Throwable exception) {...}
    public DistributedException(String message,Throwable exception) {...}
    public DistributedException(String resourceBundleName,
                                String resourceKey,
                                Object[] formatArguments,
                                String defaultText) 
    {...}
    public DistributedException(String resourceBundleName,
                                String resourceKey,
                                Object[] formatArguments,
                                String defaultText, 
                                Throwable exception) 
    {...}
    // Other methods
    ...
}
The class also provides methods for extracting exceptions from the chain and querying the chain. These methods include:

Localization support
Support for localized messages is provided by two of the constructors for distributed exceptions. These constructors take arguments representing a resource bundle, a resource key, a default message, and the set of replacement strings for variables in the message. A resource bundle is a collection of resources or resource names representing information associated with a specific locale. Resource bundles are provided as either a subclass of the ResourceBundle class or in a properties file. The resource key indicates which resource in the bundle to retrieve. The default message is returned if either the name of the resource bundle or the key is null or invalid.

The DistributedExceptionEnabled interface
Use the DistributedExceptionEnabled interface to create distributed exceptions when your exception cannot extend the DistributedException class. Because Java does not permit multiple inheritance, you cannot extend multiple exception classes. If you are extending an existing exception class, for example, javax.ejb.CreateException, you cannot also extend the DistributedException class. To allow your new exception class to chain other exceptions, you must implement the DistributedExceptionEnabled interface instead. The DistributedExceptionEnabled interface declares eight methods you must implement in your exception class:

When implementing the DistributedExceptionEnabled interface, you must declare a DistributedExceptionInfo attribute. This attribute provides implementations for most of these methods, so implementing them in your exception class consists of calling the corresponding methods on the DistributedExceptionInfo object. For more information, see Implementing the methods from the DistributedExceptionEnabled interface.

The DistributedExceptionInfo class
The DistributedExceptionInfo class provides the functionality required for distributed exceptions. It must be used by any exception that implements the DistributedExceptionEnabled interface (which includes the DistributedException class). A DistributedExceptionInfo object contains the exception itself, and it provides constructors for creating exception chains and methods for retrieving the information within those chains. It also provides the underlying methods for managing chained exceptions.

Extending the DistributedException class
The DistributedException class provides the root exception for exception hierarchies defined by applications. The class also provides methods for extracting exceptions from the chain and querying the chain. You must provide constructors corresponding to the constructors in the DistributedException class (see Figure 55). The constructors can simply pass arguments to the constructor in the DistributedException class by using super methods, as illustrated in Figure 56.

Figure 56. Code example: Constructors in an exception class that extends the DistributedException class

...
import com.ibm.websphere.exception.*;
public class MyDistributedException extends DistributedException
{
    // Constructors
    public MyDistributedException() {
        super();
    }
    public MyDistributedException(String message) {
        super(message);
    }
    public MyDistributedException(Throwable exception) {
        super(exception);
    }
    public MyDistributedException(String message, Throwable exception) {
        super(message, exception);
    }
    public MyDistributedException(String resourceBundleName,
                      String resourceKey, Object[] formatArguments,
                      String defaultText) 
    {
        super(resourceBundleName, resourceKey, formatArguments, defaultText);
    }
    public MyDistributedException(String resourceBundleName,
                    String resourceKey, Object[] formatArguments,
                    String defaultText, Throwable exception)
    {
        super(resourceBundleName, resourceKey, formatArguments, defaultText,
            exception);
    }
}

Implementing the DistributedExceptionEnabled interface
Use the DistributedExceptionEnabled interface to create distributed exceptions when your exception cannot extend the DistributedException class. To allow your new exception class to be chained, you must implement the DistributedExceptionEnabled interface instead. Figure 57 shows the structure of an exception class that extends the existing javax.ejb.CreateException class and implements the DistributedExceptionEnabled interface. The class also declares the required DistributedExceptionInfo object.

Figure 57. Code example: The structure of an exception class that implements the DistributedExceptionEnabled interface

...
import javax.ejb.*;
import com.ibm.websphere.exception.*;
public class AccountCreateException extends CreateException
implements DistributedExceptionEnabled
{
    DistributedExceptionInfo exceptionInfo = null;
    // Constructors
    ...
    // Methods from the DistributedExceptionEnabled interface
    ...
}

Implementing the constructors for the exception class
The exception-chaining package supports six different ways of creating instances of exception classes (see Figure 55). When you write an exception class by implementing the DistributedExceptionEnabled interface, you must implement these constructors. In each one, you must use the DistributedExceptionInfo object to capture the information for chaining the exception. Figure 58 shows standard implementations for the six constructors.

Figure 58. Code example: Constructors for an exception class that implements the DistributedExceptionEnabled interface

...
public class AccountCreateException extends CreateException
implements DistributedExceptionEnabled
{
    DistributedExceptionInfo exceptionInfo = null;
    // Constructors
    AccountCreateException() {
        super ();
        exceptionInfo = new DistributedExceptionInfo(this);
    }
    AccountCreateException(String msg) {
        super (msg);
        exceptionInfo = new DistributedExceptionInfo(this);
    }
    AccountCreateException(Throwable e) {
        super ();
        exceptionInfo = new DistributedExceptionInfo(this, e);
    }
    AccountCreateException(String msg, Throwable e) {
        super (msg);
        exceptionInfo = new DistributedExceptionInfo(this, e);
    }
    AccountCreateException(String resourceBundleName, String resourceKey,
                                Object[] formatArguments, String defaultText)
    {
        super ();
        exceptionInfo = new DistributedExceptionInfo(resourceBundleName,
                            resourceKey, formatArguments, defaultText, this);
    }
   AccountCreateException(String resourceBundleName, String resourceKey,
                                Object[] formatArguments, String defaultText, 
                                Throwable exception) 
   {
        super ();
        exceptionInfo = new DistributedExceptionInfo(resourceBundleName,
                       resourceKey, formatArguments, defaultText, this, exception);
    }
    // Methods from the DistributedExceptionEnabled interface
    ...
}

Implementing the methods from the DistributedExceptionEnabled interface
The DistributedExceptionInfo object provides implementations for most of the methods in the DistributedExceptionEnabled interface, so you can implement the required methods in your exception class by calling the corresponding methods on the DistributedExceptionInfo object. Figure 59 illustrates this technique. The only two methods that do not involve calling a corresponding method on the DistributedExceptionInfo object are the getExceptionInfo method, which returns the object, and the printSuperStackTrace method, which calls the super.printStackTrace method.

Figure 59. Code example: Implementations of the methods in the DistributedExceptionEnabled interface

...
public class AccountCreateException extends CreateException
implements DistributedExceptionEnabled
{
    DistributedExceptionInfo exceptionInfo = null;
    // Constructors
    ...
    // Methods from the DistributedExceptionEnabled interface
    String getMessage() {
        if (exceptionInfo != null)
            return exceptionInfo.getMessage();
        else return null;
    }
    Throwable getPreviousException() {
        if (exceptionInfo != null)
            return exceptionInfo.getPreviousException();
        else return null;
    }
    Throwable getOriginalException() {
        if (exceptionInfo != null)
            return exceptionInfo.getOriginalException();
        else return null;
    }
    Throwable getException(String exceptionClassName) {
        if (exceptionInfo != null)
            return exceptionInfo.getException(exceptionClassName);
        else return null;
    }
    DistributedExceptionInfo getExceptionInfo() {
        if (exceptionInfo != null)
            return exceptionInfo;
        else return null;
    }
    void printStackTrace() {
        if (exceptionInfo != null)
            return exceptionInfo.printStackTrace();
        else return null;
    }
    void printStackTrace(PrintWriter pw) {
        if (exceptionInfo != null)
            return exceptionInfo.printStackTrace(pw);
        else return null;
    }
    void printSuperStackTrace(PrintWriter pw) 
        if (exceptionInfo != null)
            return super.printStackTrace(pw);
        else return null;
    }
}

Using distributed exceptions
Defining a distributed exception gives you the ability to chain exceptions together. The DistributedExceptionInfo class provides methods for adding information to an exception chain and for extracting information from the chain. This section illustrates the use of distributed exceptions.

Catching distributed exceptions

You can catch exceptions that extend the DistributedException class or implement the DistributedExceptionEnabled interface separately. You can also test a caught exception to see if it has implemented the DistributedExceptionEnabled interface. If it has, you can treat it as any other distributed exception. Figure 60 shows the use of the instanceof method to test for exception chaining.

Figure 60. Code example: Testing for an exception that implements the DistributedExceptionEnabled interface

....
try {
    someMethod();
}
catch (Exception e) {
    ...
    if (e instanceof DistributedExceptionEnabled) {
            ...
    }
...

Adding an exception to a chain

To add an exception to a chain, you must call one of the constructors for your distributed-exception class. This captures the previous exception information and packages it with the new exception. Figure 61 shows the use of the MyDistributedException(Throwable) constructor.

Figure 61. Code example: Adding an exception to a chain

void someMethod() throws MyDistributedException {
    try {
        someOtherMethod();
    }
    catch (DistributedExceptionEnabled e) {
        throw new MyDistributedException(e);
    }
    ...
}...

Retrieving information from a chain

Chained exceptions allow you to retrieve information about prior exceptions in the chain. For example, the getPreviousException, getOriginalException, and getException(String) methods allow you to retrieve specific exceptions from the chain. You can retrieve the message associated with the current exception by calling the getMessage method. You can also get information about the entire chain by calling one of the printStackTrace methods. Figure 62 illustrates calling the getPreviousException and getOriginalException methods.

Figure 62. Code example: Extracting exceptions from a chain

...
try {
    someMethod();
}
catch (DistributedExceptionEnabled e) {
    try {
       Throwable prev = e.getPreviousException();
    }
    catch (ExceptionInstantiationException eie) {
        DistributedExceptionInfo prevExInfo = e.getPreviousExceptionInfo();
        if (prevExInfo != null) {
            String prevExName = prevExInfo.getClassName();
            String prevExMsg = prevExInfo.getClassMessage();
            ...
        }
    }
    try {
        Throwable orig = e.getOriginalException();
    }
    catch (ExceptionInstantiationException eie) {
        DistributedExceptionInfo origExInfo = null;
        DistributedExceptionInfo prevExInfo = e.getPreviousExceptionInfo();
        while (prevExInfo != null) {
            origExInfo = prevExInfo;
            prevExInfo = prevExInfo.getPreviousExceptionInfo();
        }
        if (origExInfo != null) {
            String origExName = origExInfo.getClassName();
            String origExMsg = origExInfo.getClassMessage();
        ...
        }
    }
}
...

The command package

Distributed applications are defined by the ability to utilize remote resources as if they were local, but this remote work affects the performance of distributed applications. Distributed applications can improve performance by using remote calls sparingly. For example, if a server does several tasks for a client, the application can run more quickly if the client bundles requests together, reducing the number of individual remote calls. The command package provides a mechanism for collecting sets of requests to be submitted as a unit.

In addition to giving you a way to reduce the number of remote invocations a client makes, the command package provides a generic way of making requests. A client instantiates the command, sets its input data, and tells it to run. The command infrastructure determines the target server and passes a copy of the command to it. The server runs the command, sets any output data, and copies it back to the client. The package provides a common way to issue a command, locally or remotely, and independently of the server's implementation. Any server (an enterprise bean, a Java Database Connectivity (JDBC) server, a servlet, and so on) can be a target of a command if the server supports Java access to its resources and provides a way to copy the command between the client's Java Virtual Machine (JVM) and its own JVM.

Overview

The command facility is implemented in the com.ibm.websphere.command Java package. The classes and interfaces in the command package fall into four general categories:

This section provides a general description of the interfaces and classes in the command package.

Facilities for creating commands
The Command interface specifies the most basic aspects of a command. This interface is extended by both the TargetableCommand interface and the CompensableCommand interface, which offer additional features. To create commands for applications, you must:

In practice, most commands implement the TargetableCommand interface, which allows the command to be executed remotely. Figure 63 shows the structure of a command interface for a targetable command.

Figure 63. Code example: The structure of an interface for a targetable command

...
import com.ibm.websphere.command.*;
public interface MySimpleCommand extends TargetableCommand { 
      // Declare application methods here
}

The CompensableCommand interface allows the association of one command with another that can undo the work of the first. Compensable commands also typically implement the TargetableCommand interface. Figure 64 shows the structure of a command interface for a targetable, compensable command.

Figure 64. Code example: The structure of an interface for a targetable, compensable command

...
import com.ibm.websphere.command.*;
public interface MyCommand extends TargetableCommand, CompensableCommand { 
      // Declare application methods here
}

Facilities for implementing commands
Commands are implemented by extending the class TargetableCommandImpl, which implements the TargetableCommand interface. The TargetableCommandImpl class is an abstract class that provides some implementations for some of the methods in the TargetableCommand interface (for example, setting return values) and declares additional methods that the application itself must implement (for example, how to execute the command).

You implement your command interface by writing a class that extends the TargetableCommandImpl class and implements your command interface. This class contains the code for the methods in your interface, the methods inherited from extended interfaces (the TargetableCommand and CompensableCommand interfaces), and the required (abstract) methods in the TargetableCommandImpl class. You can also override the default implementations of other methods provided in the TargetableCommandImpl class. Figure 65 shows the structure of an implementation class for the interface in Figure 64.

Figure 65. Code example: The structure of an implementation class for a command interface

...
import java.lang.reflect.*;
import com.ibm.websphere.command.*;
public class MyCommandImpl extends TargetableCommandImpl
implements MyCommand { 
     // Set instance variables here
     ...
     // Implement methods in the MyCommand interface
     ...
     // Implement methods in the CompensableCommand interface
     ...
     // Implement abstract methods in the TargetableCommandImpl class
     ...
}

Facilities for setting and determining targets
The object that is the target of a TargetableCommand must implement the CommandTarget interface. This object can be an actual server-side object, like an entity bean, or it can be a client-side adapter for a server. The implementor of the CommandTarget interface is responsible for ensuring the proper execution of a command in the desired target server environment. This typically requires the following steps:

  1. Copying the command to the target server by using a server-specific protocol.

  2. Running the command in the server.

  3. Copying the executed command from the target server to the client by using a server-specific protocol.

Common ways to implement the CommandTarget interface include:

Figure 66. Code example: The structure of a command-target entity bean

...
import java.rmi.RemoteException;
import java.util.Properties;
import javax.ejb.*;
import com.ibm.websphere.command.*;
// Remote interface for the MyBean enterprise bean (also a command target)
public interface MyBean extends EJBObject, CommandTarget { 
     // Declare methods for the remote interface
     ...
}
// Entity bean class for the MyBean enterprise bean (also a command target)
public class MyBeanClass implements EntityBean, CommandTarget { 
     // Set instance variables here
     ...
     // Implement methods in the remote interface
     ...
     // Implement methods in the EntityBean interface
     ...
     // Implement the method in the CommandTarget interface
     ...
}
Since targetable commands can be run remotely in another JVM, the command package provides mechanisms for determining where to run the command. A target policy associates a command with a target and is specified through the TargetPolicy interface. You can design customized target policies by implementing this interface, or you can use the provided TargetPolicyDefault class. For more information, see Targets and target policies.

Exceptions in the command package
The command package defines a set of exception classes. The CommandException class extends the DistributedException class and acts as the base class for the additional command-related exceptions: UnauthorizedAccessException, UnsetInputPropertiesException, and UnavailableCompensableCommandException. Applications can extend the CommandException class to define additional exceptions, as well.

Although the CommandException class extends the DistributedException class, you do not have to import the distributed-exception package, com.ibm.websphere.exception, unless you need to use the features of the DistributedException class in your application. For more information on distributed exceptions, see The distributed-exception package.

Writing command interfaces
To write a command interface, you extend one or more of the three interfaces included in the command package. The base interface for all commands is the Command interface. This interface provides only the client-side interface for generic commands and declares three basic methods:

The implementation class for your interface must contain implementations for the isReadyToCallExecute and reset methods. The execute method is implemented for you elsewhere; for more information, see Implementing command interfaces. Most commands do not extend the Command interface directly but use one of the provided extensions: the TargetableCommand interface and the CompensableCommand interface.

The TargetableCommand interface
The TargetableCommand interface extends the Command interface and provides for remote execution of commands. Most commands will be targetable commands. The TargetableCommand interface declares several additional methods:

With the exception of the performExecute method, which you must implement, all of these methods are implemented in the TargetableCommandImpl class. This class also implements the execute method declared in the Command interface.

The CompensableCommand interface
The CompensableCommand interface also extends the Command interface. A compensable command is one that has another command (a compensator) associated with it, so that the work of the first can be undone by the compensator. For example, a command that attempts to make an airline reservation followed by a hotel reservation can offer a compensating command that allows the user to cancel the airline reservation if the hotel reservation cannot be made.

The CompensableCommand interface declares one method:

To create a compensable command, you write an interface that extends the CompensableCommand interface. Such interfaces typically extend the TargetableCommand interface as well. You must implement the getCompensatingCommand method in the implementation class for your interface. You must also implement the compensating command.

The example application

The example used throughout the remainder of this discussion uses an entity bean with container-managed persistence (CMP) called CheckingAccountBean, which allows a client to deposit money, withdraw money, set a balance, get a balance, and retrieve the name on the account. This entity bean also accepts commands from the client. The code examples illustrate the command-related programming. For a servlet-based example, see Writing a command target (client-side adapter).

Figure 67 shows the interface for the ModifyCheckingAccountCmd command. This command is both targetable and compensable, so the interface extends both TargetableCommand and CompensableCommand interfaces.

Figure 67. Code example: The ModifyCheckingAccountCmd interface

...
import com.ibm.websphere.exception.*;
import com.ibm.websphere.command.*;
public interface ModifyCheckingAccountCmd
extends TargetableCommand, CompensableCommand { 
      float getAmount();
      float getBalance();
      float getOldBalance();       // Used for compensating
      float setBalance(float amount);
      float setBalance(int amount);
      CheckingAccount getCheckingAccount();
      void setCheckingAccount(CheckingAccount newCheckingAccount);
      TargetPolicy getCmdTargetPolicy();
      ...
}

Implementing command interfaces
The command package provides a class, TargetableCommandImpl, that implements all of the methods in the TargetableCommand interface except the performExecute method. It also implements the execute method from the Command interface. To implement an application's command interface, you must write a class that extends the TargetableCommandImpl class and implements your command interface. Figure 68 shows the structure of the ModifyCheckingAccountCmdImpl class.

Figure 68. Code example: The structure of the ModifyCheckingAccountCmdImpl class

...
public class ModifyCheckingAccountCmdImpl extends TargetableCommandImpl
implements ModifyCheckingAccountCmd
{
    // Variables
    ...
    // Methods
    ...
} 

The class must declare any variables and implement these methods:

You can also override the nonfinal implementations provided in the TargetableCommandImpl class. The most likely candidate for reimplementation is the setOutputProperties method, since the default implementation does not save final, transient, or static fields.

Defining instance and class variables

The ModifyCheckingAccountCmdImpl class declares the variables used by the methods in the class, including the remote interface of the CheckingAccount entity bean; the variables used to capture operations on the checking account (balances and amounts); and a compensating command. Figure 69 shows the variables used by the ModifyCheckingAccountCmd command.

Figure 69. Code example: The variables in the ModifyCheckingAccountCmdImpl class

...
public class ModifyCheckingAccountCmdImpl extends TargetableCommandImpl
implements ModifyCheckingAccountCmd
{
    // Variables
    public float balance;
    public float amount;
    public float oldBalance;
    public CheckingAccount checkingAccount;
    public ModifyCheckingAccountCompensatorCmd
                        modifyCheckingAccountCompensatorCmd;
    ...
} 

Implementing command-specific methods
The ModifyCheckingAccountCmd interface defines several command-specific methods in addition to extending other interfaces in the command package. These command-specific methods are implemented in the ModifyCheckingAccountCmdImpl class.

You must provide a way to instantiate the command. The command package does not specify the mechanism, so you can choose the technique most appropriate for your application. The fastest and most efficient technique is to use constructors. The most flexible technique is to use a factory. Also, since commands are implemented internally as JavaBeans components, you can use the standard Beans.instantiate method. The ModifyCheckingAccountCmd command uses constructors.

Figure 70 shows the two constructors for the command. The difference between them is that the first uses the default target policy for determining the target of the command and the second allows you to specify a custom policy. (For more information on targets and target policies, see Targets and target policies.)

Both constructors take a CommandTarget object as an argument and cast it to the CheckingAccount type. The CheckingAccount interface extends both the CommandTarget interface and the EJBObject (see Figure 80). The resulting checkingAccount object routes the command to the desired server by using the bean's remote interface. (For more information on CommandTarget objects, see Writing a command target (server).)

Figure 70. Code example: Constructors in the ModifyCheckingAccountCmdImpl class

...
public class ModifyCheckingAccountCmdImpl extends TargetableCommandImpl
implements ModifyCheckingAccountCmd
{
    // Variables
    ...
    // Constructors
    // First constructor: relies on the default target policy
    public ModifyCheckingAccountCmdImpl(CommandTarget target,
                    float newAmount)
    {
        amount = newAmount;
        checkingAccount = (CheckingAccount)target;
        setCommandTarget(target);
    }
    // Second constructor: allows you to specify a custom target policy
    public ModifyCheckingAccountCmdImpl(CommandTarget target,
                    float newAmount,
                    TargetPolicy targetPolicy)
    {
        setTargetPolicy(targetPolicy);
        amount = newAmount;
        checkingAccount = (CheckingAccount)target;
        setCommandTarget(target); 
    }
    ...
}

Figure 71 shows the implementation of the command-specific methods:

Figure 71. Code example: Command-specific methods in the ModifyCheckingAccountCmdImpl class

...
public class ModifyCheckingAccountCmdImpl extends TargetableCommandImpl
implements ModifyCheckingAccountCmd
{
    // Variables
    ...
    // Constructors
    ...
    // Methods in ModifyCheckingAccountCmd interface
    public float getAmount() {
        return amount;
    }
    public float getBalance() {
        return balance;
    }
    public float getOldBalance() {
        return oldBalance;
    }
    public float setBalance(float amount) {
        balance = balance + amount;
        return balance;
    }
    public float setBalance(int amount) {
        balance += amount ;
        return balance;
    }
    public TargetPolicy getCmdTargetPolicy() {
        return getTargetPolicy();
    }
    public void setCheckingAccount(CheckingAccount newCheckingAccount) {
        if (checkingAccount == null) {
            checkingAccount = newCheckingAccount;
        }
        else
            System.out.println("Incorrect Checking Account (" +
                newCheckingAccount + ") specified");
    }
    public CheckingAccount getCheckingAccount() {
        return checkingAccount;
    }
    ...
}

The ModifyCheckingAccountCmd command operates on a checking account. Because commands are implemented as JavaBeans components, you manage input and output properties of commands using the standard JavaBeans techniques. For example, initialize input properties with set methods (like setCheckingAccount) and retrieve output properties with get methods (like getCheckingAccount). The get methods do not work until after the command's execute method has been called.

Implementing methods from the Command interface
The Command interface declares two methods, isReadyToCallExecute and reset, that must be implemented by the application programmer. Figure 72 shows the implementations for the ModifyCheckingAccountCmd command. The implementation of the isReadyToCallExecute method ensures that the checkingAccount variable is set. The reset method sets all of the variables back to starting values.

Figure 72. Code example: Methods from the Command interface in the ModifyCheckingAccountCmdImpl class

...
public class ModifyCheckingAccountCmdImpl extends TargetableCommandImpl
implements ModifyCheckingAccountCmd
{
    ...
    // Methods from the Command interface
    public boolean isReadyToCallExecute() {
        if (checkingAccount != null)
            return true;
        else
            return false;
    }
    public void reset() {
        amount = 0;
        balance = 0;
        oldBalance = 0;
        checkingAccount = null;
        targetPolicy = new TargetPolicyDefault();
    }
   ...
}

Implementing methods from the TargetableCommand interface
The TargetableCommand interface declares one method, performExecute, that must be implemented by the application programmer. Figure 73 shows the implementation for the ModifyCheckingAccountCmd command. The implementation of the performExecute method does the following:

In addition, the ModifyCheckingAccountCmdImpl class overrides the default implementation of the setOutputProperties method.

Figure 73. Code example: Methods from the TargetableCommand interface in the ModifyCheckingAccountCmdImpl class

...
public class ModifyCheckingAccountCmdImpl extends TargetableCommandImpl
implements ModifyCheckingAccountCmd
{
    ...
    // Method from the TargetableCommand interface
    public void performExecute() throws Exception {
        CheckingAccount checkingAccount = getCheckingAccount();
        oldBalance =  checkingAccount.getBalance();
        balance = oldBalance+amount;
        checkingAccount.setBalance(balance);
        setHasOutputProperties(true);
    }
    public void setOutputProperties(TargetableCommand fromCommand) {
        try {
            if (fromCommand != null) {
                ModifyCheckingAccountCmd modifyCheckingAccountCmd =
               	    (ModifyCheckingAccountCmd) fromCommand;
               	this.oldBalance = modifyCheckingAccountCmd.getOldBalance();
                this.balance = modifyCheckingAccountCmd.getBalance();
                this.checkingAccount =
                    modifyCheckingAccountCmd.getCheckingAccount();
                this.amount = modifyCheckingAccountCmd.getAmount();
            }
        }
        catch (Exception ex) {
             System.out.println("Error in setOutputProperties.");
        }
    }
    ...
}

Implementing the CompensableCommand interface
The CompensableCommand interface declares one method, getCompensatingCommand, that must be implemented by the application programmer. Figure 74 shows the implementation for the ModifyCheckingAccountCmd command. The implementation simply returns an instance of the ModifyCheckingAccountCompensatorCmd command associated with the current command.

Figure 74. Code example: Method from the CompensableCommand interface in the ModifyCheckingAccountCmdImpl class

...
public class ModifyCheckingAccountCmdImpl extends TargetableCommandImpl
implements ModifyCheckingAccountCmd
{
    ...
    // Method from CompensableCommand interface
    public Command getCompensatingCommand() throws CommandException {
        modifyCheckingAccountCompensatorCmd =
            new ModifyCheckingAccountCompensatorCmd(this);
        return (Command)modifyCheckingAccountCompensatorCmd;
    }                                                
}

Writing the compensating command

An application that uses a compensable command requires two separate commands: the primary command (declared as a CompensableCommand) and the compensating command. In the example application, the primary command is declared in the ModifyCheckingAccountCmd interface and implemented in the ModifyCheckingAccountCmdImpl class. Because this command is also a compensable command, there is a second command associated with it that is designed to undo its work. When you create a compensable command, you also have to write the compensating command.

Writing a compensating command can require exactly the same steps as writing the original command: writing the interface and providing an implementation class. In some cases, it may be simpler. For example, the command to compensate for the ModifyCheckingAccountCmd does not require any methods beyond those defined for the original command, so it does not need an interface. The compensating command, called ModifyCheckingAccountCompensatorCmd, simply needs to be implemented in a class that extends the TargetableCommandImpl class. This class must:

Figure 75 shows the structure of the implementation class, its variables (references to the original command and to the relevant checking account), and the constructor. The constructor simply instantiates the references to the primary command and account.

Figure 75. Code example: Variables and constructor in the ModifyCheckingAccountCompensatorCmd class

...
public class ModifyCheckingAccountCompensatorCmd extends TargetableCommandImpl
{
    public ModifyCheckingAccountCmdImpl modifyCheckingAccountCmdImpl;
    public CheckingAccount checkingAccount;
   
    public ModifyCheckingAccountCompensatorCmd(
                 ModifyCheckingAccountCmdImpl originalCmd)
    {
        // Get an instance of the original command
        modifyCheckingAccountCmdImpl = originalCmd;
        // Get the relevant account
        checkingAccount = originalCmd.getCheckingAccount();
     }
     // Methods from the Command and Targetable Command interfaces
    ....
}

Figure 76 shows the implementation of the inherited methods. The implementation of the isReadyToCallExecute method ensures that the checkingAccount variable has been instantiated.

The performExecute method verifies that the actual checking-account balance is consistent with what the original command returns. If so, it replaces the current balance with the previously stored balance by using the ModifyCheckingAccountCmd command. Finally, it saves the most-recent balances in case the compensating command needs to be undone. The reset method has no work to do.

Figure 76. Code example: Methods in ModifyCheckingAccountCompensatorCmd class

...
public class ModifyCheckingAccountCompensatorCmd extends TargetableCommandImpl
{
    // Variables and constructor
    ....
   // Methods from the Command and TargetableCommand interfaces
   public boolean isReadyToCallExecute() {
        if (checkingAccount != null)
            return true;
        else
            return false;
    }
    public void performExecute() throws CommandException 
    {
        try {
            ModifyCheckingAccountCmdImpl originalCmd = 
					modifyCheckingAccountCmdImpl;
            // Retrieve the checking account modified by the original command
            CheckingAccount checkingAccount = originalCmd.getCheckingAccount();
            if (modifyCheckingAccountCmdImpl.balance ==
                checkingAccount.getBalance()) {
                    // Reset the values on the original command
                    checkingAccount.setBalance(originalCmd.oldBalance);
                    float temp = modifyCheckingAccountCmdImpl.balance;
                    originalCmd.balance = originalCmd.oldBalance;
                    originalCmd.oldBalance = temp;
            }
            else {
                // Balances are inconsistent, so we cannot compensate
                throw new CommandException(
			"Object modified since this command ran.");
            }
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
    public void reset() {}
}

Using a command

To use a command, the client creates an instance of the command and calls the command's execute method. Depending on the command, calling other methods can be necessary. The specifics will vary with the application.

In the example application, the server is the CheckingAccountBean, an entity enterprise bean. In order to use this enterprise bean, the client gets a reference to the bean's home interface. The client then uses the reference to the home interface and one of the bean's finder methods to obtain a reference to the bean's remote interface. If there is no appropriate bean, the client can create one using a create method on the home interface. All of this work is standard enterprise bean programming covered elsewhere in this document.

Figure 77 illustrates the use of the ModifyCheckingAccountCmd command. This work takes place after an appropriate CheckingAccount bean has been found or created. The code instantiates a command, setting the input values by using one of the constructors defined for the command. The null argument indicates that the command should look up the server using the default target policy, and 1000 is the amount the command attempts to add to the balance of the checking account. (For more information on how the command package uses defaults to determine the target of a command, see The default target policy.) After the command is instantiated, the code calls the setCheckingAccount method to identify the account to be modified. Finally, the execute method on the command is called.

Figure 77. Code example: Using the ModifyCheckingAccountCmd command

{
    ...
    CheckingAccount checkingAccount
    ...
    try {
        ModifyCheckingAccountCmd cmd = 
            new ModifyCheckingAccountCmdImpl(null, 1000);
        cmd.setCheckingAccount(checkingAccount);
        cmd.execute();
    }
    catch (Exception e) {
       System.out.println(e.getMessage());
    }
    ...
}

Using a compensating command

To use a compensating command, you must retrieve the compensator associated with the primary command and call its execute method. Figure 78 shows the code used to run the original command and to give the user the option of undoing the work by running the compensating command.

Figure 78. Code example: Using the ModifyCheckingAccountCompensator command

{
    ...
    CheckingAccount checkingAccount
    ....
    try {
        ModifyCheckingAccountCmd cmd = 
            new ModifyCheckingAccountCmdImpl(null, 1000);
        cmd.setCheckingAccount(checkingAccount);
        cmd.execute();
        ...
        System.out.println("Would you like to undo this work?  Enter Y or N");
        try {
            // Retrieve and validate user's response
            ...
        }
        ...
        if (answer.equalsIgnoreCase(Y)) {
            Command compensatingCommand = cmd.getCompensatingCommand();
            compensatingCommand.execute();
        }
    }
    catch (Exception e) {
       System.out.println(e.getMessage());
    }
    ...
}

Using the WebSphere EJBCommandTarget bean as a command target

WebSphere ships a CommandTarget enterprise bean to allow administrators to execute a command in a designated server without providing their own implementation of CommandTarget. The EJBCommandTarget class, along with the EJBCommandTarget bean (CommandServerSessionBean), are located in the EJBCommandTarget.jar file in the lib directory under the WebSphere installation directory. This is a deployed jar file. You can use this JAR file in a new application or add it into an existing application.

The EJBCommandTarget class serves as a wrapper for a CommandTarget bean. CommandServerSessionBean is the WebSphere implementation of this CommandTarget bean. A command developer can set this EJBCommandTarget object into the Command. Figure 79 shows an example.

Figure 79. Code example: Using an EJBCommandTarget bean

EJBCommandTarget target = new EJBCommandTarget();
 
MyCommand cmd = new MyCommandImpl(Arguments...);
cmd.setCommandTarget(target);
cmd.execute();

In this example, the client creates a MyCommand object. It is then executed in the application server. When the execute method is performed, the target (EJBCommandTarget) looks up the CommandServerSessionHome from the InitialContext and executes the executeCommand method on the CommandServerSessionBean. The EJBCommandTarget object ensures that there is only one CommandServerSessionBean per object to avoid extra naming lookup.

An EJBCommandTarget object can be created using four different constructors:

The first constructor allows the application to specify the naming server name and the port. The JNDI name of the CommandServerSessionBean can also be specified. The EJBCommandTarget constructs a provider URL of "iiop://MyNamingServerName:PortNumber" and looks up the CommandServerSessionBean with the given JNDI name. If null values are passed in for any of the parameters the WebSphere defaults for server and port and a default JNDI name of CommandServerSession are used.

The second constructor allows the application to specify its own initial context. The EJBCommandTarget object then uses this initial context to look up the CommandServerSession bean with the specified JNDI name.

The third constructor allows the application to set up the naming server (the provider URL) in property files.

The default constructor uses the default values for the provider URL and default JNDI name for the CommandServerSession bean (CommandServerSession).

You do not need to use the EJBCommandTarget class. You can instead create your own custom target policy that uses the EJBCommandTarget bean (CommandServerSessionBean). The EJBCommandTarget object is a convenience class and attempts to address most usage scenarios

Writing a command target (server)
In order to accept commands, a server must implement the CommandTarget interface and its single method, executeCommand.

The example application implements the CommandTarget interface in an enterprise bean. (For a servlet-based example, see Writing a command target (client-side adapter).) The target enterprise bean can be a session bean or an entity bean. You can write a target enterprise bean that forwards commands to a specific server, such as another entity bean. In this case, all commands directed at a specific target go through the target enterprise bean. You can also write a target enterprise bean that does the work of the command locally.

Make an enterprise bean the target of a command by:

The target of the example application is an enterprise bean called CheckingAccountBean. This bean's remote interface, CheckingAccount, extends the CommandTarget interface in addition to the EJBObject interface. The methods declared in the remote interface are independent of those used by the command. The executeCommand is declared in neither the bean's home nor remote interfaces. Figure 80 shows the CheckingAccount interface.

Figure 80. Code example: The remote interface for the CheckingAccount entity bean, also a command target

...
import com.ibm.websphere.command.*;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface CheckingAccount extends CommandTarget, EJBObject {
    float deposit (float amount) throws RemoteException;
    float deposit (int amount) throws RemoteException;
    String getAccountName() throws RemoteException;
    float getBalance() throws RemoteException;
    float setBalance(float amount) throws RemoteException;
    float withdrawal (float amount) throws RemoteException, Exception;
    float withdrawal (int amount) throws RemoteException, Exception;
}
The enterprise bean class, CheckingAccountBean, implements the EntityBean interface as well as the CommandTarget interface. The class contains the business logic for the methods in the remote interface, the necessary life-cycle methods (ejbActivate, ejbStore, and so on), and the executeCommand declared by the CommandTarget interface. The executeCommand method is the only command-specific code in the enterprise bean class. It attempts to run the performExecute method on the command and throws a CommandException if an error occurs. If the performExecute method runs successfully, the executeCommand method uses the hasOutputProperties method to determine if there are output properties that must be returned. If the command has output properties, the method returns the command object to the client. Figure 81 shows the relevant parts of the CheckingAccountBean class.

Figure 81. Code example: The bean class for the CheckingAccount entity bean, also a command target

...
public class CheckingAccountBean implements EntityBean, CommandTarget {
    // Bean variables
    ...
    // Business methods from remote interface
    ...
    // Life-cycle methods for CMP entity beans
    ...
    // Method from the CommandTarget interface
    public TargetableCommand executeCommand(TargetableCommand command)
    throws RemoteException, CommandException
    {
        try {
            command.performExecute();
        }
        catch (Exception ex) {
            if (ex instanceof RemoteException) {
                RemoveException remoteException = (RemoteException)ex;
                if (remoteException.detail != null) {
                    throw new CommandException(remoteException.detail);
                }
                throw new CommandException(ex);
            } 
        }
        if (command.hasOutputProperties()) {
            return command;
        }
        return null;
    }
}

Targets and target policies
A targetable command extends the TargetableCommand interface, which allows the client to direct a command to a particular server. The TargetableCommand interface (and the TargetableCommandImpl class) provide two ways for a client to specify a target: the setCommandTarget and setCommandTargetName methods. (These methods were introduced in The TargetableCommand interface.) The setCommandTarget methods allows the client to set the target object directly on the command. The setCommandTargetName method allows the client to refer to the server by name; this approach is useful when the client is not directly aware of server objects. A targetable command also has corresponding getCommandTarget and getCommandTargetName methods.

The command package needs to be able to identify the target of a command. Because there is more than one way to specify the target and because different applications can have different requirements, the command package does not specify a selection algorithm. Instead, it provides a TargetPolicy interface with one method, getCommandTarget, and a default implementation. This allows applications to devise custom algorithms for determining the target of a command when appropriate.

The default target policy
The command package provides a default implementation of the TargetPolicy interface in the TargetPolicyDefault class. If you use this default implementation, the command determines the target by looking through an ordered sequence of four options:

  1. The CommandTarget value

  2. The CommandTargetName value

  3. A registered mapping of a target for a specific command

  4. A defined default target

If it finds no target, it returns null. The TargetPolicyDefault class provides methods for managing the assignment of commands with targets (registerCommand, unregisterCommand, and listMappings), and a method for setting a default name for the target (setDefaultTargetName). The default target name is com.ibm.websphere.command.LocalTarget, where LocalTarget is a class that runs the command's performExecute method locally. Figure 82 shows the relevant variables and the methods in the TargetPolicyDefault class.

Figure 82. Code example: The TargetPolicyDefault class

...
public class TargetPolicyDefault implements TargetPolicy, Serializable
{
    ...
    protected String defaultTargetName = "com.ibm.websphere.command.LocalTarget";
    public CommandTarget getCommandTarget(TargetableCommand command) {
        ... }
    public Dictionary listMappings() {
        ... }
    public void registerCommand(String commandName, String targetName) {
        ... }
    public void unregisterCommand(String commandName) {
        ... }
    public void seDefaultTargetName(String defaultTargetName) {
        ... }
}

Setting the command target
The ModifyCheckingAccountImpl class provides two command constructors (see Figure 70). One of them takes a command target as an argument and implicitly uses the default target policy to locate the target. The constructor used in Figure 77 passes a null target, so that the default target policy traverses its choices and eventually finds the default target name, LocalTarget.

The example in Figure 83 uses the same constructor to set the target explicitly. This example differs from Figure 77 as follows:

Figure 83. Code example: Identifying a target with CommandTarget

{
    ...
    CheckingAccount checkingAccount
    ....
    try {
        ModifyCheckingAccountCmd cmd = 
            new ModifyCheckingAccountCmdImpl(checkingAccount, 1000);
        cmd.execute();
    }
    catch (Exception e) {
       System.out.println(e.getMessage());
    }
    ...
}

Setting the command target name
If a client needs to set the target of the command by name, it can use the command's setCommandTargetName method. Figure 84 illustrates this technique. This example compares with Figure 77 as follows:

Figure 84. Code example: Identifying a target with CommandTargetName

{
    ...
    CheckingAccount checkingAccount
    ....
    try {
        ModifyCheckingAccountCmd cmd = 
            new ModifyCheckingAccountCmdImpl(null, 1000);
        cmd.setCheckingAccount(checkingAccount);
        cmd.setCommandTargetName("com.ibm.sfc.cmd.test.CheckingAccountBean");
        cmd.execute();
    }
    catch (Exception e) {
       System.out.println(e.getMessage());
    }
    ...
}

Mapping the command to a target name
The default target policy also permits commands to be registered with targets. Mapping a command to a target is an administrative task that most appropriately done through a configuration tool. The WebSphere Application Server administrative console does not yet support the configuration of mappings between commands and targets. Applications that require support for the registration of commands with targets must supply the tools to manage the mappings. These tools can be visual interfaces or command-line tools.

Figure 85 shows the registration of a command with a target. The names of the command class and the target are explicit in the code, but in practice, these values would come from fields in a user interface or arguments to a command-line tool. If a program creates a command as shown in Figure 77, with a null for the target, when the default target policy traverses its choices, it finds a null for the first and second choices and a mapping for the third.

Figure 85. Code example: Mapping a command to a target in an external application

{
    ...
   targetPolicy.registerCommand(
        "com.ibm.sfc.cmd.test.ModifyCheckingAccountImpl",
        "com.ibm.sfc.cmd.test.CheckingAccountBean");
    ...
}

Customizing target policies
You can define custom target policies by implementing the TargetPolicy interface and providing a getCommandTarget method appropriate for your application. The TargetableCommandImpl class provides setTargetPolicy and getTargetPolicy methods for managing custom target policies.

So far, the target of all the commands has been a checking-account entity bean. Suppose that someone introduces a session enterprise bean (MySessionBean) that can also act as a command target. Figure 86 shows a simple custom policy that sets the target of every command to MySessionBean.

Figure 86. Code example: Creating a custom target policy

...
import java.io.*;
import java.util.*;
import java.beans.*;
import com.ibm.websphere.command.*;
public class CustomTargetPolicy implements TargetPolicy, Serializable {
    public CustomTargetPolicy {
        super();
    }
    public CommandTarget getCommandTarget(TargetableCommand command) {
        CommandTarget = null;
        try {
            target = (CommandTarget)Beans.instantiate(null,
                       "com.ibm.sfc.cmd.test.MySessionBean");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Since commands are implemented as JavaBeans components, using custom target policies requires importing the java.beans package and writing some elementary JavaBeans code. Also, your custom target-policy class must also implement the java.io.Serializable interface.

Using a custom target policy
The ModifyCheckingAccountImpl class provides two command constructors (see Figure 70). One of them implicitly uses the default target policy; the other takes a target policy object as an argument, which allows you to use a custom target policy. The example in Figure 87 uses the second constructor, passing a null target and a custom target policy, so that the custom policy is used to determine the target. After the command is executed, the code uses the reset method to return the target policy to the default.

Figure 87. Code example: Using a custom target policy

{
    ...
    CheckingAccount checkingAccount
    ....
    try {
        CustomTargetPolicy customPolicy = new CustomTargetPolicy();
        ModifyCheckingAccountCmd cmd = 
            new ModifyCheckingAccountCmdImpl(null, 1000, customPolicy);
        cmd.setCheckingAccount(checkingAccount);
        cmd.execute();
        cmd.reset();
    }
    catch (Exception e) {
       System.out.println(e.getMessage());
    }
}

Writing a command target (client-side adapter)
Commands can be used with any Java application, but the means of sending the command from the client to the server varies. The application described in The example application used enterprise beans. The example in this section shows how you can send a command to a servlet over the HTTP protocol.

In this example, the client implements the CommandTarget interface locally. Figure 88 shows the structure of the client-side class; it implements the CommandTarget interface by implementing the executeCommand method.

Figure 88. Code example: The structure of a client-side adapter for a target

...
import java.io.*;
import java.rmi.*;
import com.ibm.websphere.command.*;
public class ServletCommandTarget implements CommandTarget, Serializable
{
    protected String hostName = "localhost";
  public static void main(String args[]) throws Exception
    {
        ....
    }
    public TargetableCommand executeCommand(TargetableCommand command)
         throws CommandException
    {
        ....
    }
      public static final byte[] serialize(Serializable serializable)
         throws IOException {
        ...  }
    public String getHostName() {
        ... }
    public void setHostName(String hostName) {
        ... }
    private static void showHelp() {
         ... }
}

The main method in the client-side adapter constructs and intializes the CommandTarget object, as shown in Figure 89.

Figure 89. Code example: Instantiating the client-side adapter

public static void main(String args[]) throws Exception
{
    String hostName = InetAddress.getLocalHost().getHostName();
    String fileName = "MyServletCommandTarget.ser";
    // Parse the command line
    ...
    // Create and initialize the client-side CommandTarget adapter
    ServletCommandTarget servletCommandTarget = new ServletCommandTarget();
    servletCommandTarget.setHostName(hostName);
    ...
    // Flush and close output streams
    ...
}    

Implementing a client-side adapter
The CommandTarget interface declares one method, executeCommand, which the client implements. The executeCommand method takes a TargetableCommand object as input; it also returns a TargetableCommand. Figure 90 shows the implementation of the method used in the client-side adapter. This implementation does the following:

Figure 90. Code example: A client-side implementation of the executeCommand method

public TargetableCommand executeCommand(TargetableCommand command)
     throws CommandException
{
    try { 
        // Serialize the command
        byte[] array = serialize(command);
        // Create a connection to the servlet
        URL url = new URL
            ("http://" + hostName + 
            "/servlet/com.ibm.websphere.command.servlet.CommandServlet");
        HttpURLConnection httpURLConnection = 
            (HttpURLConnection) url.openConnection();
        // Set the properties of the connection
        ...
        // Put the serialized command on the output stream
        OutputStream outputStream = httpURLConnection.getOutputStream();
        outputStream.write(array);
        // Create a return stream
        InputStream inputStream = httpURLConnection.getInputStream();
        // Send the command to the servlet
        httpURLConnection.connect();
        ObjectInputStream objectInputStream = 
            new ObjectInputStream(inputStream);
        // Retrieve the command returned from the servlet
        Object object = objectInputStream.readObject();
        if (object instanceof CommandException) {
            throw ((CommandException) object);
        }
        // Pass the returned command back to the calling method
        return (TargetableCommand) object;
    }
    // Handle exceptions
    ....
}

Running the command in the servlet
The servlet that runs the command is shown in Figure 91. The service method retrieves the command from the input stream and runs the performExecute method on the command. The resulting object, with any output properties that must be returned to the client, is placed on the output stream and sent back to the client.

Figure 91. Code example: Running the command in the servlet

...
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.ibm.websphere.command.*;
public class CommandServlet extends HttpServlet {
    ...
    public void service(HttpServletRequest request, 
                        HttpServletResponse response)
        throws ServletException, IOException
     {
        try {
            ...
            // Create input and output streams
            InputStream inputStream = request.getInputStream();
            OutputStream outputStream = response.getOutputStream();
            // Retrieve the command from the input stream
            ObjectInputStream objectInputStream = 
                new ObjectInputStream(inputStream);
            TargetableCommand command = (TargetableCommand) 
                objectInputStream.readObject();
            // Create the command for the return stream
            Object returnObject = command;
  
            // Try to run the command's performExecute method
            try {
                command.performExecute();        
            }
            // Handle exceptions from the performExecute method 
            ...
  
            // Return the command with any output properties
            ObjectOutputStream objectOutputStream = 
                new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(returnObject);
            // Flush and close output streams
            ...
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

In this example, the target invokes the performExecute method on the command, but this is not always necessary. In some applications, it can be preferable to implement the work of the command locally. For example, the command can be used only to send input data, so that the target retrieves the data from the command and runs a local database procedure based on the input. You must decide the appropriate way to use commands in your application.


The localizable-text package

Overview

Users of distributed applications can come from widely varying areas; they can speak different languages, represent dates and times in regionally specific ways, and use different currencies. An application intended to be used by such an audience must either force them all to use the same interface (for example, an English-based interface), or it can be written in such a way that it can be configured to the linguistic conventions of the users, so English-speaking users can use the English interface but French-speaking users can interact with the application through a French interface.

An application that can present information to users in formats that abide by the users' linguistic conventions is said to be localizable: the application can be configured to interact with users from different localities in linguistically appropriate ways. In a localized application, a user in one region sees error messages, output, and interface elements (like menu options) in the requested language. Additionally, other elements that are not strictly linguistic, like date and time formats and currencies, are presented in the appropriate style for users in the specified region. A user in another region sees output in the conventional language or format for that region.

Historically, the creation of localizable applications has been restricted to large corporations writing complex systems. The strategies for writing localizable code, collectively called internationalization techniques, have traditionally been expensive and difficult to implement, so they have been applied only to major development efforts. However, given the rise in distributed computing and in use of the World Wide Web, application developers have been pressured to make a much wider variety of applications localizable. This requires making internationalization--the techniques for writing localizable programs--much more accessible to application developers. The WebSphere localizable-text package is a set of Java classes and interfaces that can be used by WebSphere application developers to localize distributed WebSphere applications easily. Language catalogs for distributed WebSphere applications can be stored centrally, so the catalogs can be maintained and administered efficiently.

Writing localizable programs

In a nonlocalizable application, parts of the application that a user sees are unalterably coded into the application. For example, a routine that prints an error message simply prints a string, probably in English, to a file or the console. A localizable program adds a layer of abstraction into the design. Instead of going simply from error condition to output string, a localizable program represents error messages with some language-neutral information; in the simplest case, each error condition corresponds to a key. In order to print a usable error string for the user, the application looks up the key in the configured message catalog. A message catalog is a list of keys with corresponding strings. Different message catalogs provide the strings in different languages. The application looks up the key in the appropriate catalog, retrieves the corresponding error message in the desired language, and prints this string for the user.

The technique of localization can be used for far more than translating error messages. For example, by using keys to represent each element--button, label, menu item, and so forth--in a graphical user interface and by providing a message catalog containing translations of the button names, labels, and menu items, the graphical interface can be automatically translated into multiple languages. In addition, extending support to additional languages requires providing message catalogs for those languages; the application itself requires no modification.

Localization of an application is driven by two variables, the time zone and the locale. The time zone variable indicates how to compute the local time as an offset from a standard time like Greenwich Mean Time. The locale is a collection of information that indicates a geographic, political, or cultural region. It provides information on language, currency, and the conventions for presenting information like dates, and in a localizable program, the locale also indicates the message catalog from which an application retrieves messages. A time zone can cover many locales, and a single locale can span time zones. With both time zone and locale, the date, time, currency, and language for users in a specific region can be determined.

Identifying localizable text

To write a localizable application, an application developer must determine which aspects of the application need to be translatable. These are typically the parts of an application a user must read and understand. Application developers must consider the parts of an application with which all users directly interact, like the application's interface, and the parts serving more specialized purposes, like messages in log files. Good candidates for localization include:

After identifying each element of the application to be localized, application developers must assign a unique key to each element and provide a message catalog for each language to be supported. Each message catalog consists of keys and the corresponding language-specific strings. The key, therefore, is the link between the program and the message catalog; the program internally refers to localizable elements by key and uses the message catalog to generate the output seen by the user. Translated strings are generated by calling the format method on a LocalizableTextFormatter object, which represents a key and a resource bundle (a set of message catalogs). The locale setting of the program determines the message catalog in which to search for the key.

Creating message catalogs

After identifying each element to be localized, message catalogs must be created for each language to be supported. These catalogs, which are implemented as Java resource bundles, can be created in two ways, either as subclasses of the ResourceBundle class or as Java properties files. Resource bundles have a variety of uses in Java; for message catalogs, the properties-file approach is more common. If properties files are used, support for languages to be added or removed without modifying the application code, and catalogs can be prepared by people without programming expertise.

A message catalog implemented in a properties file consists of a line for each key, where a key identifies a localizable element. Each line in the file has the following structure:

key = String corresponding to the key

For example, a grapical user interface for a banking system can have a pull-down menu to be used for selecting a type of account, like savings or checking. The label for the pull-down menu and the account types on the menu are good choices for localization. There are three elements that require keys: the label for the account menu and the two items on the menu. If the keys are accountString, savingsString, and checkingString, the English properties file associates each with an English string.

Figure 92. Three elements in an English message catalog

accountString = Accounts
savingsString = Savings
checkingString = Checking
...

In the German properties files, each key is given a corresponding German value.

Figure 93. Three elements in a German message catalog

accountString = Konten
savingsString = Sparkonto
checkingString = Girokonto 
...

Properties files can be added for any other needed languages, as well.

Naming the properties files

To enable resolution to a specific properties file, Java specifies naming conventions for the properties files in a resource bundle: resourceBundleName_localeID.properties

Each file takes a fixed extension, .properties. The set of files making up the resource bundle is given a collective name; for a simple banking application, an obvious name, like BankingResources, suffices for the resource bundle. Each file is given the name of the resource bundle with a locale identifier; the specific value of the locale ID varies with the locale. These are used internally by the Java.util.ResourceBundle class to match files in a resource bundle to combinations of locale and time-zone settings. The details of the algorithm vary with the release of the JDK; see your Java documentation for information specific to your installation.

In the banking application, typical files in the BankingResources resource bundle include BankingResources_en.properties for the English message catalog and BankingResources_de.properties for the German catalog. Additionally, a default catalog, BankingResources.properties, is provided for use when the requested catalog cannot be found. The default catalog is often the English-language catalog.

Resource bundles containing message catalogs for use with localizable text need to be installed only on the systems where the formatting of strings is actually performed. The resource bundles are typically placed in an application's JAR file. See WebSphere support for more information.

Localization support in WebSphere and Java

The Java package com.ibm.websphere.i18n.localizabletext contains the classes and interfaces constituting the localizable-text package. This package makes extensive use of the internationalization and localization features of the Java language; programmers using the WebSphere localizable-text package must understand the underlying Java support, which are not documented in any detail here.

Java support

The WebSphere localizable-text package relies primarily on the following Java components:

This list is not exhaustive. WebSphere and these Java classes can also use related Java classes, but the related classes--for example, java.util.Calendar--are typically special-purposes classes. This section briefly describes only the primary classes.

Locale

A Locale object in Java encapsulates a language and a geographic region, for example, the java.util.Locale.US object contains locale information for the United States. An application that specifies a locale can then take advantage of the locale-sensitive formatters built into the Java language. These formatters, in the java.text package, handle the presentation of numbers, currency values, dates, and times.

TimeZone

A TimeZone object in Java encapsulates a representation of the time and provides methods for tasks like reporting the time and accommodating seasonal time shifts. Applications use the time zone to determine the local date and time.

ResourceBundle

A resource bundle is a named collection of resources--information used by the application, for example, strings, fonts, and images--used by a specific locale. The ResourceBundle class allows an application to retrieve the named resource bundle appropriate to the locale. Resource bundles are used to hold the messages catalogs, as described in Writing localizable programs. Resource bundles can be implemented in two ways, either as subclasses of the ResourceBundle class or as Java properties files.

MessageFormat

The MessageFormat class can be used to construct strings based on parameters. As a simple example, suppose a localized application represents a particular error condition with a numeric key. When the application reports the error condition, it uses a message formatter to convert the numeric key into a meaningful string. The message formatter constructs the output string by looking up the code (the parameter) in an appropriate resource bundle and retrieving the corresponding string from the message catalog. Additional parameters--for example, another key representing the program module--can also be used in assembling the output message.

WebSphere support

The WebSphere localizable-text package wraps the Java support and extends it for efficient and simple use in a distributed environment. The primary class used by application programmers is the LocalizableTextFormatter class. Objects of this class are created, typically in server programs, but clients can also create them. LocalizableTextFormatter objects are created for specific resource-bundle names and keys. Client programs that receive LocalizableTextFormatter objects call the object's format method. This method uses the locale of the client application to retrieve the appropriate resource bundle and assemble the locale-specific message based on the key.

For example, suppose that a WebSphere client-server application supports both French and English locales; the server is using an English locale and the client, a French locale. The server creates two resource bundles, one for English and one for French. When the client makes a request that triggers a message, the server creates a LocalizableTextFormatter object containing the name of the resource bundle and the key for the message, and passes the object back to the client.

When the client receives the LocalizableTextFormatter object, it calls the object's format method, which returns the message corresponding to the key from the French resource bundle. The format method retrieves the client's locale and, using the locale and name of the resource bundle, determines the resource bundle corresponding to the locale. (If the client has set an English locale, calling the format method results in the retrieval of an English message.) The formatting of the message is transparent to the client. In this simple client-server example, the resource bundles reside centrally with the server. The client machine does not have to install them. Part of what the WebSphere localizable-text package provides is the infrastructure to support centralized catalogs. WebSphere uses an enterprise bean, a stateless session bean provided with the localizable-text package, to access the message catalogs. When the client calls the format method on the LocalizableTextFormatter object, the following events occur internally:

  1. The client application sets the time zone and locale values in the LocalizableTextFormatter object, either by passing them explicitly or through defaults.

  2. A call, LocalizableTextFormatterEJBFinder, is made to retrieve a reference to the formatting enterprise bean.

  3. Information from the LocalizableTextFormatter object, including the client's time zone and locale, is sent to the formatting bean.

  4. The formatting bean uses the name of the resource bundle, the message key, the time zone, and the locale to assemble the language-specific message.

  5. The enterprise bean returns the formatted message to the client.

  6. The formatted message is inserted into the LocalizableTextFormatter object and returned by the format method.
A call to a LocalizableTextFormatter.format method requires at most one remote invocation, to contact the formatting enterprise bean. However, the LocalizableTextFormatter object can optionally cache formatted messages, eliminating the formatting call for subsequent uses. It also allows the application to set a fallback string; this means the application can still return a readable string even if it cannot access a message catalog to retrieve the language-specific string. Additionally, the resource bundles can be stored locally. The localizable-text package provides a static variable that indicates whether the bundles are stored locally (LocalizableConfiguration.LOCAL) or remotely (LocalizableConfiguration.REMOTE), but the setting of this variable applies to all applications running within a Java Virtual Machine (JVM).

The LocalizableTextFormatter class

The LocalizableTextFormatter class, found in the package com.ibm.websphere.i18n.localizabletext, is the primary programming interface for using the localizable-text package. Objects of this class contain the information needed to create language-specific strings from keys and resource bundles.

Location of message catalogs and the ApplicationName value

Applications written with the WebSphere localizable-text package can store message catalogs locally or remotely. In a distributed environment, the use of remote, centrally stored catalogs is appropriate. All applications can use the same catalogs, and administration and maintenance of the catalogs are simplified; each component does not need to store and maintain copies of the message catalogs. Local formatting is useful in test situations and appropriate under some circumstances. In order to support both local and remote formatting, a LocalizableTextFormatter object must indicate the name of the formatting application. For example, when an application formats a message by using remote, centrally stored catalogs, the message is actually formatted by a simple enterprise bean (see WebSphere support for more information). Although the localizable-text package contains the code to automate looking up the enterprise bean and issuing a call to it, the application needs to know the name of the formatting enterprise bean. Several methods in the LocalizableTextFormatter class use a value described as application name; this refers to the name of the formatting application, which is not necessarily the name of the application in which the value is set.

Caching messages

The LocalizableTextFomatter object can optionally cache formatted messages so that they do not have to be reformatted when needed again. By default, caching is not used, but the LocalizableTextFormatter.setCacheSetting method can be used to enable caching. When caching is enabled and the LocalizableTextFormatter.format method is called, the method determines whether the message has already been formatted. If so, the cached message is returned. If the message is not found in the cache, the message is formatted and returned to the caller, and a copy of the message is cached for future use.

If caching is disabled after messages have been cached, those messages remain in the cache until the cache is cleared by a call to the LocalizableTextFormatter.clearCache method. The cache can be cleared at any time. The cache within a LocalizableTextFormatter object is automatically cleared when any of the following methods are called on the object:

Fallback information

Under some circumstances, it can be impossible to format a message. The localizable-text package implements a fallback strategy, making it possible to get some information even if a message cannot be correctly formatted into the desired language. The LocalizableTextFomatter object can optionally store a fallback value for a message string, the time zone, and the locale. These can be ignored unless the LocalizableTextFormatter object throws an exception.

Application-specific variables

The localizable-text package provides native support for localization based on time zone and locale, but application developers can construct messages on the basis of other values as well. The localizable-text package provides an illustrative class, LocalizableTextDateTimeArgument, which reports the date and time. The date and time information is localized by using the locale and time-zone values, but the class also uses additional variables to determine how the output is presented. The date and time information can be requested in a variety of styles, from the fully detailed to the terse. In this example, the construction of message strings is driven by three variables: the locale, the time zone, and the style. Applications can use any number of variables in addition to locale and time zone for constructing messages. See Using optional arguments for more information.

Writing a localizable application

To develop a WebSphere application that uses localizable text, application developers must do the following:

Creating a LocalizableTextFormatter object

Server programs typically create LocalizableTextFormatter objects, which are sent to clients as the result of some operation; clients format the objects at the appropriate time. Less typically, clients can create LocalizableTextFormatter objects locally. To create a LocalizableTextFormatter object, applications use one of the constructors in the LocalizableTextFormatter class:

The LocalizableTextFormatter object must have values set for the name of the resource bundle, the key, the name of the formatting application, and for any optional values so the object can be formatted. The LocalizableTextFormatter object can be created and the values set in one step by using the constructor that takes the necessary arguments, or the object can be created and the values set in separate steps. Values are set by using methods on the LocalizableTextFormatter object; for setting the values manually, rather than by using a constructor, use these methods:

Note:
When values in the array of optional arguments are set within a LocalizableTextFormatter object, they are copied into the object, not referenced. If an array variable holding a value is changed after the value has been copied into the LocalizableTextFormatter object, the value in the LocalizableTextFormatter object will not reflect the change unless it is also reset.

A LocalizableTextFormatter object also has methods that can be used to set values that cannot be set when the object is created, for example:

Each of these set methods also has a corresponding get method for retrieving the value. The clearLocalizableTextFormatter method unsets all values, returning the LocalizableTextFormatter object to a blank state. After clearing the object, reuse the object by setting new values and calling the format method again.

Figure 94 creates a LocalizableTextFormatter object by using the default constructor and uses methods on the new object to set values for the key, name of the resource bundle, name of the formatting application, and fallback string on the object.

Figure 94. Code example: Creating a LocalizableTextFormatter object and setting values on it

import com.ibm.websphere.i18n.localizabletext.LocalizableException;
import com.ibm.websphere.i18n.localizabletext.LocalizableTextFormatter;
import java.util.Locale;
public void drawAccountNumberGUI(String accountType) {
  ...
  LocalizableTextFormatter ltf = new LocalizableTextFormatter();
  ltf.setPatternKey("accountNumber");
  ltf.setResourceBundleName("BankingSample.BankingResources");
  ltf.setApplicationName("BankingSample");
  ltf.setFallBackString("Enter account number: ");
  ...
}

Setting localization values

The application requesting a localized message can specify the locale and time zone for which the message is to be formatted, or the application can use the default values set for the JVM. For example, a graphical user interface can allow users to select the language in which to display the menus. A default value must be set, either in the environment or programmatically, so the menus can be generated when the application first starts, but users can then change the menu language to suit their needs. Figure 95 illustrates how to change the locale used by the application based on the selection of a menu item.

Figure 95. Code example: Setting the locale programmatically

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
...
import java.util.Locale;
public void actionPerformed(ActionEvent event) {
  String action = event.getActionCommand();
  ...
   if (action.equals("en_us")) { 
      applicationLocale = new Locale("en", "US");
     ...
  }
  else if (action.equals("de_de")) {
     applicationLocale = new Locale("de", "DE");
     ...
  }
  else if (action.equals("fr_fr")) {
     applicationLocale = new Locale("fr", "FR");
     ...
  }
  ...
}

When an application calls a format method, it can specify no arguments, which causes the message to be formatted using the JVM's default values for locale and time zone, or a combination of locale and time zone can be specified to override the JVM's defaults. (See Generating the localized text for more information on the arguments to the format methods.)

Generating the localized text

After the LocalizableTextFormatter object has been created and the appropriate values set, the object can be formatted to generate the string appropriate to the locale and time zone. The format methods in the LocalizableTextFormatter class perform the work necessary to generate a string from a set of message keys and resource bundles, based on locale and time zone. The LocalizableTextFormatter class provides four format methods. Each format method returns the formatted message string. The methods take a combination of java.util.Locale and java.util.TimeZone objects and throw LocalizableException objects:

The format method with no arguments uses the locale and time-zone values set as defaults for the JVM. The other format methods can be used to override either or both of these values.

Figure 96 shows the creation of a localized string for the LocalizableTextFormatter object created in Figure 94; formatting is based on the locale set in Figure 95. If the formatting fails, the application retrieves and uses the fallback string instead of the localized string.

Figure 96. Code example: Formatting a LocalizableTextFormatter object

import com.ibm.websphere.i18n.localizabletext.LocalizableException;
import com.ibm.websphere.i18n.localizabletext.LocalizableTextFormatter;
import java.util.Locale;
public void drawAccountNumberGUI(String accountType) {
  ...
  LocalizableTextFormatter ltf = new LocalizableTextFormatter();
  ltf.setPatternKey("accountNumber");
  ltf.setResourceBundleName("BankingSample.BankingResources");
  ltf.setApplicationName("BankingSample");
  ltf.setFallBackString("Enter account number: ");
  try {
     msg = new Label (ltf.format(this.applicationLocale) , Label.CENTER);
  }
  catch (LocalizableException le) {
     msg = new Label(ltf.getFallBackString(), Label.CENTER);
  }
  ...
}

Using optional arguments
The localizable-text package allows users to specify an array of optional arguments in a LocalizableTextFormatter object. These optional arguments can greatly enhance the kinds of localization done in WebSphere applications. This section describes two ways in which applications can use the optional arguments:

Assembling complex strings
All of the keys discussed so far have represented flat strings; during localization, a string in the appropriate language is substituted for the key. The localizable-text package also supports substitution into the strings, which can include variables as placeholders. For example, an application that needs to report that an operation on a specified account was successful must provide a string like "The operation on account number was successful"; the variable number is to be replaced by the actual account number. Without support for creating strings with variable pieces, each possible string would need its own key, or the strings would have to be built phrase by phrase.

Both of these approaches quickly become intractable if a variable can take many values or if a string has several variable components. Instead, the localizable text package supports substitution of variables in strings with optional arguments. A string in a message catalog uses integers in braces--for example, {0} or {1}--to represent variable components. Figure 97 shows an example from an English message catalog for a string with a single variable substitution. (The same key in message catalogs for other languages has a translation of this string with the variable in the appropriate location for the language.)

Figure 97. A message-catalog entry with a variable substring

successfulTransaction = The operation on account {0} was successful.

The values that are substituted into the string come from an array of optional arguments. One of the constructors for LocalizableTextFormatter objects takes an array of objects as an argument, and such an array of objects can be set within any LocalizableTextFormatter object. The array is used to hold values for variable parts of a string. When a format method is called on the object, the array is passed to the format method, which takes an element of the array and substitutes it into a placeholder with the matching index in the string. The value at index 0 in the array replaces the {0} variable in the string, the value at index 1 replaces {1}, and so forth.

Figure 98 shows the creation of a single-element argument array and the creation and use of a LocalizableTextFormatter. The element in the argument array is the account number entered by the user. The LocalizableTextFormatter is created by using a constructor that takes the array of optional arguments; this can also be set directly by using the setArguments method on the LocalizableTextFormatter object. Later in the code, the application calls the format method. The format method automatically substitutes values from the array of arguments into the string returned from the appropriate message catalog.

Figure 98. Code example: Formatting a message with a variable substring

public void updateAccount(String transactionType) {
  ...
  Object[] arg = { new String(this.accountNumber)};
  ...
  LocalizableTextFormatter successLTF = 
     new LocalizableTextFormatter("BankingResources",
                                  "successfulTransaction",
                                  "BankingSample",
                                  arg); 
  ...
  successLTF.format(this.applicationLocale); 
  ...
}

Nesting LocalizableTextFormatter objects
The ability to substitute variables into the strings in message catalogs adds a level of flexibility to the localizable-text package, but the additional flexibility is limited, at least in an international environment, unless the substituted arguments themselves can be localized. For example, if an application needs to report that an operation on a specific account was successful, a string like "The operation on account number was successful"--where the only variable is an account number--can be translated and used in message catalogs for multiple languages. A string in which a variable is also a string, for example, "The type operation on account number was successful"--where the new type variable takes values like "deposit" and "withdrawal"--cannot be as easily translated. The values assumed by the type variable also need to be localized.

Figure 99 shows a message string in an English catalog with two variables, one of which will be localized, and the keys for two possible values. (The second variable in the string, the account number, is simply a number that must be substituted into the string; it does not need to be localized.)

Figure 99. A message-catalog entry with two variable substrings

sucessfulTransaction = The {0} operation on account {1} was successful.
depositOpString = deposit
withdrawOpString = withdrawal

To support localization of substrings, the localizable-text package allows the nesting of LocalizableTextFormatter objects. This is done simply by inserting a LocalizableTextFormatter object into the array of arguments for another LocalizableTextFormatter. When the format method does the variable substitution, it formats any LocalizableTextFormatter objects as it substitutes array elements for variables. This allows substrings to be formatted independently of the string in which they are embedded.

Figure 100 modifies the example in Figure 98 to format a message with a localizable substring. First, a LocalizableTextFormatter object for the localizable substring (referring to a deposit operation) is created. This object is inserted, along with the account-number information, into the array of arguments. The array of arguments is then used in constructing the LocalizableTextFormatter object for the complete string; when the format method is called, the embedded LocalizableTextFormatter object is formatted to replace the first variable, and the account number is substituted for the second variable.

Figure 100. Code example: Formatting a message with a localizable variable substring

public void updateAccount(String transactionType) {
   ...
   // Successful Deposit.
   LocalizableTextFormatter opLTF =
      new LocalizableTextFormatter("BankingResources,
                                   "depositOpString", "BankingSample");
   Object[] args = {opLTF, new String(this.accountNumber)};
   LocalizableTextFormatter successLTF = 
      new LocalizableTextFormatter("BankingResources",
                                   "successfulTransaction",
                                   "BankingSample",
                                   args);
   ...
   successLTF.format(this.applicationLocale);
   ...
}

Customizing the behavior of a format method
The array of optional arguments can contain simple values, like an account number to be substituted into a formatted string, and other LocalizableTextFormatter objects, representing localizable substrings to be substituted into a larger formatted string. These techniques are described in Assembling complex strings. In addition, the optional-argument array can contain objects of user-defined classes.

User-defined classes used as optional arguments provide application-specific format methods, which programmers can use to perform localization on the basis of any number of values, not just locale and time zone. These user-defined classes need to be available only on the systems where they are constructed and inserted into LocalizableTextFormatter objects and where the actual formatting is done; client applications do not need to install these classes.

The localizable-text package provides an example of such a user-defined class in the LocalizableTextDateTimeArgument class. This class allows date and time information to be selectively formatted according to the style values defined in the java.text.DateFormat class and according to the constants defined by the LocalizableTextDateTimeArgument class.

The DateFormat styles determine how information is reported about a date. For example, when the DateFormat.FULL style is chosen, the twenty-second day of February in 2000 is represented in English as Tuesday, February 22, 2000. When the DateFormat.SHORT style is used, the same date is represented as 2/22/00. The valid values are:

The LocalizableTextDateTimeArgument class defines constants that can be used to request only date or time information, or both, either in date-time order or in time-date order. The defined values are:

An object of a user-defined class like the LocalizableTextDateTimeArgument class can be set in the optional-argument array of a LocalizableTextFormatter object, and when the LocalizableTextFormatter object attempts to format the user-defined object, it calls the format method on that object. That format method, written by the application developer, can do whatever is appropriate with the application-specific values. In the case of the LocalizableTextDateTimeArgument class, the format method determines if date, time, or both are required, formats them according to the DateFormat value, and assembles them in the order requested in the LocalizableTextDateTimeArgument style. The date and time information are also affected by the locale and time-zone values, but the refinements in the formatting are accomplished by the DateFormat class and the user-defined values.

The string assembled from a user-defined class like the LocalizableTextDateTimeArgument class can then be substituted into a larger string, just as the return values of nested LocalizableTextFormatter objects can be. When writing such user-defined classes, it is helpful to think of them as specialized versions of the generic LocalizableTextFormatter class, and the way in which the LocalizableTextFormatter class is written provides a model for writing user-defined classes.

Structure of the LocalizableTextFormatter class
The LocalizableTextFormatter class is a general-purpose class for localizable text. It extends the java.lang.Object class and implements the java.io.Serializable interface and four localizable-text interfaces: Each of the localizable-text interfaces implemented by the LocalizableTextFormatter class implements the Localizable interface (which simply extends the Serializable interface) and defines a single format method: Because the LocalizableTextFormatter class implements all four of these interfaces, it must provide an implementation for each of these format methods.

Writing a user-defined class
A user-defined class must implement at least one of the localizable-text interfaces and its corresponding format method, as well as the Serializable interface. If the class implements more than one of the localizable-text interfaces and format methods, the order of evaluation of the interfaces is:

  1. LocalizableTextLTZ

  2. LocalizableTextL

  3. LocalizableTextTZ

  4. LocalizableText

For example, the LocalizableTextDateTimeArgument class implements only the LocalizableTextLTZ interface, as shown in Figure 101.

Figure 101. Code example: The structure of the LocalizableTextDateTimeArgument class

package com.ibm.websphere.i18n.localizabletext;
import java.util.Locale;
import java.util.Date;
import java.text.DateFormat;
import java.util.TimeZone;
import java.io.Serializable;
public class LocalizableTextDateTimeArgument implements LocalizableTextLTZ,
                                                        Serializable
{
   ...
}

A user-defined class must contain a constructor and an implementation of the format methods as defined in the localizable-text interfaces that the class implements. It can also contain other methods as needed. The LocalizableTextDateTimeArgument class contains a constructor, a single format method, an equality method, a hash-code generator, and a string-conversion method.

Figure 102. Code example: The methods in the LocalizableTextDateTimeArgument class

...
public class LocalizableTextDateTimeArgument implements LocalizableTextLTZ,
                                                        Serializable
{
   public final static int DATE = 1;
   public final static int TIME = 2;
   public final static int DATEANDTIME = 3;
   public final static int TIMEANDDATE = 4;
   private Date date = null;
   private dateTimeStyle = LocalizableTextDateTimeArgument.DATE;
   private int dateFormatStyle = DateFormat.FULL;
   ...
   public LocalizableTextDateTimeArgument(Date date, int dateTimeStyle,
                                          int dateFormatStyle)
   { ... }
   public boolean equals(Object param)
   { ... }
   public format (Locale locale, TimeZone timeZone)
      throws IllegalArgumentException
   { ... }
 
   public int hashCode()
   { ... }
 
   public String toString()
   { ... }
}

Each format method in the user-defined class can do whatever is appropriate for the application. In the LocalizableTextDateTimeArgument class, the format method (see Figure 103 for the implementation) examines the setting of the date-time style set within the object, for example, DATEANDTIME. It then assembles the requested information in the requested order, according to the date-format value.

Figure 103. Code example: The format method in the LocalizableTextDateTimeArgument class

public format (Locale locale, TimeZone timeZone)
   throws IllegalArgumentException
{
   String returnString = null;
   
   switch(dateTimeStyle) {
      case LocalizableTextDateTimeArgument.DATE :
      {
         returnString = DateFormat.getDateInstance(dateFormatStyle,
                                                   locale).format(date);
         break;
      }
      case LocalizableTextDateTimeArgument.TIME :
      {
         df = DateFormat.getTimeInstance(dateFormatStyle, locale);
         df.setTimeZone(timeZone);
         returnString = df.format(date);
         break;
      }
      case LocalizableTextDateTimeArgument.DATEANDTIME :
      {
         dateString = DateFormat.getDateInstance(dateFormatStyle,
                                                 locale).format(date);
         df = DateFormat.getTimeInstance(dateFormatStyle, locale);
         df.setTimeZone(timeZone);
         timeString = df.format(date);
         returnString = dateString + " " + timeString;
         break;
      }
      case LocalizableTextDateTimeArgument.TIMEANDDATE :
      {
         dateString = DateFormat.getDateInstance(dateFormatStyle,
                                                 locale).format(date);
         df = DateFormat.getTimeInstance(dateFormatStyle, locale);
         df.setTimeZone(timeZone);
         returnString = timeString + " " + dateString;
         break;
      }
      default :
      {
         throw new IllegalArgumentException();
      }
   }
   return returnString;
}

An application can create a LocalizableTextDateTimeArgument object (or an object of any other user-defined class) and place it in the optional-argument array of a LocalizableTextFormatter object. When the LocalizableTextFormatter object reaches the user-defined object, it will attempt to format it by calling the object's format method. The returned string is then substituted for a variable as the LocalizableTextFormatter processes each element in the array of optional arguments.

Deploying the formatter enterprise bean
The LocalizableTextEJBDeploy tool is used by the application deployer to create a deployed LocalizableText JAR file for the LocalizableText service. You must deploy the enterprise bean for each server per application where the service is to be run. There may be servers for which the LocalizableText service does not need to be installed. The same deployed JAR file can be included in several application Enterprise Archive (EAR) files, but additional steps are required when the EAR file is deployed. The application deployer must also make sure that the application resource bundles are added to the application EAR file as files. The server's CLASSPATH variable must be adjusted to include the deployed location of the EAR file. This is so that the resource bundles can be located on the host and server.

Setting up the tool
Before the LocalizableTextEJBDeploy tool can be used, the following conditions must be met:

Using the LocalizableTextEJBDeploy Tool
After the prerequisites for the tool have been met, the tool can be used to deploy formatting session beans. The tool requires values for five arguments:
LocalizableTextEJBDeploy -a <appName>
          -h <hostName>
          -i <installationDir>
          -s <serverName>
          -w <workingDir>							

The required arguments, which can be specified in any order, follow:

After the tool is run, a deployed JAR file is located in the working directory specified to the tool. This JAR file can be included in the application EAR or WAR file.

Special considerations when deploying a LocalizableText enterprise bean

When the application is being deployed onto a host and server, during the deployment process you will be asked if you want to regenerate the deployment code for the LocalizableText enterprise bean. Do not redeploy the bean. If the bean is redeployed, the JNDI name will be wrong.

If more than one LocalizableText enterprise bean is deployed with an application, there are two ways to handle the situation.