An application using Devsphere Mapping Framework can be made of:
Devsphere Mapping Framework uses JavaBeans to encapsulate data. It introspects them in order to get information that is necessary to the mapping process: property names and property types. Additional information, like default values and error messages, is loaded from optional resource files, which are discussed in a later section.
A data bean is a class that meets the following requirements:
- It is declared public.
- It has a no arg public constructor.
- It has getter and setter methods (accessors) for its properties.
To be conformant with the JavaBeans specification, a data bean should also implement java.io.Serializable.
We recommend declaring the beans in a package to avoid naming conflicts.
package packageName;
public class BeanName implements java.io.Serializable {
private PropertyType propertyName;
...
public PropertyType getPropertyName() {
return propertyName;
}
public void setPropertyType(PropertyType value) {
propertyName = value;
}
...
}
The properties of the data beans can be
- strings (String) or string arrays (String[])
- primitives (boolean, int, float, etc) or primitive arrays (boolean[], int[], float[], etc)
- primitive wrappers (Boolean, Integer, Float, etc) or wrapper arrays (Boolean[], Integer[], Float[], etc)
- BigInteger, BigDecimal, BigInteger[], or BigDecimal[]
- data beans or arrays of data beans
The mapping utilities separate the properties in four categories:
- standard properties (strings, primitives, primitive wrappers, BigInteger and BigDecimal)
- standard indexed properties (string arrays, primitive arrays, wrapper arrays, BigInteger[] and BigDecimal[])
- data beans
- arrays of data beans
If a property is an array then it should be an indexed property. (In addition to property accessors, it should have accessors for array elements, though the latter ones aren't used. The introspection mechanism of JavaBeans needs them in order to recognize indexed properties.)
Examples of data beans:
Devsphere Mapping Framework takes pure HTML forms and maps them to data beans. This process consists of making associations between bean properties and form elements. For example,
- a string property (String propertyName) can be mapped to a text element (Label <INPUT TYPE="TEXT" NAME="propertyName">),
- a boolean property (boolean propertyName) can be mapped to a checkbox (<INPUT TYPE="CHECKBOX" NAME="propertyName" VALUE="true"> Label),
- an integer (int propertyName) can be mapped to a group of radio buttons (<INPUT TYPE="RADIO" NAME="propertyName" VALUE="1"> Label1 <INPUT TYPE="RADIO" NAME="propertyName" VALUE="2"> Label2 ... <INPUT TYPE="RADIO" NAME="propertyName" VALUE="N"> LabelN),
- a string array (String propertyName[]) can be mapped to a group of text fields (Label1 <INPUT TYPE="TEXT" NAME="propertyName"> Label2 <INPUT TYPE="TEXT" NAME="propertyName"> ... LabelN <INPUT TYPE="TEXT" NAME="propertyName">),
- an integer array (int propertyName[]) can be mapped to a list that allows the selection of multiple items (<SELECT NAME="propertyName" SIZE="N" MULTIPLE> <OPTION VALUE="1"> Item1 </OPTION> <OPTION VALUE="2"> Item2 </OPTION> ... <OPTION VALUE="N"> ItemN </OPTION></SELECT>).
Note: These are just a few of the mapping possibilities.
The associations between bean properties and form elements are made by their names. The mapping utilities can take the form data and set the values of the bean properties. The reverse process is possible too, i.e. the mapping utilities can take the bean data and insert VALUE attributes for text fields, CHECKED attributes for checkboxes and radio buttons and SELECTED attributes for list items.
The mapping between data beans and HTML forms is the subject of a later chapter.
The form documents may contain variables that are replaced with dynamically generated content using the setValue() methods of FormDocument. Each variable has a name and is defined using an HTML comment:
<!-- VARIABLE NAME="variableName" -->
You may also define an indexed variable like this:
static_html_content
<!-- VARIABLE NAME="variableName" INDEX="0" -->
static_html_content
<!-- VARIABLE NAME="variableName" INDEX="1" -->
static_html_content
...
<!-- VARIABLE NAME="variableName" INDEX="N" -->
static_html_content
The index attribute is optional, but you have to use it if you want to control the indexing of multi-value variables.
The framework defines automatically the variables that are used to insert default values and error messages. The names of these variables are [DEFAULT_VALUE.propertyName] and [ERROR_MESSAGE.propertyName]. You may see where they are placed using the toString() method of FormTemplate.
You may not access the values of these internal variables defined by the framework. Their values are set automatically by the mapping utilities. You may, however, relocate the [ERROR_MESSAGE.propertyName] variables by simply redefining them:
<!-- VARIABLE NAME="[ERROR_MESSAGE.propertyName]" -->
If you redefine an error message variable you also get access to its value. The default value variables cannot be redefined.
Examples of forms:
You can attach a resource bundle to each bean. The mapping utilities of Devsphere Mapping Framework can operate without them. However, we recommend you to define the resources, because they allow you to specify default values, error message and more.
A bean resource bundle is a class that meets the following requirements:
- It is declared public.
- It is declared within the bean's package.
- Its base name starts with the bean name and continues with "Resources".
- It extends java.util.ListResourceBundle and defines the getContents() method.
The bean resource bundle can also be a .properties file as long as
- It belongs to the bean's package.
- Its base name starts with the bean name and continues with "Resources".
We recommend enclosing the resource keys within squared brackets and starting them with a prefix followed by dot: "[prefix.keyName]" or "[prefix.keyName.propertyName]". The prefix may be the package name or the application's name. Using a prefix you avoid name collisions with the resources used by the framework whose keys have no prefix.
All mapping utilities use the following resources if they exist:
key: [DEFAULT_VALUE.propertyName]
value: the default value of a bean property.
Only standard properties (strings, primitives, primitive wrappers, BigInteger and BigDecimal) and standard indexed properties (string arrays, primitive arrays, wrapper arrays, BigInteger[] and BigDecimal[]) may have default values.
Since you may not store primitives in a resource bundle, use strings or primitive wrappers for their default values:
{ "[DEFAULT_VALUE.propertyName]", "value" } or
{ "[DEFAULT_VALUE.propertyName]", new Wrapper(value) } or
[DEFAULT_VALUE.propertyName]=value
Arrays may be stored as default values of the indexed standard properties, in case you use a list resource bundle:
{ "[DEFAULT_VALUE.propertyName]",
new PropertyType[] { value0, value1, ..., valueN } }
If you want to use .properties files an array may be encoded using multiple key-value pairs:
[DEFAULT_VALUE.propertyName.length]=N
[DEFAULT_VALUE.propertyName.0]=value0
[DEFAULT_VALUE.propertyName.1]=value1
...
[DEFAULT_VALUE.propertyName.N]=valueN
If you don't specify a default value for a standard property, the mapping utilities will use an empty string, 0, false or an empty array, depending on the property's type.
key: [ERROR_MESSAGE.propertyName]
value: the error message of a bean property.
The error message should be a string:
{ "[ERROR_MESSAGE.propertyName]", "errorMessage" } or
[ERROR_MESSAGE.propertyName]=errorMessage
The error message of a property is shown in one of the following situations:
- the value of a non-optional parameter is missing
- a NumberFormatException occurs when a parameter is mapped to a numeric property
- the setter method of a property throws IllegalArgumentException
If you don't specify an error message for a property, a default message will be used to signal the errors.
key: [OPTIONAL_PROPERTIES]
value: the list of optional properties of a bean.
An optional parameter should be mapped to an optional property so that no error is signaled when the parameter value is missing.
The list of optional properties must be declared as a string array or as a string. If it isn't present, the mapping utilities assume that there are no optional properties.
{
"[OPTIONAL_PROPERTIES]",
new String[] {
"propertyName1",
"propertyName2",
...
"propertyNameK"
}
}
or
[OPTIONAL_PROPERTIES]=propertyName1 propertyName2 ... propertyNameK
key: [PROCESSING_ORDER]
value: the list of bean properties that participate to the mapping process.
By default, all properties of a bean participate to the mapping process, their processing order being determined by the introspection API of JavaBeans.
You can define the list of properties as a string array or as a string if you want to control the order in which the mapping utilities set/get the values of the bean properties. That could be useful if the validation of a property value is done in the setter method and it depends on the values of other properties.
Also, you may exclude some of the properties from the mapping process by not including them in the property list. You will have to do this for read only properties whose values are computed as a function of other property values.
{
"[PROCESSING_ORDER]",
new String[] {
"propertyName1",
"propertyName2",
...
"propertyNameN"
}
}
or
[PROCESSING_ORDER]=propertyName1 propertyName2 ... propertyNameN
The XML mapping utilities use the following resource if it is present:
key: [XML_INDENTATION_STRING]
value: string used to indent the XML markup
default value: TAB ("\t")
The form mapping utilities use the following resources if they are present:
key: [HTML_MESSAGE_START]
value: starter of error messages
default value: <FONT COLOR="#FF0000">
key: [HTML_MESSAGE_SEPARATOR]
value: separator of error messages
default value: <BR>\r\n
key: [HTML_MESSAGE_END]
value: terminator of error messages
default value: </FONT><BR>\r\n
key: [FIXED_LENGTH.propertyName]
value: the fixed length of an indexed property whose type is a bean array
If you use static HTML forms, the bean arrays will be mapped to a fixed number of groups of form elements. In this case, you may specify that fixed number within the bean's resources. If you use dynamic forms, the length of a bean array will have to be stored as the default value of a hidden form element. The use of these techniques will be exemplified in later chapters.
The following resources aren't used by the mapping utilities, but they may be used by your application:
key: [FORM_NAME]
value: the name of the HTML form that is associated with the bean.
Forms are used to collect bean data using a browser and they are described in the previous section of this chapter.
key: [PROC_NAME]
value: the name of the processor that is associated with the bean.
Processors receive valid beans from handlers and they are described in a later section of this chapter.
Examples of bean resources:
A handler is a component that receives form data via HTTP and tries to map it to one or more data beans. If the mapping succeeds, the valid beans are forwarded to a processor, which is responsible for responding to the HTTP request. Otherwise, the handler sends back to the user an HTML form containing already inputted valid data and messages that signal the errors.
The handler is also used to get the HTML form the first time.
The handlers make heavy use of the APIs provided by Devsphere Mapping Framework. For example, a JSP handler could look like this:
<%@ page language="java" import="com.devsphere.mapping.*, com.devsphere.logging.*" %>
<jsp:useBean id="beanID" class="BeanName" scope="request"/>
<%
// 1. Get the bean resources
java.util.ResourceBundle beanRes = HandlerUtils.getBeanResources(beanID.getClass());
// 2. Construct the base path
String basePath = request.getServletPath();
int slashIndex = basePath.lastIndexOf('/');
basePath = slashIndex != -1 ? basePath.substring(0, slashIndex+1) : "";
// 3. Determine the HTTP method
boolean isPostMethod = request.getMethod().equals("POST");
// 4. Create a logger that wraps the servlet context
ServletLogger logger = new ServletLogger(application);
// 5. Wrap the form data
FormData formData = new ServletFormData(request);
// 6. Form-to-bean mapping: request parameters are mapped to bean properties
java.util.Hashtable errorTable = FormUtils.formToBean(formData, beanID, logger);
if (isPostMethod && errorTable == null) {
// 7. Construct the processor's path
String procPath = basePath + beanRes.getString("[PROC_NAME]").trim();
// 8. Process the valid data bean instance
application.getRequestDispatcher(procPath).forward(request, response);
} else {
if (!isPostMethod)
// 9. Ignore the user errors if the form is requested with GET.
errorTable = HandlerUtils.removeUserErrors(errorTable);
// 10. Construct the form's path
String formPath = basePath + beanRes.getString("[FORM_NAME]").trim();
formPath = application.getRealPath(formPath);
// 11. Get the form template
FormTemplate template = FormUtils.getTemplate(new java.io.File(formPath));
// 12. Get a new document
FormDocument document = template.getDocument();
// 13. Bean-to-form mapping: bean properties are mapped to form elements
FormUtils.beanToForm(beanID, errorTable, document, logger);
// 14. Send the form document
document.send(out);
}
%>
The first line of the handler imports the packages of the framework: com.devsphere.mapping and com.devsphere.logging.
The second line declares and instantiates a bean whose class is BeanName. Processors (discussed in the next section) can use the same line to get the bean object. The JSP engine translates the following line
<jsp:useBean id="beanID" class="BeanName" scope="request"/>
into something like this:
BeanName beanID = null;
synchronized (request) {
beanID = (BeanName) pageContext.getAttribute("beanID", PageContext.REQUEST_SCOPE);
if ( beanID == null ) {
try {
beanID = (BeanName) Beans.instantiate(getClassLoader(), "BeanName");
} catch (Exception e) {
throw new ServletException("Cannot create bean of class " + "BeanName");
}
pageContext.setAttribute("beanID", beanID, PageContext.REQUEST_SCOPE);
}
}
Therefore, instead of using the <jsp:useBean> tag twice, in handler and processor, it would be sufficient to use
BeanName beanID = new BeanName();
request.setAttribute("beanID", beanID);
in handler and
BeanName beanID = (BeanName) request.getAttribute("beanID");
in processor.
Of course, the use of <jsp:useBean> is simpler and better. The above Java snippets are just meant to explain that the <jsp:useBean> tag creates the bean in handler and retrieves the bean in processor. This is true, for the application model described here, because the handler receives the HTTP request and forwards it to the processor.
The first two lines are followed by a scriptlet whose steps are described next:
- The getBeanResources() method of com.devsphere.mapping.HandlerUtils is called to get the bean resources, which contain the file names of the HTML form and JSP processor.
- A base path is constructed by removing the name of the handler from its servlet path. The base path will be used to build the paths of the HTML form and the JSP processor.
- The handler needs to know the HTTP method. GET means that the form is requested for the first time, since the HTML form is supposed to use POST.
- The mapping utilities use a logging mechanism described by the com.devsphere.logging.AbstractLogger class. This way, it is possible to use the framework in different environments (application servers, servlet engines, standalone applications, etc). The com.devsphere.logging.ServletLogger class extends the AbstractLogger class, by delegating the logging operations to a javax.servlet.ServletContext, which is referenced, in JSPs, by a local variable called application.
- The com.devsphere.mapping.FormData abstract class contains getParameter() and getParameterValues() methods similar to those of javax.servlet.ServletRequest. The use of an abstract class allows you to define different mechanisms for form data retrieval even in the absence of the Servlet API. The com.devsphere.mapping.ServletFormData class implements the methods of the FormData abstract class by delegating the form data parsing to javax.servlet.http.HttpServletRequest.
- The formToBean() method of com.devsphere.mapping.FormUtils fills the properties of the bean object with the values of the parameters of the HTTP request. All errors that occur during the mapping process are stored in a hashtable.
- If the HTTP method is POST and there are no errors, the path of the processor is constructed by adding its name to the base path.
- Next, the HTTP request containing the bean object as an attribute is forwarded to the processor, which is responsible for responding to the HTTP request.
- Otherwise, the form must be sent to the user. If the HTTP method is GET, the form was requested for the first time. Therefore, the form data was empty and the formToBean() method reported "missing data" errors. These user errors must be removed with removeUserErrors() of com.devsphere.mapping.HandlerUtils. In the case of the GET method, the roles of formToBean() are to set the default values of the bean properties and signal possible application errors.
- The path of the HTML form is constructed by adding its name to the base path.
- A form template is a factory of form documents. The getTemplate() method of com.devsphere.mapping.FormUtils takes the pure HTML document containing the form, parses it and builds a template. These operations are performed only at the first invocation of the handler because the com.devsphere.mapping.FormTemplate objects created from files are cached.
- Further, the getDocument() method of the FormTemplate object is used to create an instance of com.devsphere.mapping.FormDocument.
- The beanToForm() method of com.devsphere.mapping.FormUtils inserts the values of the properties of the bean object within the form document as the default values of the form elements. The error messages contained by the errorTable object are inserted within the form document too.
- The form document containing default values and error messages is sent to the user.
This is just a typical use of the mapping utilities within a JSP handler that works with a static HTML form and a JSP processor. The mapping utilities are very flexible and can be used in many ways. A later chapter will exemplify how to build handlers that work with dynamic HTML forms. It is also possible to replace the JSPs with servlets. The next chapter will present a generic handler servlet.
Examples of handlers:
A processor is a JSP, a servlet or a Java class that receives valid beans for processing. It could store the bean to a database, file or another repository, it could send it over a network using any protocol or it could perform other tasks.
Devsphere Mapping Framework can be used to convert the bean data to XML or plain text. These mappings are the subject of some later chapters.
Examples of processors:
When you build an application that uses Devsphere Mapping Framework,
- declare the data beans and the resource bundles within the same package.
- provide default values and error messages only for standard and standard indexed properties.
- define the processing order list to control the properties that participate to the mapping process. Don't include read-only properties or other properties that cannot be mapped.
- use list resource bundles to define the default values as primitive wrappers and arrays of primitives. You may use any character encoding supported by the Java compiler, which is helpful if you build internationalized applications.
- use .properties files if you want to be able to change the bean resources without having to recompile the Java code. However, these files are ASCII text files and you must apply the encoding rules used by java.util.Properties.
- the next chapter shows you how to replace the bean-dependent JSP handlers with generic servlets.
- the processors may be servlets too. They can get the bean objects using request.getAttribute(beanID). Or, instead of forwarding the bean for processing, you could pass it to a Java method and return static HTML.
|