GlassFish distinguishes between servlet-based and EJB-based web services. Servlet-based services include
REST-style and SOAP-based services of the sort published earlier with Tomcat, Jetty, or a command-line publisher.
EJB-based services also may be REST-style or SOAP-based services implemented as
Session EJBs. For
example, a JAX-RS service might be implemented as a Session EJB. Yet GlassFish and other JASes make it especially attractive to
implement legacy @Stateless
EJBs as SOAP-based web services because this requires only an additional annotation,
@WebService
.
For
servlet-based services under the JAX-WS umbrella, @WebService
and @WebServiceProvider
instances, the deployment under
GlassFish is simpler than the deployment under Tomcat because GlassFish includes, among its libraries, the full Metro
implementation of JAX-WS; hence, the Metro JAR files need not be (indeed, should not be) packaged in the deployed WAR file.
GlassFish can handle JAX-WS out of the box.
Among the services in the previous chapters deployed with Tomcat or Jetty, all would count as servlet-based in GlassFish terms. They can be deployed, as is, to GlassFish as servlet-based services. Here is a sample selection of services from Chapters 2 and 5. This review focuses on what needs to be included in a WAR file for GlassFish deployment of servlet-based services.
This is the predictions RESTful service implemented as an HttpServlet
. Here, for review, are the contents of the deployed WAR file
under GlassFish:
WEB
-
INF
/
web
.
xml
WEB
-
INF
/
classes
/
predictions2
/
Prediction
.
class
WEB
-
INF
/
classes
/
predictions2
/
Predictions
.
class
WEB
-
INF
/
classes
/
predictions2
/
PredictionsServlet
.
class
WEB
-
INF
/
data
/
predictions
.
db
WEB
-
INF
/
lib
/
json
.
jar
None of the source code needs to change; indeed, this WAR file can be deployed, as is, under Tomcat, Jetty, or GlassFish.
For review, the service supports all of the CRUD operations and responds with either XML (the default)
or JSON (if the HTTP header includes the key/value pair Accept: application/json
). The json.jar file in the deployed WAR generates the
JSON.
There is a subtle difference between the URLs used in calls against the Tomcat or Jetty deployment, on the one side, and the GlassFish deployment, on the other side. For example, for Tomcat/Jetty, the curl call:
%
curl
localhost:
8080
/
predictions2
?
id
=
31
returns in XML format the Prediction
with an id
value of 31
. Against the GlassFish deployment, the URL becomes:
%
curl
localhost:
8081
/
predictions2
/?
id
=
31
;;
/?
id
=
31
instead
of
?
id
=
31
Under GlassFish deployment, a slash, /
, occurs after the WAR filename. In this example,
the port number for Tomcat is 8080 as usual. For the sample runs in this section, the assumption is that
GlassFish is started after Tomcat was already running; hence, GlassFish awaits HTTP connections on port 8081.
The predictions2 service does not require an interceptor servlet that acts as the intermediary between client requests and
the web service. In the case of the predictions2 service, the implementation class is PredictionsServlet
, a subclass of
HttpServlet
. In short, the service instance is itself a servlet. In this sense, the predictions2 service is the least
complicated implementation among the REST-style and SOAP-based implementations of the predictions and adages services in Chapter 2.
The SOAP-based predictionsSOAP
implementation uses the Metro WSServlet
as the interceptor. The JAX-RS implementation, predictions3,
uses the Jersey ServletContainer
as the interceptor, and the Restlet implementation of the adages
RESTful service relies upon the ServerServlet
as the interceptor.
This a RESTful version of the service using JAX-RS. The WAR file can be deployed, as is, under Tomcat, Jetty, or GlassFish. Here, for review, are the contents of the deployed WAR file:
WEB
-
INF
/
web
.
xml
WEB
-
INF
/
classes
/
predictions3
/
Prediction
.
class
WEB
-
INF
/
classes
/
predictions3
/
PredictionsList
.
class
WEB
-
INF
/
classes
/
predictions3
/
PredictionsRS
.
class
WEB
-
INF
/
classes
/
predictions3
/
RestfulPrediction
.
class
WEB
-
INF
/
data
/
predictions
.
db
WEB
-
INF
/
lib
/
asm
.
jar
WEB
-
INF
/
lib
/
jackson
-
annotations
.
jar
WEB
-
INF
/
lib
/
jackson
-
core
.
jar
WEB
-
INF
/
lib
/
jackson
-
databind
.
jar
WEB
-
INF
/
lib
/
jersey
-
core
.
jar
WEB
-
INF
/
lib
/
jersey
-
server
.
jar
WEB
-
INF
/
lib
/
jersey
-
servlet
.
jar
The various jackson JAR files provide the JSON support and the jersey JARs are the RI implementation of JAX-RS.
The predictions3 service also supports all of the CRUD operations. The syntax of the CRUD calls changes deliberately
in order to highlight the JAX-WS @Path
annotation. For example, the curl call:
%
curl
http:
//localhost:8081/predictions3/resourcesP/json/31
would return, in JSON format, the Prediction
with the id
value 31
.
This is a Restlet implementation of the RESTful adages service. Nothing in the WAR file
changes from the Tomcat or Jetty deployments. The service supports all of the
CRUD operations with intuitive URIs such as /create
to create a new Adage
or
/delete/9
to delete the Adage
with the id
value of 9
. For review,
here are the contents of the deployed WAR file:
WEB
-
INF
/
web
.
xml
WEB
-
INF
/
classes
/
aphorism2
/
Adage
.
class
WEB
-
INF
/
classes
/
aphorism2
/
Adages
.
class
WEB
-
INF
/
classes
/
aphorism2
/
AdagesApplication$1
.
class
WEB
-
INF
/
classes
/
aphorism2
/
AdagesApplication
.
class
WEB
-
INF
/
classes
/
aphorism2
/
CreateResource
.
class
WEB
-
INF
/
classes
/
aphorism2
/
JsonAllResource
.
class
WEB
-
INF
/
classes
/
aphorism2
/
PlainResource
.
class
WEB
-
INF
/
classes
/
aphorism2
/
UpdateResource
.
class
WEB
-
INF
/
classes
/
aphorism2
/
XmlAllResource
.
class
WEB
-
INF
/
classes
/
aphorism2
/
XmlOneResource
.
class
WEB
-
INF
/
lib
/
org
.
json
.
jar
WEB
-
INF
/
lib
/
org
.
restlet
.
ext
.
json
.
jar
WEB
-
INF
/
lib
/
org
.
restlet
.
ext
.
servlet
.
jar
WEB
-
INF
/
lib
/
org
.
restlet
.
ext
.
xml
.
jar
WEB
-
INF
/
lib
/
org
.
restlet
.
jar
This service, like the others, deploys straightforwardly to GlassFish.
This is the SOAP-based implementation of the predictions service, which also supports all of the
CRUD operations but in this case with four methods annotated with @WebMethod
. The implementation includes a
service-side handler that verifies a security credential sent with a request. For the GlassFish deployment,
the interceptor servlet is the Metro WSServlet
, but the Metro JAR files are not in the deployed WAR file
because GlassFish comes with the Metro libraries. Here, for review, are the contents of the deployed WAR file:
WEB
-
INF
/
web
.
xml
WEB
-
INF
/
classes
/
predictions
/
DataStore
.
class
WEB
-
INF
/
classes
/
predictions
/
Prediction
.
class
WEB
-
INF
/
classes
/
predictions
/
Predictions
.
class
WEB
-
INF
/
classes
/
predictions
/
PredictionsSOAP
.
class
WEB
-
INF
/
classes
/
predictions
/
ServiceHashHandler
.
class
WEB
-
INF
/
classes
/
predictions
/
VerbosityException
.
class
WEB
-
INF
/
data
/
predictions
.
db
WEB
-
INF
/
lib
/
commons
-
codec
.
jar
WEB
-
INF
/
serviceHandler
.
xml
WEB
-
INF
/
sun
-
jaxws
.
xml
The upshot of this review is that services deployed under Endpoint
, Tomcat, or Jetty should deploy either as is or with very
small changes (for instance, removing Metro JARs from the WAR file) to GlassFish.
The next section picks up a theme from Chapter 3, which focused on clients against REST-style services. The idea is to
illustrate how a service-side API (in this case, Restlet) can be combined with a different client-side API (in this case,
the JAX-WS Dispatch
interface). In the world of JASes, mixed APIs are more the rule than the exception.
The Restlet/JAX-WS combination is not without complication, however; the section is thus an opportunity to
review, in the context of GlassFish deployment, the challenges of RESTful services before moving on to SOAP-based services
under GlassFish.
JAX-RS, Restlet, and JAX-WS with @WebServiceProvider
have service-side and client-side APIs. Chapter 3 includes
an example of the JAX-RS client-side API. This section introduces a client-side API designed specifically for @WebServiceProvider
services, but this API will be used, as an illustration, against the Restlet adages2 service deployed with GlassFish.
In summary, the service-side API is Restlet and the
client-side API is JAX-WS, in particular the Dispatch
client-side API targeted at @WebServiceProvider
services.
A RESTful Service as a @WebServiceProvider of Chapter 2 covers the REST-style adages3 service, which is implemented as a @WebServiceProvider
. The implementation class begins as follows:
public
class
AdagesProvider
implements
Provider
<
Source
>
{
The Provider<Source>
provides XML documents: a Source
is a
source of XML specifically. To implement the Provider
interface, the AdagesProvider
class defines the method:
public
Source
invoke
(
Source
request
);
// declaration
The invoke
method on the service side expects an XML Source
, perhaps null
, and returns an XML Source
,
which also could be null
. In the usual case, the response Source
is not null
; the
request Source
would be null
on HTTP bodyless requests such as GETs and DELETEs. A Source
can serve as the
source of a transformation, which yields a Result
. For example, a Source
of XML might be transformed into
an HTML, plain text, or some other MIME type of document. The standard JAX-P Transformer
class
encapsulates a transform
method that
takes two arguments: the first is a Source
of XML and the second is a Result
(see Figure 7-3).
The Provider
interface used on the service side has, as a client-side counterpart, the Dispatch
interface. A
Dispatch
object, which serves as dynamic service-proxy, likewise encapsulates an invoke
method that
expects a Source
(perhaps null
) and returns a Source
(perhaps null
but typically not). The details of
a Dispatch
and Provider
interaction can be summed up as follows (see Figure 7-4):
Dispatch
method invoke
with an XML document as the Source
. If the request does not
require such a document as an argument, the Source
can be null
.
invoke
method in a Provider
. The Source
argument
passed to the service-side invoke
corresponds to the Source
argument passed to the client-side invoke
.
Source
into an appropriate Result
—for instance, a DOM tree that can be searched for
content of interest or an HTML document suitable for display in a browser.
Source
as a response; the response is typically not null
.
Source
from the service as the return value of the Dispatch
method invoke
. The client then transforms
this Source
, as needed, into an appropriate Result
for client-side processing.
The DispatchClient
(see Example 7-1) uses the invoke
method in a Dispatch
service-proxy to
make CRUD calls against the Restlet adages2 service. This client creates a Service
instance
(line 1), whose identity is a QName
, in this case uri:restlet
. The name is arbitrary but
should be unique. The Restlet service, written with an altogether different API, has no invoke
method to pair up with the Dispatch
method named invoke
. Nonetheless, the communication
between the two is mostly trouble-free. The trouble spot is clarified next.
Example 7-1. The DispatchClient
against the Restlet adages2 service
import
javax.xml.ws.Service
;
import
javax.xml.namespace.QName
;
import
javax.xml.ws.http.HTTPBinding
;
import
javax.xml.ws.ServiceMode
;
import
javax.xml.ws.Dispatch
;
import
javax.xml.transform.Source
;
import
javax.xml.transform.stream.StreamSource
;
import
javax.xml.transform.stream.StreamResult
;
import
javax.xml.ws.handler.MessageContext
;
import
java.util.Map
;
import
java.io.StringReader
;
public
class
DispatchClient
{
private
static
final
String
baseUrl
=
"http://localhost:8081/aphorisms2/"
;
public
static
void
main
(
String
[
]
args
)
{
new
DispatchClient
().
callRestlet
();
}
private
void
callRestlet
()
{
QName
qname
=
getQName
(
"uri"
,
"restlet"
);
// service's name
Service
service
=
Service
.
create
(
qname
);
![]()
runTests
(
service
);
}
private
void
runTests
(
Service
service
)
{
// get all -- plain text
Dispatch
<
Source
>
dispatch
=
getDispatch
(
service
,
getQName
(
"get"
,
"All"
),
![]()
baseUrl
);
setRequestMethod
(
dispatch
,
"GET"
);
![]()
Source
result
=
dispatch
.
invoke
(
null
);
![]()
stringifyAndPrintResponse
(
"Result (plaintext):"
,
result
);
// get all -- xml
dispatch
=
getDispatch
(
service
,
getQName
(
"get"
,
"AllXml"
),
baseUrl
+
"xml"
);
setRequestMethod
(
dispatch
,
"GET"
);
result
=
dispatch
.
invoke
(
null
);
stringifyAndPrintResponse
(
"Result (xml):"
,
result
);
// get all -- json
dispatch
=
getDispatch
(
service
,
getQName
(
"get"
,
"AllJson"
),
baseUrl
+
"json"
);
setRequestMethod
(
dispatch
,
"GET"
);
result
=
dispatch
.
invoke
(
null
);
stringifyAndPrintResponse
(
"Result (json):"
,
result
);
// get one -- xml
dispatch
=
getDispatch
(
service
,
getQName
(
"get"
,
"OneXml"
),
baseUrl
+
"xml/2"
);
setRequestMethod
(
dispatch
,
"GET"
);
result
=
dispatch
.
invoke
(
null
);
stringifyAndPrintResponse
(
"Result (one--xml):"
,
result
);
// delete
dispatch
=
getDispatch
(
service
,
getQName
(
"delete"
,
"One"
),
baseUrl
+
"delete/3"
);
setRequestMethod
(
dispatch
,
"DELETE"
);
result
=
dispatch
.
invoke
(
null
);
stringifyAndPrintResponse
(
"Result (delete):"
,
result
);
// post -- failure
dispatch
=
getDispatch
(
service
,
getQName
(
"post"
,
"Create"
),
baseUrl
+
"create"
);
setRequestMethod
(
dispatch
,
"POST"
);
String
cargo
=
"<a>words=This test will not work!</a>"
;
// minimal XML
![]()
StringReader
reader
=
new
StringReader
(
cargo
);
result
=
dispatch
.
invoke
(
new
StreamSource
(
reader
));
stringifyAndPrintResponse
(
"Result (post):"
,
result
);
}
private
Dispatch
<
Source
>
getDispatch
(
Service
service
,
QName
portName
,
String
url
)
{
service
.
addPort
(
portName
,
HTTPBinding
.
HTTP_BINDING
,
url
);
return
service
.
createDispatch
(
portName
,
Source
.
class
,
javax
.
xml
.
ws
.
Service
.
Mode
.
MESSAGE
);
}
private
void
setRequestMethod
(
Dispatch
<
Source
>
dispatcher
,
String
method
)
{
Map
<
String
,
Object
>
rc
=
dispatcher
.
getRequestContext
();
rc
.
put
(
MessageContext
.
HTTP_REQUEST_METHOD
,
method
);
}
private
QName
getQName
(
String
ns
,
String
ln
)
{
return
new
QName
(
ns
,
ln
);
}
private
void
stringifyAndPrintResponse
(
String
msg
,
Source
result
)
{
String
str
=
null
;
if
(
result
instanceof
StreamSource
)
{
try
{
StreamSource
source
=
(
StreamSource
)
result
;
byte
[
]
buff
=
new
byte
[
1024
];
// adages are short
source
.
getInputStream
().
read
(
buff
);
str
=
new
String
(
buff
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
System
.
out
.
println
(
"\n"
+
msg
+
"\n"
+
str
);
}
}
After the setup, the DispatchClient
makes six calls against the Restlet service (lines 2 through
4 illustrate), which
can be summarized as follows:
getAllPT
The first call gets all of the Adages
in plain text. The response, extracted from the
Source
, is:
1
:
What
can
be
shown
cannot
be
said
.
--
7
words
2
:
If
a
lion
could
talk
,
we
could
not
understand
him
.
--
10
words
...
This call uses a GET request with the default URI /
, the slash.
For all of the calls against the
Restlet service, the Source
of XML returned as a response is sent to the
stringifyAndPrintResponse
method. This method first checks whether the Source
is, in fact,
a StreamSource
and, if so, extracts the bytes from the InputStream
encapsulated in the
StreamSource
. These bytes then are fed to a String
constructor, which produces a string. The
resulting string may be plain text, as in this first sample call, or XML and JSON, as in later
sample calls.
getAllXml
/xml
. The response has the
same informational content as getAllPT
but the format is XML.
getAllJson
/json
. The response
is in JSON format.
getOne
/xml/2
, which specifies
the Adage
with an id
of 2. The response is an XML document.
deleteOne
/delete/3
, which specifies
the Adage
with an id
of 3 as the one to remove from the list of Adages
. The
response is a plain text confirmation of the deletion.
create
This call fails. The response is an error message:
No
words
were
given
for
the
adage
.
The problem arises because, with a POST request against the URI /create
, the Source
argument
to the invoke
method cannot be null
but, rather, must contain the words
in the Adage
to
be created. The Restlet service expects a simple HTML-like form in the body of the POST request,
and this form has key/value pairs such as:
words
=
This
is
the
way
the
world
ends
The Restlet service searches on the key words
to get the value, in this case the line from the T. S. Eliot
poem “The Hollow Men.” On the client side, however, the call to the create operation uses a POST request
against the URI /create
, and the contents of the POST body are given as a Source
instance—of XML. When the
underlying XML libraries parse the non-XML string above, the parser throws an exception. The fix is to
turn the string into XML, for example, the minimalist XML document (line 5):
<
a
>
words
=
This
is
the
way
the
world
ends
</
a
>
When this document is turned into a StreamSource
argument to the Dispatch
method invoke
, the
XML parser is satisfied. The problem now shifts to the service side because the Restlet service expects
a simple key/value pair, not a key/value pair embedded as text in an XML element. As a result, the Restlet service complains that it cannot
find the lookup key words
in the body of the POST request.
The example reinforces a hard lesson in programming and many other activities: the devil is in the details. The fix to the problem would be
relatively straightforward; perhaps the easiest fix would be to make the Restlet service flexible enough to handle POST requests whose cargo is either
plain text or XML. The example also
underscores that the JAX-WS @WebServiceProvider
API is XML-centric. In this API, a Dispatch
client against a Provider
service such as
the adages3 service (see Section 2.5 in Chapter 2) would be natural because each side would be dealing with XML Source
arguments and return values.