Implementing Presentation Tier Classes


Because the presentation tier leverages the Struts framework for providing the controller component, the number of classes participating in realizing each GreaterCause use case are minimal; the heavy duty work is performed by the Struts itself. Whether you have a preference for Struts or not, one important aspect of this discussion is to understand how simple the development process is when an MVC-based implementation is provided as a bundled solution. Our focus is on creating request handlers, and supporting helper classes such as business Delegates, ActionForm subclasses (form-beans), and DTOs (data transfer objects). We endeavor to identify the relationships between request handlers and the rest of the helper classes, and use sequence diagrams to model the interactions between these classes. For each use case, we shall create a single class diagram, and subsequently identify design patterns that will abstract key interactions between the Views (JSPs), the Struts Framework, and the classes participating in the realization of each use case.

The detailed use case view provides us with a clear understanding of the work flow involved in accomplishing various application tasks. These application tasks, or actions, can be represented as methods in the request handlers, and subsequently mapped to the business interfaces provided by the service layer via the business delegates. At this juncture, we may find the need to evolve the coarse-grained tasks defined in the use case into its constituent parts, and identify suitable operations for these tasks on the class diagrams.

DEFINITION

A DTO (Data Transfer Object) represents a coarse-grained object that aggregates server side data before it is serialized and marshalled across the wire from the business tier to the presentation tier or vice versa. The purpose of using DTO is to reduce network traffic since calls made to EJBs are expensive. The DTO pattern is explained in Chapter 7, in the section "Data Transfer Object Pattern."

DEFINITION

A Business Delegate is used to reduce the coupling between the presentation tier and the business tier; it hides the implementation details of the business interfaces. Details are available in the section "Implementing the Business Delegate Pattern."

Note

The sequence diagrams depicted in this chapter have been distilled to make them easy to read while maintaining focus on the key aspects of object interactions. As such, please refer to the code illustrations or the accompanying source distribution for complete details.

Implementing ActionForm Subclasses

The properties defined in an ActionForm subclass follow the JavaBean patterns described in Chapter 4. The Struts framework uses these patterns, that is the get and set accessor methods, to manage the ActionForm (a.k.a. the form-bean, which is discussed in Chapter 4) state. Struts uses the org.apache.commons.beanutils package to perform operations on JavaBeans; this includes automatic type conversion (from request parameters to the form-bean, and vice versa), handling simple and nested bean properties, and automatic field initialization based on field type. The beanutils package provides increased productivity and convenience of working with JavaBean-compliant classes.

Capturing Form Data

The primary function of an ActionForm subclass is to capture form data submitted by an HTML document. The key/value pairs submitted as part of the HTTP request are used to populate the properties specified in the ActionForm subclass. As such, you can implement an ActionForm subclass for staging the data provided by the HTML form. The form-beans used by the GreaterCause application are not limited to capturing information from a single form. In our sample application, the site administrator has to typically go through two screens, one for identifying the entity that it is going to impersonate (either the portal-alliance or a non-profit), and the other for working with data pertaining to the entity. Multi-page interactions are explained in the section "Multi-Page Pattern." Another case of multi-page interaction is involved with search semantics, where up to four screen interactions are possible; this is explained in the section "Shared Request Handler Pattern." As explained in Chapter 4, the data types used in form-beans are transformed automatically from the String type of HTTP protocol to the target type used by the bean properties. The initial value for blank fields is also set automatically by the framework using helper classes from the beanutils package. Care should be taken to ensure that all form fields are represented in the form-bean, otherwise the Struts framework simply ignores extra parameters in the request. Also, you must try to prevent naming conflicts between the field names used in the HTML form with the field names used within the form-bean for the purpose of managing application state. In several cases, you can design a form that can handle input from multiple pages; this technique reduces the number of forms used by the application, which in turn reduces form clutter, increases manageability, and promotes modularity.

Validating Data

