Chapter 1 has a sample RESTful service implemented as a JSP script and two backend classes: Prediction
and
Predictions
. The JSP-based service supported only GET requests. This section revises the example to provide an
HttpServlet
implementation with support for the four CRUD operations:
Prediction
can be created with a POST request whose body should have two key/value pairs: a who
key whose value
is the name of the predictor and a what
key whose value is the prediction.
Prediction
objects can be read one at a time or all together with a GET request.
If the GET request has a query string with an id
key, then the corresponding Prediction
, if any, is returned.
If the GET request has no query string, then the full list of Predictions
is returned. On any GET request,
the client can indicate a preference for JSON rather than the default XML format.
Prediction
can be updated with a PUT request that provides the identifier for the
Prediction
and either
a new who
or a new what
.
Prediction
can be deleted.
The earlier JSP service is predictions and the servlet revision is predictions2.
The structure of predictions2 differs from that of predictions in several ways.
The most obvious change is that
an explicit HttpServlet
subclass replaces the JSP script. There are also changes in the details of the Prediction
and
Predictions
classes, which still provide backend support. The details follow.
There are small but important changes to the Prediction
class (see Example 2-1), which now includes an id
property (line 1), an auto-incremented integer that the service sets when a Prediction
object is constructed.
Example 2-1. The backend Prediction
class
package
predictions2
;
import
java.io.Serializable
;
public
class
Prediction
implements
Serializable
,
Comparable
<
Prediction
>
{
private
String
who
;
// person
private
String
what
;
// his/her prediction
private
int
id
;
// identifier used as lookup key
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
;
}
public
void
setId
(
int
id
)
{
this
.
id
=
id
;
}
![]()
public
int
getId
()
{
return
this
.
id
;
}
public
int
compareTo
(
Prediction
other
)
{
return
this
.
id
-
other
.
id
;
}
}
The id
property is used to sort the Prediction
objects, which explains why the Prediction
class implements
the interface Comparable
used in sorting:
public
class
Prediction
implements
Serializable
,
Comparable
<
Prediction
>
{
Implementing the Comparable
interface requires that the compareTo
method be defined:
public
int
compareTo
(
Prediction
other
)
{
return
this
.
id
-
other
.
id
;
}
The compareTo
method uses the comparison semantics of the age-old C function qsort
. For illustration,
suppose that this.id
in the code above is 7 and other.id
is 12, where this
is the current object
and other
is another Prediction
object against which the current Prediction
object is being
compared. The difference of 7–12 is the negative integer –5, which signals that the current Prediction
precedes the other Prediction
because 7 precedes 12. In general:
The implementation of the compareTo
method means the sort is to be in ascending order. Were the return
statement changed to:
return
other
.
id
-
this
.
id
;
the sort would be in descending order. The Prediction
objects are sorted for ease of confirming that
the CRUD operations work correctly. For example, if a Prediction
object is created with the
appropriate POST request, then the newly created Prediction
occurs at the end of the
Prediction
list. In similar fashion, it is easy to confirm that the other destructive CRUD
operations—PUT (update) and DELETE—work as intended by inspecting the resulting sorted list of Prediction
objects.
A Prediction
is still Serializable
so that a list of these can be serialized into
XML using the XmlEncoder
utility. An added feature is that this list can be formatted in
JSON if the client so requests.
The utility class Predictions
has changed as well (see Example 2-2). As explained in the
sidebar about thread synchronization and servlets, the Map
of the earlier JSP
implementation gives way to a ConcurrentMap
so that the code can avoid explicit
locks in the form of synchronized
blocks.
Example 2-2. The backend Predictions
class
package
predictions2
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.io.BufferedReader
;
import
java.io.ByteArrayOutputStream
;
import
java.util.concurrent.ConcurrentMap
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
java.util.Collections
;
import
java.beans.XMLEncoder
;
// simple and effective
import
javax.servlet.ServletContext
;
public
class
Predictions
{
private
ConcurrentMap
<
Integer
,
Prediction
>
predictions
;
private
ServletContext
sctx
;
private
AtomicInteger
mapKey
;
public
Predictions
()
{
predictions
=
new
ConcurrentHashMap
<
Integer
,
Prediction
>();
mapKey
=
new
AtomicInteger
();
}
public
void
setServletContext
(
ServletContext
sctx
)
{
this
.
sctx
=
sctx
;
}
public
ServletContext
getServletContext
()
{
return
this
.
sctx
;
}
public
void
setMap
(
ConcurrentMap
<
String
,
Prediction
>
predictions
)
{
// no-op for now
}
public
ConcurrentMap
<
Integer
,
Prediction
>
getMap
()
{
// Has the ServletContext been set?
if
(
getServletContext
()
==
null
)
return
null
;
// Has the data been read already?
if
(
predictions
.
size
()
<
1
)
populate
();
return
this
.
predictions
;
}
public
String
toXML
(
Object
obj
)
{
String
xml
=
null
;
try
{
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
();
XMLEncoder
encoder
=
new
XMLEncoder
(
out
);
encoder
.
writeObject
(
obj
);
// serialize to XML
encoder
.
close
();
xml
=
out
.
toString
();
// stringify
}
catch
(
Exception
e
)
{
}
return
xml
;
}
public
int
addPrediction
(
Prediction
p
)
{
int
id
=
mapKey
.
incrementAndGet
();
p
.
setId
(
id
);
predictions
.
put
(
id
,
p
);
return
id
;
}
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
);
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
]);
addPrediction
(
p
);
}
}
catch
(
IOException
e
)
{
}
}
}
}
The Predictions
class now has an addPrediction
method:
public
int
addPrediction
(
Prediction
p
)
{
int
id
=
mapKey
.
incrementAndGet
();
// AtomicInteger
p
.
setId
(
id
);
predictions
.
put
(
id
,
p
);
return
id
;
}
to support POST requests. The servlet’s doPost
method creates a new Prediction
, sets
the who
and what
properties with data from the POST message’s body, and then
invokes addPrediction
to add the newly constructed Prediction
to the map whose
object reference is predictions
. The mapKey
, a thread-safe AtomicInteger
, gets incremented
with each new Prediction
and behaves like an auto-incremented integer in a database system; the
mapKey
value becomes the id
of each newly constructed
Prediction
, thereby ensuring that each Prediction
has a unique id
.
The remaining Predictions
code is slightly changed, if at all, from the earlier version.
For example, the populate
method is modified slightly to give each newly constructed
Prediction
an id
, but the method’s main job is still to read data from the text
file encapsulated in the WAR—data that contain the who
and what
of each
Prediction
.
The PredictionServlet
(see Example 2-3) replaces the JSP script and differs from this
script in supporting all of the CRUD operations. The servlet offers new functionality
by allowing the client to request the JSON format for the response of any GET request. Further,
the earlier JSP script interpreted GET to mean read all but the servlet allows the client to
request one specified Prediction
or all of them. The code for the PredictionServlet
is long enough
that it makes sense to isolate important code segments for clarification.
Example 2-3. The PredictionsServlet
with full support for the CRUD operations
package
predictions2
;
import
java.util.concurrent.ConcurrentMap
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServlet
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
javax.xml.ws.http.HTTPException
;
import
java.util.Arrays
;
import
java.io.ByteArrayInputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.OutputStream
;
import
java.io.BufferedReader
;
import
java.io.InputStreamReader
;
import
java.beans.XMLEncoder
;
import
org.json.JSONObject
;
import
org.json.XML
;
public
class
PredictionsServlet
extends
HttpServlet
{
private
Predictions
predictions
;
// backend bean
// Executed when servlet is first loaded into container.
// Create a Predictions object and set its servletContext
// property so that the object can do I/O.
@Override
public
void
init
()
{
predictions
=
new
Predictions
();
predictions
.
setServletContext
(
this
.
getServletContext
());
}
// GET /predictions2
// GET /predictions2?id=1
// If the HTTP Accept header is set to application/json (or an equivalent
// such as text/x-json), the response is JSON and XML otherwise.
@Override
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
String
param
=
request
.
getParameter
(
"id"
);
Integer
key
=
(
param
==
null
)
?
null
:
new
Integer
(
param
.
trim
());
// Check user preference for XML or JSON by inspecting
// the HTTP headers for the Accept key.
boolean
json
=
false
;
String
accept
=
request
.
getHeader
(
"accept"
);
if
(
accept
!=
null
&&
accept
.
contains
(
"json"
))
json
=
true
;
// If no query string, assume client wants the full list.
if
(
key
==
null
)
{
ConcurrentMap
<
Integer
,
Prediction
>
map
=
predictions
.
getMap
();
// Sort the map's values for readability.
Object
[]
list
=
map
.
values
().
toArray
();
Arrays
.
sort
(
list
);
String
xml
=
predictions
.
toXML
(
list
);
sendResponse
(
response
,
xml
,
json
);
}
// Otherwise, return the specified Prediction.
else
{
Prediction
pred
=
predictions
.
getMap
().
get
(
key
);
if
(
pred
==
null
)
{
// no such Prediction
String
msg
=
key
+
" does not map to a prediction.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
}
else
{
// requested Prediction found
sendResponse
(
response
,
predictions
.
toXML
(
pred
),
json
);
}
}
}
// POST /predictions2
// HTTP body should contain two keys, one for the predictor ("who") and
// another for the prediction ("what").
@Override
public
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
String
who
=
request
.
getParameter
(
"who"
);
String
what
=
request
.
getParameter
(
"what"
);
// Are the data to create a new prediction present?
if
(
who
==
null
||
what
==
null
)
throw
new
HTTPException
(
HttpServletResponse
.
SC_BAD_REQUEST
);
// Create a Prediction.
Prediction
p
=
new
Prediction
();
p
.
setWho
(
who
);
p
.
setWhat
(
what
);
// Save the ID of the newly created Prediction.
int
id
=
predictions
.
addPrediction
(
p
);
// Generate the confirmation message.
String
msg
=
"Prediction "
+
id
+
" created.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
}
// PUT /predictions
// HTTP body should contain at least two keys: the prediction's id
// and either who or what.
@Override
public
void
doPut
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
/* A workaround is necessary for a PUT request because neither Tomcat
nor Jetty generates a workable parameter map for this HTTP verb. */
String
key
=
null
;
String
rest
=
null
;
boolean
who
=
false
;
/* Let the hack begin. */
try
{
BufferedReader
br
=
new
BufferedReader
(
new
InputStreamReader
(
request
.
getInputStream
()));
String
data
=
br
.
readLine
();
/* To simplify the hack, assume that the PUT request has exactly
two parameters: the id and either who or what. Assume, further,
that the id comes first. From the client side, a hash character
# separates the id and the who/what, e.g.,
id=33#who=Homer Allision
*/
String
[]
args
=
data
.
split
(
"#"
);
// id in args[0], rest in args[1]
String
[]
parts1
=
args
[
0
].
split
(
"="
);
// id = parts1[1]
key
=
parts1
[
1
];
String
[]
parts2
=
args
[
1
].
split
(
"="
);
// parts2[0] is key
if
(
parts2
[
0
].
contains
(
"who"
))
who
=
true
;
rest
=
parts2
[
1
];
}
catch
(
Exception
e
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_INTERNAL_SERVER_ERROR
);
}
// If no key, then the request is ill formed.
if
(
key
==
null
)
throw
new
HTTPException
(
HttpServletResponse
.
SC_BAD_REQUEST
);
// Look up the specified prediction.
Prediction
p
=
predictions
.
getMap
().
get
(
new
Integer
(
key
.
trim
()));
if
(
p
==
null
)
{
// not found?
String
msg
=
key
+
" does not map to a Prediction.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
}
else
{
// found
if
(
rest
==
null
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_BAD_REQUEST
);
}
// Do the editing.
else
{
if
(
who
)
p
.
setWho
(
rest
);
else
p
.
setWhat
(
rest
);
String
msg
=
"Prediction "
+
key
+
" has been edited.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
}
}
}
// DELETE /predictions2?id=1
@Override
public
void
doDelete
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
String
param
=
request
.
getParameter
(
"id"
);
Integer
key
=
(
param
==
null
)
?
null
:
new
Integer
(
param
.
trim
());
// Only one Prediction can be deleted at a time.
if
(
key
==
null
)
throw
new
HTTPException
(
HttpServletResponse
.
SC_BAD_REQUEST
);
try
{
predictions
.
getMap
().
remove
(
key
);
String
msg
=
"Prediction "
+
key
+
" removed.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
}
catch
(
Exception
e
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_INTERNAL_SERVER_ERROR
);
}
}
// Method Not Allowed
@Override
public
void
doTrace
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_METHOD_NOT_ALLOWED
);
}
@Override
public
void
doHead
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_METHOD_NOT_ALLOWED
);
}
@Override
public
void
doOptions
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_METHOD_NOT_ALLOWED
);
}
// Send the response payload to the client.
private
void
sendResponse
(
HttpServletResponse
response
,
String
payload
,
boolean
json
)
{
try
{
// Convert to JSON?
if
(
json
)
{
JSONObject
jobt
=
XML
.
toJSONObject
(
payload
);
payload
=
jobt
.
toString
(
3
);
// 3 is indentation level for nice look
}
OutputStream
out
=
response
.
getOutputStream
();
out
.
write
(
payload
.
getBytes
());
out
.
flush
();
}
catch
(
Exception
e
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_INTERNAL_SERVER_ERROR
);
}
}
}
Recall that each of the do-methods in an HttpServlet
take the same arguments: an
HttpServletRequest
; a map that contains the information encapsulated in the HTTP request; and an
HttpServletResponse
, which encapsulates an output stream for communicating back with the client.
Here is the start of the doGet
method:
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
The HttpServletRequest
has a getParameter
method that expects a string argument, a
key into the request map, and returns either null
if there is no such key or the
key’s value as a string otherwise. The getParameter
method is agnostic about whether the
key/value pairs are in the body of, for example, a POST request or in the
query string of, for example, a GET request. The method works the same in either case.
There is also a getParameters
method that returns the parameter collection as a whole.
In the case of PredictionsServlet
, the doGet
method needs to answer two questions
about the incoming request:
Does the bodyless GET request include a key named id
whose value identifies a particular Prediction
?
If the id
is present, the doGet
method uses the id
to perform a lookup against the ConcurrentMap
, which
holds references to all of the Prediction
objects. If the lookup fails, then the doGet
method returns an XML message to that effect:
Prediction
pred
=
predictions
.
getMap
().
get
(
key
);
if
(
pred
==
null
)
{
// no such Prediction
String
msg
=
key
+
" does not map to a prediction.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
}
The last argument to the sendResponse
method indicates whether JSON rather than XML should be
sent back to the client. In this example, XML is returned because the JSON flag is false
.
If the id
parameter is not present, the doGet
method assumes that the client wants to read a
list of all Predictions
and returns this list in either JSON or XML format:
ConcurrentMap
<
String
,
Prediction
>
map
=
predictions
.
getMap
();
// Sort the map's values for readability.
Object
[]
list
=
map
.
values
().
toArray
();
Arrays
.
sort
(
list
);
// other ways to sort shown later
...
Does the client prefer JSON over XML?
In an HTTP request, the requester can express a preference for the MIME type of the returned representation. For example, the header element:
Accept:
text
/
html
expresses a preference for the MIME type text/html
. Among the MIME combinations is
application/json
that, together with several variants, expresses a preference for JSON. The doGet
method therefore uses the getHeader
method in the HttpServletRequest
to inspect the HTTP header
element with Accept
as its key:
boolean
json
=
false
;
String
accept
=
request
.
getHeader
(
"accept"
);
if
(
accept
!=
null
&&
accept
.
contains
(
"json"
))
json
=
true
;
This check determines whether the client prefers JSON over XML. (Recall that HTTP is case insensitive; hence,
the key could be Accept
, accept
, ACCEPT
, and so on.) The json
flag is the third argument
to the sendResponse
method:
private
void
sendResponse
(
HttpServletResponse
res
,
String
payload
,
boolean
json
)
{
// json format?
try
{
if
(
json
)
{
JSONObject
jobt
=
XML
.
toJSONObject
(
payload
);
payload
=
jobt
.
toString
(
3
);
// 3 is indentation level
}
OutputStream
out
=
res
.
getOutputStream
();
out
.
write
(
payload
.
getBytes
());
out
.
flush
();
}
catch
(
Exception
e
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_INTERNAL_SERVER_ERROR
);
}
}
Details about generating the JSON are considered next.
The deployed WAR file predictions2.war includes a lightweight, third-party JSON library in the
JAR file json.jar (it is available at the JSON in Java website). If the client prefers JSON over XML, then the response payload
is converted to JSON. If anything goes awry in sending the response back to the client,
the servlet throws an HTTPException
, which in this case generates a response with
HTTP status code 500 for Internal Server Error, a catchall for request-processing errors on
the server.
The doPost and doPut operations are similar in that doPost
creates an altogether new Prediction
using data in the body of a POST request, whereas doPut
updates an existing Prediction
from
data in the body of a PUT request. The main difference is that a PUT request needs to include
the id
of the Prediction
to be updated, whereas a POST request creates a new
Prediction
and then sets its id
to an auto-incremented integer. In implementation, however,
doPost
and doPut
differ significantly because the servlet container’s runtime does not generate a
usable parameter map, the HttpServletRequest
, on a PUT request; on a POST request, the
map is usable. (This is the case in both Tomcat and Jetty.) As a result, the doPut
implementation
extracts the data directly from an input stream.
To begin, here is the doPost
implementation, without the comments:
public
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
String
who
=
request
.
getParameter
(
"who"
);
![]()
String
what
=
request
.
getParameter
(
"what"
);
![]()
if
(
who
==
null
||
what
==
null
)
throw
new
HTTPException
(
HttpServletResponse
.
SC_BAD_REQUEST
);
Prediction
p
=
new
Prediction
();
![]()
p
.
setWho
(
who
);
![]()
p
.
setWhat
(
what
);
![]()
int
id
=
predictions
.
addPrediction
(
p
);
![]()
String
msg
=
"Prediction "
+
id
+
" created.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
![]()
}
The two calls to the getParameter
method extract the required data (lines 1 and 2).
A new Prediction
is then constructed, its who
and what
properties are set, and a confirmation is
generated for the client (lines 3 through 7).
In the doPut
method, the getParameter
method does not work correctly because neither
Tomcat nor Jetty builds a usable parameter map in HttpServletRequest
.
The workaround is to access directly the input stream encapsulated in the
request structure:
BufferedReader
br
=
new
BufferedReader
(
new
InputStreamReader
(
request
.
getInputStream
()));
String
data
=
br
.
readLine
();
...
The next step is to extract the data
from this stream. The code, though not pretty, gets
the job done. The point of interest is that the HttpServletRequest
does provide
access to the underlying input stream from which the PUT data can be extracted. Using
the getParameter
method is, of course, much easier.
The body of doDelete
method has simple logic:
String
key
=
request
.
getParameter
(
"id"
);
if
(
key
==
null
)
throw
new
HTTPException
(
HttpServletResponse
.
SC_BAD_REQUEST
);
try
{
predictions
.
getMap
().
remove
(
key
);
![]()
String
msg
=
"Prediction "
+
key
+
" removed.\n"
;
sendResponse
(
response
,
predictions
.
toXML
(
msg
),
false
);
}
catch
(
Exception
e
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_INTERNAL_SERVER_ERROR
);
}
If the id
for the Prediction
can be extracted from the parameter map, the
prediction is effectively removed from the collection by removing the lookup key
from the ConcurrentMap
(line 1).
The PredictionsServlet
also implements three other do-methods, and all in the same way. Here, for example,
is the implementation of doHead
:
public
void
doHead
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
throw
new
HTTPException
(
HttpServletResponse
.
SC_METHOD_NOT_ALLOWED
);
}
Throwing the HTTPException
signals to the client that the underlying HTTP
verb, in this case HEAD
, is not supported. The numeric status code for
Method Not Allowed
is 405. The web service designer thus has an idiomatic
way to reject particular HTTP verbs: throw a Method Not Allowed
exception.
Example 2-4 is a list of curl calls against the service. These calls serve as a very preliminary test of the service. Two semicolons introduce comments that explain the purpose of the curl call. Recall that the Ant script can be used to deploy the predictions2 service under Tomcat:
%
ant
-
Dwar
.
name
=
predictions2
deploy
Example 2-4. A suite of curl calls against the predictions RESTful service
;;
GET
all
predictions
(
XML
response
)
%
curl
localhost:
8080
/
predictions2
/
;;
curl
--
request
GET
...
;;
GET
a
specified
saying
(
XML
response
)
%
curl
localhost:
8080
/
predictions2
?
id
=
31
;;
GET
all
predictions
(
JSON
response
)
%
curl
--
header
"Accept: application/json"
localhost:
8080
/
predictions2
/
;;
GET
a
specified
saying
(
JSON
response
)
%
curl
--
header
"Accept: application/json"
localhost:
8080
/
predictions2
?
id
=
31
;;
POST
a
new
saying
%
curl
--
request
POST
--
data
"who=TSEliot& \
what=This is the way the world ends"
localhost:
8080
/
predictions2
/
;;
GET
all
predictions
to
confirm
the
POST
(
new
saying
is
at
the
end
)
%
curl
localhost:
8080
/
predictions2
/
;;
PUT
new
data
into
an
existing
saying
%
curl
--
request
PUT
\
--
data
"id=33#what=This is an update"
localhost:
8080
/
predictions2
/
;;
GET
all
predictions
to
confirm
the
PUT
(
edited
saying
is
at
the
end
)
%
curl
localhost:
8080
/
predictions2
/
;;
DELETE
a
specificed
saying
%
curl
--
request
DELETE
localhost:
8080
/
predictions2
?
id
=
33
;;
GET
all
predictions
to
confirm
the
DELETE
%
curl
localhost:
8080
/
predictions2
/
The XML responses from the predictions2 service are formatted exactly the same as in the
original version, which did not support JSON responses. Here is a sample JSON response from a GET
request on the Prediction
with id
31:
{
"java"
:
{
"class"
:
"java.beans.XMLDecoder"
,
"object"
:
{
"void"
:
[
{
"int"
:
31
,
"property"
:
"id"
},
{
"string"
:
"Balanced clear-thinking utilisation
will expedite collaborative initiatives."
,
"property"
:
"what"
},
{
"string"
:
"Deven Blanda"
,
"property"
:
"who"
}],
"class"
:
"predictions2.Prediction"
},
"version"
:
"1.7.0_17"
}}