C H A P T E R  3

Programming Guide

This Suntrademark ONE Application Framework Programming Guide chapter describes common programming scenarios and explains how to use certain fundamental objects in the Sun ONE Application Framework.

In general, the information here is supplemental to the Sun ONE Application Framework JavaDoc. Refer to the JavaDoc for detailed API usage information.


Using the RequestContext

The RequestContext is the primary object that provides Sun-ONE-Application-Framework-related services during a request. It can be obtained from virtually anywhere and at anytime within a Sun ONE Application Framework request, and used to access both J2EE and Sun ONE Application Framework features.

Getting the RequestContext

There are two main ways of getting the current RequestContext instance. First, if the current object such as a ViewBean or Model in which you are writing code implements the com.iplanet.jato.RequestParticipant interface, it likely already has an instance of the RequestContext before control is passed to your code. Both the ViewBeanManager and ModelManager will automatically set the RequestContext on ViewBeans and Models that implement the RequestParticipant interface before returning these objects to the caller. BasicViewBeans, BasicContainerViews, BasicTiledViews, and BasicTreeViews in particular all have a method called getRequestContext() that can be used at virtually any point inside these objects to obtain the RequestContext. One notable exception is that this method cannot be used during the construction of one of these objects, since the RequestContext cannot be set on these objects until they are fully constructed.

The second main technique for obtaining the RequestContext (and some of its primary sub-objects) is to use the static methods in the com.iplanet.jato.RequestManager class.

The following table lists these methods and their descriptions.

Method

Description

getRequestContext()

Returns the current request's RequestContext object. This method can only be used within the scope of a request.

getRequest()

Returns the current request's javax.servlet.http.HttpServletRequest object. This method can only be used within the scope of a request.

getResponse()

Returns the current request's javax.servlet.http.HttpServletResponse object. This method can only be used within the scope of a request.

getSession()

Returns the current request's javax.servlet.http.HttpSession object. This method can only be used within the scope of a request.


These static methods can be used to obtain the Sun ONE Application Framework RequestContext from inside any object or method in the context of a request, and is most useful to avoid having to add a RequestContext parameter to your object's method calls. These methods can be used without any performance impact on your application; specifically, they do not cause any thread synchronization.

None of these methods can be used outside the scope of a request. For example, you cannot use them to obtain a session object inside an object's static initializer, or inside a servlet's init() method.

The servlet event methods specify the RequestContext as a parameter, and many of the event objects used when firing other application events have a RequestContext member. You can use the instance provided in this fashion instead of using one of the other techniques described above.

Getting the Servlet Request and Response Objects

Because a Sun ONE Application Framework application is ultimately a servlet-based application, it has access to the current request's javax.servet.http.HttpServletRequest and javax.servet.http.HttpServletResponse objects. You can obtain the request and response objects through the RequestContext's getRequest() and getResponse() methods. You can also conveniently obtain these objects via the RequestManager methods described above.

With only a few cautions (on the response object primarily, explained elsewhere), you can use the request and response objects just as you would within any servlet- or JSP-based J2EE application.

Getting the Session Object

You can obtain the javax.servlet.http.HttpSession object if you have either the current request's HttpServletRequest object (see above), or directly via the RequestManager method getSession() described above.

Other Available Objects

In addition to the objects mentioned above and a few other conveniences, the RequestContext also provides access to three key Sun ONE Application Framework-specific manager objects.

The following table shows these three key objects.

Method

Description

com.iplanet.jato.ViewBeanManager

Obtained via a call to getViewBeanManager().

The ViewBeanManager helps manage ViewBean instances within the current request. All ViewBeans should be obtained via this object.

com.iplanet.jato.ModelManager

Obtained via a call to getModelManager().

The ModelManager helps manage Model instances within the current request. All Models should be obtained via this object (or through a ModelReference).

com.iplanet.jato.SQLConnectionManager

Obtained via a call to getSQLConnectionManager().

For applications that use JDBC to communicate to RDBMS backends, the SQLConnectionManager provides a thin utility layer on top of the J2EE container's database connection support. Use of the SQLConnectionManager is not mandatory, but is recommended.


RequestCompletionListener Interface

Objects that are interested in being notified of the completion of the current request can register themselves with the RequestContext as RequestCompletionListeners. Registered listeners will receive notification at the end of the current request processing, regardless of the outcome of the current request (for example, regardless of whether the request completed normally or ended in an error).

Objects can use this mechanism to perform last minute tasks, such as saving state in the session, closing open request-scoped resources, or virtually anything else, provided the task does not affect the output stream. Objects must register themselves as RequestCompletionListeners on each request if so interested.

Using the Message Writer

The RequestContext also provides a useful tool called the message writer. The message writer is a java.io.PrintWriter whose contents are accumulated during the course of a request and then appended to the rendered page right before it is sent to the client. This mechanism works much like the console does in a non-server based application, and is extremely useful for debugging purposes. Interested objects can write to the message writer via the getMessageWriter() method on the RequestContext. ViewBeans also have a convenience method called appMessage() that can be used to obtain and use the message writer in a single step.

The output of the message writer is necessarily HTML-specific, and so might be incompatible with pages rendered in a different markup language. Furthermore, the message writer is automatically appended by the ViewBean JSP tag at the end of a page, and so will not appear if a page is rendered using some other non-JSP mechanism. See the sidebar for information on how to turn off the message writer.

The appearance of messages written to the message writer can be turned on or off using a configuration parameter in the web.xml file. This allows you to turn on messages during development, but turn them off during deployment. For complete information, see Chapter 4, Deploy an Application.


Using ViewBeanManager

When two different parts of the application want to work with a ViewBean instance, they always want to use the same instance. Within a given request, ViewBeans are singletons, meaning that there is only one instance per type of ViewBean in any given request. To ensure singleton instances, ViewBean's are managed by the ViewBeanManager, which is an instance of com.iplanet.jato.ViewBeanManager.

The ViewBeanManager is available from the RequestContext by calling the getViewBeanManager() method. Each request receives a new ViewBeanManager instance. Because the ViewBeanManager is available via the RequestContext, and the RequestContext is widely available using one of several techniques (described previously), you can generally obtain a ViewBean instance from anywhere in your application code, not just inside Sun-ONE_Application-Framework-specific objects or methods.

After obtaining the ViewBeanManager, getting a ViewBean instance is as easy as calling the getViewBean() method with the class of the desired ViewBean. For example, this code returns the singleton instance of the Login ViewBean:

requestContext.getViewBeanManager().getViewBean(Login.class);

The ViewBeaManager also has a method that will return a ViewBean by class name, but this method does not provide the compile-time safety of the method shown above.