The ActionForm bean can optionally contain a validate method that is called either by the framework or through the request handler classes. If the validate method is not to be invoked by the framework, then you must set the validate attribute in the <action> element of the struts-config.xml to "false"; otherwise the framework will automatically call the validate method immediately after populating the form-bean. The semantics of the framework are explained in Chapter 4. An alternate technique allowing declarative validation is provided by the framework by extending the form-bean with the ValidatorForm class; this is discussed in the section "Factoring Validator into the Design Process."

For our sample application, we have deliberately set the validate attribute to "false". One reason for doing this is the way the ValidatorForm behaves when used with the page property. The validation.xml file (explained later) contains declarative validation, which could be tied to a numeric page identifier. The page attribute in validation.xml controls which validations must be evaluated based on the value of the page property in the form-bean. If the page number associated with the page property of the form is n, then all validations with page value n and less are evaluated. For the GreaterCause application, this behavior is not desired since the sequence of pages shown to the user is based on the administrator type, and therefore the set of validations associated with a site administrator will fail for other types of administrators for whom a corresponding form was not processed and the data was not collected; the sample application therefore explicitly calls the validate method from the request handlers. Another reason for not letting the framework automatically call the validation is to have control over which pages get shown when the validation fails. The automatic validate method by the framework is inflexible when dealing with several <forward> possibilities. Should a validation fail, the framework will automatically invoke the URL associated with the <forward> element that has the same name attribute as the input attribute on the <action> element. Using automatic validation implies that only one response view is possible no matter which form gets submitted. The GreaterCause application has several <forward>elements associated with an <action> element, therefore the validate method is explicitly called by the request handlers, and the use of the input attribute on <action> elements is not entertained.

Managing Application State

The GreaterCause application uses form-beans to manage the application state. One reason for doing this is because the state of the application is very much influenced by the form-bean's page and action properties; the second reason is that state information cannot be stored in the request handlers since request handlers are not thread safe. The action property is used in identifying an action associated with a link or a button that the system remembers and adapts its behavior based on the value; one such case is when a single form is used to create, update, and view the registration information associated with a Portal-Alliance or a non-profit (NPO). The page property is useful in identifying the pages in a multi-page interaction; the ValidatorForm (that extends ActionForm) contains the page property that can be used to number the forms participating in a multi-form interaction. The page property is useful in creation of wizard-like behavior. The action property combined with the page property makes the request handlers highly modular in that a single request handler can be used to handle a variety of forms and user actions. The multi-page pattern, multi-action pattern, and Shared Request Handler pattern (all patterns are explained later in the chapter) rely on this mechanism in creating highly flexible request handlers. Using state information, we are able to package related functionality within a single request handler class rather than spreading out related functionality across multiple action classes; this increases manageability and promotes modularity.

Transferring ActionForm Properties to DTO

Although form-beans can function as data transfer objects (DTOs), it is not advisable to do so because form-beans are "presentation layer centric." The UI is the most volatile part of the system, therefore we want to shield the business tier from changes in the form-beans of the presentation tier. For service layer calls, especially when using EJBs, it is desirable to make fewer calls to increase throughput. The Value Object pattern recommended by Core J2EE Patterns [Core] is used for transferring data between application tiers using objects (a.k.a. DTOs) whose level of granularity is coarse; this is further explained in Chapter 7. The process of transferring form data staged in the form-bean to data transfer objects, and vice versa, is greatly simplified if data transfer object and form-beans use the same naming convention for property accessors. The beanutils package provides helper classes for transferring the state from one bean to another; the GreaterCause application uses the method PropertyUtils.copyProperties( toBean, fromBean ) from the beanutils package to accomplish this transfer in a single method call. The DTOs are typically designed by the service layer developer and contain flags for identifying whether a particular property was modified; these flags are used in optimizing method calls in the domain layer (see Chapter 6 for details) when modifying entity bean properties. The DTOs are packaged with both the web module (.war) and the EJB module (.jar) because these objects are common to both the web tier (presentation tier) and the EJB tier (business tier for GreaterCause).

