Reviewing the View Layer of the Mini HR Application


Struts and the View Layer

Struts provides a rich set of functionality and features for developing the View layer of MVC applications. There are several forms that the View layer of a Struts application can take. It can be HTML/JSP (the most common case) or it can be XML/XSLT, Velocity, Swing, or whatever your application requires. This is the power of Struts and MVC. Because HTML/JSP is the typical View technology used for Java-based Web applications, Struts provides the most functionality and features for developing your application this way. The remainder of this chapter focuses on Struts' support for creating the View layer using HTML/JSP.

Struts' HTML/JSP View layer support can be broken down into the following major components:

  • JSP pages

  • Form Beans

  • JSP tag libraries

  • Resource bundles

Each of these components is examined in detail in this chapter, but first it is helpful to understand how they fit together in the View layer.

JSP pages are at the center of the View components; they contain the HTML that is sent to browsers for users to see and they contain JSP library tags. The library tags are used to retrieve data from Form Beans and to generate HTML forms that, when submitted, will populate Form Beans. Additionally, library tags are used to retrieve content from resource bundles. Together, all of the Struts View layer components are used to generate HTML that browsers render. This is what the user sees.

On the back side, the View layer populates Form Beans with data coming from the HTML interface. The Controller layer then takes the Form Beans and manages getting their data and putting it into the Model layer. Additionally, the Controller layer takes data from the Model layer and populates Form Beans so that the data can be presented in the View layer.

The following sections explain each of these major View components in detail.

JSP Pages

JSPs are the centerpiece of the Struts View layer. They contain the static HTML and JSP library tags that generate dynamic HTML. Together the static and dynamically generated HTML gets sent to the user's browser for rendering. That is, the JSPs contain the code for the user interface with which a user interacts.

JSPs in Struts applications are like JSPs in any other Java-based Web application. However, to adhere to the MVC paradigm, the JSPs should not contain any code for performing business logic or code for directly accessing data sources. Instead, the JSPs are intended to be used solely for displaying data and capturing data. Struts provides a set of tag libraries that supports displaying data and creating HTML forms that capture data. Additionally, the tags support displaying content stored in resource bundles. Therefore, JSPs (coupled with Form Beans) provide the bulk of the Struts View layer. The JSP tag libraries glue those two together and the resource bundles provide a means of content management.

Form Beans

Form Beans provide the conduit for transferring data between the View and Controller layers of Struts applications. When HTML forms are submitted to a Struts application, Struts takes the incoming form data and uses it to populate the form's corresponding Form Bean. The Struts Controller layer then uses the Form Beans to access data that must be sent to the Model layer. On the flip side, the Controller layer populates Form Beans with Model layer data so that it can be displayed with the View layer. Essentially, Form Beans are simple data containers. They either contain data from an HTML form that is headed to the Model via the Controller or contain data from the Model headed to the View via the Controller.

Form Beans are basic Java beans with getter and setter methods for each of their properties, allowing their data to be set and retrieved easily. The org.apache.struts.action.ActionForm class is the base abstract class that all Form Beans must descend from (be it directly or via a subclass). Because Form Beans are simple data containers, they are principally composed of fields, and getter and setter methods for those fields. Business logic and data access code should not be placed in these classes. That code goes in Model layer classes. The only other methods that should be in these classes are helper methods or methods that override ActionForm's base reset( ) and validate( ) methods.

Note 

Form Beans are based on the JavaBeans specification and must have proper getter and setter methods for each field in order for introspection by the Struts framework to function properly. For more information on the JavaBeans specification visit: http://java.sun.com/products/javabeans/.

The ActionForm class has a reset( ) method and a validate( ) method that are intended to be overridden by subclasses where necessary. The reset( ) method is a hook that Struts calls before the Form Bean is populated from an application request (e.g., HTML form submission). The validate( ) method is a hook that Struts calls after the Form Bean has been populated from an application request. Both of these methods are described in detail later in this section.

Following is an example Form Bean:

import org.apache.struts.action.ActionForm;     public class EmployeeForm extends ActionForm {   private String firstName;   private String lastName;   private String department;       public void setFirstName(String firstName) {     this.firstName = firstName;   }       public String getFirstName() {     return firstName;   }       public void setLastName(String lastName) {     this.lastName = lastName;   }       public String getLastName() {     return lastName;   }       public void setDepartment(String department) {     this.department = department;   }       public String getDepartment() {     return department;   } }

