One of the challenges with large scale Enterprise Application development is dealing with the dependencies between teams and parts of the system being developed. Despite Agile software development methodologies a waterfall style of producing artifacts can occur. For example, the UI can not be completed because the web services are not built and the web services are not built because the data model is not complete. The data model is not complete because there are outstanding questions that the customer hasn’t answered.
In an ideal world we would all be able to agree the integration interfaces up front and then farm out the development effort so that the UI and Server Side teams can get coding straight away. One way to make this happen is for the UI team to define the WSDL interfaces for the services they plan to invoke. It is good practice for the ‘caller’ to define the interface where possible.
The UI team could also go ahead and provide their own simple implementation. Included in this article is some code for a servlet that accepts a SOAP Envelope request and returns a SOAP Envelope response. It uses the element name in the SOAP Body to look up an XML file with the same name in the classpath and then returns the contents. A client can define a WSDL, set the URL for the Servlet as the SOAP address and provide a ‘canned’ response XML file for each operation. There is also an Enterprise Service Bus (ESB) approach that is outlined as an alternative at the end of the article.
Simple SOAP Servlet
The servlet uses the JAXP libraries…
DocumentBuilderFactory builderFactory
= DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
public void init() throws ServletException {
try {
this.builderFactory.setNamespaceAware(true);
this.builder = this.builderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new ServletException("Error initialising servlet", e);
}
}
A mechanism to get the SOAP Body from the request is needed. This method is part of that. It is inelegant as getElementsByTagName() just did not work during testing. Probably due to some configuration issue in my environment or codebase. Since the SOAP Envelope might contain a Header element, the Body element may be the second element.
private Element getSOAPBodyElement(Element requestSOAPEnvelope) {
Element firstChild = (Element) requestSOAPEnvelope.getFirstChild();
if (firstChild.getLocalName().equalsIgnoreCase("body")) {
return firstChild;
}
Element secondChild = (Element) firstChild.getNextSibling();
if (secondChild.getLocalName().equalsIgnoreCase("body")) {
return secondChild;
}
return null;
}
The following method is where the real work is done. Most of the effort is spent on making sure that the request is a SOAP Envelope.
private QName getRequestPayLoadQualifiedName(HttpServletRequest request)
throws ServletException, IOException
{
try
{
Document requestXML = this.builder.parse(request.getInputStream());
Element requestSOAPEnvelope = requestXML.getDocumentElement();
if (!requestSOAPEnvelope.getLocalName().equalsIgnoreCase("envelope"))
{
throw new ServletException(
"Unable to parse request. Are you sure it is a SOAP Envelope?"
);
}
Element requestSOAPBody = this.getSOAPBodyElement(requestSOAPEnvelope);
if (requestSOAPBody == null) {
throw new ServletException(
"Unable to parse request. Body element not found. Are you sure it is a SOAP Envelope?"
);
}
Element requestPayload = (Element) requestSOAPBody.getFirstChild();
QName name = new QName(
requestPayload.getNamespaceURI(),
requestPayload.getLocalName());
return name;
} catch (SAXException e) {
throw new ServletException("Unable to parse request. Are you sure it is a SOAP Envelope?", e);
}
}
And finally the doPost method which looks up the file and writes the contents to the response stream. Note that due to the use of getResourceAsStream, the XML file is expected to be in the classpath in the same package as the servlet. Also, the response content must be set to text/xml for a SOAP response.
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
QName requestPayLoadQualifiedName =
this.getRequestPayLoadQualifiedName(request);
InputStream responseXML = this.getClass()
.getResourceAsStream(
requestPayLoadQualifiedName.getLocalPart() + ".xml");
if (responseXML == null) {
throw new ServletException("Unable to find "
+ requestPayLoadQualifiedName.getLocalPart()
+ ".xml in the classpath.");
}
response.setContentType("text/xml");
int respInt = responseXML.read();
while (respInt != -1) {
response.getOutputStream().write(respInt);
respInt = responseXML.read();
}
}
An example of where this could be used is with a Flex front end where the SWF file is host in a web app. The WAR file could be organised as follows:
/MyApplication.SWF (the Flex front end)
/MyApplication.html (wrapper html which embeds the SWF file)
/MyService.wsdl (WSDL defining the interface for the web service. Has servlet address as endpoint)
/WEB-INF/web.xml (registers the SimpleSOAP class as a servlet)
/WEB-INF/classes/SimpleSOAP.class (the servlet)
/WEB-INF/classes/myOperation.xml (the canned response for a call to ‘myOperation’)
The UI development team can now orchestrate their screens, making web service calls to web services that haven’t been implemented yet. Once they are implemented, the soap address in the WSDL can change. The above servlet could be further worked on with some XPATH expresssions to map certain combination of parameters to responses. One could make it really sophisticated to follow a sequence like a demonstration script. However, putting all that together takes the pressure of the server side team in delivering the real implementation, doesn’t it?
Simple ESB Solution
Another approach would be to use the Enterprise Service Bus and have routing rules to read responses from files. The ESB approach would also allow for more content based routing allowing for different responses to be given depending on parameters passed at runtime. If the development environment is going to involve an Enterprise Service Bus, then the above servlet approach is best limited to individual developers environment or for simple automated component testing of the UI.