Managing the Form-Bean Life Cycle

The scope attribute on the <action> element in the struts-config.xml file instructs the Struts framework about placement of the form-bean, upon its creation, in either the request object or the session object. When request scope is chosen, the form is placed in the request object and made available to the next resource invoked by the framework using the RequestDisptacher.forward method. The request objects are valid only within the scope of a servlet's service method, therefore the form-bean is not valid in the next invocation of the service method. The GreaterCause application uses the session scope to store all forms because all form-beans are designed to support multiple forms; employing this technique puts the responsibility of removing the form-beans on the developer when such beans are no longer useful. Keeping the form-beans in the session provides an added benefit of reuse when existing form-beans need to be frequently recycled during a user session; for such forms the reset method is implemented to prevent the annoyance of stale data being displayed to the users. Recall from Chapter 4 that the reset method is automatically called by the Struts framework just prior to form-bean population, so care must be taken in creating a reset method for cases where form data is captured from multiple forms. For such cases, it is best to check the page property and then conditionally initialize form-bean properties.

Implementing Request Handlers

Request handlers implement the Command pattern [Gof]. The controller servlet maps a request to the execute method of a request handler. The request handler is a subclass of the Action class, or any of the classes specified in the org.apache.struts.actions package; all classes in this package extend the Action class. In our sample application, we use the Action class as well as a variant of this class, the DispatchAction class from the actions package. The request handlers are cached by the controller and used for servicing subsequent requests from any user, as such, the request handlers are not thread safe; any state information pertaining to a user must not be stored in request handlers. Consider request handlers as an extension of the controller servlet; as discussed in Chapter 4, it is the controller servlet that instantiates the dispatcher, which in turn instantiates a request handler. One can write directly to the response stream from request handlers or pass control to another resource using the RequestDispatcher .forward method; the common practice is that a request handler will service a request, and when exiting its execute method, it will return an ActionForward object to the dispatcher instructing the dispatcher which view should be displayed next. When the request handler uses the RequestDispatcher.forward method, it can return a null as ActionForward to indicate to the controller that a response has already been sent and that the controller should take no further action. A typical request handler will have the following logic. This snippet has been taken from the class PortalAllianceRegistrationAction (Register Portal-Alliance use case); the code has been modified and made generic. Please refer to the accompanying source distribution for the exact code.

 public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest req,HttpServletResponse res ) throws Exception {     PortalAllianceRegistrationForm regForm =                       ( PortalAllianceRegistrationForm )form;     ActionErrors errors = new ActionErrors();     /* Check transaction token to ensure that the page is not stale.      * This check will also invalidate the token. isTokenValid()      * method is synchronized on the session object */     if ( !isTokenValid( req, true ) ) {         errors.add( ActionErrors.GLOBAL_ERROR,         new ActionError( "error.invalidToken" ) );         saveErrors( req, errors );         /* Redisplay the input form; this stale page does not          * need transaction token */         return mapping.findForward( "ShowPage" );     }     /* Validate the form fields */     errors = form.validate( mapping, req );     if ( !errors.empty() ) {         saveErrors( req, errors );         saveToken( req );         /* Redisplay the input form */         return mapping.findForward( "ShowPage" );     }     PortalAllianceRegistrationDTO dto = new PortalAllianceRegistrationDTO();     /* Transfer form properties to DTO */     try { PropertyUtils.copyProperties( dto, regForm ); }     catch ( InvocationTargetException e ) {         Throwable rootCause = e.getTargetException();         if ( rootCause == null ) { rootCause = e; }         throw new ServletException( rootCause );     }     catch ( Throwable e ) { throw new ServletException( e ); }     /* Access service layer using the delegate */     PortalAllianceRegistrationDelegate delegate =             PortalAllianceRegistrationDelegate.getInstance();     try { delegate.createPortalAllianceRegistration( req, dto ); }     /* Catch service layer Exception */     catch ( GCNestingException e ) {         errors.add( ActionErrors.GLOBAL_ERROR,         new ActionError( e.getMessageToken() ) );         saveErrors( req, errors );         /* Create a new token before redisplaying the page */         saveToken( req );         return mapping.findForward( "ShowPage" );     }     /* Clean up the form-bean */     req.getSession().removeAttribute( "PortalAllianceRegistrationForm" );     /* Specify the next View */     return mapping.findForward( "success" ); } 