The getLocalViewBean() method deserves additional explanation, although it is not generally used by application developers. This method obtains a ViewBean instance that corresponds to the supplied logical name (this name is also referred to as the ViewBean's local name). A local view bean name is the unqualified name of the ViewBean class without the ViewBean suffix. For example, if a module has a ViewBean class com.foo.Page1, the local ViewBean name is simply Page1. Because the local ViewBean name is automatically qualified by the current module's base package name, local names can only be used to obtain references to ViewBeans within the module whose servlet initially handled the current request.


Using ModelManager

When two different parts of the application want to work with a Model instance, they frequently want to use the same instance so that they access the same data. To allow for sharing of Model instances within a request, the RequestContext contains a ModelManager object, which should be used to obtain nearly all Model instances (possible exceptions to this are noted in a following section).

The ModelManager provides a convenient lookup mechanism for Model instances based on their type and an optional instance name. All lookups within the same request will return the same Model instance specified by type and instance name. With the exception of Models that are specifically stored in the session (see section below), Models obtained via the ModelManager are instantiated and shared on each request. This is generally more efficient for the application when Model data does not need to be stored between requests and can be easily obtained again when/if needed, because storing objects in the session is an expensive operation.

The key method in ModelManager is getModel() and its variations. In general, it is recommended that you provide a Model class as the parameter to getModel() versus providing a Model class name--this allows for compile-time checking of the method call.

Although it is possible, it is fairly uncommon to provide an instance name when retrieving a Model from the ModelManager. If you use one of the getModel() method variations that does not specify an instance name, the ModelManager will use a default instance name based on the Model's class name, which is generally sufficient for most applications. However, there are specific cases when you want to use two different instances of the same Model type within the same request, and so this feature can be very useful.

Getting and Saving Models in the Session

Occasionally, you will find that you need to store Model data between requests in the HTTP session. The ModelManager provides an easy way to get and save Models in the session for you as you look them up.

In general, you should bias your application to not store Model data between requests because storing data in the session is typically expensive (in relative terms) and can limit your application's scalability if overused.

There are several getModel() variations that take one or two additional session-related boolean parameters. The first is the lookInSession parameter, which if true will cause the ModelManager to check first its local Model cache, and then the HTTP session, before instantiating a new Model instance. The second parameter is storeInSession, which if true, will cause the ModelManager to schedule the returned Model instance for setting in the session. The Model instance is not actually set in the session until the end of the current request, to ensure that all changes to the object in the current request are written to the session regardless of the J2EE container's session implementation.

If you have a Model instance and decide that you want to add or remove it from the session manually, you can call the ModelManager's addToSession() or removeFromSession() methods with the Model instance. Of course, you can also set a Model in the session yourself using the session API, but the addToSession() method helps ensure that the object added to the session is later accessible using the lookInSession parameter.

ModelTypeMap

Although this feature is seldom used in later versions of the Sun ONE Application Framework (and not in the IDE toolset at all), it deserves some mention. Keep in mind that this feature is completely optional in your application and you should only use it if you expect it to bring you some concrete advantage.

Early in the design cycle of the Sun ONE Application Framework, it was decided that it would be useful to be able to lookup a Model instance (implementation) by specifying just a Model interface. This ability would allow application developers to create implementation-neutral Model interfaces and work exclusively with those throughout the application, instead of relying on implementation details of the Model in their application code. This provided the utmost in loose coupling between the View and Model tiers, and allowed developers to plug in different Model implementations without affecting the View tier at all.

Therefore, the com.iplanet.jato.ModelTypeMap class was added to allow for mapping of interface type to implementation type within the ModelManager. When a developer calls the ModelManager's getModel() methods, the ModelTypeMap is used to transform the provided Model type to an implementation type. If no mapping has been specified, the original type is returned without changes. This originally provided a fallback mechanism, through which developers could specify implementation types and still obtain valid Model instances.

In reality, most developers found the ModelTypeMap feature cumbersome and limiting, and is therefore seldom used any more. However, it is still an assumed part of the architecture of the ModelManger, and the application servlet still provides an instance of an empty ModelTypeMap to the ModelManager when it is created. Except for those rare cases where you think the ModelTypeMap will provide some value to your application, you can essentially ignore it as just framework infrastructure.

If you do find that you want to use the ModelTypeMap, you should add a static initializer to your application servlet class and add one entry to the ModelTypeMap for each mapping you want. For example:

ModeTypeMapBase.addModelInterfaceMapping(     apppkg.modulepkg.CustomerModel.class,     apppkg.modulepkg.CustomerModelImpl.class);

Then, in your application code, you would call ModelManager.getModel(CustomerModel.class) to get the Model, but get back an instance of CustomerModelImpl.

Exceptions to using the ModelManager

Most Model types provided with the Sun ONE Application Framework require some initialization to be useful. The normal approach for configuring these Models is to create a subclass that configures the Model appropriately when it is instantiated. However, it might simply be easier to use some of the Model types as configured instances (especially since the IDE toolset does not currently support some Model types as extensible components.) These Models are com.iplanet.jato.model.BeanAdapterModel, com.iplanet.jato.model.SessionModel, com.iplanet.jato.model.MultipleModelAdapter, and com.iplanet.jato.model.ResourceBundleModel.

You might occasionally find it useful to instantiate some of these models in your code, but then add them to the session using the ModelManager.addToSession() method. This is a good fit for Models that require only one-time initialization but are long-lived, and will allow these Models to be obtained from the ModelManager seamlessly in subsequent requests so they can easily be bound to DisplayFields or other Views.


Using SQLConnectionManager

A large segment of Sun ONE Application Framework applications use an RDBMS to store at least some application data. J2EE provides the JDBC Standard Extension API to make obtaining a database connection in scalable way feasible from within a J2EE application. However, there are some additional concerns when actually developing a real-world application.

Therefore, the Sun ONE Application Framework's SQLConnectionManager adds one additional feature to help developers more easily work with database connections through the lifetime of an application in development. Specifically, it is common for an application in development to run in a container that does not readily support database connection pools. Furthermore, using a connection pool requires additional configuration of that pool in the container, separate from the deployment of the application. This can make it hard to simply deploy a utility or demo application.

SQLConnectionManager adds a thin abstraction to the task of obtaining a JDBC connection. It provides the ability to map datasource names to other values, and easily switch between JNDI and java.sql.DriverManager connection lookup techniques, for those cases where it is convenient to be able to do so.

When obtaining a connection from SQLConnectionManager, you provide a JDNI-like datasource name that follows the JDBC Standard Extension convention, such as jdbc/mydb. If the SQLConnectionManager is currently using a JNDI lookup to obtain JDBC connections, it will use this name as the key in the lookup. If instead the SQLConnectionManager is using the DriverManager to obtain JDBC connections, it will use this name to perform a local lookup in its datasource mapping table to obtain a JDBC URL that can be used to obtain a connection.

You set the current connection lookup mode using the setUsingJNDI() method of the SQLConnectionManager class. This method is normally called from the static initializer of the SQLConnectionManagerImpl class in your application package. If you provide a true value for this parameter, the SQLConnectionManager will lookup JDBC connections using the standard JNDI/JDBC connection lookup technique. This assumes that database connection pools have been already configured and registered in your J2EE container.

If you provide a false value in the call to setUsingJNDI(), the SQLConnectionManager will use its own local set of datasource mappings to obtain a JDBC URL that can be sent to the java.sql.DriverManager to obtain a connection. This technique is at least an order of magnitude less efficient that using a connection pool, but is generally acceptable in development, or in certain classes of applications that need to work standalone without additional container configuration.

When using DriverManager connection lookups, you can add a datasource mapping using the SQLConnectionManagerBase.addDataSourceMapping() method. This mapping will specify a logical datasource name that should be mapped to a physical JDBC URL. For example (using a PointBase URL):

SQLConnectionManagerBase.addDataSourceMapping("jdbc/sample",     "jdbc:PointBase://localhost:9092/sample");

The addDataSourceMapping() method is normally called from the static initializer of your application servlet class in your application package, so that the mapping is initialized when the application is loaded by the container. (Theoretically, you could add mappings at a later time, but there is little reason to do so since the mapping needs to be consistent for the life of the application.) When using JNDI connection lookups, there is no need to add datasource mappings, though you can if for some reason you want to map one datasource name to another.

Under no circumstances should you use the DriverManager to obtain JDBC connections in a production application! Such usage causes a new database connection to be opened for every use of the connection, and will cause enormous performance and scalability problems with your application. Make sure you use SQLConnectionManager's JNDI lookup mechanism along with a database connection pool in your container when you finally deploy your applications into production.

In your application, you can obtain a JDBC connection from SQLConnectionManager directory using its getConection() or obtainConnection() methods. The static obtainConnection() method is used to obtain connections outside of the scope of a request, such as during application initialization. You can also bypass SQLConnectionManager and go directly to a JNDI lookup or to DriverManager if you want, but the Sun ONE Application Framework classes that use JDBC, such as com.iplanet.jato.model.sql.QueryModelBase, will always use SQLConnectionManager to obtain database connections.


Using the RequestManager

The RequestManager provides a handful of static methods you can use to obtain key request-scoped objects.

The following table shows these methods.

Method

Description

getRequestContext()

Returns the current request's RequestContext object. This method can only be used within the scope of a request.

getRequest()

Returns the current request's javax.servlet.http.HttpServletRequest object. This method can only be used within the scope of a request.

getResponse()

Returns the current request's javax.servlet.http.HttpServletResponse object. This method can only be used within the scope of a request.

getSession()

Returns the current request's javax.servlet.http.HttpSession object. This method can only be used within the scope of a request.

getHandlingServlet()

Returns the javax.servlet.Servlet instance that initially handled the current request. For the foreseeable future, this servlet is expected to be a subclass of com.iplanet.jato.ApplicationServletBase. This method can only be used within the scope of a request.




Note - The name RequestManager is used for the class that is a placeholder for a number of features that are to be added in a future version of the Sun ONE Application Framework. Its current status as just a collection of static utility methods is temporary.




Logging

Logging support in the Sun ONE Application Framework is built on top of the standard ServletContext-based logging feature, which itself is a fairly minimal. Therefore, the Sun ONE Application Framework's logging feature is only intended to add minor functionality to this existing baseline mechanism, and is not intended to supply a full-featured logging solution such as that provided by Log4J or JDK 1.4's logging package. Instead, it is meant to be lightweight and convenient to use for developers who want to use their J2EE container's native logging features.

You can access the Sun ONE Application Framework's logging via the static methods in the com.iplanet.jato.Log class. In addition to these methods, this class provides the ability to filter messages based on log levels and to echo the log to the standard out.

Logging Messages

To log a message, simply call one of various static log() methods in the com.iplanet.jato.Log class, optionally providing a log level parameter. If a log level is provided, the Log class determines if that message should be logged based on the currently enabled log levels. If a log level is not provided, the currently enabled log levels are not considered, and the message is always logged.

The messages that are allowed to pass are sent to the container via the ServletContext's various log() methods, and generally appear in the container's log and/or console.

Log Levels

The primary value added to the baseline ServletContext logging mechanism is the ability to log messages using levels and to filter the current log output by these levels. Log levels fall into a few major categories.

The following table shows these log level categories.

Log Level Category

Description

Error levels

Levels that specify errors or warnings in the application. Includes the WARNING, ERROR, and CRITICAL levels. All levels in this category can be enabled via the ANY_ERROR level.

Debug levels

Levels that are used by the developer for informational or debugging purposes. Includes the STANDARD, TERSE_DEBUG (less information) and VERBOSE_DEBUG (more information) levels. All levels in this category can be enabled via the ANY_DEBUG level.

Trace levels

Levels that are used by the developer and/or the Sun ONE Application Framework to trace request execution. Includes the JATO_TRACE, JATO_QOS_TRACE and APP_TRACE levels. All levels in this category can be enabled via the ANY_TRACE level. The JATO_TRACE and JATO_QOS_TRACE levels are not for developer use and are only used to view trace information logged by the framework itself.

User levels

Levels that are defined and used by the developer in whatever fashion desired; the meaning of each of these levels is application- and developer-defined. Includes the USER_LEVEL_1, USER_LEVEL_2, and USER_LEVEL_3 levels. All levels in this category can be enabled via the ANY_USER_LEVEL level.


Except for the JATO_TRACE and JATO_QOS_TRACE log level, all log levels are primarily for developer use. In other words, the core runtime will generally not log at any of these log levels (with the exception of some of the error levels). However, Sun ONE Application Framework components written by other authors might log at these levels, so do not expect to have full control over them. The one exception to this rule is the several USER_LEVEL_* log levels, which by convention should be reserved for current application usage only. Distributable components should not use these levels once published.

Multiple log levels can be enabled at a time by logically ORing multiple levels together in a call to Log.setEnabledLevels(). For example, the following code enables several log levels at once:

Log.setEnabledLevels( STANDARD | ERROR | CRITICAL | JATO_TRACE );

You can check if a level is currently enabled by calling the isLevelEnabled() method with the level to check. In addition, there are several convenience log levels that automatically include several others, such as the various ANY_* log levels, and the DEFAULT_LOG_LEVELS level.

Finally, you can change log levels at any time from application code, a servlet init() method, a static initializer, or basically anywhere you have static access to the Log class at runtime. However, there is only one set of Log settings per deployed application, and the current settings are shared by all currently executing request threads. Therefore, it generally wouldn't make sense to change the log levels on a per-request basis.

There are no current plans to significantly improve the Sun ONE Application Framework's logging facility, as it is meant to satisfy minimal requirements using only baseline J2EE logging features. If you need a richer logging facility for your application, you are encouraged to use Log4J or JDK 1.4's built-in logging package.

Logging to Standard Out

Because it can be difficult to easily access the J2EE container's log file, the Log class has the ability to echo whatever is sent to the ServletContext log to the console's System.out stream. This feature is frequently helpful during development since the current container's console is readily visible on the developer's machine.

Making Log Messages Stand Out

Because the Sun ONE Application Framework logging outputs to the J2EE container's log, which is probably also filled with other log messages, the Log class provides the ability to set a message prefix via the setMessagePrefix() method. This prefix will be appended to every messages logged via the Log class. With proper choice of prefix, Sun ONE Application Framework log messages can be made to stand out from other messages in the same log. The default prefix is three dashes followed by a space, "--- ".


Working with Values

The following sections describe several ways for working with values in a Sun ONE Application Framework application.

Working with DisplayField Values

The first, and most common, approach for obtaining values is to get them from DisplayFields directly. For example, you can ask a TextField for its value by obtaining a reference to the TextField and then calling its getValue() method. This approach is comfortable for View developers, because they can interact directly with the View hierarchy they are building, and all the information needed to get the data is generally contained in a single file.

You can get a reference to a DisplayField or any other View (with the exception of ViewBeans) by calling its parent's getChild() method, providing the unqualified local name of the child View component. (This method is defined in the ContainerView interface, and so is present for all types of container Views, including ViewBeans.) Because getChild() returns an instance of View, and not just display fields, you must cast the result to a usable type before using it.

The following is an example of getting a BasicDisplayField child from within a ViewBean and getting its value:

View childView=getChild("textField1");
Object value=((BasicDisplayField)childView).getValue();

Because this technique can require lots of casting, there is a convenience method within the core ContainerView implementations (BasicContainerView, BasicTiledView, and so on):

DisplayField field=getDisplayField("textField1");
Object value=field.getValue();

Because the value returned is a DisplayField instance, you can call getValue() on it directly, even without casting it to the specific component type.

Finally, you can call another convenience method, getDisplayFieldValue(), to get a value in a single step:

Object value=getDisplayFieldValue("textField1");

There are also variants of this method that return specific types of objects, such as getDisplayFieldStringValue(), getDisplayFieldBooleanValue(), and getDisplayFieldIntValue().

The IDE toolset automatically generates child View component name constants in your ViewBean or ContainerView classes, using the pattern CHILD_<childName>. Therefore, it is not necessary to use string literals in the calls to getChild() or its variations as shown above. Also, the IDE generates child View component accessor methods such as get<childName>Child(), so that you can obtain a reference to a View component without casting.

Setting DisplayField values is similar to getting them. Once you have a DisplayField instance, you can call its setValue() or setValues() methods to change its value. The core ContainerView implementations also provide a convenience method setDisplayFieldValue() with several variants to allow developers to easily set values.

The following is an example of getting DisplayField values, processing them, and then setting the result on another DisplayField:

int value1=getDisplayFieldIntValue("intField1");
int value2=getDisplayFieldIntValue("intField2");
int result=value1+value2;
setDisplayFieldValue("message",value1+"+"+value2+"="+result);

Some ContainerView variations, such as TiledViews, might require the use of additional methods to properly obtain and set data. These details are covered elsewhere in this guide.

Working with Model Values

The second technique for getting application values is to go to a Model directly. Normally, DisplayFields are bound to Models, which provide storage for their values. This binding occurs via a ModelReference object, and a field name. Generally, a DisplayField is bound to a Model field.

Thus, it follows that if you have a reference to a Model and know which field to get, you can ask the Model for data directly using the Mode.getValue() or Model.getValues() methods. The easiest way to get a reference to a Model within an IDE-created View component is to use a ModelReference object. ModelReference objects are created automatically as developers define bindings between DisplayFields and Models, and they are available to methods in the class to use also (this allows everyone to share the same Model reference).

Once you have a ModelReference instance, simply call its getModel() method to obtain a Model reference. You can then get values from the Model:

Model model=modelReference1.getModel();
Object value1=model.getValue("field1");
Object value2=model.getValue("field2");

The IDE toolset automatically generates field name constants in Model classes created within the IDE. You can use these constants instead of string literals to more easily refer to fields within the model.

You can also call the Model.setValue() and Model.setValues() methods to set values on a Model. You simply provide the field name along with the new value in the call.

Some Model specializations, such as DatasetModel, might require the use of additional methods to properly obtain and set data. These details are covered elsewhere in this guide.

Getting Values Using the J2EE API

Finally, developers can obtain application data using the low-level J2EE/Servlet API. The Servlet API defines the javax.servlet.http.HttpServletRequest class, with methods such as getParameter() and getParameterValues() that can be used to get values that were directly submitted in the request. You simply need to know the names of the appropriate parameters to get their values.

Sun ONE Application Framework View components use a naming scheme to automatically generate qualified names for DisplayField components. This qualified name uses the name of the DisplayField prefixed in order of upward traversal to the root by the names of its parents. These names are qualified by the dot (".") character by default (see the deployment section for information on using a different value).

For example, if I have a ViewBean named Foo which contains a ContainerView named bar, which further contains a text DisplayField named bat, the qualified name of the field would be Foo.bar.bat. If this field existed within an HTML form, the developer could get the value of this field by looking for the parameter named Foo.bar.bat in the request.

Some ContainerView variants like TiledView add additional information to the qualified name, to allow decoding of multiple values from the request. TiledView adds a subscript to the field name to distinguish the row upon which it appears. Depending on the number of tiles submitted in the request, there might be several similar parameter names distinguished by subscripts. If in the above example, if bar were a TiledView, you might see the following parameters in the request:

Foo.bar[0].bat
Foo.bar[1].bat
Foo.bar[2].bat

See the documentation for each component to see how it generates child names that are contained within it.

As you might suspect, the qualified names generated for components are what allows the framework to automatically map parameters in the request back to display fields during the submit cycle, and keep components from different authors distinct from one another. If field names instead used a flat namespace, it would not be possible to create ready-to-use components that could assembled to create an application.

The core DisplayField implementations (BasicDisplayField, etc.) have methods called getRequestValue() and getRequestValues() that can be used to obtain the values that correspond to those fields from the request parameters. This is generally a far more convenient way to obtain the value submitted for a field than going to the request parameters directly using the field name. These methods can be used to refer to the values originally submitted in the request, even if the current field's value has changed.

You cannot set values in the request, as they are read-only.


Using Display Events

Many times when rendering a page, you will find that you want to modify the way a View renders, or even skip displaying it at all. In other frameworks that are JSP-centric, this is difficult or impossible to do without using complex control or other logic in your JSP, where it is inherently hard to debug and maintain. Even solutions like the JSP Standard Tag Library can result in complex code-like structures in your JSP, essentially trading programming in Java for programming in JSP tags.

By contrast, the Sun ONE Application Framework provides what are called display events to allow developers to perform complex display-oriented logic, but outside of the JSP and instead in their View classes, where Java code makes sense. There are two main display-related events, one triggered for the beginning of display of a component, and one triggered for the end of display of a component. These events differ based on whether the component is a ContainerView itself, or a child View.

Container Display Events

When a ContainerView is displayed during rendering of a JSP, its beginDisplay() and endDisplay() methods are automatically called when the corresponding JSP tags begin and end its display, respectively. While developers can override these methods in subclasses, many superclasses have implemented these methods to perform useful and necessary tasks upon display notification. For this reason, developers must always call the super version of the method when they override these methods, thereby making them harder to use during application assembly.

Therefore, the event methods reserved for application developer use (at least in components derived from com.iplanet.jato.view.ContainerViewBase) are the beginComponentDisplay() and endComponentDisplay() methods. These methods will be triggered as appropriate by the superclass, and are more or less guaranteed not to have any implementation in the superclass (or more precisely, no implementation that can't be completely ignored by the application developer). Application developers can find these methods listed as events in the Events context menu on ContainerViews added as children to other ContainerViews.

Component developers should directly override the beginDisplay() and endDisplay() methods to respond to these events, thereby leaving the component versions for application assemblers.

Child Display Events

The container-related display events described in the section above allow the ContainerView component itself to respond to display notifications, but frequently, containers need to respond to rendering of their children. Thus, the ContainerView defines the beginChildDisplay() and endChildDisplay() methods to be used during rendering to tell the container that one of its children is about to be rendered. The beginChildDisplay() method returns a boolean result, where true indicates that the child should be rendered, and false indicates that the child should be skipped.

Component developers can override these methods directly, but application developers have an easier mechanism for responding to fine-grained child display. The various extensible pagelet components included in the Sun ONE Application Framework component library look for methods of a certain signature when responding to the begin or end child display notification. If these events exist, they are called each time the corresponding child is rendered. The signature of these methods looks like the following:

public boolean begin<child name>Display(ChildDisplayEvent event)
    throws ModelControlException;
public String end<child name>Display(ChildContentDisplayEvent event)
    throws ModelControlException;

where <child name> is the capitalized name of the child view. For example, if a page has a child called foo, the application developer could respond to its rendering by simply implementing the beginFooDisplay() method with the signature show above.

The boolean return value in the case of the begin<child>Display() method allows the developer to optionally skip rendering of the child. The String return value in the case of the end<child>Display() method is the actual markup that will be rendered for that child. The event object contains the markup calculated for the child during JSP rendering, but developers can tweak or completely override the markup in the end display event.

Use of the child display events is a powerful technique for creating dynamic pages, and ensures that JSPs are kept as code free as possible. Common uses of display events are: to skip display of a child based on user role or other per-user information; to calculate a child's value just before it is rendered (each time it is rendered); to execute a model to determine the number of rows it returned; to add additional markup to a rendered HTML control; to dynamically change the color of a table row; to periodically insert header information during TiledView rendering; and many other users. Developers will find display events to be an indispensable tool when writing enterprise applications, and because display-related code is kept in one place in the ViewBean or ContainerView, every JSP that uses that component can use the same logic.

Content Tag

In many cases, it is useful to be able to associate Sun ONE Application Framework display events with arbitrary sections of a JSP, whether they include dynamic content, static content, or a mixture of both types. The <jato:content> tag provides exactly this feature. The name attribute of this tag is used to determine the display event callback to the enclosing ContainerView.

When using the content tag, you implement display events in exactly the same fashion as you would for any child View. The difference is that there is no child View, and the content that will be rendered will come from whatever the content tag encloses (or whatever the endDisplay event for that tag returns).

A common use for content tags is to conditionalize rendering of a portion of a page, or to inject arbitrary markup into a page at a specific point. For example, content tags are one easy way to change the color of each tile of a TIledView as it is rendered.


Using ViewBeans

ViewBeans are just special types of ContainerViews and inherit most of their behavior from the superclass. The section Using ContainerViews, contains most of what developers need to know about using ViewBeans. The following sections detail the additional ViewBean specifics.

forwardTo() Method

ViewBeans, as pages, are the primary artifact developers create in their applications. As such, they have key methods for controlling the request. Although developers are free to directly use servlet features such as RequestDispatcher to render a response to a client, in practice it is much easier to use the forwardTo() method on a ViewBean to begin rendering of a page. The primary reason this approach is easier is because each ViewBean knows which JSP it is associated with, and so can handle the details of forward the servlet request for you. Furthermore, it keeps application developers thinking about pages instead of lower-level J2EE primitives.

The forwardTo() method offers an opportunity for a ViewBean to perform some logic before beginning the display phase. The most notable technique is for the ViewBean to dynamically select the JSP it wants to render before actually forwarding the request. For example, a ViewBean can determine that the current request comes from a WAP device, and so it instead will render a WML JSP as the response instead of an HTML JSP. This ability to render the same ViewBean using multiple associated JSPs is called parallel content, and is covered in Chapter 2, Develop an Application, under Display URLs and Parallel Content.

Page Session

Often, developers need to retain some information between requests, but putting this information in the server-side HTTP session can get the application out of sync if the user uses the browser's Back button. Instead, ViewBeans have what is called a page session to help solve this problem. Page session works much like the HTTP session, except page session attributes are stored in the rendered response to the client and resubmitted on the next request. If the user uses the Back button, the page session is automatically kept in sync because each version of the page session for the specifically rendered page is cached in each page on the client.

Page session is most often used to track a user's context in an application, much as an HTML hidden field might be used by traditional Web applications. However, the advantage of page session over hidden fields is that it is automatically appended to all links and forms, and might contain complex objects, not just strings. Furthermore, because browsers have a limitation on the length of URLs sent to them during an HTTP GET request, the page session mechanism can compress the page session if it reaches a certain size (the compression is normally around 40-50% effective).

A given set of page session attributes is specific to a single ViewBean. Page session attributes are not shared between ViewBeans. That application will need to copy page session attributes from the handling page to the displaying next if the attributes should be preserved for the next request.

The page session API is covered in detail in the com.iplanet.jato.view.ViewBean JavaDoc.

Important: The page session is no more secure than any other unencrypted value send to the client, such as values stored in an HTML hidden field. It is merely encoded, not encrypted, when sent to the client, so a determined malicious user could craft a request that contained a modified or bogus page session. Be sure to take this fact into account when deciding what your application stores in the page session.

Client Session

The client session is similar in concept to the page session, but it is shared by all pages (and other objects) in the application. Like the page session, it is automatically appended to all Sun ONE Application Framework URLs, and might be automatically compressed if it becomes large.

The client session is available from the RequestContext. The API is detailed in the com.iplanet.jato.ClientSession JavaDoc.

Important: The page session is no more secure than any other unencrypted value send to the client, such as values stored in an HTML hidden field. It is merely encoded, not encrypted, when sent to the client, so a determined malicious user could craft a request that contained a modified or bogus client session. Be sure to take this fact into account when deciding what your application stores in the client session.


Using ContainerViews

ContainerViews are analogous to panel components in other visual development environments. They provide a way to group a set of contained components so that they can be manipulated as a group. ContainerViews also form the basis for most complex components (both distributable and non-distributable).

There are several standard specializations of ContainerView that add additional behavior, such as the ability to repeatedly render its contained components. For more information, see the section Using TiledViews.

The ability to add additional behavior to ContainerViews beyond the basic ability to contain other components is what makes them so powerful and versatile.

IDE Support for ContainerViews

The Sun ONE Application Framework IDE toolset helps developers build ContainerViews as easily as selecting them from a list and adding a ContainerView subclass into their application. However, if you have studied the com.iplanet.jato.view.ContainerView interface, you might have noticed that it does not contain the methods that would allow an automated tool to describe components that should be created as children. The interface is, rather, written from the perspective of runtime framework requirements, and the framework does not care how child components are actually created and managed by the ContainerView at runtime.

Therefore, IDE support for creating ContainerView-based components relies upon a convention. To create a new extensible ContainerView component that the IDE can manipulate, the component must provide the following methods. However, if the component ultimately derives from com.iplanet.jato.view.ContainerViewBase, these conventions are already satisfied, and you need not worry about them. This convention also applies to all specializations of ContainerView, including ViewBeans and TiledViews.

The following table shows the methods provided by the component to create a new extensible Container View component that the IDE can manipulate.

Method

Description

void registerChild(String name, Class type)

This method will be called during initialization of the component to register all its child component's names and types. This information can then be used to satisfy certain ContainerView interface and implementation methods without requiring instantiation of the child components.

View createChildReserved(String name)

This factory method will be called to create the named child component as needed. The implementation of this method will automatically be generated by the IDE toolset as the developer adds child components to and manipulates child components in the container.

View createChild(String name)*

This alternate factory method will be called to create the named child component as needed. The implementation of this method will not be automatically generated by the IDE toolset, but is conventionally left available for the developer to manually add components to the container through code.

*Unlike the other two methods in this list, this method is not required by the IDE toolset, but its inclusion is strongly encouraged in new component types, as Sun ONE Application Framework developers are generally accustomed to its presence.


At a future time, these methods might be included in an additional interface to be implemented by IDE-supported ContainerView components. However, this interface is not a requirement at this time.

ContainerView API

The discussion below focuses on the default ContainerView implementation (ContainerViewBase) provided as part of the Sun ONE Application Framework component library, as it implements the conventions described in the previous section, IDE Support for ContainerViews. However, the ContainerView interface is the minimal requirement for a ContainerView components, and implementations might differ. See the JavaDoc for the com.iplanet.jato.view.ContainerView class to understand the minimum requirements for implementing a ContainerView component.

The baseline ContainerView API focuses on the ability to contain other View components, including other ContainerViews. Inherent in this feature is the notion of parent and child components. A parent component contains one or more child components. A child component can also be a parent to its own children, etc., so that an arbitrarily complex containership hierarchy can be established simply by adding components as children of other components. Methods such as ContainerView.getChildNames(), ContainerView.getChildType(), and ContainerView.getChild() are the core methods that allow consumers of the ContainerView (both developers and the framework) to work with its child components.

To define a component as a child of a ContainerView, there are generally two steps. If you are using the IDE toolset, these steps will automatically be managed for you (via code generation) as you visually add child components to your ViewBeans or other visual components.

The first step is to invoke the registerChild() method on the parent component to register a name-type mapping. This step is necessary to help the ContainerView make decisions about its child components without actually needing to instantiate them. This feature is important for efficiency reasons, as the framework does not want to have to create child component objects on every request, regardless of whether they will be used or not during that request. The information gathered via registration is used to satisfy calls to the ContainerView.getChildType() and ContainerView.getNumChildren() methods without instantiating all child components.

The second step is to tell the ContainerView how to instantiate a given child by name. Each ContainerView has two methods for this purpose, createChild() and createChildReserved(). These methods are identical in behavior, but the latter is reserved for automated tool use. These methods are factory methods that instantiate, configure, and return the child specified by the name parameter. This method is called at most once per child during a request, as the implementation of the component will cache the component returned by this method. Not all child components will be instantiated on every request, or at the same time during a request. This method is called lazily to instantiate the child component on an as-needed basis. For example, if you call the getChild() method to obtain a reference to a child and it hasn't been instantiated yet within that request, createChild() (or createChildReserved()) will be called to obtain a child component instance. Additional calls to getChild() within that request will not result in a call to createChild(), since the component has already been instantiated and cached by the container.

You should never call createChild() or createChildReserved() directly to obtain a child component instance. Instead, call getChild(), getDisplayField(), or an IDE-generated accessor method (for example, getFooChild()) to obtain a reference to a component. Also, in general, you cannot reliably call any of these methods or obtain a child component reference from within the ContainerView's constructor. The only caveat to this prohibition is if the RequestContext has been set on the ContainerView before the call to obtain a child component (or if all child components have static access to the RequestContext). This helps ensure that the ContainerView and all its children have access to whatever request resources they require (such as Models) to be properly instantiated and configured.

Using ContainerViews in Your Application

ContainerViews are in many ways just like any other child component you might add to a page (or other ContainerView)--they have properties that can be set and have display events associated with them.

Default Model

All ContainerViews have what is called a default model. This is a model that should act as the default storage for any child components that do not have Model storage otherwise specified. Unless otherwise specified, the default model is an instance of com.iplanet.jato.model.DefaultModel, which implements a basic in-memory storage mechanism.

The advantage of using the default model is that it does not require a complex Model binding, and allows the field to take any value the developer wants. Furthermore, DisplayField values are submitted and stored in the default model as is, so the developer has considerable flexibility in inspecting and manipulating the values before sending them to a backend system or another Model.

In the current Sun ONE Application Framework implementation of the various BasicDisplayField types, if a ModelReference is not explicitly set on the component, it uses its parent's default model.

Child View Paths

This name of a child in a call to ContainerView.getChild(String) might be a qualified view path, using forward slashes ("/") as delimiters. All components in the path except the last must refer to a ContainerView or a derivative of ContainerView (such as TiledView). Both relative and absolute paths are possible. If a name path begins with a forward slash, the name is assumed to be relative to the root (the ViewBean). If the path does not begin with a forward slash, the name is assumed to refer to a child relative to the current container. Two dots ("..") can be used to refer to the container that is the parent of the current container.


Using TiledViews

TiledViews are special types of ContainerViews that render their children repeatedly. Each repetition is called a tile. Tiles are most commonly used to generate rows in a table, but they can also be used to generate a set of tabs, a breadcrumb control, or any other structure that requires an iterative rendering. In addition to the standard ability to manage children, TiledViews provide methods such as next(), first(), and last() to control iteration through a set of tiles.

Each TiledView must be associated with a primary model of type DatasetModel. DatasetModels provide data in a tabular fashion, and when a TiledView is rendered, it essentially acts like an iterator over the associated primary model's data. At each row of the model, child Views are rendered. If these children (particularly DisplayField children) are bound to the primary model, their values will change with each tile iteration.

The TiledView's nextTile() method is used during rendering to move to each tile as needed. This method returns a boolean value that indicates whether the TiledView moved to a new tile, and the default implementation uses the primary model's next() method to determine if any more data is available to render. A common technique is to override the nextTile() method to perform additional checks or to initialize data needed for that tile's rendering.

During the submit phase, a TiledView must first be positioned to a given tile before that tile's DisplayField values can be read. If you place a CommandField component inside a TiledView and want to read its value when handling a request initiated by its activation, you must use the following code snippet to position the TiledView to the correct row before getting the CommandField's value:

getPrimaryModel().setLocation(
    ((TiledViewRequestInvocationEvent)event).getTileNumber());

where event is the RequestInvocationEvent parameter provided to your request event handler.

Nesting TiledViews (putting a TiledView within a TiledView) works for rendering of read-only data, but generally does not work as expected if data from the inner TiledView is submitted back to the application. The submitted data is present in the request parameters, but cannot be mapped properly to target models for reasons beyond the scope of this guide. Instead, a technique that might work for your application is to set the inner TiledView to use a SimpleCustomModel (or its own default model) as its primary model. Then, ignore that data that is pushed into the model, and instead iterate over the nested TiledViews while calling the getRequestValue() method on each BasicDisplayField child. This will return the servlet parameter value submitted for that combination of TiledView positions without you having to do a lot of string parsing to manually construct the parameter names.


Using TreeViews

TreeViews are special types of ContainerViews that render children in a hierarchical way. In addition to the standard ability to manage children, they provide support for determining the current node location when rendering a hierarchy of data, and have state data associated with them that determines which nodes are expanded or collapsed.

Each TreeView must be associated with a primary model of type TreeModel. TreeModels provide data in a hierarchical fashion, and when a TreeView is rendered, it essentially acts like an iterator over the associated primary model's data. At each node of the tree, child Views can be shown, hidden, or customized based on rules developers declare in the associated JSP tags. For this reason, TreeViews are relatively complex compared to other types of ContainerViews (but are considerably easier to use than trying to accomplish the same tasks manually).

The TreeView works heavily in conjunction with the Sun ONE Application Framework tag library, and has several tags defined just for it's special requirements. The tag library reference documentation covers these tags in detail.


Using Executing Models

In addition to containing data, most models act as business delegates and need to support operations to obtain data from or modify data in some back-end system. Therefore, the Sun ONE Application Framework defines the notion of executing models as models that can also have operations invoked on them.

There are several types of executing models in the Sun ONE Application Framework. The most basic type is represented by the com.iplanet.jato.model.ExecutingModel interface. This interface exposes a single method, execute(), that can be used to invoke arbitrary name operations on the model. Each model that implements this interface must document the operations it supports so that developers can call the execute() method with the appropriate operation name.

However, there are a handful of operations common to most models. These operations fall into four types: retrieve, insert, update, or delete. (These types are akin to SQL operations, but have no formal relationship, though they might be mapped to SQL operations in the case of a JDBC-based model.) The ability to process these operations is represented by the RetrievingModel, InsertingModel, UpdatingModel, and DeletingModel interfaces. Models that implement these interfaces declare that they support this type of execution interaction; thus no operation name is required to be specified by callers.

Although the most general type of executing model interface, ExecutingModel, allows nearly any operation to be invoked on the model, the more specific interface types allow the framework to work with models in a generic but well-understood way. In fact, these specific types are the foundation for the WebActions feature. However, they also provide significant value to Sun ONE Application Framework developers in that they clearly define the semantics of the models they build or with which they interact.

In general, executing models (of all types) will be executed by developers at specific points in their applications. For example, a request event handler might get some data from the current request, put it into a model that implements UpdatingModel, and then call update() on that model to push that data into the back-end system represented by that model. However, WebActions (covered below) can also execute specific types of models at certain points in the application automatically, freeing the developer from having to write code to move data into and out of models.


Using BeanAdapterModel

The BeanAdapterModel allows developers to use one or more JavaBeans as the backing datastore for a model. This allows DisplayFields to be bound to JavaBean properties, and is a convenient approach when you have an application object model and want to leverage automatic binding of these objects to a view.

The JavaBeans to use in the model can be set by the developer explicitly via the setBean() or setBeans() methods, or obtained via lookup mechanism in the standard J2EE request, session, or application scopes. This feature allows for easy interoperation of the model with other J2EE components. To automatically have the model look up its beans, specify the scope using the Bean Scope property or the setBeanScope() method.

The BeanAdapterModel supports the DatasetModel interface, allowing arrays of JavaBeans to be used in the model, one element per row. It also supports pagination via WebActions. Developers that create subclasses of BeanAdapterModel can also implement alternative executing model methods to allow them to obtain beans from EJBs, object-relational mapping layers, or some other system that provides data as JavaBeans. This allows BeanAdapterModel to be used in a wide variety of situations and as a flexible but simple way to integrate non-Sun-ONE-Application-Framework objects into an application.


Using ObjectAdapterModel

See the JavaDoc for com.iplanet.jato.model.object.ObjectAdapterModel for usage information.


Using WebActions

WebActions are special behaviors built into the Sun ONE Application Framework View components that allow them to provide automatic model execution and pagination. Specifically, the BasicViewBean, BasicContainerView, and BasicTiledView components support WebActions as implementations of the com.iplanet.jato.view.WebActionHandler interface.

Hand in hand with the WebAction View components are WebAction models. These are not new types of models, but rather the association of a model with a WebAction View component. Models are associated with WebAction View components via the following properties: Auto Deleting Models, Auto Executing Models, Auto Inserting Models, Auto Retrieving Models, and Auto Updating Models. These properties mirror the standard categories of executing models, meaning any models that implement one or more of the standard ExecutingModel interfaces can be used as WebAction models.

Once a model is associated with WebAction component, it can be executed in the context of a WebAction by calling its handle handleWebAction(int) method. Developers can call this method directly from event handlers, or it can be called automatically, such as by a WebAction Command component. Here is a typical example usage of a WebAction within a request event handler method:

public void handleButton1Request(RequestInvocationEvent event)     throws Exception
{
     handleWebAction(WebActions.ACTION_NEXT);
     getParentViewBean().forwardTo(getRequestContext());
}

The generally correct behavior after executing a WebAction is to reload the current page, though for certain WebActions such as ACTION_UPDATE, ACTION_INSERT, and ACTION_DELETE, it might make sense to forward to a different page.

WebAction Types

Developers must specify the type of WebAction that should occur when calling the handleWebAction(int) method.

The following table outlines the available types of WebActions.

WebAction Type

Description

ACTION_FIRST

Execute all auto-retrieving dataset models, and move to the first row of the dataset.

ACTION_NEXT

Execute all auto-retrieving dataset models, and move to the next group of rows (as defined by a previous action).

ACTION_PREVIOUS

Execute all auto-retrieving dataset models, and move to the prior group of rows (as defined by a previous action).

ACTION_LAST

Execute all auto-retrieving dataset models, and move to the last group of rows.

ACTION_UPDATE

Execute all updating web action models.

ACTION_INSERT

Execute all inserting web action models.

ACTION_DELETE

Execute all deleting web action models.

ACTION_CLEAR

Execute no models, resulting in a blank resulting page.

ACTION_EXECUTE

Execute all executing web action models.

ACTION_REFRESH

Re-execute the current auto-retrieving dataset models.


WebAction Events

Because WebActions can occur in contexts where the application developer might have no way to prepare a model for execution or respond to error conditions (such as within a try...catch block), there are a number of WebAction events that developers can implement.

The following table shows these WebAction events.

WebAction Event

Description

beforeWebActionModelExecutes()

Called before each WebAction model is executed, regardless of the WebAction context (for example, retrieve, insert, update, delete). Developers can use this method to prime the models for execution, perhaps by setting request-specific values on them.

afterWebActionModelExecutes()

Called after each WebAction model is executed, regardless of the WebAction context (for example, retrieve, insert, update, delete). Can be used to check, summarize, or even sort model data before it is rendered by the View.

afterAllWebActionModelsExecute()

Called after all WebAction models are executed for the current invocation of handleWebAction(), regardless of the WebAction context (for example, retrieve, insert, update, delete). Can be used to take last minute action before the View is rendered in association with a WebAction.

onWebActionExecutionError()

Called when an error occurs executing a WebAction model. Can be used to take corrective action or to abort the request.


Auto-Retrieving Models

The most common use of WebActions is to automatically execute a model when a WebAction View component renders. Rather than requiring the developer to insert code into the application at a specific point to execute a model for display of a page or pagelet (such as the beginComponentDisplay() event), associating one or more models as retrieving WebAction models allows the component to retrieve data automatically when display begins.

The various WebAction View components automatically try to execute any retrieving WebAction models when they begin rendering. If there are no retrieving WebAction models, no models are automatically executed. Similarly, auto-retrieval of models can be manually controlled via the setAutoRetrieveEnabled(boolean) method. Setting this value to false skips auto-execution.

There might be cases where you might want to manually execute an auto-retrieving model instead of waiting for it to be executed when its associated WebAction View component is displayed. (For example, this is common if you want to know the number of rows of data in a model before you begin rendering a child component with an associated auto-retrieving model). In such cases, you can execute the model manually and then call setAutoRetrieveEnabled(false) on the associated View component. The View component will render normally.

Auto-retrieving models are executed each time the associated View component is rendered on a page. To see any benefit to auto-execution, you generally need to bind a component's child DisplayField components to the model that will be auto-executed.

Pagination Using WebActions

One of the most valuable WebAction features is the ability to paginate through sets of data using arbitrary models. The pagination WebActions (ACTION_FIRST, ACTION_NEXT, ACTION_PREVIOUS, and ACTION_LAST) use retrieving WebAction models to allow users to page through sets of data that might be too large to display on a single page.

As long as a model implements the RetrievingModel and DatasetModel interfaces (or the combined RetrievingDatasetModel interface), it can be used with the pagination WebActions. This means that you can essentially any kind of model with these actions, regardless of its data's origin. For example, models using JDBC, XML, Web services, CICS mainframe data, or just about any other data source can be paginated using WebActions--providing they implement the RetrievingModel and DatasetModel interfaces.

There is nothing special you need to do to enable pagination support; just use these WebActions like any other WebActions, keeping in mind the additional model type restrictions. Among the components shipped with the Sun ONE Application Framework component library, the various custom model components, JDBC QueryModel, BeanAdapterModel, ObjectAdapterModel, and WebServiceModel generally support pagination as both RetrievingModel and DatasetModel implementations.

When to Use WebActions

WebActions are a purely value-added feature of some of the standard Sun ONE Application Framework components. As such, they are at the developer's discretion to use as desired. In general, the auto-retrieval and pagination WebActions are the most useful, the former because of the lessening of developer code, and the latter because of the general difficulty of writing pagination code.

As a general rule of thumb, use WebActions when they make things easier. However, if you feel constrained by them for some reason, feel free to do things manually--all of the behavior the WebActions provide can be replicated using existing Sun ONE Application Framework features, though arguably without the same level of convenience.


Interoperating With Sun ONE Application Framework Applications

Because Sun ONE Application Framework applications are ultimately J2EE Web applications, other Web applications can interoperate with them in well-defined ways. The techniques for interoperation with Sun ONE Application Framework applications depend on how the application components will be accessed.

Interoperating From an External Application

An external application is a completely separate application in a different servlet context, application server, Web server, J2EE or non-J2EE container, and so on.

This type of interoperation is the same as it would be for any other Web application--Sun ONE Application Framework applications are invokable through a standard HTTP URL. If you want to pass parameters to a Sun ONE Application Framework application, you can append query parameters to the HTTP invocation and access these from within the invoked Sun ONE Application Framework component.

However, this type of invocation will result in a first-touch request, a request that does not invoke a request event handler. In most cases, this is fine. In some cases, though, you might want to simulate a button press or HREF (hyperlink) click. If so, you need to include at least one name-value pair as a query parameter or posted value.

Each request resulting from a Sun ONE Application Framework button or HREF includes a parameter that signals to the application's request handling infrastructure that it should invoke a request event handler. The name of this parameter is the fully-qualified name of the button or HREF. For example, a button name SubmitButton on PgFoo would generate a parameter like this:

PgFoo.SubmitButton=Submit

Therefore, to simulate this button press, you would send an HTTP request to the Sun ONE Application Framework application that looks something like this:

http://<host>/fooapp/main/PageFoo?PageFoo.SubmitButton=Submit&...

This would typically result in the handleSubmitButtonRequest() method being invoked on PageFoo. If you want to populate any of the other fields on the same page, as most request event handler methods would expect, you need to append additional parameters to the query string or posted content.

If you want to invoke a request event handler within a child TiledView of a page, you need to also include a row number subscript in the parameter name:

PageFoo.FooTiledView[3].SubmitButton=Submit

This would invoke the request event handler in the TiledView named FooTiledView as if the button press had occurred on the fourth tile (tile numbers are zero-based).

Interoperating From Within the Same Application

In some cases, a single Web application might contain both Sun ONE Application Framework and other J2EE components, such as non-Sun-ONE-Application-Framework servlets and JSPs. The advantage of interoperating with the Sun ONE Application Framework from within the same application is that the application components can share session and other objects via the request and servlet context attributes. This makes it much easier to move seamlessly between components.

In most situations, you can simply follow the same basic guidelines for interoperating from an external application. However, you instead need to forward to or include the Sun ONE Application Framework component in the request using the standard javax.servlet.RequestDispatcher mechanism. When forwarding or including in this manner, you must be sure to request the standard Sun ONE Application Framework URL and not any of the Sun ONE Application Framework JSPs directly (the request must proceed via the module servlet to provide the proper Sun ONE Application Framework context):

String target="/fooapp/main/PageFoo?PageFoo.SubmitButton=Submit&...";
RequestDispatcher dispatcher=    request.getServletContext().getRequestDispatcher(target);
dispatcher.forward(request,response);