In the predictionsSOAP service, three of the operations require the id
of a Prediction
: the getOne,
edit, and delete operations. The id
is a positive integer. What happens if the client, through
oversight or mischief, submits a negative integer or zero as the id
? The service
throws a SOAP fault. A SOAP request with a bad id
is a waste of time and bandwidth, and avoiding
such a request would be a gain in efficiency.
To guard against an invalid id
, a client-side handler could inspect every
outgoing SOAP message to determine if it has an id
and, if so, whether the id
is a positive
integer. If the id
is negative, the handler could substitute the absolute value and let the
request continue on its way to the service; if the id
is zero, the handler could throw an exception immediately and
thereby short-circuit a request that is doomed to fail on the service side. The client-side
SOAPHandler
already in place could be amended to do this work, as the SOAP handler has access to the entire SOAP message; however, a
LogicalHandler
is better suited to the proposed task because the id
is part of the payload in
the SOAP request’s body. The existing SOAPHandler
can be left as is and a LogicalHandler
can
be added to the handler chain. Modular design recommends this approach, which is also an opportunity
to show a two-member handler chain in action.
There are two ways in which the LogicalHandler
can access the payload of a SOAP message: as a native
Java object generated with JAX-B or as an XML document. This example uses a Java object so that
the id
property can be inspected and perhaps changed using the familiar get/set idiom.
If the id
in the client request message is negative, a set-method then can be used to change the
id
value. If the id
is zero, the handler can disable any further client-side handling and throw a fault
to signal that the message cannot be fixed. With JAX-B in support, the processing is straightforward.
The IdHandler
class (see Example 5-10) implements the LogicalHandler
rather than the SOAPHandler
interface (line 1). To implement this interface, the IdHandler
needs to define close
, handleFault
, and handleMessage
, but a LogicalHandler
implementation, unlike a SOAPHandler
implementation, does not define the getHeaders
method precisely because a logical handler has no
access to the SOAP headers. In this example, the close
and handleFault
methods are minimally
defined.
Example 5-10. The client-side IdLogicalHandler
class
IdHandler
implements
LogicalHandler
<
LogicalMessageContext
>
{
![]()
public
void
close
(
MessageContext
mctx
)
{
}
public
boolean
handleFault
(
LogicalMessageContext
lmctx
)
{
return
true
;
}
public
boolean
handleMessage
(
LogicalMessageContext
lmctx
)
{
Boolean
outbound
=
(
Boolean
)
lmctx
.
get
(
MessageContext
.
MESSAGE_OUTBOUND_PROPERTY
);
if
(
outbound
)
{
// request?
LogicalMessage
msg
=
lmctx
.
getMessage
();
try
{
JAXBContext
jaxbCtx
=
JAXBContext
.
newInstance
(
"clientSOAP"
);
![]()
Object
payload
=
msg
.
getPayload
(
jaxbCtx
);
![]()
// Check payload to be sure it's what we want.
if
(
payload
instanceof
JAXBElement
)
{
Object
value
=
((
JAXBElement
)
payload
).
getValue
();
// Three possibilities of interest: GetOne, Edit, or Delete
int
id
=
0
;
boolean
getOne
,
edit
,
delete
;
getOne
=
edit
=
delete
=
false
;
if
(
value
.
toString
().
contains
(
"GetOne"
))
{
![]()
id
=
((
GetOne
)
value
).
getArg0
();
getOne
=
true
;
}
else
if
(
value
.
toString
().
contains
(
"Edit"
))
{
id
=
((
Edit
)
value
).
getArg0
();
edit
=
true
;
}
else
if
(
value
.
toString
().
contains
(
"Delete"
))
{
id
=
((
Delete
)
value
).
getArg0
();
delete
=
true
;
}
else
return
true
;
// GetAll or Create
// If id > 0, there is no problem to fix on the client side.
if
(
id
>
0
)
return
true
;
![]()
// If the request is GetOne, Edit, or Delete and the id is zero,
// there is a problem that cannot be fixed.
if
(
getOne
||
edit
||
delete
)
{
if
(
id
==
0
)
// can't fix
throw
new
RuntimeException
(
"ID cannot be zero!"
);
![]()
// id < 0 and operation is GetOne, Edit, or Delete
int
newId
=
Math
.
abs
(
id
);
// Update argument.
if
(
getOne
)
((
GetOne
)
value
).
setArg0
(
newId
);
![]()
else
if
(
edit
)
((
Edit
)
value
).
setArg0
(
newId
);
else
if
(
delete
)
((
Delete
)
value
).
setArg0
(
newId
);
// Update payload.
((
JAXBElement
)
payload
).
setValue
(
value
);
![]()
// Update message
msg
.
setPayload
(
payload
,
jaxbCtx
);
![]()
}
}
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
return
true
;
}
}
The logic in the handleMessage
method is a bit tricky. Like the ClientHashHandler
, the
IdHandler
needs to work only on outgoing (that is, request) messages; hence, the IdHandler
first
checks on the direction of the SOAP message. The logical handler then extracts the payload from an
outgoing message. The payload is available in two formats:
getPayload
method is invoked with no arguments (line 3),
the payload would be returned as an XML document. This document then could be parsed and edited,
as required; the corresponding setPayload
method would then be used to update the message
payload.
getPayload
method is invoked with a JAXBContext
as the argument, a reference to an Object
is returned (line 3).
In this example, the JAXBContext
is created from the soapClient
package (line 2) because three classes in
this package are of interest: GetOne
, Edit
, and Delete
. These wsimport-generated classes are the Java
data types that represent SOAP request messages against the service’s getOne, edit, and delete operations, respectively.
If only a single SOAP request type were of interest, then the JAXBContext
could be created for a single class instead
of for the entire package of classes.
The extracted payload could be from any request. The next task, therefore, is
to exclude requests that do not include an id
. There are two
such requests, one against the getAll operation and another against the create operation. The requests of
interest are against the getOne, edit, and delete operations; an if
construct, which starts on line 4,
then determines whether the request is against one of these three operations. In summary, requests against the
create and getAll operations are filtered out, and the remaining requests are inspected to determine if the
outgoing id
is a bad value.
If the outgoing message has an id
, the handler extracts the id
from the payload with a statement such as:
id
=
((
GetOne
)
value
).
getArg0
();
There is one such statement for each of the three types: GetOne
, Edit
, and Delete
. If the outgoing SOAP
message is none of these, then true
is returned from the else
clause to signal that the IdHandler
has finished its work and other handlers in the chain, if any, can begin theirs. Here is the code
segment for review:
if
(
value
.
toString
().
contains
(
"GetOne"
))
{
id
=
((
GetOne
)
value
).
getArg0
();
getOne
=
true
;
}
else
if
(
value
.
toString
().
contains
(
"Edit"
))
{
id
=
((
Edit
)
value
).
getArg0
();
edit
=
true
;
}
...
else
return
true
;
// GetAll or Create: no id
Once the payload’s id
value has been extracted, there are three possible outcomes:
id
is a positive value, there is nothing more for the IdHandler
to do and, therefore, the handler
returns true
(line 5), thereby indicating that other handlers in the chain, in any, can begin their processing. In
fact, there is now another handler in the chain: the ClientHashHandler
, which executes after the logical
IdHandler
. By the way, the id
still may be out of bounds in the sense that no Prediction
has such an id
, but this is
a problem that must be detected and managed on the service side, not the client side.
id
is zero, a RuntimeException
is thrown (line 6) to terminate the
entire request process because zero is not a legitimate value for an id
. It makes no sense to send an id
of zero
to the predictionsSOAP service.
id
is negative (for instance, -31
), then its absolute value (in this case, 31
) is treated
as the intended value. A set-method is invoked with the new id
value (line 7), which updates the payload’s value.
The payload itself is updated (line 8) and, finally, this newly edited payload is inserted as a replacement in the
outgoing SOAP message (line 9).
This handler logic is clearly lower level than is the application logic of invoking service operations such as edit or getAll. JAX-WS is designed under the separation of concerns principle: low-level message inspection and tweaking occur at the handler level, whereas high-level operation invocations occur at the application level.
The IdHandler
, like the ClientHashHandler
, is a nonpublic class in the same file as the ClientHandlerResolver
,
a public
class. The reason is convenience: one file holds all of the low-level artifacts.
The handlers and the resolver all could be public
classes and, accordingly, in their own files. The IdHandler
, again like the ClientHashHandler
,
needs to be registered with the runtime.
Here is the revised getHandlerChain
method in the ClientHandlerResolver
class:
public
List
<
Handler
>
getHandlerChain
(
PortInfo
portInfo
)
{
List
<
Handler
>
handlerChain
=
new
ArrayList
<
Handler
>();
handlerChain
.
add
(
new
IdHandler
());
![]()
handlerChain
.
add
(
new
ClientHashHandler
(
this
.
name
,
this
.
key
));
![]()
return
handlerChain
;
}
The logical handler IdHandler
is added to the chain (line 1) in front of the
message handler ClientHashHandler
(line 2) because, on an outgoing message,
logical handlers execute before message handlers. Yet even if the order were
reversed in this code, the runtime still would ensure that the logical IdHandler
executed
before the message handler ClientHashHandler
. For handlers in the same group (e.g., logical
handlers), the specified order in the getHandlerChain
method matters; for handlers in
different groups, as in this example, the runtime orders the execution so that
all logical handlers execute before any message handlers do.
The handler examples illustrate the various ways in which SOAP messages can be inspected and
manipulated. The client-side AwsSoapHandler
, a message handler, adds elements to the body of a SOAP request, and
the client-side ClientHashHandler
, also a message handler, adds blocks to the header of a SOAP request. The
logical handler IdHandler
inspects the payload of a SOAP request and, under the right circumstances, edits
this payload so that the request has a chance of succeeding with the service. The code in all three handlers
is appropriately low-level, as befits code designed to be close to the SOAP metal.
There are three possible parts to a SOAP message: the header blocks, the body, and attachments. The first and the last are optional. The examples so far have examined the SOAP header and the SOAP body through code examples. The next section does the same for SOAP attachments.