The request handler can check the transaction token, perform form validations, and interact with the model using the business delegate. Token usage is discussed in Chapter 4. The sample application sets the transaction token for every transactional page; it then checks this token for validity when the request handler gets the control, and rejects any request with a stale token.

Managing User-Specific State

An ActionMapping object, created from the <action> element of the struts-config.xml file, is associated with a single request handler and a single form-bean. Other than the simplest scenarios, one can seldom expect that a single request handler will process a single page with a single operation. An example of such operations (which we subsequently refer to as "action") in the context of the sample application are the create/update/view actions on the registration page. It is highly unlikely that one will create three different request handlers for accommodating three different actions, because it will create manageability issues and defeat modularity. Among the available solutions, we will discuss the multi-page pattern and the multi-action pattern (using both the Action subclass strategy and DispatchAction subclass strategy) that uses the form-bean to manage application state; this state is queried by the request handler for deciding the process flow.

Although in most cases the Struts framework will instantiate and initialize the form-bean associated with a request handler (as specified by the type attribute in the <form-bean> element of struts-config.xml file), sometimes it becomes necessary for the request handlers to instantiate the form-beans required by a subsequent view. The sample application uses this technique when it invokes the search facility. The search facility is a common service available to any request handler wanting to couple itself with the search facility. The search facility's request handler has to remember the calling request handler such that it can seamlessly transfer control back to the calling request handler after the search request is satisfied. To enable this, the calling request handler has to instantiate the form-bean associated with the search facility and set the action property that will instruct the search facility which ActionForward it should use when exiting; this is explained in the section "Shared Request Handler Pattern."

Implementing the Business Delegate Pattern

A business delegate [Core] provides an extra level of indirection in accessing business tier services. A delegate essentially decouples the presentation tier from the business tier by brokering all calls from the presentation tier to the business tier. This design protects the presentation tier from changes in the business tier interfaces so long as the delegate is able to adapt the new business tier interface to existing method calls from the presentation tier. The business delegate also encapsulates the JNDI lookups, which reduces the complexity of the request handlers. Following is an example of a business delegate that is implemented to access the registration services of the EJB com.gc.services.admin.SiteAdminBean. The complete code is available in the accompanying source distribution. If you want to learn more about the package structure and naming conventions used by the sample application, please refer to the section "Identifying Package Dependencies."

 package com.gc.prez.admin; public class ManageNPODelegate {     private ManageNPODelegate() {         super();     }     /* Implement the Singleton pattern */     public static ManageNPODelegate delegate =         new ManageNPODelegate();     public static ManageNPODelegate getInstance() {         return delegate;     }     /* Get the remote reference of the EJB */     public NPOAdmin getBusinessInterface( HttpServletRequest req )     throws Exception {              /* Use the generic Service Locator */         ServiceLocator service = ServiceLocator.getInstance();         NPOAdmin businessInterface =         ( NPOAdmin )service.getRemoteForStateless( NPOAdminHome.class );         return businessInterface;     }     /* Access the business tier service */     public NPOProfileDTO getNPOProfileDTO( HttpServletRequest req,     String ein, String adminID ) throws Exception {         NPOAdmin businessInterface =         getBusinessInterface( req );         try {             return ( NPOProfileDTO )businessInterface.getNPOProfile(             ein, adminID );         }         catch ( RemoteException e ) {             throw new ServletException             ( Constants.Communication_Error, e );         }     }     ... rest of the code ... } 