Form Bean properties can be of any object type, be it a built-in class like String, Integer, or Boolean or a complex application-specific class such as an Address object that has fields for street address, city, state, and ZIP. Struts uses reflection to populate the Form Beans and can traverse nested object hierarchies to any level so long as the getter and setter methods are public. For example, if your Form Bean had an Address object field named address, to access the city field on the Address object the Form Bean would need a public getAddress( ) method that returned an Address object. The Address object would need a public getCity( ) method that would return a String. Properties that are themselves objects with properties are known as nested properties. Nested properties is the name given to properties that represent an object hierarchy.

Often, it's best to have Form Bean fields be Strings instead of other types. For example, instead of having an Integer-type field for storing a number, it's best to use a String-type field. This is because all HTML form data comes in the form of strings. If a letter rather than a number is entered in a numeric field, it's better to store the value in a String so that the original data can be returned to the form for correcting. If instead the data is stored in a Long, when Struts attempts to convert the string value to a number, it will throw a NumberFormatException if the value is a letter. Then, when the form is redisplayed showing the invalid data, it will show 0 instead of the originally entered value, because letters cannot be stored in numeric-type fields.

Note 

Although Struts best practices dictate that all Form Bean fields should be of type String, the simplicity of using types that more naturally match the data sometimes prevails. For this scenario, the Struts ActionServlet has an initialization parameter, convertNull, that informs Struts to default to null for Java wrapper type classes (e.g., null instead of 0 for numeric types).

Configuring Form Beans

To use Form Beans, you have to configure them in the Struts configuration file. Following is a basic Form Bean definition:

<!-- Form Beans Configuration --> <form-beans>   <form-bean name="searchForm"              type="com.jamesholmes.minihr.SearchForm"/> </form-beans>

Form Bean definitions specify a logical name and the class type for a Form Bean. Once defined, Form Beans are associated with actions by action mapping definitions, as shown next:

<!-- Action Mappings Configuration --> <action-mappings>   <action path="/search"           type="com.jamesholmes.minihr.SearchAction"           name="searchForm"           scope="request"           validate="true"           input="/search.jsp">   </action> </action-mappings>

Actions specify their associated Form Bean with the name attribute of the action tag, as shown in the preceding snippet. The value specified for the name attribute is the logical name of a Form Bean defined with the form-bean tag. The action tag also has a scope attribute to specify the scope that the Form Bean will be stored in and a validate attribute to specify whether the Form Bean's validate( ) method should be invoked after the Form Bean is populated. The input attribute of the action tag is typically used to specify a path that Struts should forward to if the validate( ) method generates any errors.

The reset( ) Method

As previously stated, the abstract ActionForm class has a reset( ) method that subclasses can override. The reset( ) method is a hook that gets called before a Form Bean is populated with request data from an HTML form. This method hook was designed to account for a shortcoming in the way the HTML specification dictates that browsers should handle check boxes. Browsers send the value of a check box only if it is checked when the HTML form is submitted. For example, consider an HTML form with a check box for whether or not a file is read-only:

<input type="checkbox" name="readonly" value="true">

When the form containing this check box is submitted, the value of "true" is sent to the server only if the check box is checked. If the check box is not checked, no value is sent.

For most cases, this behavior is fine; however, it is problematic when Form Bean boolean properties have a default value of "true." For example, consider the read-only file scenario again. If your application has a Form Bean with a read-only property set to true and the Form Bean is used to populate a form with default settings, the read-only property will set the read-only check box's state to checked when it is rendered. If a user decides to uncheck the check box and then submits the form, no value will be sent to the server to indicate that the check box has been unchecked (i.e., set to false). By using the reset( ) method, this can be solved by setting all properties tied to check boxes to false before the Form Bean is populated. Following is an example implementation of a Form Bean with a reset( ) method that accounts for unchecked check boxes:

import org.apache.struts.action.ActionForm;     public class FileForm extends ActionForm {   private boolean readOnly;       public void setReadOnly(boolean readOnly) {     this.readOnly = readOnly;   }       public boolean getReadOnly() {     return readOnly;   }       public void reset() {     readOnly = false;   } }

