8.2 Validating User Input
You should never trust your users, at least not when it comes to entering information in the format you need. Often, you need to make sure the input is valid before you continue to process a request. A date, for instance, can be written in many different formats. If you've traveled to the United States, and you're not a U.S. citizen, you probably have had to fill out both an I-94 and a customs declaration form to be admitted by an immigration officer. You may have noticed that on one of the forms you need to write your birth date as yy/mm/dd and on the other as mm/dd/yy. I always get it wrong.
The entry form used in the examples in this chapter has a number of fields that must be validated: a name must be entered, the birth date must be a valid date, the email address must at least look like a real email address (it's basically impossible to verify that it is in fact real), the gender must be one of m (male) or f (female), the lucky number must be a number between 1 and 100, and if any food favorites are selected, each must be one of z (pizza), p (pasta), or c (Chinese).
Simple input can be validated using the standard JSTL actions, but for more complex validation rules, a bean is a good choice. We will look at both approaches next. If you use JSP combined with servlets, the input validation is typically done by the servlet and the JSP pages are invoked only if the input turns out to be okay. This approach is described in Chapter 19.
8.2.1 Validating User Input Using JSTL Actions
Besides adding validation, let's make the input form example a bit more realistic. Instead of just echoing the entered values at the end of the page, we use them to set the initial values of the form fields. This makes it easier for the user to correct the mistakes. For each invalid value, an error message is also inserted above the incorrect field.
I use a number of JSTL actions that we have not discussed so far and a few tricks to implement these changes. To make all the new stuff easier to digest, we look at the new page in pieces. Example 8-5 shows the top part of the form with the validation and initialization of the Name field.
Example 8-5. Validating the name parameter with JSTL (validate_jstl.jsp)
<%@ page contentType="text/html" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>User Info Entry Form</title> </head> <body bgcolor="white"> <form action="validate_jstl.jsp" method="post"> <input type="hidden" name="submitted" value="true"> <table> <c:if test="${param.submitted && empty param.userName}"> <tr><td></td> <td colspan="2"><font color="red"> Please enter your Name </font></td></tr> </c:if> <tr> <td>Name:</td> <td> <input type="text" name="userName" value="<c:out value="${param.userName}" />"> </td> </tr>
The first thing to notice in this example is the HTML field of type "hidden", named submitted with the value true. The browser doesn't display a hidden field, but its value is sent as a regular request parameter when the form is submitted. I use it in this example to avoid displaying error messages when the page is loaded for the first time, before the user has had a chance to enter any data. The submitted parameter isn't part of the first request for the page, but when the user submits the form, the submitted parameter is sent along with all parameters representing the other HTML fields. Hence, it can be used to test if the parameters should be validated or not.
The validation of the Name field illustrates how it works. The JSTL <c:if> action, described in Table 8-5, is used with an EL expression that evaluates to true only if the submitted parameter has the value true and the userName parameter is empty. Since the submitted parameter isn't part of the initial request to load the page, it doesn't have the value true, causing the EL expression to evaluate to false. The <c:if> action's body is therefore ignored in this case. After submitting the form, however, the submitted parameter has the value true, so if the userName parameter contains an empty string (the user didn't enter a value in the Name field), the body is processed, adding the error message.
Table 8-5. Attributes for JSTL <c:if>|
test
|
boolean
|
Yes
|
Mandatory. An expression that evaluates to true or false.
|
var
|
String
|
No
|
Optional. The name of the variable to hold the Boolean result.
|
scope
|
String
|
No
|
Optional. The scope for the variable, one of page, request, session, or application. page is the default.
|
To make it easy for the user to correct mistakes, the form fields are initialized with the submitted values. The <c:out> action with an EL expression that gets the corresponding parameter value takes care of this.
A note about the empty operator seems warranted, because this is an operator you don't find in most languages. It's included in the EL to avoid having to deal with the difference between a null value (the absence of a value) and the empty string value ("") because in a web application, you typically want to treat both cases the same way. Without the empty operator, you would have to write all tests like the ones in Example 8-5 like this instead:
<c:if test="${param.submitted && (param.userName == null || param.userName == '')}">
The empty operator is shorthand for the combination of the last two tests. In addition to empty strings and null, it also evaluates to true for an empty array, java.util.Collection, or java.util.Map. In other words, you can use it to test for empty collections of all types.
Another fairly unique feature in the EL is that you have a choice with regards to the symbols for the common operators. For instance, instead of using && as the logical AND operator, || for logical OR, and ! for logical NOT, you can use and, or, and not. The relational operators can be written as ==, !=, <, <=, >, and >=, or as eq, ne, lt, le, gt, and ge, respectively. Besides catering to different personal preferences, the motivation for this is to provide a consistent set of operator symbols for use in pure XML documents (as described in Chapter 17) in which some of the most commonly used symbols can cause problems (e.g., < and &&).
Example 8-6 shows the validation and initialization of the Birth Date and Email Address fields.
Example 8-6. Validating the birth date and email parameters with JSTL (validate_jstl.jsp)
<c:if test="${param.submitted && empty param.birthDate}"> <tr><td></td> <td colspan="2"><font color="red"> Please enter your Birth Date </font></td></tr> </c:if> <tr> <td>Birth Date:</td> <td> <input type="text" name="birthDate" value="<c:out value="${param.birthDate}" />"> </td> <td>(Use format yyyy-mm-dd)</td> </tr> <c:if test="${param.submitted && empty param.emailAddr}"> <tr><td></td> <td colspan="2"><font color="red"> Please enter your Email Address </font></td></tr> </c:if> <tr> <td>Email Address:</td> <td> <input type="text" name="emailAddr" value="<c:out value="${param.emailAddr}" />"> </td> <td>(Use format name@company.com)</td> </tr>
As you can see, the processing for these fields is identical to the pattern used for the Name field. A <c:if> action tests if the form is submitted and the parameter corresponding to the field is empty, and if so, adds an error message. The submitted value of the field is added with a <c:out> action.
For the Gender field (radio button), the value must be either m (male) or f (female). This requires a slightly different test condition, as shown in Example 8-7.
Example 8-7. Validating the gender parameter with JSTL (validate_jstl.jsp)
<c:if test="${param.submitted && param.gender != 'm' && param.gender != 'f'}"> <tr><td></td> <td colspan="2"><font color="red"> Please select a valid Gender </font></td></tr> </c:if> <tr> <td>Gender:</td> <td> <c:choose> <c:when test="${param.gender == 'f'}"> <input type="radio" name="gender" value="m"> Male<br> <input type="radio" name="gender" value="f" checked> Female </c:when> <c:otherwise> <input type="radio" name="gender" value="m" checked> Male<br> <input type="radio" name="gender" value="f"> Female </c:otherwise> </c:choose> </td> </tr>
In addition to testing if the form is submitted, we must test if the value is m or f. It's done by simply adding more subexpressions, combined using the && operator. You can combine as many subexpressions as you need in this way.
The Gender field isn't represented by a text field but by a radio button, so another approach is also needed for initializing it with the submitted value. To make a radio button be displayed as selected, the checked attribute must be added to the HTML element. The JSTL <c:choose> action helps us with this task.
The <c:choose> action has no attributes; it just groups and controls any number of nested <c:when> actions and optionally one <c:otherwise> action. These are the only actions that are accepted as direct child elements of a <c:choose> element. A <c:choose> block is used to pick one of a set of related, mutually exclusive alternatives. The <c:choose> action makes sure that only the first <c:when> action (Table 8-6) with a test attribute value that evaluates to true is processed. If no <c:when> action meets its test condition, the <c:otherwise> body is processed instead. If you're a programmer, you may recognize this as being similar to a switch statement.
Table 8-6. Attributes for JSTL <c:when>|
test
|
boolean
|
Yes
|
Mandatory. An expression that evaluates to true or false.
|
In Example 8-7, the <c:choose> action contains one <c:when> action that tests if the gender parameter has the value f, and if so, adds both radio button fields with the one representing the f choice as selected. The <c:otherwise> action adds the radio button fields with the one representing m as selected.
The effect is that the m choice becomes the default, used if the submitted value is invalid. It may seem redundant to handle invalid values for a parameter representing a radio button, but it isn't. Even though using a group of radio buttons helps the regular user pick a valid value, you must guard against requests submitted through other means than the form. It's easy for someone to submit an HTTP request to your page with any value. For instance, see what happens if you request the page with a query string like this:
http://localhost:8080/ora/ch8/validate_jstl.jsp?submitted=true&gender=x
Since the page checks for valid values even for the radio buttons, the x value for the gender parameter results in an error message.
Next up is the processing of the Lucky Number field, in which the value must be a number between 1 and 100. Example 8-8 shows how you can test for this.
Example 8-8. Validating the lucky number parameter with JSTL (validate_jstl.jsp)
<c:if test="${param.submitted && (param.luckyNumber < 1 || param.luckyNumber > 100)}"> <tr><td></td> <td colspan="2"><font color="red"> Please enter a Lucky Number between 1 and 100 </font></td></tr> </c:if> <tr> <td>Lucky number:</td> <td> <input type="text" name="luckyNumber" value="<c:out value="${param.luckyNumber}" />"> </td> <td>(A number between 1 and 100)</td> </tr>
Compared to the test for the Gender field, there's one difference: the subexpressions for less than 1 or greater than 100 are placed within parentheses. Parentheses can be used in an EL expression to override the default rules for in which order subexpressions are evaluated, known as the operator precedence rules. The EL operator precedence rules say that the && operator is evaluated before the || operator. Without the parentheses around the range check, the expression is evaluated as "if submitted, and the number is less than 1," and only if that is false, evaluate "if the number is greater than 100." With the parentheses, it's evaluated as "if submitted" and if that's true, evaluate "if the number is less than 1 or greater than 100." In this particular case, the result would be the same, but when you mix && and || operators, it's always a good idea to group the subexpressions with parentheses to avoid surprises.
Example 8-9 shows the most complex validation case: the list of food choices. Here the food parameter may have none or many values, and each value must be one of z (pizza), p (pasta), or c (Chinese).
Example 8-9. Validating the food parameter with JSTL (validate_jstl.jsp)
<c:forEach items="${paramValues.food}" var="current"> <c:choose> <c:when test="${current == 'z'}"> <c:set var="pizzaSelected" value="true" /> </c:when> <c:when test="${current == 'p'}"> <c:set var="pastaSelected" value="true" /> </c:when> <c:when test="${current == 'c'}"> <c:set var="chineseSelected" value="true" /> </c:when> <c:otherwise> <c:set var="invalidSelection" value="true" /> </c:otherwise> </c:choose> </c:forEach> <c:if test="${invalidSelection}"> <tr><td></td> <td colspan="2"><font color="red"> Please select only valid Favorite Foods </font></td></tr> </c:if> <tr> <td>Favorite Foods:</td> <td> <input type="checkbox" name="food" value="z" ${pizzaSelected ? 'checked' : ''}>Pizza<br> <input type="checkbox" name="food" value="p" ${pastaSelected ? 'checked' : ''}>Pasta<br> <input type="checkbox" name="food" value="c" ${chineseSelected ? 'checked' : ''}>Chinese </td> </tr> <tr> <td colspan="3"> <input type="submit" value="Send Data"> </td> </tr> </table> </form> </body> </html>
The approach I use for this test is to loop through all submitted values (using the paramValues variable) with <c:forEach>, testing each value with the <c:choose> action and nested <c:when> and <c:otherwise> actions, setting a "selected" variable to true for each valid value and an invalidSelection variable to true for an invalid value. To set the variables, I use the JSTL <c:set> action, described in Table 8-7.
Table 8-7. Attributes for JSTL <c:set>|
value
|
Any type
|
Yes
|
Mandatory, unless the body is used to provide the value. The value to set.
|
var
|
String
|
No
|
Optional. The name of the variable to hold the value. If not specified, the target and property attributes must be used.
|
scope
|
String
|
No
|
Optional. The scope for the variable specified by var, one of page, request, session, or application. page is the default.
|
target
|
A JavaBeans object or a java.util.Map
|
Yes
|
Optional. A Map or a JavaBeans object with a property specified by property.
|
property
|
String
|
Yes
|
Optional. The property name for the object specified by target that should be set.
|
Once these test variables are set based on the input, it's easy to decide whether to add an error message; just test if invalidSelection is true.
The test variables also allow us to use the conditional EL operator to decide when to add the checked attribute for checkboxes:
<input type="checkbox" name="food" value="z" ${pizzaSelected ? 'checked' : ''}>Pizza<br> <input type="checkbox" name="food" value="p" ${pastaSelected ? 'checked' : ''}>Pasta<br> <input type="checkbox" name="food" value="c" ${chineseSelected ? 'checked' : ''}>Chinese
The conditional operator works with a Boolean subexpression (an expression that evaluates to true or false), followed by a question mark and two alternative clauses separated by a colon. The first clause is used if the Boolean expression evaluates to true; the second clause is used if it's false. If the test variable for the checkbox is set to true, the text "checked" is added; otherwise an empty string is added.
8.2.2 Validating User Input Using a Bean
If you think using JSTL to validate input looks complicated, you're right. It works fine for simple validation, like making sure a value has a value at all (as for the Name field) or that a parameter has one of a few specific values (as for the Gender choice). But with more complex validation, such as verifying that a parameter holds an email address or a credit-card number, or that a value matches a list of valid values held in a database, we can do a lot better with a bean. In fact, the format of the Birth Date and Email Address fields, and if the Lucky Number is something else than a number, isn't checked at all in the JSTL validation example. Other examples in this book will show how you can use custom actions to do more thorough validation of these types of values, but here we look at how it's done using a bean. Figure 8-3 shows a typical response when some fields have invalid values.
Since a bean is implemented with Java code and has access to all Java APIs, it can do any kind of validation you can dream of. The UserInfoBean used in the previous bean example also has a number of validation properties, described in Table 8-8. If you're curious about the bean implementation, it's described in Chapter 20.
Table 8-8. Validation properties for com.ora.jsp.beans.userinfo.UserInfoBean|
userNameValid
|
boolean
|
Read
|
Is a user name set?
|
birthDateValid
|
boolean
|
Read
|
Is the birth date in the format yyyy-mm-dd?
|
emailAddrValid
|
boolean
|
Read
|
Is the email address in the format name@company.com?
|
genderValid
|
boolean
|
Read
|
Is the gender m or f?
|
luckyNumberValid
|
boolean
|
Read
|
Is lucky number between 1 and 100?
|
foodValid
|
boolean
|
Read
|
Does the food list only contain z, p, and c elements?
|
valid
|
boolean
|
Read
|
Do all properties have valid values?
|
pizzaSelected
|
boolean
|
Read
|
Is one of the elements in the food list a z?
|
pastaSelected
|
boolean
|
Read
|
Is one of the elements in the food list a p?
|
chineseSelected
|
boolean
|
Read
|
Is one of the elements in the food list a c?
|
All these properties are read-only, because the bean calculates their values based on the properties holding user data. The first six properties correspond one-to-one to the individual user data properties, while the valid property provides an easy way to see if all properties have valid values. The last three aren't really validation properties; they tell if a specific food type is part of the list of favorite foods.
These properties make the validation task much easier than in the JSTL example. As before, we look at one piece at the time, starting with the Name field processing in Example 8-10.
Example 8-10. Validating the name with a bean (validate_bean.jsp)
<%@ page contentType="text/html" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>User Info Entry Form</title> </head> <body bgcolor="white"> <jsp:useBean id="userInfo" class="com.ora.jsp.beans.userinfo.UserInfoBean"> <jsp:setProperty name="userInfo" property="*" /> </jsp:useBean> <form action="validate_bean.jsp" method="post"> <input type="hidden" name="submitted" value="true"> <table> <c:if test="${param.submitted && userInfo.userNameValid == false}"> <tr><td></td> <td colspan="2"><font color="red"> Please enter your Name </font></td></tr> </c:if> <tr> <td>Name:</td> <td> <input type="text" name="userName" value="<c:out value="${userInfo.userName}" />"> </td> </tr>
Like in Example 8-4, the <jsp:useBean> and <jsp:setProperty> actions capture the user input. The only difference is that these action elements are now at the top of the page. The bean is created and initialized before it tests for valid input and fills out the form with the previously entered values. Using the hidden field to avoid displaying error messages the first time the page is loaded is a trick we used in the JSTL version of the page as well.
The validation and setting the field value is a little bit different than in the JSTL example, but not much. Instead of testing if the userName parameter is equal to an empty string, the userNameValid bean property is compared to the Boolean value false. Even though it doesn't look like we have simplified life much, we have. All logic for deciding what is a valid value is now encapsulated in the bean instead of being coded in the page. If at a future date you decide to develop stricter rules for what a name must look like (maybe scan for profanities), you have to change only the bean; all pages where the bean is used remain the same. The Name field is then set to the value the user submitted, if any, with a <c:out> action using the bean's userName property value.
Example 8-11 shows how the birth date value is processed.
Example 8-11. Validating the birth date with a bean (validate_bean.jsp)
<c:if test="${param.submitted && !userInfo.birthDateValid}"> <tr><td></td> <td colspan="2"><font color="red"> Please enter a valid Birth Date </font></td></tr> </c:if> <tr> <td>Birth Date:</td> <td> <input type="text" name="birthDate" value="<c:out value="${userInfo.birthDate}" />"> </td> <td>(Use format yyyy-mm-dd)</td> </tr>
Testing the value of the bean's birthDateValid property, following the same pattern as for the name, handles the validation. But if you look carefully, you notice that instead of testing for equality with the value false, this test uses the !userInfo.birthDateValid syntax instead. This is just shorthand for the same kind of test. The ! operator means "if the value is true, treat it as false, and vice versa." Formally, this operator is called the logical complement operator. I normally use the shorthand syntax because it's easier to type.
What's more interesting in Example 8-6 than the syntax difference is that as with the name parameter test, all validation logic is encapsulated in the bean. Testing if a date is valid can be quite a challenge. For instance, February 29 is a valid date only for a leap year. By delegating the validation to the bean and using only the result in the JSP page, the page author doesn't need to know any of these details. The Birth Date field value is set by, you guessed it, a <c:out> action using the bean's birthDate property.
The Email Address and the Lucky Number fields are handled the same way as Name and Birth Date.
The Gender field is dealt with pretty much the same as in the JSTL version, as shown in Example 8-12.
Example 8-12. Validating the gender choice with a bean (validate_bean.jsp)
<c:if test="${param.submitted && !userInfo.genderValid}"> <tr><td></td> <td colspan="2"><font color="red"> Please select a valid Gender </font></td></tr> </c:if> <tr> <td>Gender:</td> <td> <c:choose> <c:when test="${userInfo.gender == 'f'}"> <input type="radio" name="gender" value="m"> Male<br> <input type="radio" name="gender" value="f" checked> Female </c:when> <c:otherwise> <input type="radio" name="gender" value="m" checked> Male<br> <input type="radio" name="gender" value="f"> Female </c:otherwise> </c:choose> </td> </tr>
The only differences are that the bean's genderValid property is used for the validation test, and the gender property is used to decide which choice to mark as checked, instead of the parameter value used for both these tasks in the JSTL version.
Example 8-13 shows that the biggest bang for the buck we get from using a bean instead of just JSTL is the simplified processing of the favorite food choices.
Example 8-13. Validating the food choices with a bean (validate_bean.jsp)
<c:if test="${param.submitted && !userInfo.foodValid}"> <tr><td></td> <td colspan="2"><font color="red"> Please select only valid Favorite Foods </font></td></tr> </c:if> <tr> <td>Favorite Foods:</td> <td> <input type="checkbox" name="food" value="z" ${userInfo.pizzaSelected ? 'checked' : ''}>Pizza<br> <input type="checkbox" name="food" value="p" ${userInfo.pastaSelected ? 'checked' : ''}>Pasta<br> <input type="checkbox" name="food" value="c" ${userInfo.chineseSelected ? 'checked' : ''}>Chinese </td> </tr>
All the looping and testing of the individual values that is necessary in the JSTL version of the page are now encapsulated in the bean, so all that's needed here is to use the bean's properties to decide whether to add an error message and which checkboxes to check.
|
No comments:
Post a Comment