The sequence diagram of Figure 5-1 illustrates the business delegate interactions. The request handler method ManageNPOAction.showNPOProfile() will access the delegate. The delegate in turn will get the remote reference to the business layer EJB using the getBusinessInterface method, and subsequently access the business tier service getNPOProfile().

click to expand
Figure 5-1: Business delegate sequence diagram

Implementing the Service Locator Pattern

The Service Locator encapsulates the logic for creating the initial context, JNDI lookup, and EJB remote reference creation. The service locator also optimizes access to EJBs by caching home references and EJB objects. It reduces code complexity for the business delegates whose only concern is to obtain the remote reference from the service locator and use it for making calls to the business tier. The sample application provides a more generic approach to implementing service locators using the reflection API; this has resulted in a single service locator for the entire GreaterCause application. You may want to evolve this implementation to suit your unique project requirements. The service locator implemented with the GreaterCause application provides the following generic service locator methods.

Note

For the GreaterCause application, the JNDI names follow the naming convention ejb/homeInterfaceName. This convention is used while implementing the methods of the service locator.

  • getRemoteForStateless( Class homeClass, ) This method will accept a stateless EJB home interface name and return a remote reference for the EJB. Home reference caching is used for optimization.

  • getRemoteForStateless( Class homeClass, Object[] args, ) This method will accept a stateless EJB home interface name and return a remote reference for the EJB. This method is called when the create method of the home interface accepts arguments; the arguments are passed to the create method using Object[]. Home reference caching is used for optimization.

  • getRemoteForStateful( Class homeClass, HttpSession session ) This method will accept a stateful EJB home interface name and return a remote reference for the EJB. This method caches an EJB handle in the HttpSession for subsequently getting the remote reference. Because the bean is stateful in nature, the corresponding EJB handle is saved in HttpSession as against a globally available cache that was used for caching home references in getRemoteForStateless method implementations. An EJB handle object is saved in the HttpSession instead of the remote reference because remote references are not guaranteed to be serializable when the HttpSession is passivated by the servlet engine.