The reset( ) method in this example class ensures that the readOnly property is set to false before the form is populated. Having the reset( ) method hook is equivalent to having the HTML form actually send a value for unchecked check boxes.

A side benefit of the reset( ) method hook is that it offers a convenient place to reset data between requests when using Form Beans that are stored in session scope. When Form Beans are stored in session scope, they persist across multiple requests. This solution is most often used for wizard-style process flows. Sometimes it's necessary to reset data between requests, and the reset( ) method provides a convenient place for doing this.

The validate( ) Method

In addition to the reset( ) method hook, the ActionForm class provides a validate( ) method hook that can be overridden by subclasses to perform validations on incoming form data. The validate( ) method hook gets called after a Form Bean has been populated with incoming form data. Following is the method signature for the validate( ) method:

public ActionErrors validate(ActionMapping mapping,                              HttpServletRequest request)

Notice that the validate( ) method has a return type of ActionErrors. The org.apache.struts. action.ActionErrors class is a Struts class that is used for storing validation errors that have occurred in the validate( ) method. If all validations in the validate( ) method pass, a return value of null indicates to Struts that no errors occurred.

Note 

Data validations can be performed in the execute( ) method of action classes; however, having them in Form Beans allows them to be reused across multiple actions where more than one action uses the same Form Bean. Having the validation code in each action would be redundant.

Following is an example Form Bean with a validate( ) method:

import javax.servlet.http.HttpServletRequest;     import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage;     public class NameForm extends ActionForm {   private String name;       public void setName(String name) {     this.name = name;   }       public String getName() {      return name;   }       public ActionErrors validate(ActionMapping mapping,     HttpServletRequest request)   {     if (name == null || name.length() < 1) {       ActionErrors errors = new ActionErrors();       errors.add("name",         new ActionMessage("error.name.required"));       return errors;     }         return null;   } } 

This example Form Bean has one field, name, that is validated in the validate( ) method. The validate( ) method checks whether or not the name field is empty. If it is empty, it returns an error indicating that fact. The ActionErrors object is basically a collection class for storing org.apache.struts.action.ActionMessage instances. Each validation inside the validate( ) method creates an ActionMessage instance that gets stored in the ActionErrors object. The ActionMessage class takes a key to an error message stored in the application resource bundle. Struts uses the key to look up the corresponding error message. Furthermore, the ActionMessage class has constructors that take additional arguments that contain replacement values for the error message associated with the specified key.

Struts also has a built-in Validator framework that greatly simplifies performing data validations. The Validator framework allows you to declaratively configure in an XML file the validations that should be applied to Form Beans. For more information on the Validator framework, see Chapter 6.

Note 

The validate( ) method will be called unless the Action Mapping has been configured with "validate=false" in the Struts configuration file.

The Lifecycle of Form Beans

Form Beans have a defined lifecycle in Struts applications. To fully understand how Form Beans work, it's necessary to understand this lifecycle. The Form Bean lifecycle is shown in Figure 4-1.

image from book
Figure 4-1: The Form Bean lifecycle

Following is an explanation of the Form Bean lifecycle. When a request is received by the Struts controller servlet, Struts maps the request to an action class that is delegated to process the request. If the action being delegated to has an associated Form Bean, Struts attempts to look up the specified Form Bean in request or session scope, based on how the action is configured in the Struts configuration file. If an instance of the Form Bean is not found in the specified scope, an instance is created and placed in the specified scope. Next, Struts calls the reset( ) method on the Form Bean so that any processing is executed that needs to occur before the Form Bean is populated. After that, Struts populates the Form Bean with data from the incoming request. Next, the Form Bean's validate( ) method is called. The next step in the process is based on the return value from the validate( ) method. If the validate( ) method records any errors and subsequently returns a non-null ActionErrors object, Struts forwards back to the action's input page. If, however, the return value from the validate( ) method is null, Struts continues processing the request by calling the action's execute( ) method.

Dynamic Form Beans

A useful addition to the 1.1 release of Struts was the introduction of Dynamic Form Beans. Dynamic Form Beans are an extension of Form Beans that allows you to specify their properties inside the Struts configuration file instead of having to create a concrete class, with a getter and setter method for each property. The concept of Dynamic Form Beans originated because many developers found it tedious to create for every page a Form Bean that had a getter method and a setter method for each of the fields on the page's HTML form. Using Dynamic Form Beans allows the properties to be specified in a Struts configuration file. To change a property, simply update the configuration file. No code has to be recompiled.

