Abstraction of the intermediate layers in the processing chain
and transport.
What is a
Pipe ?
Transport is a kind of pipe. It sends the
Packet through, say, HTTP connection, and receives the data back into another
Packet .
More often, a pipe is a filter. It acts on a packet,
and then it passes the packet into another pipe. It can
do the same on the way back.
For example, XWSS will be a
Pipe that delegates to another
Pipe , and it can wrap a
Packet into
another
Packet to encrypt the body and add a header, for example.
Yet another kind of filter pipe is those that wraps
LogicalHandler and
SOAPHandler . These pipes are heavy-weight; they often consume
a message in a packet and create a new one, and then pass it to the next pipe.
For performance reason it probably makes sense to have one
Pipe instance that invokes a series of
LogicalHandler s, another one
for
SOAPHandler .
There would be a
Pipe implementation that invokes
Provider .
There would be a
Pipe implementation that invokes a service method
on the user's code.
There would be a
Dispatch implementation that invokes a
Pipe .
WS-MEX can be implemented as a
Pipe that looks for
Message.getPayloadNamespaceURI and serves the request.
Pipe Lifecycle
Pipe line is expensive to set up, so once it's created it will be reused.
A
Pipe line is not reentrant; one pipeline is used to process one request/response
at at time. The same pipeline instance may serve request/response for different threads,
if one comes after another and they don't overlap.
Where a need arises to process multiple requests concurrently, a pipeline
gets cloned through
PipeCloner . Note that this need may happen on
both server (because it quite often serves multiple requests concurrently)
and client (because it needs to support asynchronous method invocations.)
Created pipelines (including cloned ones and the original) may be discarded and GCed
at any time at the discretion of whoever owns pipelines. Pipes can, however, expect
at least one copy (or original) of pipeline to live at any given time while a pipeline
owner is interested in the given pipeline configuration (in more concerete terms,
for example, as long as a dispatch object lives, it's going to keep at least one
copy of a pipeline alive.)
Before a pipeline owner dies, it may invoke
Pipe.preDestroy() on the last
remaining pipeline. It is "may" for pipeline owners that live in the client-side
of JAX-WS (such as dispatches and proxies), but it is a "must" for pipeline owners
that live in the server-side of JAX-WS.
This last invocation gives a chance for some pipes to clean up any state/resource
acquired (such as WS-RM's sequence, WS-Trust's SecurityToken), although as stated above,
this is not required for clients.
Pipe and State
The lifecycle of pipelines is designed to allow a
Pipe to store various
state in easily accessible fashion.
Per-packet state
Any information that changes from a packet to packet should be
stored in
Packet . This includes information like
transport-specific headers.
Per-thread state
Any expensive objects that are non-reentrant can be stored in
instance variables of a
Pipe , since
Pipe.process(Packet) is
non reentrant. When a pipe is copied, new instances should be allocated
so that two
Pipe instances don't share thread-unsafe resources.
This includes things like canonicalizers, JAXB unmarshallers, buffers,
and so on.
Per-proxy/per-endpoint state
Information that is tied to a particular proxy/dispatch can be stored
in a separate object that is referenced from a pipe. When
a new pipe is copied, you can simply hand out a reference to the newly
created one, so that all copied pipes refer to the same instance.
See the following code as an example:
class PipeImpl {
// this object stores per-proxy state
class DataStore {
int counter;
}
private DataStore ds;
// create a fresh new pipe
public PipeImpl(...) {
....
ds = new DataStore();
}
// copy constructor
private PipeImpl(PipeImpl that, PipeCloner cloner) {
cloner.add(that,this);
...
this.ds = that.ds;
}
public PipeImpl copy(PipeCloner pc) {
return new PipeImpl(this,pc);
}
}
Note that access to such resource often needs to be synchronized,
since multiple copies of pipelines may execute concurrently.
If such information is read-only,
it can be stored as instance variables of a pipe,
and its reference copied as pipes get copied. (The only difference between
this and per-thread state is that you just won't allocate new things when
pipes get copied here.)
VM-wide state
static is always there for you to use.
Pipes and Handlers
JAX-WS has a notion of
LogicalHandler and
SOAPHandler , and
we intend to have one
Pipe implementation that invokes all the
LogicalHandler s and another
Pipe implementation that invokes
all the
SOAPHandler s. Those implementations need to convert a
Message into an appropriate format, but grouping all the handlers together eliminates
the intermediate
Message instanciation between such handlers.
This grouping also allows such implementations to follow the event notifications
to handlers (i.e.
Handler.close(MessageContext) method.
TODO: Possible types of pipe:
creator: create message from wire
to SAAJ SOAP message
to cached representation
directly to JAXB beans
transformer: transform message from one representation to another
JAXB beans to encoded SOAP message
StAX writing + JAXB bean to encoded SOAP message
modifier: modify message
add SOAP header blocks
security processing
header block processor:
process certain SOAP header blocks
outbound initiator: input from the client
Manage input e.g. JAXB beans and associated with parts of the SOAP message
inbound invoker: invoke the service
Inkoke SEI, e.g. EJB or SEI in servlet.
See Also: AbstractPipeImpl See Also: AbstractFilterPipeImplTube |