Up until now, all the bean properties have have been simple types: strings of text or numbers. This feature of the examples that have appeared is not a fundamental restriction on beans themselves. Beans can contain compound values as well.
Compound values, as the name implies, contain multiple pieces of data. If this sounds familiar, it should; beans themselves hold multiple pieces of data. Indeed, beans can contain other beans, which can contain yet other beans, and so on, indefinitely.
As an example of how this might be useful, consider how a bean would be used to model a home entertainment system, which may contain many individual components, such as an amplifier, CD player, cassette player, and radio tuner. The system as a whole may have certain properties, such as which component is currently playing and the overall color and size of the system. In addition, each individual component has its own set of properties. The CD player has a property representing the name of the disc it currently contains, the tuner has a property indicating the station it is currently tuned to, and so on.
It would in principle be possible to give all the properties of the components to the system as a whole, but this is bad design. It is much better to encapsulate logical units as separate beans. This design allows more complex beans to be constructed incrementally by using the individual building blocks, in the same way that using the jsp:include tag allows complex pages to be built up from smaller ones.
Given the home entertainment system bean, there is no way in which the jsp:getProperty tag could be used to determine the name of the CD in the CD player. The whole CD player bean could be obtained with
<jsp:getProperty id="homeEntertainment" property="cdPlayer"/>
However, this will display the whole CD player bean. Beans as a whole have no standard representation; this might display as something cryptic, such as com.awl.ch04.jspbook.CdBean10b053, or it might display as a list of all the properties of the bean or anything else that the bean programmer has chosen. In any case, it is unlikely to display only the name of the current disc. What is needed is a way to traverse a set of compound data. Fortunately, the expression language provides a mechanism to do this.
As discussed previously, within the expression language, a single dot between two names indicates that the name on the left should be a bean and the name on the right a property. This extends in a natural way; if a property is itself a bean, it is legal to add another dot followed by the name of a property within that bean, and so on. Getting the name of CD from a CD player within a home entertainment system would therefore look something like
${homeEntertainment.cdPlayer.currentDisk}
The meaning of the multiple dots in Listing 4.7 should make more sense now. An object called pageContext holds information about the page currently being generated. Within this object is another object, called request, which holds information pertaining to the request being processed. Finally, the request object has such data as the name of the local computer, the remote computer, and so on.
4.6.1 Repeating a Section of a Page
Another important kind of compound data is a collection of an arbitrary number of values. A CD has a number of tracks, but as this number is different for different CDs, a CD bean cannot simply have a different property for each track. Similarly, a shopping cart bean will contain a number of items, but this number will change as the bean is used.
Java has many ways to manage collections of varying size, but the simplest is called an array. Arrays are lists of objects of the same type, such as arrays of strings, arrays of numbers, and arrays of CDs. Within these arrays, items are referenced by a number called the index, starting with 0.
The expression language makes it possible to pull a particular element out of such an array by placing its index within brackets. Obtaining the first track of a CD could be done with an expression like this:
${cd.tracks[0]}
Again, note how this logically follows from the way properties work: ${cd.tracks} would return the entire array; following this with [0] pulls out a particular element from that array.
Errors to Watch For
If a request is made for an index beyond the number of elements in the array, the result will be empty.
It is unusual to need to access a particular element in an array; it is more common to need to repeat some action for every element, regardless of how many there are. This process is known as iteration, and it should come as no surprise that a tag in the standard library handles it: jsp:forEach. Recall that Listing 3.13 obtained information about a CD from a serialized bean. At that point, however, there was no way to list the tracks, because the page could not know in advance how many there would be. Listing 4.8 uses the c:forEach tag to solve this problem, and the resulting page is shown in Figure 4.2.
Figure 4.2. Iteration used to display every element in an array.
Listing 4.8 The forEach tag
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <jsp:useBean id="album" beanName="tinderbox4" type="com.awl.jspbook.ch04.AlbumInfo"/> <h1><c:out value="${album.name}"/></h1> Artist: <jsp:getProperty name="album" property="artist"/><p> Year: <c:out value="${album.year}"/></p> Here are the tracks: <ul> <c:forEach items="${album.tracks}" var="track"> <li><c:out value="${track}"/> </c:forEach> </ul>
The c:forEach tag takes a number of parameters. The first is the items to iterate over, which is specified by a script. The second is a name to use as a variable; within the body of c:forEach, this variable will be set to each element in the array in turn. This variable can be accessed by the expression language as a bean, which means, among other things, that the c:out tag can be used to display it.
Errors to Watch For
If something other than an array is used as the items parameter, the c:forEach tag will treat it as if it were an array with one element.
4.6.2 Optionally Including Sections of a Page
Iteration allows a page to do one thing many times. The other major type of control a page may need is determining whether to do something at all. The custom awl:maybeShow tag introduced at the beginning of this chapter handled a limited version of that problem, but the standard tag library provides a number of much more general mechanisms, called collectively the conditional tags. The most basic of these tags is called c:if.
In its most common form, the c:if tag takes a single parameter, test, whose value will be a script. This script should perform a logical check, such as comparing two values, and facilities are provided to determine whether two values are equal, the first is less than the second, the first is greater than the second, and a number of other possibilities. Listing 4.9 shows how the c:if tag can work with a bean to determine whether to show a block of text.
Listing 4.9 The if tag
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="awl" uri="http://jspbook.awl.com/samples" %> <jsp:useBean id="form" class="com.awl.jspbook.ch04.FormBean"/> <jsp:setProperty name="form" property="*"/> <c:if test="${form.shouldShow == 'yes'}"> The time is: <awl:date format="hh:mm:ss MM/dd/yy"/> </c:if>
Note the expression in the script for the test parameter. Two equal signs, ==, are used to check two values for equality. Here, the first value comes from a property and is obtained with the normal dotted notation. The second value, yes, is a constant, or literal, which is reflected by the single quotes around it in the script. If these quotes were not present, the expression language would look for a bean called "yes"; as no such bean exists, the result would be an error.
Listing 4.9 is similar to Listing 4.3; the major difference is that Listing 4.9 uses the standard tag instead of the custom awl:maybeShow. The downside is that the c:if tag cannot reverse a block of text; all it can do is decide whether to include its body content in the final page.
This may seem like a shortcoming but in fact reflects a good design pattern. Note that awl:maybeShow does two completely unrelated things: checks whether a value is yes, no, or reverse and reverses a block of text. Rather than making one tag do two things, it is better to have two different tags. According to the so-called UNIX philosophy of software, each piece of code should do only one thing and do it well, and there should be easy ways to knit these small pieces together. For tags, this means that each tag can be used independently or combined with other tags. In this case, if an awl:reverse tag did nothing but reverse its body content, it could be combined with the c:if tag to do the same thing as Listing 4.3. This is shown in Listing 4.10.
Listing 4.10 Splitting tags
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="awl" uri="http://jspbook.awl.com/samples" %> <jsp:useBean id="form" class="com.awl.jspbook.ch04.FormBean"/> <jsp:setProperty name="form" property="*"/> <c:if test="${form.shouldShow == 'yes'}"> The time is: <awl:date format="hh:mm:ss MM/dd/yy"/> </c:if> <c:if test="${form.shouldShow == 'reverse'}"> <awl:reverse> The time is: <awl:date format="hh:mm:ss MM/dd/yy"/> </awl:reverse> </c:if>
Note that two c:if tags are used here: one to check whether the value is yes and another to check whether it is reverse. The body content of both of these tags is the same, which is rather wasteful. It means that if the body ever needs to change, it will need to be modified in two places in order to keep everything consistent. It would be better in this case to put the body in a separate file and then have both of the if tags include that file with a jsp:include tag. Now that the functionality of awl:maybeShow has been divided into two pieces, the c:if tag can be used for many other things, and the awl:reverse tag can be used to reverse unconditionally a block of text, should such a thing ever be useful.
Listing 4.10 imports two tag libraries: the standard one, which is installed as c and provides the c:if tag, and the custom one installed as awl, which provides the awl:reverse tag. This is perfectly valid; often a page will need many different tags from different libraries, and it will then need to import all of them. The only catch is that each tag library must be given a different prefix.