As befits a first example, the implementation is simple but sufficient to highlight key aspects of a RESTful web service. The implementation consists of a JSP (Java Server Pages) script and two backend JavaBeans that the JSP script uses to get the data returned to the client (see Figure 1-6). The data is composed of sage corporate predictions. Here is a sample:
Decentralized
24
/
7
hub
will
target
robust
web
-
readiness
.
Synergistic
disintermediate
policy
will
expedite
backend
experiences
.
Universal
fault
-
tolerant
architecture
will
synthesize
bleeding
-
edge
channels
.
There is an Ant script (see An Ant script for service deployment) that automates the deployment of this and other service examples. Here is a summary of the service parts and how the Ant script puts the parts together:
The service consists of a JSP script together with two POJO (JavaBean) classes. The classes provide backend support for the JSP script. There is also a small configuration file, web.xml, that allows the URI to be shortened from:
/
predictions
/
predictions
.
jsp
to:
/
predictions
/
- The Ant script compiles the .java files and then packages the JSP script, the compiled .class files, and—only for convenience—the .java files into a JAR file with a .war extension (hereafter, a WAR file).
- The WAR file is copied to the Tomcat webapps subdirectory, which thereby deploys the service. The section The Tomcat web server goes into the details of Tomcat installation and management.
In the predictions service, each prediction has an associated human predictor. The RESTful resource is thus a list of predictor names (e.g., Hollis McCullough) and their predictions (Hollis is responsible for the third prediction shown above). The resource name or URI is /predictions/, and the only allowable HTTP verb is GET, which corresponds to read among the CRUD operations. If the HTTP request is correct, the RESTful service returns an XML representation of the predictor/prediction list; otherwise, the service returns the appropriate HTTP status code (e.g., 404 for “Not Found” if the URI is incorrect or 405 for “Method Not Allowed” if the verb is not GET). Example 1-5 shows a slice of the XML payload returned upon a successful request.
Example 1-5. The XML response from the predictions service
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
java
version
=
"1.7.0"
class
=
"java.beans.XMLDecoder"
>
<
array
class
=
"predictions.Prediction"
length
=
"32"
>
<
void
index
=
"0"
>
<
object
class
=
"predictions.Prediction"
>
<
void
property
=
"what"
>
<
string
>
Managed
holistic
contingency
will
grow
killer
action
-
items
.
</
string
>
</
void
>
<
void
property
=
"who"
>
<
string
>
Cornelius
Tillman
</
string
>
</
void
>
</
object
>
</
void
>
...
<
void
index
=
"31"
>
<
object
class
=
"predictions.Prediction"
>
<
void
property
=
"what"
>
<
string
>
Versatile
tangible
application
will
maximize
rich
ebusiness
.
</
string
>
</
void
>
<
void
property
=
"who"
>
<
string
>
Hiram
Gulgowski
</
string
>
</
void
>
</
object
>
</
void
>
</
array
>
</
java
>
When the predictions service is deployed to a web server such as Tomcat, the server translates the JSP script predictions.jsp (see Example 1-6) into a servlet instance. For now, this technical detail is overlooked because it is convenient to talk about the JSP script itself as the target of a request.
Example 1-6. The JSP script predictions.jsp
<
jsp:
useBean
id
=
"preds"
type
=
"predictions.Predictions"
class
= "
predictions
.
Predictions
">
<% // Check the HTTP verb: if it's anything but GET,
// return 405 (Method Not Allowed).
String verb = request.getMethod();
if (!verb.equalsIgnoreCase("
GET
")) {
response.sendError(response.SC_METHOD_NOT_ALLOWED,
"
Only
GET
requests
are
allowed
.
"
);
}
// If it's a GET request, return the predictions.
else
{
preds
.
setServletContext
(
application
);
out
.
println
(
preds
.
getPredictions
());
}
%>
</
jsp:
useBean
>
As requests come to the JSP script, the script first checks the request’s HTTP method. If the method is GET, an XML representation of the predictions is returned to the requester. If the verb is not GET, the script returns an error message together with the HTTP status code. The relevant code follows:
String
verb
=
request
.
getMethod
();
if
(!
verb
.
equalsIgnoreCase
(
"GET"
))
{
response
.
sendError
(
response
.
SC_METHOD_NOT_ALLOWED
,
"Only GET requests are allowed."
);
}
JSP scripts have implicit object references such as request
, response
, and out
; each of these is a
field or a parameter in the servlet code into which the web server, such as Tomcat or Jetty,
translates the JSP script. A JSP script can make the same calls as an
HttpServlet
.
On a successful request, the JSP script returns a list of predictions and
their predictors, a list available from the backend JavaBean
Predictions
. The JSP code is pretty straightforward:
out
.
println
(
preds
.
getPredictions
());
The object reference out
, available in every JSP script, refers to an output stream
through which the JSP script can communicate with the client. In this example,
the object reference preds
(line 1) refers to the backend JavaBean that maintains
the collection of predictions; the getPredictions
method in the backend bean converts the
Java list of Predictions
into an XML document.
The backend code consists of two POJO classes, Prediction
(see Example 1-7) and Predictions
(see Example 1-8). The
Prediction
class is quite simple, consisting of two properties: who
(line 1) is the
person making the prediction and what
(line 2) is the prediction.
Example 1-7. The backend predictions.Prediction
class
package
predictions
;
import
java.io.Serializable
;
public
class
Prediction
implements
Serializable
{
private
String
who
;
// person
private
String
what
;
// his/her prediction
public
Prediction
()
{
}
public
void
setWho
(
String
who
)
{
this
.
who
=
who
;
}
public
String
getWho
()
{
return
this
.
who
;
}
public
void
setWhat
(
String
what
)
{
this
.
what
=
what
;
}
public
String
getWhat
()
{
return
this
.
what
;
}
}
The Predictions
class does the grunt work. For example, its populate
method (line 3) reads the prediction
data from a text file, predictions.db, encapsulated in the deployed WAR file, and
the toXML
method serializes a Java List<Prediction>
into an XML document,
which is sent back to the client. If there were problems reading or formatting the
data, the predictions service would return null
to the client.
Example 1-8. The backend predictions.Predictions
class
package
predictions
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.io.BufferedReader
;
import
java.io.ByteArrayOutputStream
;
import
java.beans.XMLEncoder
;
// simple and effective
import
javax.servlet.ServletContext
;
public
class
Predictions
{
private
int
n
=
32
;
private
Prediction
[
]
predictions
;
private
ServletContext
sctx
;
public
Predictions
()
{
}
public
void
setServletContext
(
ServletContext
sctx
)
{
this
.
sctx
=
sctx
;
}
public
ServletContext
getServletContext
()
{
return
this
.
sctx
;
}
// getPredictions returns an XML representation of
// the Predictions array
public
void
setPredictions
(
String
ps
)
{
}
// no-op
public
String
getPredictions
()
{
// Has the ServletContext been set?
if
(
getServletContext
()
==
null
)
return
null
;
// Has the data been read already?
if
(
predictions
==
null
)
populate
();
// Convert the Predictions array into an XML document
return
toXML
();
}
private
void
populate
()
{
String
filename
=
"/WEB-INF/data/predictions.db"
;
InputStream
in
=
sctx
.
getResourceAsStream
(
filename
);
// Read the data into the array of Predictions.
if
(
in
!=
null
)
{
try
{
InputStreamReader
isr
=
new
InputStreamReader
(
in
);
BufferedReader
reader
=
new
BufferedReader
(
isr
);
predictions
=
new
Prediction
[
n
];
int
i
=
0
;
String
record
=
null
;
while
((
record
=
reader
.
readLine
())
!=
null
)
{
String
[]
parts
=
record
.
split
(
"!"
);
Prediction
p
=
new
Prediction
();
p
.
setWho
(
parts
[
0
]);
p
.
setWhat
(
parts
[
1
]);
predictions
[
i
++]
=
p
;
}
}
catch
(
IOException
e
)
{
}
}
}
private
String
toXML
()
{
String
xml
=
null
;
try
{
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
();
XMLEncoder
encoder
=
new
XMLEncoder
(
out
);
encoder
.
writeObject
(
predictions
);
// serialize to XML
encoder
.
close
();
xml
=
out
.
toString
();
// stringify
}
catch
(
Exception
e
)
{
}
return
xml
;
}
}
On a GET request, the JSP script invokes the method
setServletContext
(line 1 in the bean, line 2 in the JSP script) with an argument,
the implicit object reference named application
.
The backend bean needs access to the servlet context
in order to read data from a
text file embedded in the deployed WAR file. The ServletContext
is a data structure
through which a servlet/JSP script can interact explicitly with the servlet container.
The call to the setServletContext
method sets up
the subsequent call to the getPredictions
method (line 2), which returns the XML representation shown in
Example 1-5. Here is the getPredictions
method without the comments:
public
String
getPredictions
()
{
if
(
getServletContext
()
==
null
)
return
null
;
if
(
predictions
==
null
)
populate
();
return
toXML
();
}
The method populate
(line 1 immediately above) reads the data.
The predictions
reference in the code segment above refers to the Map
in which Prediction
references are values.
If the JSP script fails to set the servlet context, there is no way for the back-end
Predictions
bean to provide the requested data. The reason is that the populate
method requires the servlet context (the reference is sctx
, line 1, in the code below)
in order to access the data:
private
void
populate
()
{
String
filename
=
"/WEB-INF/data/predictions.db"
;
InputStream
in
=
sctx
.
getResourceAsStream
(
filename
);
...
}
If the servlet context has been set but the predictions
reference is null
, then the data
must be read from the predictions.db file into the Map
that makes the data available to
clients. Each entry in the Map
is a Prediction
, which again is a pair: who
predicts what
.
Finally, the toXML
method serializes the
Java predictions into an XML document using the Java utility class XMLEncoder
(line 1):
private
String
toXML
()
{
String
xml
=
null
;
try
{
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
();
XMLEncoder
encoder
=
new
XMLEncoder
(
out
);
encoder
.
writeObject
(
predictions
);
// serialize to XML
encoder
.
close
();
xml
=
out
.
toString
();
// stringify
}
catch
(
Exception
e
)
{
}
return
xml
;
}
The XML document from the toXML
method (line 2) becomes the body of the HTTP response to the client.
Although the XML from the predictions service is generated using the XMLEncoder
class,
Java does provide other ways to generate XML—but perhaps none quite as simple as
XMLEncoder
. The Prediction
objects must be serializable in order to be encoded as
XML using the XMLEncoder
; hence, the Prediction
class implements the empty (or marker) Serializable
interface and defines the
get/set
methods for the properties who
(the predictor) and what
(the
prediction). The Prediction
properties are serialized into XML elements in the
response document.
The predictions service can be deployed under the Tomcat web server using a provided
Ant script (with %
as the command-line prompt):
%
ant
-
Dwar
.
name
=
predictions
deploy
The deployed WAR file would be predictions.war in this case. The next section (see The Tomcat web server) elaborates on the Apache Tomcat server and explains how to install and use this server. The section An Ant script for service deployment clarifies the Ant script, which is packaged with the book’s code examples. The deployed WAR file predictions.war includes a standard web deployment document, web.xml, so that the URI can be shortened to /predictions/.
Apache Tomcat is a commercial-grade yet lightweight web server implemented in Java. Tomcat has various subsystems for administration, security, logging, and troubleshooting, but its central subsystem is Catalina, a container that executes servlets, including JSP and other scripts (e.g., JSF scripts) that Tomcat automatically translates into servlets. Tomcat is the popular name for the web server, and Catalina is the official name for the servlet container that comes with Tomcat.
Tomcat also includes a web console, tutorials, and sample code. This section focuses on installing Tomcat and on basic post-installation tasks such as starting and stopping the web server. The current version is 7.x, which requires Java SE 6 or higher. Earlier Tomcat versions are still available.
There are different ways to download Tomcat, including as a ZIP file. Tomcat can be installed in any directory. For convenience, let TOMCAT_HOME be the install directory. The directory TOMCAT_HOME/bin has startup and shutdown scripts for Unixy and Windows systems. For instance, the startup script is startup.sh for Unix and startup.bat for Windows. Tomcat is written in Java but does not ship with the Java runtime; instead, Tomcat uses the Java runtime on the host system. To that end, the environment variable JAVA_HOME should be set to the Java install directory (e.g., to /usr/local/java7, D:\java7, and the like).
In summary, the key commands (with comments introduced with two semicolons) are:
%
startup
.
sh
;;
or
startup
.
bat
on
Windows
to
start
Tomcat
%
shutdown
.
sh
;;
or
shutdown
.
bat
on
Windows
to
stop
Tomcat
The commands can be given at a command-line prompt. On startup, a message similar to:
Using
CATALINA_BASE:
/
home
/
mkalin
/
tomcat7
Using
CATALINA_HOME:
/
home
/
mkalin
/
tomcat7
Using
CATALINA_TMPDIR:
/
home
/
mkalin
/
tomcat7
/
temp
Using
JRE_HOME:
/
usr
/
local
/
java
Using
CLASSPATH:
/
home
/
mkalin
/
tomcat7
/
bin
/
bootstrap
.
jar
appears.
Under TOMCAT_HOME there is directory named logs, which contains various logfiles, and several other directories, some of which will be clarified later. A important directory for now is TOMCAT_HOME/webapps, which holds JAR files with a .war extension (hence the name WAR file). Subdirectories under TOMCAT_HOME/webapps can be added as needed. Deploying a web service under Tomcat is the same as deploying a website: a WAR file containing the site or the service is copied to the webapps directory, and a website or web service is undeployed by removing its WAR file.
Tomcat maintains various logfiles in TOMCAT_HOME/logs, one of which is especially
convenient for ad hoc debugging. In standard configuration, Tomcat redirects output to
System.err
and System.out
to logs/catalina.out. Accordingly, if a servlet executes:
System
.
err
.
println
(
"Goodbye, cruel world!"
);
the farewell message will appear in the catalina.out logfile.
Apache Tomcat is not the only game in town. There is the related TomEE web server, basically Tomcat with support for Java EE beyond servlets. Another popular Java-centric web server is Jetty. The sample services in this book can be deployed, as is, with either Tomcat or Jetty; the next chapter has a sidebar on how to install and run Jetty.
The first sample web service is published with a web server such as Tomcat or Jetty. The ZIP file with my code examples includes an Ant script to ease the task of deployment. The Ant utility, written in Java, is available on all platforms. My script requires Ant 1.6 or higher.
To begin, let the current working directory (cwd) be any directory on the local filesystem. The cwd holds the Ant script build.xml:
cwd:
build
.
xml
The cwd has a subdirectory named src that holds the web service’s artifacts. Suppose, for example, that a web service includes a JSP script, a backend JavaBean, the standard Tomcat or Jetty deployment file web.xml, and a JAR file that holds a JSON library. Here is a depiction:
cwd:
build
.
xml
|
src:
products
.
jsp
,
json
.
jar
,
web
.
xml
Suppose, further, that the backend JavaBean has the fully qualified name:
acme
.
Products
The file structure is now:
cwd:
build
.
xml
|
src:
products
.
jsp
,
json
.
jar
,
web
.
xml
|
acme:
Products
.
java
Finally, assume that the src directory also holds the datafile new_products.db. From the cwd command line, the command:
%
ant
-
Dwar
.
name
=
products
deploy
does the following:
- Creates the directory cwd/build, which holds copies of files in directory src and any subdirectories
- Compiles any .java files, in this case acme.Products.java
Builds the WAR file, whose major contents are:
WEB
-
INF
/
web
.
xml
WEB
-
INF
/
classes
/
acme
/
Products
.
class
WEB
-
INF
/
data
/
new_products
.
db
WEB
-
INF
/
lib
/
json
.
jar
acme
/
Products
.
java
products
.
jsp
In the constructed WAR file:
- Any .xml file winds up in WEB-INF.
- Any .jar file winds up in WEB-INF/lib.
- Any .db file winds up in WEB-INF/data.
- Any .java or .class file winds up in its package/subdirectory.
- Other files, such as .jsp files, wind up in the WAR file’s top level.
For convenience, the Ant script includes, in the WAR file, Java source (.java) and compiled (.class) files. In production, the source files would be omitted.
Finally, the Ant script copies the constructed WAR file to TOMCAT_HOME/webapps and thereby deploys the web service. The script also leaves a copy of the WAR file in cwd.
The command:
%
ant
displays the three most useful commands. The command:
%
ant
clean
removes any .war files from the cwd and deletes the build directory. The command:
%
ant
compile
compiles any .java files but does not build or deploy a WAR file. The command:
%
ant
-
Dwar
.
name
=
predictions
deploy
first cleans and compiles; then the command builds and deploys the WAR file predictions.war.
The Ant file build.xml has extensive documentation and explains, in particular, what needs to be done to customize this file for your environment. Only one line in the file needs to be edited. Although the Ant script is targeted at Tomcat deployment, the WAR files that the script produces can be deployed as is to Jetty as well. As noted earlier, Chapter 2 goes into the details of installing and running Jetty.
Later examples introduce RESTful clients in Java and other languages; for now, either a browser or a utility such as curl (see The curl Utility) is good enough. On a successful curl request to the service:
%
curl
-
v
http:
//localhost:8080/predictions/
the response includes not only the XML shown earlier in Example 1-5 but also a trace (thanks to the -v flag) of the HTTP request and response messages. The HTTP request is:
GET
/
predictions
/
HTTP
/
1.1
User
-
Agent:
curl
/
7.19
.
7
Host:
localhost:
8080
Accept:
*/*
The HTTP response start line and headers are:
HTTP
/
1.1
200
OK
Server:
Apache
-
Coyote
/
1.1
Set
-
Cookie:
JSESSIONID
=
96
C78773C190884EDE76C714728164EC
;
Path
=/
test1
/;
Content
-
Type:
text
/
html
;
charset
=
ISO
-
8859
-
1
Transfer
-
Encoding:
chunked
Recall that an HTTP GET message has no body; hence, the entire message is the start line and the headers. The response shows the session identifier (a 128-bit statistically unique number, in hex, that Tomcat generates) in the header. In the JSP script, the session identifier could be disabled, as it is not needed; for now, the goal is brevity and simplicity in the code.
If a POST request were sent to the RESTful predictions service:
%
curl
--
request
POST
--
data
"foo=bar"
http:
//localhost:8080/predictions/
the request message header becomes:
POST
/
predictions
/
HTTP
/
1.1
User
-
Agent:
curl
/
7.19
.
7
Host:
localhost:
8080
Accept:
*/*
Content
-
Length:
7
Content
-
Type:
application
/
x
-
www
-
form
-
urlencoded
The response header is now:
HTTP
/
1.1
405
Method
Not
Allowed
Server:
Apache
-
Coyote
/
1.1
Set
-
Cookie:
JSESSIONID
=
34
A013CDC5A9F9F8995A28E30CF31332
;
Path
=/
test1
/;
Content
-
Type:
text
/
html
;
charset
=
ISO
-
8859
-
1
Content
-
Length:
1037
The error message:
Only
GET
requests
are
allowed
.
is in an HTML document that makes up the response message’s body. Tomcat generates an HTML response because my code does not (but could) stipulate a format other than HTML, the default Tomcat format for a Tomcat response.
This first example illustrates how a JSP script is readily adapted to support web services in addition to websites. The next section goes into more detail on servlets and JSP scripts. In summary, the predictions web service is implemented as a JSP script with the two backend JavaBeans in support. This first example highlights key aspects of a REST-style service:
- The service provides access to resource under the URI /predictions/.
- The service filters access on the HTTP request verb. In this example, only GET requests are successful; any other type of request generates a bad method error.
- The service responds with an XML payload, which the consumer now must process in some appropriate way.
Get Java Web Services: Up and Running, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.