Previous Section Next Section

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.

graphics/04fig01.gif

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 Enginegraphics/book.gif— The main entry point into the SOAP processor

  • Handlers graphics/book.gif— The basic building blocks inside Axis that link Axis to existing back-end systems

  • Chaingraphics/book.gif— An ordered collection of handlers (and a handler itself)

  • TransportsMechanisms 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 followed—for 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) mechanism—which 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 graphics/book.gif and any possible response message graphics/book.gif that might exist during the processing flow through the Axis processor.

Figure 4.2. Pre- and post-processing is done by defining a chain.

graphics/04fig02.gif

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 complication—certain types of chains have the notion of a pivot point graphics/book.gif 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 exists—to 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 graphics/book.gif because it is viewed as pointing to the pivot point handler) is the Web service–specific 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 point—it 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 graphics/book.gif and global chains graphics/book.gif.

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.

graphics/04fig03.gif

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 is—for 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 service–specific 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:

  1. If a transport-specific chain is defined, then the handlers labeled as the request handlers defined for that chain are invoked.

  2. If a chain named global.request is defined, then it is invoked.

  3. The Web service–specific chain is invoked. Exactly how the Web service–specific 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.

  4. If a chain named global.response is defined, it is invoked.

  5. 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 graphics/book.gif.

Figure 4.4 shows a dispatcher as the pivot point handler in the Web service–specific 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 graphics/book.gif 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.

graphics/04fig04.gif

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 listenersgraphics/book.gif (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.

graphics/04fig05.gif

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 used—this 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 graphics/book.gif 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 message—in 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 graphics/book.gif (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.

graphics/04fig06.gif

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.

graphics/04fig07.gif

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:

  1. The request handlers of the Web service–specific request chain are invoked.

  2. The global.request chain is invoked.

  3. 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 graphics/book.gif; more on this later.

  4. The global.response chain is invoked.

  5. The response handlers of the Web service–specific 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 odd—but 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 blocks—as 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.

    Previous Section Next Section