Simplifying WML generation – A WML helper class
In this section, we'll look at a WML class
that will make the generation of WML easier, and to separate some of the WML
syntax logic from the Java servlet code. This class models the formatting and
header information needed to produce a valid WML document, thus allowing our
servlet code later in the chapter to concentrate on the business of being a
servlet, and hides some of the underlying complexity of creating WML from our
JavaMail code.
The WML class is fairly basic and could be
extended to interpret more of the tags that can be used within WML cards. The
abstraction and separation of this presentation code is vital, as all WAP
applications will eventually need to cope with a wide variety of different
device capabilities, and being able to separate our business logic from our
presentation logic is the first step on the road to achieving this goal.
The maximum defined size of a WSP data unit
is 1400 bytes. This means that if our decks become too large the WAP device
will not be able to receive them. It is good practice to keep deck sizes to
below one kilobyte; with this in mind, the size of our deck contents is
specified as:
private static final int _deckSize = 1024;
This header string defines the document has
being a WML deck. Beware of including unnecessary line breaks in this string –
it shouldn't matter, but certain WML browsers currently have problems with
linefeed characters.
<?xml
version=\"1.0\"?>
<DOCTYPE wml PUBLIC
\"-//WAPFORUM//DTD WML 1.1//EN\"
"http://www.wapforum.org/DTD/wml_1.1.xml\">
Here is the WML class in full.
WML.java
package com.wrox.util;
import java.util.Date;
import
javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
//Utility class
encapsulating some basic WML
public class WML
{
private StringBuffer _buffer;
private static final int _deckSize
= 1024;
private void setBuffer(StringBuffer newBuffer) {
_buffer = newBuffer;
}
private StringBuffer getBuffer() {
return _buffer;
}
public WML() {
setBuffer(new StringBuffer(_deckSize));
beginDeck();
}
private void beginDeck () {
getBuffer().append("<?xml
version=\"1.0\"?>");
getBuffer().append("<!DOCTYPE wml PUBLIC
\"-//WAPFORUM//DTD WML 1.1//EN\"" +
"\"http://www.wapforum.org/DTD/wml_1.1.xml\">");
getBuffer().append("<wml>");
}
private void endDeck() {
getBuffer().append("</wml>");
}
public void addCard(String id) {
getBuffer().append("<card id=\"" + id +
"\">");
}
public void addCard(String id, String title) {
getBuffer().append("<card id= \"" + id +
"\" title=\"" + title + "\">");
}
public void endCard() {
getBuffer().append("</card>");
}
public void println(String line) {
getBuffer().append(line);
}
private String getDeck()
{
endDeck();
return getBuffer().toString();
}
public void outputWML (HttpServletResponse response, boolean
disableCaching) {
PrintWriter writer = null;
try {
response.setContentType("text/vnd.wap.wml");
if (disableCaching) {
//Send a NO CACHING instruction to the user agent
response.setHeader("Cache-Control",
"must-revalidate, no-store");
}
//Set the Date header to help calculate cache time outs
Date now = new Date();
long timeNow = now.getTime();
response.setDateHeader("Date", timeNow);
writer = response.getWriter();
writer.println(this.getDeck());
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
if (writer!=null) writer.close();
}
}
}
The WML class encapsulates the details and
logic needed by the servlet to generate a correctly formed WML page to send as
the response to the user's WAP browser request.
Using the WML class
To use the WML class the client code simply
creates a new WML instance, and then sends it the strings that make up the
content of the WML cards. In this simplistic implementation <p> paragraph and <br/> line breaks are input by the client, but a more sophisticated WML
class would want to encapsulate this logic as well.
As an example of how to use the WML class,
consider the following code snippet:
WML wml = new WML();
wml.addCard("WAPMailLogout");
wml.println("<p
align=\"left\">");
wml.println("Thank you
for using WAP Mail<br/>");
wml.println("<anchor>Restart
E-mail");
wml.println("<go
href=\"" + request.getRequestURI() + "\"/>");
wml.println("</anchor>");
wml.println("</p>");
wml.endCard();
wml.outputWML(response,
true);
This produces a deck that contains a single
card; this card in turn contains a single paragraph, which displays a message
and provides a hyperlink.
Caching
Caching presents the developer of WAP
applications with both benefits and problems. The benefits of caching are
obvious; it reduces the need for a device to repeatedly fetch information from
the server, when it is unnecessary to do so. In this sense caching is a feature and can drastically improve the
performance of some applications.
However, this feature can also present a
problem for the developer, who has to handle this caching correctly. For an
application that visits the same page again and again it is sometimes necessary
to convince the browser to fetch a fresh version page if the information
contained therein has changed, and not rely on any cached version it may have
tucked away in memory.
The knowledge of how to disable the caching of WAP pages is then an important item in the development of WAP applications; to disable caching our WML class contains the following
boolean test:
if (disableCaching)
{
//Send a NO CACHING instruction to the user agent
response.setHeader("Cache-Control",
"must-revalidate, no-store ");
}
The Cache-Control instruction
we have used in the above WML class should give us the ability to disable the
caching of the page by the WAP browser. However, this does not seem to be fully
implemented in the Nokia and Phone.com simulators at the time of writing.
Cache-Control Header
The Cache-Control header can take various values, including those used in our WML
class, which are summarized below:
no-cache
This indicates that the browser should not cache
the WML deck.
no-store
This indicates not only that the WML deck should
not be cached by the browser, but that it shouldn't even be stored by proxy
servers. This setting is usually used with sensitive, or time-critical,
fast-changing information.
max-age=seconds
This specifies a period of time after which the
document should be considered invalid and retrieved. For example a 'clock' page
might specify max-age=60 so that
it is freshly retrieved every minute.
must-revalidate
The must-revalidate response header instructs the
browser to revalidate, and if necessary re-retrieve each page stored in the
history stack even when doing a backwards fetch. It can be combined with the
other Cache-Control instructions. For example, the following code instructs the browser
to force a reload of a page, even on a <prev> fetch from the
browser's recent history stack:
response.setHeader("Cache-Control",
"must-revalidate, no-cache");
Expires Header
The Expires header could also
be used (although it isn't in our WML class), and specifies a time after which
the cached page is invalid and should be re-retrieved. For instance to set the
page to expire at 11am GMT on Friday 21st April 2000, you would
write:
Buy this book