Figure 5-3 shows the structure of a SOAP message. In SOAP over HTTP, such a message is itself the body of either an HTTP request, typically a POST request, or an HTTP response. The message consists of:
SOAP attachments are covered later in this chapter. For now, the distinction between the SOAP header and the SOAP
body is of interest because this difference is a way to focus on two related but separate SOAP APIs: an API for what might be called the
application level and a separate API for what might be called the handler level. (This distinction roughly mirrors,
in Java-based websites, the distinction between the servlet API and the filter API, although servlets and filters occur
only on the server side.) The application and handler levels occur
on the service side and on the client side. In the examples so far, the focus has been on the application level with
the use of annotations such as @WebService
and @WebMethod
. The Amazon E-Commerce clients in Chapter 4 touched on the
handler level because a hash generated from the user’s secretKey together with a timestamp had to be inserted in a
specified location within a SOAP message;
a handler did the required work.
This section introduces the handler level in two steps: first, by examining
the AwsHandlerResolver
class used in the two SOAP-based E-Commerce clients of Chapter 4, and second, with an
example that has code
at the handler level on both the client and the service side.
A quick look at how the application and handler levels interact may be useful before a full code example. Recall
the RandClient
from Chapter 4, in particular these three lines of code:
RandServiceService
service
=
new
RandServiceService
();
RandService
port
=
service
.
getRandServicePort
();
System
.
out
.
println
(
port
.
next1
());
// invoke next1
The first two lines set up the call to next1
, which returns a randomly generated integer. When this call executes,
the underlying SOAP libraries
generate the appropriate SOAP envelope and send it, over HTTP, to the RandService
. The three lines of
code are at the application level. The RandClient
does not use any handlers; hence, nothing happens
at the handler level. Suppose that there was a handler; for now, the code details can be ignored, but a depiction (see Figure 5-4) may be helpful.
A handler is inherently bidirectional: on either side, client or service, a handler handles incoming and outgoing messages. A handler can detect whether a given SOAP message is coming in or going out and react accordingly. Figure 5-4 depicts a single handler, but multiple ones, a handler chain, are possible on either side. The service side in the figure has no handlers.
SOAPHandler
, abbreviated as a message handler, has access to the entire SOAP message: headers, body, and attachments.
LogicalHandler
has access only to the payload in the body of a SOAP message.
A SOAPHandler
can do anything that a LogicalHandler
can do because the former has access to the entire SOAP
message, but a LogicalHandler
is convenient if the handler code requires access only to the payload in the body of a
SOAP message. Examples of both LogicalHandler
code and SOAPHandler
code are forthcoming.
How handlers of different types can be chained together also is covered in the examples.
Handlers are close to the metal in that they have access to either the entire SOAP message or just the payload in the body.
In Figure 5-4, the SOAP libraries that underlie the RandClient
generate a SOAP request
message in calls to next1
or nextN
. By default, these libraries create a message without
a SOAP header. The message then goes to the handler before being handed over to HTTP for
transport to the service. The handler can perform arbitrary logic on the SOAP message. For example,
the handler could add a SOAP header if the SOAP message did not have one already. The handler could
add, edit, remove, and otherwise process elements in the SOAP message—header, body, and attachments.
JAX-WS confines such low-level processing to the handler level so that the application level remains a high level
at which service operations are invoked and response values are processed. In short, the handler/application
distinction represents a separation of concerns.
The AwsHandlerResolver
and AwsSoapHandler
classes (see Example 5-1) make up
the handler-level code mentioned but not explained in Chapter 4.
Example 5-1. The AwsHandlerResolver
and AwsSoapHandler
classes
package
amazon
;
import
java.text.SimpleDateFormat
;
import
java.util.ArrayList
;
import
java.util.Calendar
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.TimeZone
;
import
javax.crypto.Mac
;
import
javax.crypto.spec.SecretKeySpec
;
import
javax.xml.namespace.QName
;
import
javax.xml.soap.SOAPBody
;
import
javax.xml.soap.SOAPMessage
;
import
javax.xml.ws.handler.Handler
;
import
javax.xml.ws.handler.HandlerResolver
;
import
javax.xml.ws.handler.MessageContext
;
import
javax.xml.ws.handler.PortInfo
;
import
javax.xml.ws.handler.soap.SOAPHandler
;
import
javax.xml.ws.handler.soap.SOAPMessageContext
;
import
org.apache.commons.codec.binary.Base64
;
import
org.w3c.dom.Element
;
import
org.w3c.dom.Node
;
public
class
AwsHandlerResolver
implements
HandlerResolver
{
private
String
awsSecretKey
;
public
AwsHandlerResolver
(
String
awsSecretKey
)
{
this
.
awsSecretKey
=
awsSecretKey
;
}
public
List
<
Handler
>
getHandlerChain
(
PortInfo
portInfo
)
{
List
<
Handler
>
handlerChain
=
new
ArrayList
<
Handler
>();
QName
serviceQName
=
portInfo
.
getServiceName
();
if
(
serviceQName
.
getLocalPart
().
equals
(
"AWSECommerceService"
))
{
handlerChain
.
add
(
new
AwsSoapHandler
(
awsSecretKey
));
}
return
handlerChain
;
}
}
class
AwsSoapHandler
implements
SOAPHandler
<
SOAPMessageContext
>
{
private
byte
[
]
secretBytes
;
public
AwsSoapHandler
(
String
awsSecretKey
)
{
secretBytes
=
getBytes
(
awsSecretKey
);
}
public
void
close
(
MessageContext
mCtx
)
{
}
public
Set
<
QName
>
getHeaders
()
{
return
null
;
}
public
boolean
handleFault
(
SOAPMessageContext
mCtx
)
{
return
true
;
}
public
boolean
handleMessage
(
SOAPMessageContext
mCtx
)
{
Boolean
outbound
=
(
Boolean
)
mCtx
.
get
(
MessageContext
.
MESSAGE_OUTBOUND_PROPERTY
);
if
(
outbound
)
{
try
{
SOAPMessage
soapMessage
=
mCtx
.
getMessage
();
SOAPBody
soapBody
=
soapMessage
.
getSOAPBody
();
Node
firstChild
=
soapBody
.
getFirstChild
();
// operation name
String
timeStamp
=
getTimestamp
();
String
signature
=
getSignature
(
firstChild
.
getLocalName
(),
timeStamp
,
secretBytes
);
append
(
firstChild
,
"Signature"
,
signature
);
append
(
firstChild
,
"Timestamp"
,
timeStamp
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"SOAPException thrown."
,
e
);
}
}
return
true
;
// continue down the handler chain
}
private
String
getSignature
(
String
operation
,
String
timeStamp
,
byte
[
]
secretBytes
)
{
try
{
String
toSign
=
operation
+
timeStamp
;
byte
[]
toSignBytes
=
getBytes
(
toSign
);
Mac
signer
=
Mac
.
getInstance
(
"HmacSHA256"
);
SecretKeySpec
keySpec
=
new
SecretKeySpec
(
secretBytes
,
"HmacSHA256"
);
signer
.
init
(
keySpec
);
signer
.
update
(
toSignBytes
);
byte
[
]
signBytes
=
signer
.
doFinal
();
String
signature
=
new
String
(
Base64
.
encodeBase64
(
signBytes
));
return
signature
;
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
private
String
getTimestamp
()
{
Calendar
calendar
=
Calendar
.
getInstance
();
SimpleDateFormat
dateFormat
=
new
SimpleDateFormat
(
"yyyy-MM-dd'T'HH:mm:ss'Z'"
);
dateFormat
.
setTimeZone
(
TimeZone
.
getTimeZone
(
"UTC"
));
return
dateFormat
.
format
(
calendar
.
getTime
());
}
private
void
append
(
Node
node
,
String
elementName
,
String
elementText
)
{
Element
element
=
node
.
getOwnerDocument
().
createElement
(
elementName
);
element
.
setTextContent
(
elementText
);
node
.
appendChild
(
element
);
}
private
byte
[
]
getBytes
(
String
str
)
{
try
{
return
str
.
getBytes
(
"UTF-8"
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
}
The code in the AwsHandlerResolver
class registers handlers with the
runtime, and the AwsSoapHandler
is the message rather than a logical handler that
gets registered. The handler API is event-driven: the application registers a handler chain,
which may be a chain with just one handler, with the Java runtime; the runtime then invokes
the appropriate handler methods on incoming and outgoing messages. The handler itself can detect
a message’s direction and react accordingly.
In the E-Commerce clients of Chapter 4, the AwsHandlerResolver
class is used in this context (line 1):
AWSECommerceService
service
=
new
AWSECommerceService
();
service
.
setHandlerResolver
(
new
AwsHandlerResolver
(
secretKey
));
![]()
AWSECommerceServicePortType
port
=
service
.
getAWSECommerceServicePort
();
Line 1 links the service
with an AwsHandlerResolver
instance that encapsulates
the user’s secretKey.
As the name
suggests, the AwsHandlerResolver
resolves which handlers, if any, are to be in play.
To implement the HandlerResolver
interface, a class must define one method:
public
List
<
Handler
>
getHandlerChain
(
PortInfo
portInfo
);
In this case, the implementation is short because there is only one handler to put into the chain:
public
List
<
Handler
>
getHandlerChain
(
PortInfo
portInfo
)
{
List
<
Handler
>
handlerChain
=
new
ArrayList
<
Handler
>();
![]()
QName
serviceQName
=
portInfo
.
getServiceName
();
if
(
serviceQName
.
getLocalPart
().
equals
(
"AWSECommerceService"
))
![]()
handlerChain
.
add
(
new
AwsSoapHandler
(
awsSecretKey
));
![]()
return
handlerChain
;
}
The getHandlerChain
method creates an empty List<Handler>
(line 1) and then
checks whether the service in question is Amazon’s AWSECommerceService
,
the official name for the E-Commerce service (line 2). If so, an instance of the
AwsSoapHandler
class, initialized with the user’s secretKey, is
constructed (line 3). The Java runtime now ensures that the handlers in
the list, in this case just one, are invoked after the SOAP message
has been built but before this message is handed off to HTTP for
transport to the service.
The handler class AwsSoapHandler
, which the resolver registers with the runtime, is a SOAPHandler
rather than a LogicalHandler
:
class
AwsSoapHandler
implements
SOAPHandler
<
SOAPMessageContext
>
{
...
The reason is that the AwsSoapHandler
needs to add elements to the message body, and
a LogicalHandler
provides access only to the current payload in the body. The methods
in the AwsSoapHandler
class
add to this initial payload.
To implement the
SOAPHandler
interface and its superinterface Handler
,
the AwsSoapHandler
class must define four methods:
getHeaders
getHeaders
method.
close
handleMessage
and handleFault
handleMessage
in the
handler; if a SOAP fault, then the runtime invokes handleFault
in the handler.
The return type for the methods handleMessage
and handleFault
is boolean
.
A return value of true
means
continue executing other handlers, if any, in the chain; a return value of
false
means do not execute other handlers, if any, in the chain. The
logic is similar to the logic of filters in servlet-based websites. In this
example, handleFault
is minimally defined (that is, the method simply
returns true
), but handleMessage
has logic to make the SOAP request
comply with the requirements of the E-Commerce service.
The handleMessage
method deserves a closer look. Here is the main part of the code,
slightly reformatted:
Boolean
outbound
=
(
Boolean
)
mCtx
.
get
(
MessageContext
.
MESSAGE_OUTBOUND_PROPERTY
);
if
(
outbound
)
{
![]()
try
{
SOAPMessage
soapMessage
=
mCtx
.
getMessage
();
SOAPBody
soapBody
=
soapMessage
.
getSOAPBody
();
![]()
Node
firstChild
=
soapBody
.
getFirstChild
();
// operation name
![]()
String
timeStamp
=
getTimestamp
();
String
signature
=
getSignature
(
firstChild
.
getLocalName
(),
timeStamp
,
secretBytes
);
append
(
firstChild
,
"Signature"
,
signature
);
![]()
append
(
firstChild
,
"Timestamp"
,
timeStamp
);
![]()
}
...
The handleMessage
method first checks, in line 1, whether it is being invoked on an outgoing (that is, request)
or incoming (that is, response) SOAP message. Incoming messages are of no interest
to this handler. For an outgoing or request SOAP message, the method uses the mCtx
argument (of type SOAPMessageContext
) to get the full SOAP message from which the SOAPBody
is extracted (line 2). The
first child in the SOAP body is the wrapper element, the name of the
E-Commerce service operation (in this example, the search operation), because the E-Commerce service uses wrapped document
style (line 3). Two utility methods provide the current time in the required format and
an HmacSHA256
digest generated from the user’s secretKey and other information (lines
4 and 5).
These low-level operations are not of particular interest right now, but they are required
in any SOAP-based request to E-Commerce service. (Chapter 6, on security, goes into the
details of a hash such as HmacSHA256
, and the next example in this chapter clarifies HmacSHA256
further.)
Once the required timestamp and hash value
are in hand, the
handleMessage
method appends two XML elements to the wrapper element in the SOAP body:
one with the so-called signature, which is the message digest or hash value, and another with the
timestamp. After handleMessage
returns true
, the runtime invokes the close
method—and then hands the amended SOAP message off to the HTTP transport. Example 5-2 depicts
the before and after situation with respect to the work of the AwsSoapHandler
: this
handler adds lines 1 and 2 to the already created SOAP request message.
The SOAP-based version of Amazon’s E-Commerce service enforces strict conditions on the structure of a SOAP request message. In particular, this service requires that a message digest (the signature) and a timestamp be in the request body as children of the wrapper element.
JAX-WS handlers are a way to separate low-level concerns, which require inspection and even manipulation of SOAP messages, from the high-level concerns of invoking, as transparently as possible, web service operations. At the application level, the SOAP is completely hidden; at the handler level, the SOAP is exposed for whatever processing is required. The next example uses handlers on the client and on the service side; this example also introduces SOAP faults, which can be thrown at either the application or the handler level.