Simple Messaging Framework Architecture | |
Author: Naresh Bhatia (nbhatia@sapient.com) Created: July 2003 Version: $Revision: 1.1 $ ($Author: nbhatia $ / $Date: 2003/07/21 03:30:50 $)
|
| |
This document describes the important architectural aspects of the Simple Messaging Framework.
|
| |
The Simple Messaging Framework is a thin service layer that sits on top of a SOAP toolkit. The current implementation uses Apache Axis as the SOAP toolkit. The diagram below shows how the framework fits with other components around it.
As you can see, the framework consists of two key components - the MessageClient and the MessageService. Both these components rely on the SOAP toolkit to send and receive SOAP messages. Although the application code primarily interacts with the framework, there is no restriction to calling the SOAP toolkit directly. For example, if you need to fine-tune a SOAP message that is being transmitted, you can access the SOAP Call object directly.
The primary responsibilities of the application developer are to define a message hierarchy appropriate to the application, provide a MessageReader and a MessageWriter and finally to provide a MessageHandler for each message sent to the server.
Let's look at the diagram above to understand how a client and a server exchange messages. For the purpose of this discussion, a message sent from a client to a server is called a request, whereas a message in the opposite direction is called a response. The steps below explain the request-response sequence.
- The application logic on the client side decides to send a message to the server. It created the appropriate
Message object and calls MessageClient.sendMessage() with this Message object as a parameter.
- The
MessageClient uses the MessageWriter to convert the Message into an XML element. It then creates a SOAP Call object and calls its invoke() method passing it the XML message element as a SOAPBodyElement .
- The SOAP toolkit prepares a SOAP message, inserting the supplied
SOAPBodyElement into its body. The SOAP message is then transmitted to the server.
- The SOAP toolkit at the server end receives the SOAP message. Based on the Web service URL, it figures out that the message is destined for the
MessageService defined by the framework. It calls the processMessage() method of the MessageService , sending it the body of the SOAP message as a SOAPBodyElement .
- The
MessageService parses the SOAP body back into the original Message using the MessageReader . It then creates a MessageHandler appropriate for the message and calls its handleMessage() method. MessageHandlers are defined by the application developer and associated with appropriate messages using a configuration file.
At this point the MessageHandler processes the incoming message as appropriate. It may do this all by itself or make appropriate calls to other services. In any case, at the end of processing, it must create an appropriate response and send it back to the client. This is done by simply returning a Message object from the handleMessage() method. The MessageService consequently sends the response message back to the client using a sequence similar to the one described above.
Now that you understand the overall architecture of the framework, let's look at some important details.
|
| |
The framework is based on the simple idea of a Message pattern. A message consists of any information that needs to be exchanged between a client and a server. The Message class is part of a "Composite" pattern that allows messages to be nested to any depth. This is accomplished by defining a CompositeMessage class that extends Message and contains a list of child messages. The diagram below shows the Message class hierarchy.
You will generally extend the Message class to define application specific messages. If you need to send multiple messages to a server all in one SOAP message, you should create a CompositeMessage , add your application specific messages to it and then send this CompositeMessage to the server. The framework provides a default handler for CompositeMessages . This handler simply calls the handlers for its child messages, collects the responses and sends them back as another CompositeMessage . In situations where several application specific child messages share common information, you may wish to extend CompositeMessage to define an application specific CompositeMessage . This subclass would store the common information needed by the children. The children can subsequently access the common information using the parent link in the Message class.
|
| |
The framework specifies the MessageReader interface to read an XML document and convert it to a Message . Similarly, it specifies the MessageWriter interface to write a Message to an XML document. The two interfaces are shown below.
MessageReader Interface
package org.sape.smfw.messagingengine;
public interface MessageReader {
Message readMessage(Reader reader) throws MessagingEngineException;
}
MessageWriter Interface
package org.sape.smfw.messagingengine;
public interface MessageWriter {
void writeMessage(Writer writer, Message message) throws MessagingEngineException;
}
As an application developer, you are expected to provide implementations for these interfaces. You can use any XML parser/serializer you wish. You then supply your implementations to the framework using a configuration file called MessagingEngineConfiguration.xml. Below is a sample configuration for the Calendar Application.
smfw/MessagingEngineConfiguration.xml
<Configuration ConfigurationInterface="org.sape.smfw.messagingengine.MessagingEngineConfiguration">
<MessageReaderClassName>org.calendarapp.messageio.MessageReaderImpl</MessageReaderClassName>
<MessageWriterClassName>org.calendarapp.messageio.MessageWriterImpl</MessageWriterClassName>
</Configuration>
|
| |
For each message sent to the server, you must supply a MessageHandler that handles the message and returns a response. The MessageHandler interface is shown below.
MessageHandler Interface
package org.sape.smfw.messagingengine;
public interface MessageHandler {
Message handleMessage(MessageContext messageContext, Message message)
throws MessagingEngineException;
}
Note the MessageContext parameter passed to handleMessage() by the framework. Its definition is shown below.
MessageContext Class
/**
* The MessageContext stores the configuration of the messaging engine
* as well as the processing state of a message.
*/
package org.sape.smfw.messagingengine;
public class MessageHandler {
/** The MessageHandlerFactory for this context */
private MessageHandlerFactory messageHandlerFactory;
/** The user who sent the request */
private User user;
/** A map to store properties related to this context */
private Map properties;
...
}
In addition to defining MessageHandlers , you must associate them with appropriate messages. This is done using a configuration file called MessageHandlerConfiguration.xml. Below is an example of this configuration file taken from the Calendar Application.
smfw/MessageHandlerConfiguration.xml
<Configuration ConfigurationInterface="org.sape.carboncontrib.config.StringMap">
<StringMap>
<String Key="org.sape.smfw.message.CompositeMessage">
org.sape.smfw.messagingengine.CompositeMessageHandler
</String>
<String Key="org.calendarapp.message.CreateMeetingMessage">
org.calendarapp.messagehandlers.CreateMeetingMessageHandler
</String>
<String Key="org.calendarapp.message.GetMeetingMessage">
org.calendarapp.messagehandlers.GetMeetingMessageHandler
</String>
...
</StringMap>
</Configuration>
|
| |
The Simple Messaging Framework provides two approaches for client authentication. Both approaches are optional - you can certainly devise your own to suit your specific needs.
|
This approach uses the authentication mechanism provided by your servlet container. To use this approach simply set up your servlet container to authenticate all clients trying to access your Web service.
|
|
This approach uses SOAP headers to authenticate the clients. To use this approach clients must send their credentials in a SOAP header. Below is a sample SOAP message which includes an authentication header.
SOAP Message with Authentication Header
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<ns1:user xmlns:ns1="http://www.sape.org/smfw/user">
<principal>johndoe</principal>
<credentials>mysecretpassword</credentials>
</ns1:user>
</soapenv:Header>
<soapenv:Body>
<message xsi:type="ReserveTitleCopyMessage"
xmlns="http://www.lms.org/reservation-service"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<isbn>0-13-079666-2</isbn>
</message>
</soapenv:Body>
</soapenv:Envelope>
|
Both approaches store authentication information in the MessageContext which is supplied to your MessageHandler . The Library Management Demo shows how each approach is used.
|
| |
Now that you have a good understanding of the Simple Messaging Framework, let us look at the steps involved in creating a Web service using it. Here's a list of the high-level steps.
- Define business objects
- Define the service API
- Define request and response messages
- Write a WSDL document
- Implement a MessageReader and a MessageWriter
- Implement MessageHandlers
|
The first step is to define the business objects needed by your application. For example, to create a Web service for scheduling meetings you will probably define objects such as Meeting and Attendee . Use appropriate data structures to define relationships between these objects. For example, the Meeting may hold a Map of Attendees .
|
|
Now define a logical interface for your business service. Note that this interface has no knowledge of how it is going to be invoked. It may be called by a Java client directly, by a Java client using RMI, a .NET client using a Web service or any other way. Depending on your application, this interface could be defined as a Java interface, a Java class, an Enterprise Java Bean or any other way you wish. The only requirement is that your MessageHandler should be able to call it. Below is an example of the MeetingService interface used for creating, getting, modifying and deleting meetings.
MeetingService API
package org.calendarapp.meetingservice;
public interface MeetingService {
public String createMeeting(Meeting meeting);
public Meeting getMeeting(String meetingId);
public void addAttendee(String meetingId, Attendee attendee);
public void removeAttendee(String meetingId, String username);
public void deleteMeeting(String meetingId);
}
|
|
Now that you have a service API, define a request message and a response message for each of its methods. For example, consider the createMeeting() method in the API shown above. This method accepts a Meeting object, creates a meeting and returns its meeting id. Below you will find the request and response messages corresponding to this method.
CreateMeetingMessage
package org.calendarapp.message;
public class CreateMeetingMessage extends Message {
private Meeting meeting;
...
}
MeetingIdMessage
package org.calendarapp.message;
public class MeetingIdMessage extends Message {
private String meetingId;
...
}
Note that for the interface methods that return void , you do not have to define a response message. The framework provides a standard response message for such methods - the VoidMessage . Also note that if more than one methods return the same object type, they can share the same response message. For example, two methods that return a Meeting object can share the same MeetingMessage response.
|
|
Now that you have defined the messages, it is time to write a WSDL document describing your Web service. If you have never written a WSDL document before, don't worry! You can use any WSDL document from the supplied examples as a template. The primary change will be to replace the example schema with your application specific schema. Knowledge of the XML Schema Specification would be very useful for this step. The high-level structure of the MeetingService WSDL is shown below. You will primarily focus on replacing the MeetingService schema shown in the xsd:schema section with your own schema. Aside from this, you will make some trivial name changes to customize the WSDL to your application. For example, you will change the binding name from MeetingServiceSoap to MyServiceSoap .
meeting-service.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<definitions
<types>
<xsd:schema
targetNamespace="http://www.calendarapp.org/meeting-service"
xmlns="http://www.calendarapp.org/meeting-service"
...
</xsd:schema>
</types>
<message name="processMessageSoapIn">
...
</message>
<message name="processMessageSoapOut">
...
</message>
<portType name="MeetingServiceSoap">
...
</portType>
<binding name="MeetingServiceSoap" type="mtg:MeetingServiceSoap">
...
</binding>
<service name="MeetingService">
...
</service>
</definitions>
|
|
Now that you have written a schema for your Messages , implement a MessageReader and a MessageWriter to serialize and deserialize these messages (see MessageReader and MessageWriter above). Tell the framework about your implementations by making appropriate entries into MessagingEngineConfiguration.xml.
|
|
The last step is to implement a MessageHandler for each request that the server will receive. Make appropriate entries in MessageHandlerConfiguration.xml to associate messages with their respective handlers.
|
|
| |
Now that you understand the architecture of the Simple Messaging Framework, you have several options to explore it further. Here are a few suggestions.
|
|