The Axis Architecture
From the start, Axis was designed with a completely
open
and
pluggable architecture. In its simplest form, Axis can be
viewed
as
a thin layer that sits between the business logic and the network
transport carrying your data.
As depicted in Figure 4.1, Axis is simply the means by which the
SOAP message is taken from a transport (such as HTTP) and handed to
the Web service and the means by which any response is formatted as
a SOAP message to then be sent back to the
requestor
. Although this
might seem like an oversimplification, Axis is designed to be used
in a wide range of environments and by deployment
engineers
with
varying skill levels. When you're first experimenting with Web
services, or if you don't need complex configurations, Axis by
default will make the deployment of Web services very easy. The
"Simple Web Services" section will describe this further by showing
you how to quickly take Java code and deploy it as a Web service in
Axis. However, if you need a more complex processing model, Axis
can be configured to support that, too.
Figure 4.1. Basic overview of Axis.
Axis
Components
Next, we're going to focus on the various components of Axis.
Each item in the following list will be discussed in more detail,
but here's brief summary of the key components:
-
Axis Engine
The main entry
point into the SOAP processor
-
Handlers
The basic
building blocks inside Axis that link Axis to existing back-end
systems
-
Chain
An ordered
collection of handlers (and a handler itself)
-
Transports
Mechanisms by which SOAP messages flow
into and out of Axis
-
Deployment/Configuration
Means through which Web
services are made available through Axis
-
Serializers/Deserializers
Code that will convert
native (for example, Java) datatypes into XML and back
Axis Engine
As you could probably guess, the Axis engine is the focal point
of the Axis SOAP processor. The engine's job is to act as the main
entry point into Axis' message processing model as well as to
coordinate the SOAP message's flow through the various components.
The engine is also responsible for ensuring that the SOAP semantics
are followedfor example, it will verify that the
mustUnderstand
checks are properly performed. In the
following sections, we'll discuss the other components that make up
the processing model, but it is important to know that the engine
is the piece responsible for coordinating the order in which those
other components are invoked. As we discuss each of these
components, we'll describe their interaction with the engine in
more detail.
During the design process of Axis, it was realized that it would
be
impossible
to design a SOAP message processor in such a way that
it could work for the wide-
ranging
uses we wanted for Axis unless
it was flexible enough to allow deployment engineers (configuration
administrations
) to control the message flow itself. Allowing
people to tell Axis which message processing logic to perform, in
what order, and when, became a clear requirement. It also became
apparent that there would be no way of knowing how people would
want to process the SOAP messages. For example, many people see
SOAP as simply another Remote Procedure Call (RPC) mechanismwhich
is a valid use. However, there is no reason the exact same SOAP
message that might be treated as an RPC message by one SOAP
processor could not be treated as an XML document and simply run
through an XSLT processor by another. As long they both
adhere
to
the SOAP specification, and of course follow the semantic rules
defined by the Web service definition, exactly how the SOAP message
is
processed
is wide open. So, how does Axis handle these
challenges? Handlers and chains.
Handlers and Chains
At its most basic level, Axis is all about chaining together
pieces of message processing logic. Figure 4.1 shows just one
piece: the Web service itself. Often, you'll need to perform
additional processing on the message either before or after the Web
service itself is invoked. For example, some logging might need to
take place, or SOAP headers might need to be processed. You can
accomplish this two ways: Place this additional logic in the Web
service itself or allow for additional pieces of code to be
executed outside of, but before and after, the Web service. For
this purpose, Axis has the notion of
chains.
Chains
are simply ordered collections of components (code) that Axis will
invoke sequentially and in the order specified.
The components that are used to build these chains are called
handlers.
Each handler has the opportunity to examine
or modify the SOAP message in order to complete its job. For
example, it is possible to have a handler in the chain that will
look for any encrypted data in the message and decrypt it before
the Web service itself is invoked (SOAP encryption is explained in
more detail in Chapter 5, "Using SOAP for e-Business"). By
modularizing
the work in this way, each handler is free to focus on
its
core
job and not worry about any possible auxiliary work that
might need to be done, thereby eliminating code duplication. Also,
if new pre- or post-processing is needed in the future, it becomes
a simple matter of plugging new handlers into the chain through
configuration changes to Axis rather than having to make code
changes to the Web service. This also allows third-party
vendors
to
produce Axis handlers that can be snapped into any configuration
without prior knowledge of the exact environment, configuration, or
Web service being invoked.
Handlers are the basic building blocks inside Axis. Everything,
even the Axis engine and the chains
themselves
, is a handler. As a
result, the deployment engineer is free to configure Axis in an
unlimited number of ways. It is possible for a preprocessing
handler shown in Figure 4.2 to be a chain that itself contains a
collection of handlers (or even more chains). Aside from each
handler having access to the SOAP message, each handler can also be
involved in the production of any possible SOAP response message.
We'll give more details in the "Building Handlers" section, but it
is worth reiterating that each handler does have access to (and can
change) the
request message
and any possible
response message
that might exist during the processing flow
through the Axis processor.
Figure 4.2. Pre- and post-processing is done by defining a
chain.
Chains are the mechanisms by which handlers are grouped
together. The concept is simple; a chain is an ordered collection
of handlers that together can be viewed as a single unit of
processing. As with any good rule, there is a
slight
complicationcertain types of chains have the notion of a
pivot point
handler, which
is the point at which the chain notes that it has switched from
processing the request SOAP message (request processing) and is now
processing the response SOAP message (response processing). The
need for this logical split between request, pivot point, and
response handlers in a chain will be made clearer later. This pivot
point handler also serves as the one handler in these types of
chains that is the real reason the chain existsto dispatch the
message to the target Web service (the other handlers are there for
request and response processing of the message). The most common
use of this type of chain (also called
targeted
-chain
because it is viewed as pointing to the
pivot point handler) is the Web servicespecific chain. In Figure
4.2, a Web service chain has request and response processing
handlers defined, but the pivot point handler invokes the Web
service. Even though Axis has, for programmer convenience, defined
a particular kind of chain encapsulating these three pieces, when a
chain is defined there is no reason that all three pieces need to
be used. For example, it is possible to define a chain that is just
a collection of handlers without a specific pivot pointit is there
merely as a configuration option.
Figure 4.2 shows that it is possible to define a chain of
handlers that are invoked for a particular Web service. However,
what if chains need to be invoked for
all
Web service
invocations
flowing
through Axis? Or, what if certain chains need
to be invoked only if the message came in on HTTP, whereas if the
message was delivered via SMTP those chains should not be invoked?
To support these configurations, Axis has the notion of
transport specific chains
and
global chains
.
As shown in Figure 4.3, Axis allows the definition of chains
that are invoked based on the type of transport used in the
delivery of the SOAP message (transport specific chains) and chains
that should be invoked for all Web services (global chains).
Figure 4.3. Axis includes three different levels of
chaining.
Transport-specific chains might be defined to process such
things as transport-specific compression, or authentication. The
"Configuration Methods" section will discuss the way to deploy
chains, but it is important to note that transport specific chains
are targeted-chains. The request and response sides are clearly
separated so that the Axis engine
knows
exactly which subset of
handlers to invoke at the start of the message flow and which set
to invoke at the end of the message flow. Although it was possible
to design Axis so that there were two completely separate chains
defined (rather than one with a request and a response side), it
was decided that because they would always be used in conjunction
with each other, and because it is expected that there will be a
large number of transport specific chains, it would be easier from
a usability point of view to define them as one chain with two
sides.
Global chains come in handy in cases when all Web services
require the same processing regardless of how the message was
delivered or what the specific Web service itself isfor example,
SkatesTown might want to keep a log of all Web service
requests
,
and placing a logging handler in the global chain becomes a cleaner
way of deploying it than placing it in each Web servicespecific
chain. Unlike transport chains that use targeted-chains, the global
chain is split into two separate chains. Because, conceptually,
there is only one global chain, it was decided that it would be
easier for deployment engineers to define two separately named
chains (
global.request
and
global.response
)
rather than one chain with two sides. We'll discuss the details of
how to
name
chains in the "Configuring Axis" section.
Within the Axis server-side engine, the order of chain
processing is as
follows
:
-
If a transport-specific chain is
defined, then the handlers labeled as the request handlers defined
for that chain are invoked.
-
If a chain named
global.request
is defined, then it is invoked.
-
The Web servicespecific chain
is invoked. Exactly how the Web servicespecific chain is identified
is discussed later. As noted before, this chain is a targeted-chain
and has three groupings of handlers: the request handlers, the Web
service itself (at the pivot point), and the response handlers.
They are invoked in that order.
-
If a chain named
global.response
is defined, it is invoked.
-
If a transport specific chain
is defined, then the handlers labeled as the response handlers
defined for that chain are invoked.
This processing model should allow a deployment engineer to have
the complete flexibility of placing any handler at any point in the
flow of messages through the server Axis engine.
In Figure 4.3, the Web service is shown as a handler at the
pivot point in the Web service specific chain. One layer of code is
not shown: the
dispatcher
.
Figure 4.4 shows a dispatcher as the pivot point handler in the
Web servicespecific chain. Like all handlers, this dispatcher is
responsible for acting as the bridge between Axis and your business
logic. It is the job of the dispatcher to locate and invoke the
appropriate piece of code associated with the desired Web service.
For example, Axis comes with an
RPCDispatcher
whose job is to convert the SOAP message
from XML into Java objects, locate the appropriate Java method to
invoke, invoke it, and convert any possible response data back into
XML and place it in the response SOAP message. By having a dispatch
handler do this work, it can be used for any Java Web service
invoked. Likewise, the Web service itself does not need to be
concerned
with the details of how the data was delivered or how to
convert XML into Java objects; it can concentrate on doing its real
job.
Figure 4.4. Handlers, acting as dispatchers, are the bridges
between Axis and application logic.
Although dispatchers have been presented in the context of
invoking the Web service, any of the handlers in any of the chains
could be dispatchers. In other words, there is no reason why any of
the request/response processing handlers need to have the business
logic directly inside them. Those handlers could be dispatchers
that extract the necessary data from the SOAP message and pass it
along to external code in the proper format.
Transports
In order to complete the overall picture of the Axis
architecture, one more piece of the puzzle needs to be brought in:
transports. All the figures show a single Axis engine with a single
transport delivering the SOAP message; however, this need not be
the case.
Figure 4.5 shows that Axis can support a variety of transports,
not just HTTP. Actually, Axis itself doesn't support multiple
transports. Axis is designed such that the Axis engine can be
viewed simply as a
chunk
of code that is called with an incoming
message and returns an outgoing message.
Transport
listeners
(such as servlets
that wait for a SOAP message), although key in the overall picture
of how Axis is used, are themselves not part of the Axis engine. It
is assumed that the mechanism by which the Axis engine is created
(a new one on each request or shared instance) will be managed by
the transport listeners. Axis comes with a set of transport
listeners (such as the
AxisServlet
) that you can use, but
if you require special processing, it is assumed that you will
modify the shipped transport listener to suit your needs or write a
new one.
Figure 4.5. Multiple transports.
The role of the transport listener is to deliver the SOAP
message to the Axis engine. This could mean listening on port 80
for an HTTP request or waiting for a file to be FTPed to a certain
directory and then handing that message to the Axis engine. Aside
from invoking the Axis engine, the transport listener must also
tell the Axis engine which transport was usedthis allows Axis to
invoke any transport-specific chain that might have been
configured.
The delineation between whether certain processing should be in
the transport listener or in the transport specific chain is an
issue left up to the deployment engineer. For example, in the HTTP
case, the servlet waiting for SOAP requests can also perform any
basic HTTP authentication checks before invoking the Axis engine.
It is also
perfectly
acceptable for the servlet to not perform that
check and leave it up to a handler on the transport specific chain
to do it. The choice is left open.
Listing 4.1 shows a sample transport listener that does nothing
more than look for a file called
inMsg
in the current
directory. If the file is found, the transport listener uses it as
the requesting SOAP message, calls Axis on it, and then places any
response back into a file called
outMsg
.
Listing 4.1
FileListener.java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import org.apache.axis.AxisFault;
import org.apache.axis.AxisEngine;
import org.apache.axis.server.AxisServer;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.message.SOAPFaultElement;
public class FileListener {
public void run() {
while (true) {
try {
// Look for an incoming msg, create a new Message object
FileInputStream input = new FileInputStream("inMsg");
AxisEngine engine = AxisServer.getSingleton();
MessageContext msgContext = new MessageContext(engine);
Message msg = new Message(input);
try {
//Set it as the "request" message
msgContext.setRequestMessage(msg);
// Set the Transport
msgContext.setTransportName("file");
// Invoke the Axis engine
engine.invoke(msgContext);
}
catch(Exception e) {
// Catch any error and stick it in the response
if (!(e instanceof AxisFault))
e = new AxisFault(e);
AxisFault af = (AxisFault)e;
msg = msgContext.getResponseMessage();
if (msg == null) {
msgContext.setResponseMessage(new Message(af));
} else {
SOAPEnvelope env = msg.getAsSOAPEnvelope();
env.clearBody();
env.addBodyElement(new SOAPFaultElement(af));
}
}
// Close and delete the incoming message
input.close();
(new File("inMsg")).delete();
// Place the response message in a file called "outMsg"
msg = msgContext.getResponseMessage();
FileOutputStream output = new FileOutputStream("outMsg");
String result = (String)msg.getAsString();
output.write(result.getBytes());
output.close();
System.out.println("Processed a request");
} catch(Exception exp) {
if (!(exp instanceof FileNotFoundException))
exp.printStackTrace();
}
// Sleep for a sec and then loop
try {
Thread.sleep( 1000 );
} catch(Exception e) {
}
}
}
static public void main(String[] args) {
(new FileListener()).run();
}
}
Let's walk through this example. The
FileListener
's
run()
method will loop forever while waiting for a new
message to
arrive
. Once there, an
InputStream
is created
for it. The listener will then ask Axis for an instance of an
AxisServer
the
getSingleton()
method will return
the same instance each time. Next we create a
MessageContext
object. This will be discussed more in the "Building Handlers"
section, but for now it is simply an object that will contain all
the data (request and response messages, meta-data, and so on)
about this current SOAP message flow; it is passed to each handler
as it is invoked. Inside this object we place a new
Message
object that is created by passing in the file's
InputStream
. This will be the request message. There's
only one more thing to do before we invoke the engine, and that's
to tell Axis the name of the transport that was used to retrieve
the messagein this case,
file
. When the engine wants to
invoke the transport-specific chain, it will look for one named
file
and, if found, invoke it.
The rest of the code processes any result from the engine. We
must, of course, handle any error conditions. The Axis engine can
throw an
AxisFault
(see the
"Fault" section) that must be caught and used to create a response
message. The
catch
block will also check to see if a
response message already exists and clear any XML from the
Body
section if there is one.
In either the successful case or the faulting case, the code
will then get the response message as a
String
, write it
out to a file called
outMsg
, and then go look for more
messages.
Completing the overall picture of what the Axis engine's
architecture looks like, we have Figure 4.6.
Figure 4.6. Complete Axis architecture.
Although the final picture of the Axis engine's processing model
might seem a bit complex, it really is nothing more than defining
handlers and chains. Of course, we've overlooked one very important
issue; up to this point, the focus has been on what an Axis engine
does when it is on the receiving side of the SOAP message
path
(the
server). Axis can be used on the client side, as well. With just a
few slight changes, all the concepts we've mentioned up to now
apply on the client as shown in Figure 4.7.
Figure 4.7. Axis client architecture.
It should be clear that almost all the same components that
appeared on the server are on the client as well; they are just
invoked in a slightly different order:
-
The request handlers of the Web
servicespecific request chain are invoked.
-
The
global.request
chain is invoked.
-
The transport chain
is invoked. Notice that here the entire chain is invoked, not just
the request side. There is no need to split the request and
response invocations because they would be called one right after
the other anyway. Also note that at the pivot point of the
transport specific chain is a
transport sender
; more on this later.
-
The
global.response
chain is invoked.
-
The response handlers
of the Web servicespecific response chain are invoked.
As mentioned in Step 3, there is something new here: a
transport sender.
A transport sender is a handler that
is responsible for taking the request SOAP message and sending it
to a SOAP server. For example, one of the transport senders that is
shipped with Axis is an HTTP transport sender that will take the
request SOAP message, open an HTTP socket connection to the
specified HTTP server, do a POST of the SOAP message, and wait for
a response. The response is then placed in the response message
portion of the
MessageContext
object.
Because transport senders are just handlers, they can be placed
in any chain in any configuration. So, it is technically possible
that the transport chain shown in Figure 4.7 could have multiple
transport senders if you
desire
a multicast scenario. It is also
possible that a transport sender could be placed in one of the
chains on the server such that the response message could be sent
over a different transport than it was received on (for example,
the transport listener could be an HTTP servlet, but the SOAP
response message could be sent back via SMTP).
Locating the Service Chain
We've talked about how there are service-specific chains, but we
haven't yet touched on how the service chain is selected by the
Axis engine. An interesting aspect of SOAP is that it doesn't
mandate
how to determine the exact Web service to invoke. This
might seem oddbut it is accurate. Many different common practices
have been established, and each one is valid. Here are just three
of the more common ones:
-
SOAPAction
The value of the
SOAPAction
HTTP
header is used to match the service name.
-
URL
The URL of the incoming HTTP request is used to
match the service name. For example, if the URL used to access Axis
was
http://localhost:8080/axis/servlet/AxisServlet/MyService
,
then the SOAP engine would look for a service chain called
myService
.
-
Namespace
Perhaps the most used method. It takes
the namespace of the first XML element in the SOAP Body block and
tries
to find a service chain that matches.
Axis could have
chosen
one method, most likely the namespace
approach, but doing so would have limited the options available to
its users. Instead, Axis lets you choose how services are
determined. A handler can be written and placed in the transport
chain or global chain that can determine which service chain to
choose. This handler is free to use any algorithm it wants to make
this determination. However, if a service chain has not been
selected by the time the Axis engine gets to the point in its
processing that it wants to invoke the service specific chain, it
will default to the namespace selection method.
XML Parsing
Axis has been designed and coded with a watchful eye towards
performance. For this reason, when tackling the problem of how to
parse an XML stream
efficiently
, a SAX-based approach was chosen
over a DOM-based one. As we discussed in Chapter 2, "XML Primer,"
SAX XML parsing does not read the entire XML stream into memory;
rather, it triggers callbacks based on the types of XML tokens that
are
encountered
. It then becomes the responsibility of those
callback routines to do any buffering of data or saving of state
that is needed so that
subsequent
callbacks still have access to
any previously seen data. Even though SAX is used for parsing, a
DOM-based representation of XML sometimes is used when passing
around XML blocksas will be seen in the "Document-Centric Services"
section. In various discussions and examples in this chapter, we'll
need to talk about concepts and features in terms of using a SAX
parser or writing SAX callback routines. We assume that you are
familiar with these concepts, and we will not explain the specific
details of how to write code
utilizing
a SAX parser in great
detail.
With that overview of what Axis is and how it works, now we can
move on to actually using it.
|