An application using Devsphere Mapping Framework is made of
- Data beans
- Forms
- Bean resources
- Handlers
- Processors
All these components were described in a previous chapter. This chapter contains examples for each component type. In addition, it shows an equivalent example that doesn't use the framework and compares the two solutions. It also explains the advantages of using bean-independent servlets.
SimpleBean is a Java bean that contains several standard properties (a String, a float, an int, a boolean and another String), two indexed standard properties (a String[] and an int[]) and another data bean (a SimpleSubBean). The SimpleBean class is declared public, has a no-arg constructor and provides accessors (get & set methods) for its properties. The public constructor could have been omitted, since the Java compiler generates one in the absence of any other constructors.
SimpleBean.java:
package com.devsphere.examples.mapping.simple;
/**
* Simple bean
*/
public class SimpleBean implements java.io.Serializable {
private String string;
private float number;
private int integer;
private boolean flag;
private String colors[];
private int list[];
private String optional;
private SimpleSubBean subBean;
/**
* No-arg constructor
*/
public SimpleBean() {
}
/**
* Gets the string property
*/
public String getString() {
return this.string;
}
/**
* Sets the string property
*/
public void setString(String value) {
this.string = value;
}
/**
* Gets the number property
*/
public float getNumber() {
return this.number;
}
/**
* Sets the number property
*/
public void setNumber(float value) {
this.number = value;
}
/**
* Gets the integer property
*/
public int getInteger() {
return this.integer;
}
/**
* Sets the integer property
*/
public void setInteger(int value) {
this.integer = value;
}
/**
* Gets the flag property
*/
public boolean getFlag() {
return this.flag;
}
/**
* Sets the flag property
*/
public void setFlag(boolean value) {
this.flag = value;
}
/**
* Gets the colors property
*/
public String[] getColors() {
return this.colors;
}
/**
* Sets the colors property
*/
public void setColors(String values[]) {
this.colors = values;
}
/**
* Gets an element of the colors property
*/
public String getColors(int index) {
return this.colors[index];
}
/**
* Sets an element of the colors property
*/
public void setColors(int index, String value) {
this.colors[index] = value;
}
/**
* Gets the list property
*/
public int[] getList() {
return this.list;
}
/**
* Sets the list property
*/
public void setList(int values[]) {
this.list = values;
}
/**
* Gets an element of the list property
*/
public int getList(int index) {
return this.list[index];
}
/**
* Sets an element of the list property
*/
public void setList(int index, int value) {
this.list[index] = value;
}
/**
* Gets the optional property
*/
public String getOptional() {
return this.optional;
}
/**
* Sets the optional property
*/
public void setOptional(String value) {
this.optional = value;
}
/**
* Gets the subBean property
*/
public SimpleSubBean getSubBean() {
return this.subBean;
}
/**
* Sets the subBean property
*/
public void setSubBean(SimpleSubBean value) {
this.subBean = value;
}
}
SimpleSubBean contains only two standard properties (a String and a float).
SimpleSubBean.java:
package com.devsphere.examples.mapping.simple;
/**
* Simple sub-bean
*/
public class SimpleSubBean implements java.io.Serializable {
private String string;
private float number;
/**
* No-arg constructor
*/
public SimpleSubBean() {
}
/**
* Gets the string property
*/
public String getString() {
return this.string;
}
/**
* Sets the string property
*/
public void setString(String value) {
this.string = value;
}
/**
* Gets the number property
*/
public float getNumber() {
return this.number;
}
/**
* Sets the number property
*/
public void setNumber(float value) {
this.number = value;
}
}
The properties of SimpleBean are mapped to the form elements of SimpleForm.html:
Name |
Property type |
Element type |
|
|
|
string |
String |
text |
number |
float |
text |
integer |
int |
radio[] |
flag |
boolean |
checkbox |
colors |
String[] |
checkbox[] |
list |
int[] |
select |
optional |
String |
text |
subBean.string |
String |
text |
subBean.number |
float |
text |
The way the mapping between JavaBeans and HTML forms works was explained in a previous chapter.
SimpleForm.html:
<HTML>
<HEAD><TITLE>Simple form</TITLE></HEAD>
<BODY>
<H3>Simple Example</H3>
<FORM METHOD="POST">
<P> String <BR>
<INPUT TYPE="TEXT" NAME="string" SIZE="20">
<P> Number <BR>
<INPUT TYPE="TEXT" NAME="number" SIZE="20">
<P> Integer <BR>
<INPUT TYPE="RADIO" NAME="integer" VALUE="1">Option 1
<INPUT TYPE="RADIO" NAME="integer" VALUE="2">Option 2
<INPUT TYPE="RADIO" NAME="integer" VALUE="3">Option 3
<P> Flag <BR>
<INPUT TYPE="CHECKBOX" NAME="flag">Flag
<P> Colors <BR>
<INPUT TYPE="CHECKBOX" NAME="colors" VALUE="red">Red
<INPUT TYPE="CHECKBOX" NAME="colors" VALUE="green">Green
<INPUT TYPE="CHECKBOX" NAME="colors" VALUE="blue">Blue
<P> List <BR>
<SELECT NAME="list" SIZE="3" MULTIPLE>
<OPTION VALUE="1">Item 1</OPTION>
<OPTION VALUE="2">Item 2</OPTION>
<OPTION VALUE="3">Item 3</OPTION>
</SELECT>
<P> Optional <BR>
<INPUT TYPE="TEXT" NAME="optional" SIZE="20">
<P> String (subBean) <BR>
<INPUT TYPE="TEXT" NAME="subBean.string" SIZE="20">
<P> Number (subBean) <BR>
<INPUT TYPE="TEXT" NAME="subBean.number" SIZE="20">
<P>
<INPUT TYPE="SUBMIT" VALUE="Submit">
<INPUT TYPE="RESET" VALUE="Reset">
</FORM>
</BODY>
</HTML>
The SimpleBeanResources class is a resource bundle containing optional information that is useful to the mapping process: default values, error messages, the list of optional properties, the processing order, the form's name and the processor's name.
The default values are defined for a String, a float, a boolean and an int[]. The primitive values must be wrapped by a Float and a Boolean in order to be stored as resources. The default values for the properties of the contained bean could have been defined in another resource bundle called SimpleSubBeanResources.
There are three error messages. Their role is to help the users to correct the input errors. The mapping framework contains default error messages for each type of form element.
The list of optional properties has a single element. No error is signaled if the user doesn't provide a value for this property.
The processing order isn't necessary to this example. It has been included here just for demonstrative purposes.
The form's name and the processor's name are used by the JSP handler described in the next section. These two resources aren't accessed by the mapping utilities.
SimpleBeanResources.java:
package com.devsphere.examples.mapping.simple;
public class SimpleBeanResources extends java.util.ListResourceBundle {
private static final Object[][] contents = {
{ "[DEFAULT_VALUE.string]", "abc" },
{ "[DEFAULT_VALUE.number]", new Float(0.123) },
{ "[DEFAULT_VALUE.flag]", new Boolean(true) },
{ "[DEFAULT_VALUE.list]", new int[] { 2, 3 } },
{ "[ERROR_MESSAGE.integer]", "An option must be selected" },
{ "[ERROR_MESSAGE.colors]", "One or more colors must be selected" },
{ "[ERROR_MESSAGE.list]", "One or more items must be selected" },
{
"[OPTIONAL_PROPERTIES]",
new String[] {
"optional"
}
},
{
"[PROCESSING_ORDER]",
new String[] {
"string",
"number",
"integer",
"flag",
"colors",
"list",
"optional",
"subBean"
}
},
{ "[FORM_NAME]", "SimpleForm.html" },
{ "[PROC_NAME]", "SimpleProc.jsp" }
};
public Object[][] getContents() {
return contents;
}
}
The SimpleHndl.jsp handler is based on a template that was described in a previous chapter.
The formToBean() method of com.devsphere.mapping.FormUtils sets the bean properties to the values of the request parameters (form data). If necessary, string values are converted to numbers. A boolean property is set to true if the request parameter is present no matter what its value is (except "false"). The error messages that occur during the mapping process are stored in a Hashtable.
The beanToForm() method of com.devsphere.mapping.FormUtils inserts the bean data and the error messages into the HTML form. It inserts a VALUE attribute for text elements, a CHECKED attribute for checkboxes and radio buttons that must be selected and a SELECTED attribute for the list items that must be highlighted.
For a better understanding of this example, a later section of this chapter lists two JSPs that perform the mapping and build the HTML form without using the framework.
SimpleHndl.jsp:
<%@ page language="java" %>
<%@ page import="com.devsphere.mapping.*, com.devsphere.logging.*" %>
<jsp:useBean id="simpleBean" scope="request"
class="com.devsphere.examples.mapping.simple.SimpleBean"/>
<%
// Get the bean resources
java.util.ResourceBundle beanRes
= HandlerUtils.getBeanResources(simpleBean.getClass());
// Construct the base path
String basePath = request.getServletPath();
int slashIndex = basePath.lastIndexOf('/');
basePath = slashIndex != -1 ? basePath.substring(0, slashIndex+1) : "";
// Determine the HTTP method
boolean isPostMethod = request.getMethod().equals("POST");
// Create a logger that wraps the servlet context
ServletLogger logger = new ServletLogger(application);
// Wrap the form data
FormData formData = new ServletFormData(request);
// Form-to-bean mapping: request parameters are mapped to bean properties
java.util.Hashtable errorTable
= FormUtils.formToBean(formData, simpleBean, logger);
if (isPostMethod && errorTable == null) {
// Construct the processor's path
String procPath = basePath + beanRes.getString("[PROC_NAME]").trim();
// Process the valid data bean instance
application.getRequestDispatcher(procPath).forward(request, response);
} else {
if (!isPostMethod)
// Ignore the user errors if the form is requested with GET.
errorTable = HandlerUtils.removeUserErrors(errorTable);
// Construct the form's path
String formPath = basePath + beanRes.getString("[FORM_NAME]").trim();
formPath = application.getRealPath(formPath);
// Get the form template
FormTemplate template = FormUtils.getTemplate(new java.io.File(formPath));
// Get a new document
FormDocument document = template.getDocument();
// Bean-to-form mapping: bean properties are mapped to form elements
FormUtils.beanToForm(simpleBean, errorTable, document, logger);
// Send the form document
document.send(out);
}
%>
The SimpleProc.jsp processor gets the beans that were validated by the JSP handler and prints the values of their properties.
SimpleProc.jsp:
<%@ page language="java"%>
<jsp:useBean id="simpleBean" scope="request"
class="com.devsphere.examples.mapping.simple.SimpleBean"/>
<HTML>
<HEAD><TITLE>Simple bean</TITLE></HEAD>
<BODY>
<H3>Simple Example</H3>
<P><B> SimpleBean properties: </B>
<P> string = <jsp:getProperty name="simpleBean" property="string"/>
<P> number = <jsp:getProperty name="simpleBean" property="number"/>
<P> integer = <jsp:getProperty name="simpleBean" property="integer"/>
<P> flag = <jsp:getProperty name="simpleBean" property="flag"/>
<P> colors = <%= toString(simpleBean.getColors()) %>
<P> list = <%= toString(simpleBean.getList()) %>
<P> optional = <jsp:getProperty name="simpleBean" property="optional"/>
<P> subBean.string = <%= simpleBean.getSubBean().getString() %>
<P> subBean.number = <%= simpleBean.getSubBean().getNumber() %>
</BODY>
</HTML>
<%!
public static String toString(String list[]) {
if (list == null || list.length == 0)
return "";
if (list.length == 1 && list[0] != null)
return list[0];
StringBuffer strbuf = new StringBuffer();
strbuf.append("{ ");
for (int i = 0; i < list.length; i++)
if (list[i] != null) {
strbuf.append(list[i]);
strbuf.append(" ");
}
strbuf.append("}");
return strbuf.toString();
}
public static String toString(int list[]) {
if (list == null || list.length == 0)
return "";
if (list.length == 1)
return Integer.toString(list[0]);
StringBuffer strbuf = new StringBuffer();
strbuf.append("{ ");
for (int i = 0; i < list.length; i++) {
strbuf.append(list[i]);
strbuf.append(" ");
}
strbuf.append("}");
return strbuf.toString();
}
%>
ComplexForm.jsp generates the HTML form dynamically and inserts default values and error messages. It uses 120 lines of Java-JSP-HTML mixture to generate a 40 lines HTML form. A single call to FormUtils.beanToForm() can do the same using a pure HTML file. In addition, beanToForm() handles and logs many types of application errors, making the testing and the debugging easier.
ComplexHndl.jsp uses 150 lines of Java-JSP mixture to set the properties of a bean object to the values of the request parameters. This is the equivalent of a single FormUtils.formToBean() call.
The adding/removing of a bean property requires changes in both Complex*.jsp files. Using the framework, you only have to add/remove a form element to/from a pure HTML file.
The localization of the Complex*.jsp files to other languages requires a lot of work and could make the maintenance very hard. Using the framework you separate the HTML code from the Java/JSP code. In addition, default values and error messages are kept in localizable resource bundles. A later chapter shows how to build internationalized applications using the framework.
ComplexForm.jsp:
<%@ page language="java" %>
<jsp:useBean id="simpleBean" scope="request"
class="com.devsphere.examples.mapping.simple.SimpleBean"/>
<jsp:useBean id="errorTable" scope="request"
class="java.util.Hashtable"/>
<HTML>
<HEAD><TITLE>Without using the framework</TITLE></HEAD>
<BODY>
<H3>Equivalent of Simple Example</H3>
<FORM METHOD=POST>
<P> String <BR>
<%= getErrorMessage(errorTable, "string") %>
<INPUT TYPE="TEXT" NAME="string"
VALUE="<jsp:getProperty name="simpleBean" property="string"/>">
<P> Number <BR>
<%= getErrorMessage(errorTable, "number") %>
<INPUT TYPE="TEXT" NAME="number"
VALUE="<jsp:getProperty name="simpleBean" property="number"/>">
<P> Integer <BR>
<%= getErrorMessage(errorTable, "integer") %>
<%
String integerLabels[] = { "Option 1", "Option 2", "Option 3" };
for (int i = 0; i < integerLabels.length; i++) {
int value = i+1;
boolean checked = simpleBean.getInteger() == value;
%>
<INPUT TYPE="RADIO" NAME="integer" VALUE="<%= value %>"
<%= checked ? "CHECKED" : "" %>> <%= integerLabels[i] %>
<%
}
%>
<P> Flag <BR>
<%= getErrorMessage(errorTable, "flag") %>
<INPUT TYPE="CHECKBOX" NAME="flag"
<%= simpleBean.getFlag() ? "CHECKED" : "" %>> Flag
<P> Colors <BR>
<%= getErrorMessage(errorTable, "colors") %>
<%
String colors[] = simpleBean.getColors();
if (colors == null)
colors = new String[0];
String colorLabels[] = { "Red", "Green", "Blue" };
String colorValues[] = { "red", "green", "blue" };
for (int i = 0; i < colorValues.length; i++) {
boolean checked = false;
if (colors != null)
for (int j = 0; j < colors.length; j++)
if (colors[j].equals(colorValues[i])) {
checked = true;
break;
}
%>
<INPUT TYPE="CHECKBOX" NAME="colors" VALUE="<%= colorValues[i] %>"
<%= checked ? "CHECKED" : "" %>> <%= colorLabels[i] %>
<%
}
%>
<P> List <BR>
<%= getErrorMessage(errorTable, "list") %>
<SELECT NAME="list" SIZE="3" MULTIPLE>
<%
int list[] = simpleBean.getList();
if (list == null)
list = new int[0];
String listItems[] = { "Item 1", "Item 2", "Item 3" };
for (int i = 0; i < listItems.length; i++) {
int value = i+1;
boolean selected = false;
if (list != null)
for (int j = 0; j < list.length; j++)
if (list[j] == value) {
selected = true;
break;
}
%>
<OPTION VALUE = "<%= value %>"
<%= selected ? "SELECTED" : "" %>> <%= listItems[i] %>
<%
}
%>
</SELECT>
<P> Optional <BR>
<%= getErrorMessage(errorTable, "optional") %>
<INPUT TYPE="TEXT" NAME="optional"
VALUE="<jsp:getProperty name="simpleBean" property="optional"/>">
<% if (simpleBean.getSubBean() == null) simpleBean.setSubBean(
new com.devsphere.examples.mapping.simple.SimpleSubBean()); %>
<P> String (subBean) <BR>
<%= getErrorMessage(errorTable, "subBean.string") %>
<INPUT TYPE="TEXT" NAME="subBean.string"
VALUE="<%= simpleBean.getSubBean().getString() %>">
<P> Number (subBean) <BR>
<%= getErrorMessage(errorTable, "subBean.number") %>
<INPUT TYPE="TEXT" NAME="subBean.number"
VALUE="<%= simpleBean.getSubBean().getNumber() %>">
<P>
<INPUT TYPE="SUBMIT" VALUE="Submit">
<INPUT TYPE="RESET" VALUE="Reset">
</FORM>
</BODY>
</HTML>
<%!
String getErrorMessage(java.util.Hashtable errorTable, String property) {
String message = (String) errorTable.get(property);
if (message == null)
message = "";
return message;
}
%>
ComplexHndl.jsp:
<%@ page language="java" %>
<jsp:useBean id="simpleBean" scope="request"
class="com.devsphere.examples.mapping.simple.SimpleBean"/>
<jsp:useBean id="simpleSubBean" scope="page"
class="com.devsphere.examples.mapping.simple.SimpleSubBean"/>
<jsp:useBean id="errorTable" scope="request"
class="java.util.Hashtable"/>
<%
simpleBean.setSubBean(simpleSubBean);
boolean isPostMethod = request.getMethod().equals("POST");
if (isPostMethod) {
//* string : text
%>
<jsp:setProperty name="simpleBean" property="string"/>
<%
if (simpleBean.getString() == null
|| simpleBean.getString().length() == 0) {
simpleBean.setString("abc");
setErrorMessage(errorTable, "string", "Must be filled");
}
//* number : text
try {
String numberValue = request.getParameter("number");
if (numberValue != null && numberValue.length() != 0)
simpleBean.setNumber(new Float(numberValue).floatValue());
else {
simpleBean.setNumber(0.123f);
setErrorMessage(errorTable, "number", "Must be filled");
}
} catch (NumberFormatException e) {
simpleBean.setNumber(0.123f);
setErrorMessage(errorTable, "number", "Must be a number");
}
//* integer : radio group
%>
<jsp:setProperty name="simpleBean" property="integer"/>
<%
if (simpleBean.getInteger() == 0) {
setErrorMessage(errorTable, "integer", "An option must be selected");
}
//* flag : checkbox
String flagValue = request.getParameter("flag");
if (flagValue != null) {
flagValue = flagValue.trim();
if (flagValue.length() == 0 || flagValue.equals("false"))
flagValue = null;
}
simpleBean.setFlag(flagValue != null);
//* color : checkbox group
%>
<jsp:setProperty name="simpleBean" property="colors"/>
<%
if (simpleBean.getColors() == null
|| simpleBean.getColors().length == 0) {
setErrorMessage(errorTable, "colors",
"One or more colors must be selected");
}
//* list : select
%>
<jsp:setProperty name="simpleBean" property="list"/>
<%
if (simpleBean.getList() == null
|| simpleBean.getList().length == 0) {
simpleBean.setList(new int[] { 2, 3 });
setErrorMessage(errorTable, "list",
"One or more items must be selected");
}
//* optional : text
%>
<jsp:setProperty name="simpleBean" property="optional"/>
<%
if (simpleBean.getOptional() == null)
simpleBean.setOptional("");
//* subBean.string : text
%>
<jsp:setProperty name="simpleSubBean" property="string"
param="subBean.string"/>
<%
if (simpleSubBean.getString() == null
|| simpleSubBean.getString().length() == 0) {
simpleSubBean.setString("");
setErrorMessage(errorTable, "subBean.string", "Must be filled");
}
//* subBean.number : text
try {
String numberValue = request.getParameter("subBean.number");
if (numberValue != null && numberValue.length() != 0)
simpleSubBean.setNumber(new Float(numberValue).floatValue());
else {
setErrorMessage(errorTable, "subBean.number", "Must be filled");
}
} catch (NumberFormatException e) {
setErrorMessage(errorTable, "subBean.number", "Must be a number");
}
} else {
simpleBean.setString("abc");
simpleBean.setNumber(0.123f);
simpleBean.setFlag(true);
simpleBean.setList(new int[] { 2, 3 });
simpleBean.setOptional("");
simpleSubBean.setString("");
}
if (isPostMethod && errorTable.isEmpty()) {
%>
<jsp:forward page="SimpleProc.jsp"/>
<%
} else {
%>
<jsp:forward page="ComplexForm.jsp"/>
<%
}
%>
<%!
void setErrorMessage(java.util.Hashtable errorTable,
String property, String message) {
message = "<FONT COLOR=\"#FF0000\">" + message + "</FONT><BR>";
errorTable.put(property, message);
}
%>
The SimpleHndl.jsp handler is basically a Java scriptlet. That was a simple and compact way to present a handler. The Java code could easily be moved to a utility class. A more elegant solution is the replacement of the JSP handler with a general Java servlet.
The com.devsphere.helpers.mapping package contains an abstract class called GenericHandler. This class is extended by BeanDispatcher, which is the bean-independent equivalent of SimpleHndl.jsp. The JSP handler can be replaced by only a few lines that are added to servlets.properties or web.xml:
SimpleHndl.code=com.devsphere.helpers.mapping.BeanDispatcher
SimpleHndl.initparams=\
BEAN_NAME=com.devsphere.examples.mapping.simple.SimpleBean,\
BEAN_ID=simpleBean,\
BASE_PATH=/simple
or
<servlet>
<servlet-name>SimpleHndl</servlet-name>
<servlet-class>com.devsphere.helpers.mapping.BeanDispatcher</servlet-class>
<init-param>
<param-name>BEAN_NAME</param-name>
<param-value>com.devsphere.examples.mapping.simple.SimpleBean</param-value>
</init-param>
<init-param>
<param-name>BEAN_ID</param-name>
<param-value>simpleBean</param-value>
</init-param>
<init-param>
<param-name>BASE_PATH</param-name>
<param-value>/simple</param-value>
</init-param>
</servlet>
GenericHandler and BeanDispatcher were presented in a previous chapter.
Using a JSP, you have to declare the bean within a <jsp:useBean> tag. If your Web application contains many forms/beans, you have to provide a JSP handler for each bean. A servlet can be made bean-independent.
In many cases, a servlet is identified with its class. Users invoke the servlet by requesting a URL like this:
http://www.host.com/AppName/servlet/ServletName
The servlet engine associates a servlet to a class in the servlets.properties (or web.xml) file:
ServletName.code=com.company.ClassName
There is nothing that can stop you associating many servlets with the same class. You may use the same class to declare one servlet for each bean component. A standard servlet engine running on a single JVM will instantiate the servlet class once for each servlet declaration. All requests to one of the declared servlets will be serviced by the same instance of the servlet class.
The previous section showed how to declare a BeanDispatcher servlet. If you have another bean-form pair, you could add a few other lines to servlets.properties:
AnotherHndl.code=com.devsphere.helpers.mapping.BeanDispatcher
AnotherHndl.initparams=\
BEAN_NAME=com.devsphere.examples.mapping.another.AnotherBean,\
BEAN_ID=anotherBean,\
BASE_PATH=/another
The two servlets that share the same code could be invoked with something like this
http://www.host.com/AppName/servlet/SimpleHndl
http://www.host.com/AppName/servlet/AnotherHndl
The BeanDispatcher servlet replaces the JSP handler, but still needs the JSP processor. If you want to run the Web application on a servlet engine that doesn't support JSP then you may define your own servlet extending GenericServlet. If you just need to send the form data to an e-mail address then you may use the FormMailer servlet of the com.devsphere.apps.mapping.mailer package, which is part of a full application that uses JavaMail.
This guide contains another example application that uses a similar bean with several differences:
- the main bean contains an array of beans
- the bean resources are stored in .properties files
- the valid beans are converted to text and emailed to one or more addresses
- a standalone application monitors the mail server, converts the messages to bean objects and passes them to a dynamically loaded processor
|