The Restlet web framework supports RESTful web services, and the API is similar to JAX-RS; indeed,
a Restlet application can use JAX-RS annotations such as @Produces
instead of or in addition to
Restlet annotations. This section adapts the earlier JAX-RS adages service to a Restlet
implementation, which is published both with a web server and with a standalone Java application.
The revised service, adages2, implements the four CRUD operations.
A Restlet web service has three main parts, each of which consists of one or more Java classes:
AdagesApplication
, extends the Restlet Application
class.
The purpose of the extended class is to set up a routing table, which maps request URIs to resources.
The resources are named or anonymous Java classes; the current example illustrates both approaches. The
spirit of Restlet development is to have very simple resource classes.
ServerResource
class and the other is an anonymous class that implements the
Restlet
interface. The named classes are CreateResource
, the target of a POST request; UpdateResource
, the
target of a PUT request; XmlAllResource
, the target of a GET request for all Adages
in XML; JsonAllResource
,
the target of a GET request for all Adages
in JSON; XmlOneResource
, the target of a GET request for a
specified Adage
; and so on.
Adage
and Adages
, each slightly redefined from the earlier adages
web service.
If the Restlet service is deployed with a web server such as Tomcat or Jetty, then the Restlet ServerServlet
acts as an interceptor for requests against the Restlet service. The interceptor servlet interacts with the
Restlet Application
, which contains the routing table, so that a properly formatted request winds up at the
correct Restlet resource. The architecture is simple and clean. Deployment of adages2 service requires a
web.xml document (see Example 2-12) that sets up the Restlet interceptor (line 1) and links the
interceptor to the Restlet Application
(line 2).
Example 2-12. The web.xml file for the adages2 Restlet service
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
web
-
app
>
<!--
Restlet
adapter
-->
<
servlet
>
<
servlet
-
name
>
RestletServlet
</
servlet
-
name
>
<
servlet
-
class
>
org
.
restlet
.
ext
.
servlet
.
ServerServlet
</
servlet
-
class
>
![]()
<
init
-
param
>
<!--
Application
class
name
-->
<
param
-
name
>
org
.
restlet
.
application
</
param
-
name
>
<
param
-
value
>
adages2
.
AdagesApplication
</
param
-
value
>
![]()
</
init
-
param
>
</
servlet
>
<!--
Dispach
all
requests
to
the
Restlet
servlet
.
-->
<
servlet
-
mapping
>
<
servlet
-
name
>
RestletServlet
</
servlet
-
name
>
<
url
-
pattern
>/*</
url
-
pattern
>
</
servlet
-
mapping
>
</
web
-
app
>
The Adage
class (see Example 2-13) is mostly unchanged from the earlier version of the
service. There is now an id
property (line 1) to support searching for a specified Adage
on a GET, PUT, or DELETE request.
Example 2-13. The Adage
POJO class in the adages2 Restlet service
package
adages2
;
public
class
Adage
{
private
String
words
;
private
int
wordCount
;
private
int
id
;
public
Adage
()
{
}
@Override
public
String
toString
()
{
return
String
.
format
(
"%2d: "
,
id
)
+
words
+
" -- "
+
wordCount
+
" words"
;
}
public
void
setWords
(
String
words
)
{
this
.
words
=
words
;
this
.
wordCount
=
words
.
trim
().
split
(
"\\s+"
).
length
;
}
public
String
getWords
()
{
return
this
.
words
;
}
public
void
setWordCount
(
int
wordCount
)
{
}
public
int
getWordCount
()
{
return
this
.
wordCount
;
}
public
void
setId
(
int
id
)
{
this
.
id
=
id
;
}
![]()
public
int
getId
()
{
return
this
.
id
;
}
}
The Adages
class (see Example 2-14) has a static
list (line 1) of the thread-safe type
CopyOnWriteArrayList
, which substitutes for a persistence store such as database. The
class has a toPlain
method (line 2) to support a text/plain
response on a GET request to
the adages2 service. The find
method (line 3) supports GET, PUT, and DELETE requests for a specified
Adage
in the list adages
, and the add
method (line 4) supports POST requests by adding a
newly created Adage
to the list.
Example 2-14. The POJO class Adages
in the adages2 Restlet service
package
aphorism2
;
import
java.util.concurrent.CopyOnWriteArrayList
;
import
java.util.concurrent.atomic.AtomicInteger
;
public
class
Adages
{
private
static
CopyOnWriteArrayList
<
Adage
>
adages
;
![]()
private
static
AtomicInteger
id
;
static
{
String
[
]
aphorisms
=
{
"What can be shown cannot be said."
,
"If a lion could talk, we could not understand him."
,
"Philosophy is a battle against the bewitchment of
our intelligence by means of language."
,
"Ambition is the death of thought."
,
"The limits of my language mean the limits of my world."
};
adages
=
new
CopyOnWriteArrayList
<
Adage
>();
id
=
new
AtomicInteger
();
for
(
String
str
:
aphorisms
)
add
(
str
);
}
public
static
String
toPlain
()
{
![]()
String
retval
=
""
;
int
i
=
1
;
for
(
Adage
adage
:
adages
)
retval
+=
adage
.
toString
()
+
"\n"
;
return
retval
;
}
public
static
CopyOnWriteArrayList
<
Adage
>
getList
()
{
return
adages
;
}
public
static
Adage
find
(
int
id
)
{
![]()
Adage
adage
=
null
;
for
(
Adage
a
:
adages
)
{
if
(
a
.
getId
()
==
id
)
{
adage
=
a
;
break
;
}
}
return
adage
;
}
public
static
void
add
(
String
words
)
{
![]()
int
localId
=
id
.
incrementAndGet
();
Adage
adage
=
new
Adage
();
adage
.
setWords
(
words
);
adage
.
setId
(
localId
);
adages
.
add
(
adage
);
}
}
The AdagesApplication
class (see Example 2-15) extends the Restlet Application
class (line 1) and interacts
with the service’s publisher. In the case of a
web server such as Tomcat, this class works with the Restlet ServerServlet
to dispatch incoming
service requests to the proper resource. The dispatching is done through a pattern-driven routing table
at the end of the class.
Example 2-15. The Restlet Application
class with the routing table
package
adages2
;
import
org.restlet.Application
;
import
org.restlet.Restlet
;
import
org.restlet.Request
;
import
org.restlet.Response
;
import
org.restlet.routing.Router
;
import
org.restlet.data.Status
;
import
org.restlet.data.MediaType
;
public
class
AdagesApplication
extends
Application
{
![]()
@Override
public
synchronized
Restlet
createInboundRoot
()
{
// To illustrate the different API possibilities, implement the
// DELETE operation as an anonymous Restlet class. For the
// remaining operations, follow Restlet best practices and
// implement each as a Java class.
// DELETE handler
Restlet
janitor
=
new
Restlet
(
getContext
())
{
![]()
public
void
handle
(
Request
request
,
Response
response
)
{
String
msg
=
null
;
String
sid
=
(
String
)
request
.
getAttributes
().
get
(
"id"
);
if
(
sid
==
null
)
msg
=
badRequest
(
"No ID given.\n"
);
Integer
id
=
null
;
try
{
id
=
Integer
.
parseInt
(
sid
.
trim
());
}
catch
(
Exception
e
)
{
msg
=
badRequest
(
"Ill-formed ID.\n"
);
}
Adage
adage
=
Adages
.
find
(
id
);
if
(
adage
==
null
)
msg
=
badRequest
(
"No adage with ID "
+
id
+
"\n"
);
else
{
Adages
.
getList
().
remove
(
adage
);
msg
=
"Adage "
+
id
+
" removed.\n"
;
}
// Generate HTTP response.
response
.
setEntity
(
msg
,
MediaType
.
TEXT_PLAIN
);
}
};
// Create the routing table.
Router
router
=
new
Router
(
getContext
());
router
.
attach
(
"/"
,
PlainResource
.
class
);
![]()
router
.
attach
(
"/xml"
,
XmlAllResource
.
class
);
![]()
router
.
attach
(
"/xml/{id}"
,
XmlOneResource
.
class
);
router
.
attach
(
"/json"
,
JsonAllResource
.
class
);
![]()
router
.
attach
(
"/create"
,
CreateResource
.
class
);
![]()
router
.
attach
(
"/update"
,
UpdateResource
.
class
);
router
.
attach
(
"/delete/{id}"
,
janitor
);
// instance of anonymous class
return
router
;
}
private
String
badRequest
(
String
msg
)
{
Status
error
=
new
Status
(
Status
.
CLIENT_ERROR_BAD_REQUEST
,
msg
);
return
error
.
toString
();
}
}
Each adages2 resource could be written as an anonymous class encapsulated inside the Application
class; however, under Restlet best practices, these resources should be implemented as relatively small,
individual classes such as PlainResource
(line 3), XmlAllResource
(line 4), JsonAllResource
(line 5),
CreateResource
(line 6),
and so on. For illustration, however, the AdagesApplication
class does include an
anonymous class that implements the Restlet
interface by defining the handle
method, which
has Request
and Response
parameters; the Response
and Request
types are quite similar to the
HttpServletRequest
and HttpServletResponse
types, respectively.
The reference to an instance of this anonymous class is
named janitor
(line 2) because the class handles DELETE requests by removing a specified Adage
from the
list of adages.
The routing table at the end of AdagesApplication
class follows a popular idiom for
RESTful frameworks such as Restlet: a URI
pattern maps to a specified resource. A routing table entry such as:
router
.
attach
(
"/"
,
PlainResource
.
class
);
maps the URI /
, the single slash, to the PlainResource
, thereby making this resource the
default one.
By contrast, the entry:
router
.
attach
(
"/xml/{id}"
,
XmlOneResource
.
class
);
// {id} is a parameter
includes the parameter id
in braces, where the numerical id
identifies the desired Adage
.
The URI for the UpdateResource
does not include the identifier for the Adage
in question
because this information is supplied in the body of the PUT request.
The resource classes are quite short because each has very specialized, hence limited,
functionality. For example, the XmlAllResource
is Example 2-16.
Example 2-16. The XmlAllResource
Restlet resource
package
adages2
;
import
org.restlet.resource.Get
;
import
org.restlet.resource.ServerResource
;
import
org.restlet.representation.Representation
;
import
org.restlet.ext.xml.DomRepresentation
;
import
org.w3c.dom.Document
;
import
org.w3c.dom.Element
;
import
org.restlet.data.Status
;
import
org.restlet.data.MediaType
;
import
java.util.List
;
public
class
XmlAllResource
extends
ServerResource
{
public
XmlAllResource
()
{
}
@Get
![]()
public
Representation
toXml
()
{
List
<
Adage
>
list
=
Adages
.
getList
();
DomRepresentation
dom
=
null
;
![]()
try
{
dom
=
new
DomRepresentation
(
MediaType
.
TEXT_XML
);
dom
.
setIndenting
(
true
);
Document
doc
=
dom
.
getDocument
();
Element
root
=
doc
.
createElement
(
"adages"
);
for
(
Adage
adage
:
list
)
{
Element
next
=
doc
.
createElement
(
"adage"
);
next
.
appendChild
(
doc
.
createTextNode
(
adage
.
toString
()));
root
.
appendChild
(
next
);
}
doc
.
appendChild
(
root
);
}
catch
(
Exception
e
)
{
}
return
dom
;
}
}
There are various Java ways to generate XML, including the DOM (Document Object Model) or tree-based method shown in the
XmlAllResource
(line 2), which builds the XML tree out of the Adages
. This resource is reachable, as the @Get
annotation
signals (line 1), only with a GET request.
The CreateResource
class (see Example 2-17) highlights some nice features of the Restlet API. The argument to the
create
method, of Restlet type Representation
(line 1), represents the HTTP request body. The Restlet class Form
(line 2) makes it
easy to search for specified keys, in this case words
, with which the corresponding values can be accessed, in this
case the text for the new Adage
.
Example 2-17. The CreateResource
class in the adages2 service
package
adages2
;
import
org.restlet.resource.Post
;
import
org.restlet.resource.ServerResource
;
import
org.restlet.representation.Representation
;
import
org.restlet.representation.StringRepresentation
;
import
org.restlet.data.Status
;
import
org.restlet.data.MediaType
;
import
org.restlet.data.Form
;
public
class
CreateResource
extends
ServerResource
{
public
CreateResource
()
{
}
@Post
public
Representation
create
(
Representation
data
)
{
![]()
Status
status
=
null
;
String
msg
=
null
;
// Extract the data from the POST body.
Form
form
=
new
Form
(
data
);
![]()
String
words
=
form
.
getFirstValue
(
"words"
);
if
(
words
==
null
)
{
msg
=
"No words were given for the adage.\n"
;
status
=
Status
.
CLIENT_ERROR_BAD_REQUEST
;
}
else
{
Adages
.
add
(
words
);
msg
=
"The adage '"
+
words
+
"' has been added.\n"
;
status
=
Status
.
SUCCESS_OK
;
}
setStatus
(
status
);
return
new
StringRepresentation
(
msg
,
MediaType
.
TEXT_PLAIN
);
}
}
A JAX-RS resource typically encapsulates several methods, each annotated with a distinct combination of
HTTP verb (for instance, @GET
or @POST
) and URI (that is, @Path
). In this sense, a JAX-RS resource
class is multipurpose: its various methods handle various HTTP requests. The Restlet approach differs.
Each resource class is, in best practices, single-purpose: one annotated method is the callback for
an HTTP request targeted at a particular URI. The Restlet resource classes thus tend to be small, as
the adages2 service illustrates.
The RESTful adages2 service can be deployed to Tomcat in the usual way:
%
ant
-
Dwar
.
name
=
adages2
deploy
Once the service is deployed, some curl calls can be used to confirm that the service is behaving correctly. Here is a series of test calls. Each begins with a comment, introduced with two semicolons, followed by the curl call itself, and ending with the output, which is formatted for readability.
;;
GET
all
in
plain
text
%
curl
--
request
GET
http:
//localhost:8080/adages2/
1
:
What
can
be
shown
cannot
be
said
.
--
7
words
2
:
If
a
lion
could
talk
,
we
could
not
understand
him
.
--
10
words
...
;;
GET
all
in
XML
%
curl
--
request
GET
http:
//localhost:8080/adages2/xml
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
standalone
=
"no"
?>
<
adages
>
<
adage
>
1
:
What
can
be
shown
cannot
be
said
.
--
7
words
</
adage
>
<
adage
>
2
:
If
a
lion
could
talk
,
we
could
not
understand
him
.
--
10
words
</
adage
>
...
;;
GET
all
in
JSON
%
curl
--
request
GET
http:
//localhost:8080/adages2/json
[
1
:
What
can
be
shown
cannot
be
said
.
--
7
words
,
2
:
If
a
lion
could
talk
,
we
could
not
understand
him
.
--
10
words
,
...
;;
GET
Adage
with
id
of
2
in
XML
%
curl
--
request
GET
http:
//localhost:8080/adages2/xml/2
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
standalone
=
"no"
?>
<
adage
>
2
:
If
a
lion
could
talk
,
we
could
not
understand
him
.
--
10
words
</
adage
>
;;
Create
a
new
adage
%
curl
--
request
POST
--
data
"words=This is a test"
\
http:
//localhost:8080/adages2/create
The
adage
'
This
is
a
test
'
has
been
added
.
;;
Delete
the
newly
added
Adage
%
curl
--
request
DELETE
http:
//localhost:8080/adages2/delete/6
Adage
6
removed
.
Restlet is excellent at separating concerns: the web service is one concern and its publication is quite another. The adages2 service can be deployed with Tomcat or Jetty but also—and with ease—using a standard Java application (see Example 2-18).
Example 2-18. A Java application to publish the Restlet adages2 web service
package
adages2
;
import
org.restlet.Component
;
![]()
import
org.restlet.data.Protocol
;
![]()
public
class
Main
{
public
static
void
main
(
String
[
]
args
)
throws
Exception
{
// Create a new Component.
Component
component
=
new
Component
();
// Add a new HTTP server listening on port 8182.
component
.
getServers
().
add
(
Protocol
.
HTTP
,
8182
);
![]()
// Attach the application.
component
.
getDefaultHost
().
attach
(
"/adages"
,
new
AdagesApplication
());
![]()
// Start the web server.
component
.
start
();
}
}
The two import
statements (lines 1 and 2) in Main
require only the org.restlet.jar file to compile and
run, but the various Restlet resource classes such as XmlAllResource
and
JsonAllResource
require other JARs. The command-line server listens to HTTP
connections on port 8182 (line 3) for requests against the URI that begins with /adages (line 4).
The ZIP file with the sample applications includes Main.jar, an executable JAR file that can be used instead of Tomcat or Jetty to publish the service:
%
java
-
jar
Main
The built-in server listens for requests indefinitely, and the functionality of the adages2 service is unchanged in this command-line option for publication. For example, here is a request for the adages in plain text together with the response:
%
curl
localhost:
8182
/
adages
/
1
:
What
can
be
shown
cannot
be
said
.
--
7
words
2
:
If
a
lion
could
talk
,
we
could
not
understand
him
.
--
10
words
3
:
Philosophy
is
a
battle
against
the
bewitchment
of
our
intelligence
by
means
of
language
.
--
14
words
4
:
Ambition
is
the
death
of
thought
.
--
6
words
5
:
The
limits
of
my
language
mean
the
limits
of
my
world
.
--
11
words
The URI begins with /adages, in effect the counterpart of the WAR filename under Tomcat or Jetty.
The URI ends with the slash, giving /adages/, which maps to the PlainResource
in the adages2 service.
The command-line option for publishing is nicely suited for development. In a production environment, web servers such as Tomcat and Jetty or application servers such as GlassFish and WebSphere provide levels of support that a standalone Java application simply cannot match.