The following snippet illustrates how Dynamic Form Beans are configured in the Struts configuration file:

<!-- Form Beans Configuration --> <form-beans>   <form-bean name="employeeForm"              type="org.apache.struts.action.DynaActionForm">     <form-property name="firstName"                    type="java.lang.String"/>     <form-property name="lastName"                    type="java.lang.String"/>     <form-property name="department"                    type="java.lang.String"/>   </form-bean> </form-beans>

Dynamic Form Beans are declared in the same way as standard Form Beans, by using the form-bean tag. The difference is that the type of the Form Bean specified with the form-bean tag's type attribute must be org.apache.struts.action.DynaActionForm or a subclass thereof. Additionally, the properties for Dynamic Form Beans are specified by nesting form-property tags beneath the form-bean tag. Each property specifies its name and class type. Furthermore, an initial value for the property can be specified using the form-property tag's initial attribute, as shown next:

<form-property name="department"                type="java.lang.String"                initial="Engineering"/>

If an initial value is not supplied for a property, Struts sets the initial value using Java's initialization conventions. That is, numbers are set to zero, objects are set to null, and so on.

Because you declare Dynamic Form Beans in the Struts configuration file instead of creating concrete classes that extend ActionForm, you do not define reset( ) or validate( ) methods for the Dynamic Form Beans. The reset( ) method is no longer necessary for setting default values because the initial attribute on the form-property tag achieves the same effect. The DynaActionForm class's implementation of the reset( ) method resets all properties to their initial value when it is called. You can either code the functionality of the validate( ) method inside action classes or use the Validator framework for validation. These two options eliminate the need to create a validate( ) method on the Form Bean. If, however, you have a special case where you need to have an implementation of the reset( ) and/or validate( ) method for your Dynamic Form Bean, you can subclass DynaActionForm and create the methods there. Simply specify your DynaActionForm subclass as the type of the Form Bean in the Struts configuration file to use it.

While Dynamic Form Beans shift the declaration of Form Beans and their fields from concrete classes to the Struts configuration file, an argument can be made that they don't save that much time. You still have to explicitly define each field for the Form Beans in the Struts configuration file. Thus you are really only getting the benefit of not having to recompile classes each time a change is made to the definition of a Form Bean. To further reduce the amount of overheard required in creating Form Beans, you can use what's known as a Lazy DynaBean. Lazy DynaBeans come to Struts by way of the Jakarta Commons BeanUtils project that Struts uses throughout the framework for bean manipulation. As

of Struts 1.2.4, you can simply declare a name for a Form Bean in the Struts configuration file and specify its type as org.apache.commons.beanutils.LazyDynaBean and no other configuration is needed. The Lazy DynaBean will accommodate any form properties sent to it from an HTML Form. That is, no fields need to be explicitly declared for the Form Bean. An example of configuring a Lazy DynaBean is shown here:

<form-beans>   <form-bean name="employeeForm"              type="org.apache.commons.beanutils.LazyDynaBean"/> </form-beans>

As you can see, it is very simple and fast to set up a Lazy DynaBean, saving much of the time traditionally spent in explicitly declaring the fields a Form Bean has.

Indexed and Mapped Properties

Using simple properties and nested properties on Form Beans will satisfy most requirements; however, there are scenarios where it is necessary to use a collection as a property. For example, when creating a form that has a variable number of fields, a collection must be used to capture the field data because the exact number of fields is not known in advance. Collection properties are also useful for transferring and displaying a variable-length list of data returned from the Model layer of an application. Struts supports two types of collection properties: indexed properties (e.g., arrays and java.util.List descendants such as ArrayList) and mapped properties (e.g., java.util.Map descendants such as HashMap).

Indexed properties are those properties that are backed by an indexed collection, such as arrays, ArrayLists, etc. To use an indexed property, you must set up the proper type of getter and setter methods in your Form Bean, as shown here:

