How to Build J2EE Applications Using Novell Technologies: Part 8
Articles and Tips: article
Chief Architect
Zareus, Inc.
jeff@jeffhanson.com
01 Mar 2003
This is the eighth in a series that outlines a platform and methodology for Java 2 Platform Enterprise Edition (J2EE) application development on NetWare using a service-oriented architecture. In this AppNote, we further explore AXIS and Web services and see how AXIS can be used to handle SOAP messages as an integral component of our platform.
- Introduction
- A Review of Web Services
- SOAP and HTTP
- Chains, Chains and More Chains with AXIS
- The SOAP Gateway Configuration Manager
- The SOAP Message Handling Object
- The SOAP/HTTP Request Handling Servlet
- Conclusion
Topics |
Java application development, J2EE, AXIS, SOAP, Web services |
Products |
Java 2 Enterprise Edition |
Audience |
developers |
Level |
intermediate |
Prerequisite Skills |
familiarity with Java programming |
Operating System |
n/a |
Tools |
none |
Sample Code |
yes |
Introduction
This is the eighth in a series of AppNotes outlining a platform and methodology for Java 2 Platform Enterprise Edition (J2EE) application development on NetWare using a service-oriented architecture.
In the previous AppNote we looked at how our DAO framework can also be used to simplify and unify access to Web service provider sources.
In this AppNote, we will further explore AXIS (Apache Extensible Interaction System) and Web services and look at how we can integrate AXIS into our platform to facilitate the handling of SOAP messages targeting our business services.
A Review of Web Services
These concepts have been covered in previous AppNotes, but we'll repeat this section here for ease of reference.
The term Web services is used to describe concepts and technologies that integrate business processes and services over the Web using standard network protocols. Web services, as a technology, is a mechanism for delivering cross-platform, cross-language services and business content to any Web-enabled client or device.
Web services are URL-addressable software components that are connected by a common protocol, and which allow applications and services to access them over the Internet. Web services are based on XML envelopes containing either documents or procedures and data structures. The Web services model allows applications to interact with one another in a very loosely coupled manner over a networked environment.
The operations surrounding Web services usually fall within three major categories: publish, find, and bind. Publishing involves exposing the service in a manner that makes it easy to find and use by a service consumer. This typically involves staging the service in a public registry. When a service is listed in a registry, it can then be found or discovered, bound using SOAP and then invoked by a service consumer.
Figure 1 illustrates the relationships between the parties involved in the lifecycle of a typical Web service.
Figure 1: Parties involved in the lifecycle of a typical Web service.
SOAP and HTTP
Since HTTP is ubiquitous throughout the Web infrastructure, it is being presented as the protocol of choice for transporting SOAP request and response data. A typical SOAP request over HTTP proceeds as shown in Figure 1.
A SOAP method can be thought of simply as a SOAP-encoded HTTP POST request. SOAP requests should use the text/xml content-type. A SOAP request over HTTP should indicate the SOAP method to be called using the SOAPAction HTTP header. The SOAPAction header contains an application-specific method name.
A SOAP request is an XML document containing the in and out parameters of the SOAP method. These values are child elements within the SOAP body and share the same namespace as the SOAP method name.
A SOAP response is similar to a SOAP request, in that the content for the response must be contained within the SOAP body and typically uses the same datatypes as the request.
Handling SOAP Messages
Since HTTP is the primary protocol used to transmit SOAP messages, we want to find a mechanism in Java that will handle HTTP for us. The component we build needs to do three things:
Parse the SOAP message from XML and convert it into a Java method call.
Invoke the object responsible for handling the message.
Serialize a response or error (if either is needed) back into XML and deliver it to the submitter of the request.
Java HTTP servlets are specifically designed to handle request and response messages transported using the HTTP protocol, so that's what we'll use.
Chains, Chains and More Chains with AXIS
Handling SOAP messages with AXIS involves processing messages. When the AXIS engine receives a message, a chain of Handlers are each invoked giving each a chance to handle the message. The message object that is received by each Handler is a MessageContext. A MessageContext is a structure that contains a request message, a response message and a set of properties.
Figure 2 shows the path that a message follows in AXIS.
Figure 2: The path of a message in AXIS.
The first object that encounters a message is a transport listener. In our case this will be a Java HTTP servlet. The servlet/listener's job is to package the message and any protocol-specific data into an org.apache.axis.Message object. The Message must then be placed into a MessageContext object. At this point the listener passes the MessageContext to the AXIS engine.
After the transport listener passes the MessageContext to the AXIS engine, the engine looks for a global request handler chain. If one is found, the engine allows each handler to process the message.
A main provider or "engine handler" is finally invoked to dispatch the message to the target service. We are going to implement our engine handler as a plain-old Java object (POJO) and dispatch messages as we see fit. This gives us the flexibility to interact with just about any technology we choose to handle the messages.
Axis Integration Points
There are three points of integration that we will be addressing in order to assemble a SOAP-provider gateway for our services using AXIS:
Build a servlet that will handle the initial SOAP/HTTP request.
Build a configuration manager that will manage the environment in which our services will reside.
Build a message-handling class that will act as the primary target for all service calls.
The SOAP Gateway Configuration Manager
The first thing we must do is define a configuration manager for our gateway environment. The purpose of this object is to handle any configuration duties required by our environment, such as defining registering services, registering global service handlers, mapping types, reading and writing the configuration environment, and so on. An instance of this class must be passed to the AXIS engine when it is initialized.
Our configuration manager is implemented as follows:
public class GatewayConfigManager implements EngineConfiguration { HashMap handlers = new HashMap(); HashMap services = new HashMap(); HashMap transports = new HashMap(); Map environment = null; private ServletContext context = null; private AxisEngine engine; TypeMappingRegistry tmr = null; Hashtable globalOptions = null; Handler globalRequest = null; Handler globalResponse = null; public GatewayConfigManager(ServletContext servletContext, Map environment) { this.context = context; this.environment = environment; GatewayMsgHandler messageHandler = new GatewayMsgHandler(); // cover all possibilities handlers.put(new QName("com.jeffhanson.services.soap", "com.jeffhanson.services.soap.GatewayMsgHandler"), messageHandler); handlers.put(new QName(null, "com.jeffhanson.services.soap.GatewayMsgHandler"), messageHandler); handlers.put(new QName("", "com.jeffhanson.services.soap.GatewayMsgHandler"), messageHandler); } public void configureEngine(AxisEngine engine) throws ConfigurationException { this.engine = engine; for (Iterator i = services.values().iterator(); i.hasNext();) { ((SOAPService) i.next()).setEngine(engine); } } public void writeEngineConfig(AxisEngine engine) throws ConfigurationException { } public Handler getHandler(QName qname) throws ConfigurationException { Handler handler = (Handler) handlers.get(qname); if (handler == null) { Iterator iter = handlers.keySet().iterator(); while (iter.hasNext()) { QName tempName = (QName)iter.next(); if (tempName.getLocalPart().equals(qname.getLocalPart())) { return (Handler)handlers.get(tempName); } } } return handler; } public SOAPService getService(QName qname) throws ConfigurationException { Object serviceObj = services.get(qname); if (serviceObj == null) return null; SOAPService service = (SOAPService)serviceObj; return service; } public SOAPService getServiceByNamespaceURI(String namespace) throws ConfigurationException { Object serviceObj = services.get(new QName("", namespace)); if (serviceObj == null) return null; SOAPService service = (SOAPService)serviceObj; return service; } public TypeMappingRegistry getTypeMappingRegistry() throws ConfigurationException { if (tmr != null) return tmr; tmr = new TypeMappingRegistryImpl(); return tmr; } public TypeMapping getTypeMapping(String encodingStyle) throws ConfigurationException { return (TypeMapping) getTypeMappingRegistry().getTypeMapping(encodingStyle); } public Handler getTransport(QName qname) throws ConfigurationException { Handler transport = (Handler) transports.get(qname); return transport; } public Handler getGlobalRequest() throws ConfigurationException { if (globalRequest != null) return globalRequest; return null; } public Handler getGlobalResponse() throws ConfigurationException { if (globalResponse != null) return globalResponse; return null; } public Hashtable getGlobalOptions() throws ConfigurationException { if (globalOptions != null) return globalOptions; return null; } public void deployService(QName qname, SOAPService service) { services.put(qname, service); service.setEngine(engine); } public void deployService(String name, SOAPService service) { deployService(new QName(null, name), service); } public void deployTransport(QName qname, Handler transport) { transports.put(qname, transport); } public void deployTransport(String name, Handler transport) { deployTransport(new QName(null, name), transport); } public Iterator getDeployedServices() throws ConfigurationException { ArrayList serviceDescs = new ArrayList(); Iterator i = services.values().iterator(); while (i.hasNext()) { SOAPService service = (SOAPService) i.next(); serviceDescs.add(service.getServiceDescription()); } return serviceDescs.iterator(); } }
The SOAP Message Handling Object
The next thing we do is implement our primary gateway message handler. This is the engine-handler object which is responsible for dispatching all SOAP messages and constructing all SOAP responses.
Our gateway message handler is implemented as follows:
public class GatewayMsgHandler extends BasicHandler { public void invoke(MessageContext msgContext) throws AxisFault { HttpServlet servlet = (HttpServlet)msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLET);; ServletContext context = servlet.getServletContext(); Message reqMsg = msgContext.getRequestMessage(); // String soapPart = reqMsg.getSOAPPartAsString(); String inputStr = ""; try { Iterator iter = reqMsg.getSOAPEnvelope().getBody().getChildElements(); while (iter.hasNext()) { Object element = iter.next(); inputStr += element.toString(); } } catch (SOAPException e) { } catch (AxisFault fault) { } // process message here String sampleResponse = "<?xml version=\"1.0\"?>" + "<soap:Envelope" + "xmlns:soap=\"http://www.w3.org/2001/12/soap-envelope\"" + "soap:encodingStyle=\"http://www.w3.org/2001/12/soap-encoding\">" + " <soap:Body xmlns:m=\"http://www.jeffhanson.com/services/sample\">" + " <m:SampleResponse>" + " <m:SampleText>Response from AxisGateway. Input was:" + " " + inputStr + " </m:SampleText>" + " </m:SampleResponse>" + " </soap:Body>" + "</soap:Envelope>"; Message responseMessage = new Message(sampleResponse); msgContext.setResponseMessage(responseMessage); } }
The SOAP/HTTP Request Handling Servlet
The last thing we do is implement our SOAP/HTTP request handler. This will be a servlet that uses AXIS technologies to dispatch all SOAP/HTTP requests. The main steps we will take will be to:
Create an AXIS engine on initialization of the servlet.
Tell AXIS the name of our message-handling class.
Create and populate a MessageContext object for each HTTP request received.
Pass the MessageContext object to the AXIS engine for parsing and dispatching.
Field the response that the AXIS engine returns and return it to the client.
The SOAP message handling servlet is constructed as follows:
public class GatewayServlet extends HttpServlet { public static final String REQUEST_PARAMS = "REQUEST_PARAMS"; public static final String PARAM_DELIMITER = "|"; public static final String INIT_PROPERTY_TRANSPORT_NAME = "transport.name"; private static final String ATTR_AXIS_ENGINE = "AxisEngine"; public static AxisServer getEngine(HttpServlet servlet) throws AxisFault { AxisServer engine = null; ServletContext context = servlet.getServletContext(); synchronized (servlet) { engine = (AxisServer) context.getAttribute(ATTR_AXIS_ENGINE); if (engine == null) { Map environment = loadEngineEnvironment(servlet); GatewayConfigManager configMgr = new GatewayConfigManager(servlet.getServletContext(), environment); engine = new AxisServer(configMgr); context.setAttribute(ATTR_AXIS_ENGINE, engine); } } return engine; } private static Map loadEngineEnvironment(HttpServlet servlet) { Map environment = new HashMap(); String attdir = servlet.getInitParameter(AxisEngine.ENV_ATTACHMENT_DIR); if (attdir != null) environment.put(AxisEngine.ENV_ATTACHMENT_DIR, attdir); ServletContext context = servlet.getServletContext(); environment.put(AxisEngine.ENV_SERVLET_CONTEXT, context); String webInfPath = context.getRealPath("/WEB-INF"); if (webInfPath != null) environment.put(AxisEngine.ENV_SERVLET_REALPATH, webInfPath + File.separator + "attachments"); return environment; } private String webInfPath = null; private String homeDir = null; private ServletSecurityProvider securityProvider = null; private AxisServer axisServer = null; public void init(ServletConfig config) throws ServletException { super.init(config); } public AxisServer getEngine() throws AxisFault { if (axisServer == null) axisServer = getEngine(this); return axisServer; } private MessageContext createMessageContext(AxisEngine engine, HttpServletRequest req, HttpServletResponse res) { MessageContext msgContext = new MessageContext(engine); msgContext.setProperty(Constants.MC_HOME_DIR, homeDir); msgContext.setProperty(Constants.MC_RELATIVE_PATH, req.getServletPath()); msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLET, this); msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST, req); msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETRESPONSE, res); msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETLOCATION, webInfPath); msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETPATHINFO, req.getPathInfo()); msgContext.setProperty(HTTPConstants.HEADER_AUTHORIZATION, req.getHeader(HTTPConstants.HEADER_AUTHORIZATION)); msgContext.setProperty(Constants.MC_REMOTE_ADDR, req.getRemoteAddr()); String realpath = getServletConfig().getServletContext().getRealPath(req.getServletPath()); if (realpath != null) msgContext.setProperty(Constants.MC_REALPATH, realpath); msgContext.setProperty(Constants.MC_CONFIGPATH, webInfPath); return msgContext; } private String getSoapAction(HttpServletRequest req) { String soapAction = (String) req.getHeader(HTTPConstants.HEADER_SOAP_ACTION); if (soapAction == null) { return null; } if (soapAction.length() == 0) soapAction = req.getContextPath(); return soapAction; } public void init() throws ServletException { ServletContext context = getServletConfig().getServletContext(); webInfPath = context.getRealPath("/WEB-INF"); homeDir = context.getRealPath("/"); } public void destroy() { super.destroy(); if (axisServer != null) { axisServer.cleanup(); } } protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { process(req, res); } protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { process(req, res); } protected void process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { PrintWriter writer = res.getWriter(); try { AxisEngine engine = getEngine(); ServletContext servletContext = getServletConfig().getServletContext(); String pathInfo = req.getPathInfo(); String realpath = servletContext.getRealPath(req.getServletPath()); if (realpath != null) { MessageContext msgContext = createMessageContext(engine, req, res); try { String soapAction = getSoapAction(req); if (soapAction != null) { msgContext.setUseSOAPAction(true); msgContext.setSOAPActionURI(soapAction); } String url = HttpUtils.getRequestURL(req).toString(); msgContext.setProperty(MessageContext.TRANS_URL, url); msgContext.setProperty(MessageContext.ENGINE_HANDLER, "com.jeffhanson.services.soap.GatewayMsgHandler"); String queryString = req.getQueryString(); if (req.getParameterNames() != null) { if (req.getParameterNames().hasMoreElements()) { res.setContentType("text/xml"); Enumeration enum = req.getParameterNames(); String service = null; String method = null; String body = ""; while (enum.hasMoreElements()) { String elementName = (String) enum.nextElement(); if (elementName.equalsIgnoreCase("service")) { service = elementName; } else if (elementName.equalsIgnoreCase("method")) { method = elementName; } body += "<" + elementName + ">" + req.getParameter(elementName) + "</" + elementName + ">"; } if (service == null) { writer.println("<h2>No service specified</h2>"); } else if (method == null) { writer.println("<h2>No method specified</h2>"); } else { String msgtxt = "<SOAP-ENV:Envelope" + " xmlns:SOAP- ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<SOAP-ENV:Body>" + body + "</SOAP-ENV:Body>" + "</SOAP-ENV:Envelope>"; ByteArrayInputStream istream = new ByteArrayInputStream(msgtxt.getBytes()); Message msg = new Message(istream, false); msgContext.setRequestMessage(msg); engine.invoke(msgContext); Message respMsg = msgContext.getResponseMessage(); if (respMsg != null) { writer.println(respMsg.getSOAPPartAsString()); } } } } else { res.setContentType("text/html"); writer.println("<h2>No parameters specified</h2>"); } } catch (AxisFault fault) { res.setContentType("text/html"); writer.println("<h2>Fault in doGet: " + fault.toString() + "</h2>"); } catch (Exception e) { res.setContentType("text/html"); writer.println("<h2>Error in doGet: " + e.toString() + "</h2>"); } } } finally { writer.close(); } } }
Conclusion
This AppNote further explored AXIS and Web services and showed how to use AXIS to handle SOAP messages as an integral component of our platform. In the next AppNote in this series, we will introduce technologies that enable us to publish and find Web services using technologies on the NetWare platform.
For Additional Information
For more information about the technologies discussed in this AppNote, refer to the following resources:
For more information about SOAP, visit http://www.w3.org/TR/SOAP.
For more information about the DAO pattern, visit http://java.sun.com/blueprints/patterns/DAO.html.
For more information about the AXIS framework, visit http://xml.apache.org/axis.
For Novell-specific Java programming information, see http://developer.novell.com/ndk.
For information about Zareus and its products for building enterprise applications for the J2EE, Web services, and X-Internet environments, visit http://www.zareus.com.
Previous AppNotes in This Series
The previous AppNotes in this series are available online at the URLs indicated:
For Part 1 in this series, see http://support.novell.com/techcenter/articles/ana20020505.html.
For Part 2 in this series, see http://support.novell.com/techcenter/articles/ana20020606.html.
For Part 3 in this series, see http://support.novell.com/techcenter/articles/dnd20020703.html.
For Part 4 in this series, see http://support.novell.com/techcenter/articles/dnd20020805.html.
For Part 5 in this series, see http://support.novell.com/techcenter/articles/dnd20020906.html.
For Part 6 in this series, see http://support.novell.com/techcenter/articles/dnd20021105.html.
For Part 7 in this series, see http://support.novell.com/techcenter/articles/dnd20021206.html.
* Originally published in Novell AppNotes
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.