The following demonstrates a simple service locator implementation that employs the reflection API. Please note that this version is abridged for improved readability; only one method from those listed is shown. The complete code is available in the accompanying source distribution.

 package com.gc.prez.common; public class ServiceLocator {     /* Implement Singleton Pattern */     private static ServiceLocator service = new ServiceLocator();     private Class[] parmsGlobal           = new Class[0];     private Object[] argsGlobal                   = new Object[0];     private HashMap ejbHomeCache     private ServiceLocator() {         super();     }     public static ServiceLocator getInstance() {         return service;     }     /* Get InitialContext for JNDI lookup() */     private InitialContext getInitialContext() throws ServletException {         Properties env = new Properties();         env.put( Context.INITIAL_CONTEXT_FACTORY,                 "weblogic.jndi.WLInitialContextFactory" );         /* Provide the appropriate URL based on your server configuration */         env.put( Context.PROVIDER_URL, "t3://localhost:7001" );         try {             return new InitialContext( env );         }         catch ( NamingException e ) {         ... rest of the code ...         }     }     public Object getRemoteForStateless( Class homeClass,     session ) throws ServletException {         /* Get the cached home reference (EJBHome reference) */         Object ejbHomeInterface = ejbHomeCache.get( homeClass.getName() );         try {             if ( ejbHomeInterface == null ) {                 InitialContext ic = this.getInitialContext();                 Object home = ic.lookup( "ejb/" +                 homeClass.getName() );                 /* Create home reference (EJBHome reference) */                 ejbHomeInterface =                 PortableRemoteObject.narrow( home, homeClass );                 ejbHomeCache.put( homeClass.getName(),                 ejbHomeInterface );         }         Method method = homeClass.getMethod( "create", parmsGlobal );         /* Create remote reference (EJBObject reference) */         Object ejbRemoteInterface =                method.invoke( ejbHomeInterface, argsGlobal );         return ejbRemoteInterface;     }     catch ( NamingException e ) {     ... rest of the code ... } 

Factoring Tags into Design Process

Custom tags bundled with Struts are organized into several tag libraries; the sample application uses Struts-provided bean, html, and logic tag libraries along with an application-specific GreaterCause tag library. Only some of the custom tags provided with Struts depend on the Struts framework; most tags can be used without the Struts framework. The following brief discussion has been included to demonstrate the impact of tags on the design process. Tags are like any other Java classes and should be factored into your overall design process. For additional details and a full list of custom tags and their functionality, please refer to http://jakarta.apache.org/struts/userGuide/index.html, and http://jakarta.apache.org/struts/resources/index.html; the resources section at this URL lists several good books for learning about Struts in greater detail.

The main purpose of using custom tags is to avoid coding scriptlets in JSPs. Scriptlet usage is highly discouraged because it embeds Java code within the JSP, which makes the JSP less modular and maintainable. Factoring all logic from JSP into tags reduces the complexity of the JSP, and provides flexibility for web production engineers who have to only work with a defined set of tags without concern for coding any logic. Code reuse is yet another reason why Java code must not be embedded in JSPs. The sample application uses the custom bean tags to retrieve bean properties for dynamic HTML generation; it uses the custom logic tags to test the values of form-bean properties for conditional processing; and it uses the custom html tags for dynamically generating HTML page elements.

Note

Several tags designed to work with JavaBeans have three essential attributes. The name attribute provides an identifier using which a JavaBean is saved and retrieved from the context specified by the scope attribute, and the property attribute specifies the property of the named JavaBean.

The following is a sampling of custom tags used in the JSP page 2_1_PortalAllianceRegistration .jsp. The process of portal-alliance registration is explained in the use case "Register Portal Alliance" in Chapter 1. Let's examine how some of these custom tags factor into the design decisions:

  • <html:errors/> The request handlers and form-beans accumulate validation errors in the ActionErrors object. The sample application uses the <html:errors/> tag for subsequently displaying the accumulated errors. Chapter 4 discusses this tag in detail. Struts 1.1 offers the <html:messages> tag, which has more capabilities than the <html:errors/> tag.

  • <html:form method="POST" action="/PortalAllianceRegistration.do"> This tag makes use of the Struts framework in identifying the ActionMapping configuration object associated with the path="/PortalAllianceRegistration". The mapping specification assists the tag in identifying and creating (if not already existing in the specified scope) the form-bean associated with the ActionMapping object, and pre-populating the HTML form with the values specified in the form-bean. The action attribute of the HTML form tag is dynamically generated using the context-relative path name. This is important when changing the context path name because no corresponding change is required in the JSP since it is not hard coded in the JSP.

  • <html:hidden property="page" value="2"/> This tag is used to create an HTML <input> element with an input type of hidden. The sample application uses hidden fields for saving process flow–related state of the application in the form-beans. The request handlers of the sample application use hidden properties in the HTML form for tracking multi-page form interaction using the page property of the form beans, and for tracking the actions embedded within the forms using the action property of the form-beans.

  • <bean:message key="PortalAdminServices.registration"/> The sample application is an internationalized application. This tag plays a vital role in internationalizing the application. It accepts a key and a set of optional arguments to generate a localized label, prompt, error message, or a heading from the resource bundle specified by the <message-resources> tag in the struts-xml.config file. Please refer to Chapter 4 for information on internalization and localization.

  • <bean:write name="PortalAllianceRegistrationForm" property=

    "activationDate"/> This tag is used to extract the specified property from the bean PortalAllianceRegistrationForm that is stored in one of the contexts, and write it to the output stream. The sample application uses the <bean:write> tag for fields that are read-only, as is the case when viewing the registration data, or displaying the Portal ID, or the EIN.

  • <logic:equal name="PortalAllianceRegistrationForm" property="action" scope="session" value="Create"> This tag is used in the sample application for enabling a single JSP to create different views based on the action property of the form-bean PortalAllianceRegistrationForm. The action property associated with the form-bean PortalAllianceRegistrationForm stores the processing state of the application, which could take the value Create, Update, or View. Conditionally executing logic based on the value of the action property renders the same page differently for each action property variation.

Several other tags are used in the sample application. You can learn more about these tags at the URL suggested earlier in this section. The procedure for installing and using tag libraries is explained in Chapter 9. It is apparent from the preceding discussion that a good part of application functionality that pertains to rendering views can be factored into tags; as such, the architect must be cognizant of its impact to the overall development process, and define appropriated usage scenarios or patterns for the development team.

Factoring Validator into the Design Process

Use of the Jakarta Commons Validator influences the design direction by providing yet another option for the creation of form-beans. The validate method of the ActionForm subclass, or the validations embedded within the request handlers are only one way of doing server-side validations. By extending the form-bean with the ValidatorForm class, the framework provides the ability to perform both client-side and server-side validations using declarative style of validations. This declarative style of specifying validations for form elements greatly reduces the need to code common validations that are used with almost every form submission. Common validations such as required fields, field formats (date, phone, zip, e-mail address, and so on), numeric or not, field length checking, and so on, are repeatedly coded by developers, therefore increasing the code volume and redundancy. Abstracting these common validations into another layer promotes reuse.

The Validator services are injected into the Struts framework using the following declaration in the struts-config.xml file:

 <plug-in className="org.apache.struts.validator.ValidatorPlugIn">     <set-property property="pathnames"         value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in> 

The Validator requires the following two configuration files:

  • Validator-rules.xml This file contains the basic validators that are packaged with the framework.

  • Validation.xml In this configuration file, we specify the validations associated with the form-bean properties. A condensed version of this file from the sample application is shown next; it demonstrates several different kinds of validations.

Note

A detailed discussion of Validator is available at http://www.home.earthlink.net/dwinterfeldt/overview.html. For installation instructions, please refer to Chapter 9.

 <form-validation>     <formset>         <form name="PortalAllianceRegistrationForm">             <field property="portalID" page="1"                     depends="required,minlength,maxlength">                 <arg0   key="prompt.PortalID"/>                 <arg1   key="${var:minlength}" name="minlength"                    resource="false"/>                 <arg2   key="${var:maxlength}" name="maxlength"                    resource="false"/>                 <var>                     <var-name>maxlength</var-name>                     <var-value>16</var-value>                 </var>                 <var>                     <var-name>minlength</var-name>                     <var-value>3</var-value>                 </var>             </field>             ... rest of the declarations ...             <field property="email" page="2" depends="required,email">                 <arg0   key="prompt.email"/>             </field>             <field property="activationDate" page="2"                     depends="required,date">                 <arg0   key="prompt.ActivationDate"/>                 <var>                     <var-name>datePatternStrict</var-name>                     <var-value>yyyy-MM-dd</var-value>                 </var>             </field>             ... rest of the declarations ...         </form>         <form name="ManagePortalAllianceForm">             <field property="firstName" depends="required">                     <arg0   key="prompt.FirstName"/>                 </field>             <field property="lastName" depends="required">                     <arg0   key="prompt.LastName"/>             </field>             <field property="email" depends="required,email">                     <arg0   key="prompt.email"/>             </field>             <field property="phone" depends="required">                     <arg0   key="prompt.Phone"/>             </field>             <field property="searchLimit" depends="required,Integer">                     <arg0   key="prompt.SearchLimitLabel"/>             </field>         </form>         ... rest of the declarations ...     </formset> </form-validation> 

The Validator framework provides support for internationalization by using the same resource bundle as the Struts framework. For the sample application, this resource file is identified in the struts-config.xml file using the <message-resources> element. In the validation.xml file, the page attribute has been used on several <field> elements. The purpose of this attribute is to selectively fire away the validations depending on the value of the page property in the form-bean. The page property is provided by the base class ValidatorForm, and is populated from a hidden field specified within an HTML form. For a given page with value n, all validations that pertain to the page numbered n and less will be evaluated. When the page property is not specified in a JSP, it is initialized to 0 by the form-bean (unless initialized previously to some other value).

Note

Since this book is focused on architecture and design, we have deliberately kept the examples light on programming aspects such as writing comprehensive validations. Effort has been made to bring to light those components of the application that are significant in understanding the architecture and design aspects during the development life cycle.

When the basic validations provided by the Validator are inadequate, you may need to override the validate method of the ValidationForm subclass. When additional validations are desired in a form-bean, create a validate method in the ValidatorForm subclass that calls super.validate method to perform Validator-based validations, and then perform additional validations in the subclass's validate method. The following sample application uses this technique for the ManagePortalAllianceForm.validate method; refer to the section "Manage Portal-Alliance Profile Use Case" for additional details. By employing the page attribute property in the ManagePortalAllianceForm.validate method, we are able to process different sets of validations for different process flows.

 public ActionErrors validate( ActionMapping mapping, HttpServletRequest req ) {     ActionErrors errors = new ActionErrors();     /* page 1 is for identifying a Portal ID */     if ( ( page ==1)&&(( portalID == null ) ||                         ( portalID.trim().length() < 1 ) ) ) {         errors.add( "portalID", new ActionError( "error.portalID.required" ) );     }     /* page 2 is for updating the profile of a portal-alliance */     else if ( ( page ==2)&&( action.equals( "updateProfile" ) ) ) {         errors = super.validate( mapping, req );         if ( searchLimit.intValue() < 10 ) {             errors.add( "searchLimit",                 new ActionError( "error.PortalAllianceProfile.SearchLimit" ) );         }     }     return errors; } 

Identifying Package Dependencies

Let's revisit the package diagram depicted in Chapter 1 (Figure 1-4); the package dependencies depicted in this diagram were an approximation based on our requirements. After creating the class diagrams for the use case packages GreaterCause Site Administration, Manage Campaigns, and Search NPO, we are able to discern the true dependencies between these packages. Although it is likely that the design time packages may deviate from the analysis model, the simple nature of our system follows the same packaging convention, both at analysis and design time. For the sake of manageability, the package naming conventions used by the sample application in the presentation tier and the business tier follow the following convention.

Presentation Tier

Business Tier


com.gc.prez.admin

com.gc.services.admin


com.gc.prez.managecampaigns

com.gc.services.managecampaigns


com.gc.prez.searchnpo

com.gc.services.searchnpo

This naming convention makes the process of identifying related components fairly intuitive. The package dependencies are illustrated in Figure 5-2.

click to expand
Figure 5-2: Administration Services package diagram

Packaging is convenient for collocating related classes into self-contained units. When a large number of classes are responsible for realizing use cases, then it is best to use an extra level of package nesting; nesting more than a couple of levels will make the packaging structure unwieldy. Packaging is very helpful in promoting parallel development since each package and its constituent use cases expose an interface that provides the required services to the dependent packages. It is not uncommon to call each package a subsystem. A subsystem is a grouping of components whose behavior constitutes the behavior offered by the contained elements. The dependencies between packages shows the impact the dependent classes can have when package elements are modified. In Chapter 1, while modeling the system context of a use case, package dependencies were articulated by showing use cases in other packages as actors.




Practical J2ee Application Architecture
Practical J2EE Application Architecture
ISBN: 0072227117
EAN: 2147483647
Year: 2003
Pages: 111
Authors: Nadir Gulzar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net