import org.apache.struts.action.ActionForm;     public class EmployeeForm extends ActionForm {   private String[] departments =     {"Accounting", "Sales", "Marketing", "IT'};       public String getDepartments(int index) {     return departments[index];   }   public void setDepartments(int index, String value) {     departments[index] = value;   } }

This example uses an array-based indexed property called departments. Notice that the getDepartments( ) and setDepartments( ) methods take an index argument to specify the index in the array for the value that should be gotten or set. java.util.List-based indexed properties only require you to create a getter method, as shown next:

import java.util.ArrayList; import java.util.List; import org.apache.struts.action.ActionForm;     public class EmployeeForm extends ActionForm {   private ArrayList departments = new ArrayList();       public EmployeeForm() {     departments.add("Accounting");     departments.add("Sales");     departments.add("Marketing");     departments.add("IT");   }       public List getDepartments() {     return departments;   } }

Struts takes care of calling the getter and setter methods for indexed properties with the proper index when capturing data from a form or when populating a form. The following example illustrates how to reference an indexed property using the Struts tag libraries:

<html:form>   <html:text property="departments[0]"/> </html:form>

Notice that an index is specified for the departments field using [ ] notation. When this hypothetical form is submitted, an index of 0 and the value entered in the control will be used to populate the proper element in the collection. While this example illustrates how to reference a specific element in an indexed property, the real power of indexed properties is realized when a loop is used so that individual element indexes don't have to be specified. The following example illustrates how to use a loop to iterate over an indexed property's elements.

<html:form>   <logic:iterate name="employeeForm" property="departments"                     index>     <html:text property="<%="departments[" + index + "]"%>"/>   </logic:iterate> </html:form> 

A variable number of text input fields will be generated. If the departments property has 10 elements, 10 text input fields will be generated; if departments has no elements, no text input fields will be generated.

Mapped properties work much the same way that indexed properties do-only they are backed by java.util.Map descendants instead of arrays or java.util.List descendants. The syntax for referencing elements in a map differs as well. Instead of using [ ] notation to specify an index, ( ) notation is used to specify a key in the map. An example Form Bean with a mapped property is shown here.

import java.util.HashMap; import java.util.Map; import org.apache.struts.action.ActionForm;     public class EmployeeForm extends ActionForm {   private HashMap departments = new HashMap();       public EmployeeForm() {     departments.put("dep1", "Accounting");     departments.put("dep2", "Sales");     departments.put("dep3", "Marketing");     departments.put("dep4", "IT");   }       public Object getDepartments(String key) {     return departments.get(key);   }       public void setDepartments(String key, Object value) {     departments.put(key, value);   } }

The getDepartments( ) and setDepartments( ) methods take a key argument to specify the key in the map for the value that should be gotten or set. Referencing mapped properties is similar to referencing indexed properties, as shown here:

<html:form>   <html:text property="departments(dep1)"/> </html:form>
Note 

Indexed and mapped properties can be both nested and contain nested properties, just as any other type of property can be.

JSP Tag Libraries

Struts comes packaged with a set of its own custom JSP tag libraries that aid in the development of JSPs. The tag libraries are fundamental building blocks in Struts applications because they provide a convenient mechanism for creating HTML forms whose data will be captured in Form Beans and for displaying data stored in Form Beans. Additionally, the Struts tag libraries provide several utility tags to accomplish things such as conditional logic, iterating over collections, and so on. However, with the advent of the JSP Standard Tag Library (JSTL), many of the utility tags have been superceded. (Using JSTL with Struts is covered in Chapter 17.)

Following is a list of the Struts tag libraries and their purpose:

  • HTML  Used to generate HTML forms that interact with the Struts APIs.

  • Bean  Used to work with Java bean objects in JSPs, such as to access bean values.

  • Logic  Used to cleanly implement simple conditional logic in JSPs.

  • Nested  Used to simplify access to arbitrary levels of nested objects from the HTML, Bean, and Logic tags.

Later in this book, each of these libraries has an entire chapter dedicated to its use, but this section provides a brief introduction to using the tag libraries, focusing on the core Struts JSP tag library, the HTML Tag Library, as an example. This library is used to generate HTML forms that, when submitted, populate Form Beans. Additionally, the HTML Tag Library tags can create HTML forms populated with data from Form Beans. To use the HTML Tag Library in a Struts application, your application's JSPs must declare their use of the library with a JSP taglib directive:

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

Notice that the prefix attribute is set to "html". This attribute can be set to whatever you want; however, "html" is the accepted default for the HTML Tag Library. The prefix attribute declares the prefix that each tag must have when it is used in the JSP, as shown here:

<html:form action="/logon">

Because "html" was defined as the prefix, the form tag was used as shown. However, if you chose to use a prefix of "strutshtml", the tag would be used the following way:

<strutshtml:form action="/logon">
Note 

Modern application servers use the uri attribute of the taglib directive to automatically resolve the location of the tag library descriptor file. Older application servers that support only JSP version 1.1 and/or version 1.0 require that tag libraries be registered in the web.xml file so that they can be resolved, as shown here:

<taglib>   <taglib-uri>http://struts.apache.org/tags-html</taglib-uri>   <taglib-location>/WEB-INF/tlds/struts-html.tld</taglib-location> </taglib>

Resource Bundles

Resource bundles allow Java applications to be easily internationalized by having application content placed into bundles. This content can then be read by the application at run time. Therefore, instead of having content hard-coded in the application, the application reads its content from the bundle. A side benefit of using resource bundles to store application content (whether for internationalization or not) is that the content can be changed without having to recompile the application. Additionally, bundles serve as a central repository for content that is common to multiple uses (i.e., multiple applications). Having content in a central repository reduces unnecessary duplication.

Struts has built-in support for working with Java's resource bundle mechanism. Having this support allows the Struts framework to seamlessly support application internationalization as well as have a mechanism for externalizing content so that it can be easily changed without having to modify JSPs or application code. Struts uses resource bundle resources throughout the framework. For example, resource bundle resources can be accessed from JSPs to populate them with content. Similarly, action objects can access content stored in resource bundles to do such things as generate error or informational messages that get displayed on screen. The Struts Form Bean validation mechanism is also tied to resource bundles for managing error messages. Actually, there are several uses for resource bundles throughout Struts.

The rest of this section explains how to create a resource bundle properties file and configure Struts to use it. An example of accessing resource bundle content from a JSP is also shown. Later chapters provide specific information about how to use resource bundles in the context of those chapters' topics.

Using resource bundles in Struts is as easy as creating a properties file to store the resources in, and then configuring Struts to use the properties file. Once this is done, accessing the resources is straightforward. Following is a very simple resource bundle properties file containing a few properties:

page.title=Employee Search link.employeeSearch=Search for Employees link.addEmployee=Add a New Employee

Resource bundle properties files simply contain key/value pairs. The resources are accessed by their key. The standard name for the resource bundle properties file in Struts is MessageResources.properties. In order for Struts to be able to load this file, it must be stored on your application's classpath. For example, it could be stored in the /WEB-INF/classes directory.

The following snippet configures the resource bundle with Struts:

<!-- Message Resources Configuration --> <message-resources   parameter="com.jamesholmes.minihr.MessageResources"/>

The parameter attribute of the message-resources tag specifies the fully qualified name of the resource bundle properties file minus the .properties file extension. In this example, a file named MessageResources.properties would be stored in the /WEB-INF/classes/com/jamesholmes/minihr directory.

Once a properties file has been created and configured in the Struts configuration file, the resources in the bundle can be accessed from several places in the Struts framework. The most common place is in JSPs. The following snippet illustrates how to use the Bean Tag Library's message tag to load a message from the resource bundle:

<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>     <html> <head> <title><bean:message key="page.title"/></title> </head> <body>     ...

The value specified with the message tag's key attribute is the key for a message in the resource bundle. At run time, Struts retrieves the message and places it in the JSP.

Note 

Detailed information on resource bundles and internationalizing Struts applications is found in Chapter 10.



Struts. The Complete Reference
Struts: The Complete Reference, 2nd Edition
ISBN: 0072263865
EAN: 2147483647
Year: 2004
Pages: 165
Authors: James Holmes

Similar book on Amazon
Struts 2 in Action
Struts 2 in Action
Murach's Java Servlets and JSP, 2nd Edition
Murach's Java Servlets and JSP, 2nd Edition
Jakarta Struts For Dummies
Jakarta Struts For Dummies
Head First Servlets and JSP: Passing the Sun Certified Web Component Developer Exam
Head First Servlets and JSP: Passing the Sun Certified Web Component Developer Exam

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