Jun 062011
 

Organizations with SOA and service-based infrastructures often need to present data from multiple services in a unified, aggregated interface.  Historically, this has been the domain of portals.  One of the tenets of a portal composite pattern is aggregated content; in other words taking content from multiple, disparate sources and presenting the content in a standard look and feel.  However, portals typically have a ‘portal’ feel; they usually use frames, fieldsets, or boxes to present content from a particular source (think Portlet, mashup, or Web Part).

Recently, one of our clients had a requirement to present data and content from multiple services, but didn’t want the portal look and feel.  After some more analysis, it became evident that the website needed to invoke multiple services, take the response messages from those services, and create an HTML output that incorporated the response messages as well as ‘static’ HTML, CSS, and JavaScript.  This requirement is very similar in function to a BPEL composite or a platform that implements Enterprise Integration Patterns (EIP).   In other words, service invocation is orchestrated, messages are passed and collected, and an output is created that is the composition of the messages and other data.  The biggest difference between BPEL/EIP and this requirement is that the output is HTML, not a data protocol like XML.

Given the similarities, we designed and built a web content delivery platform that leveraged EIP messaging styles but generated an HTML response.  This particular solution used Spring Integration for the messaging integration.  Other platforms that implement the same patterns could just as easily be used, such as BPEL, Oracle Service Bus, or Mule ESB, to name a few.

The basic sequence for generating web pages in our system is as follows:

Sequence Diagram

Sequence Diagram of Content Integration

One of the key actors in this sequence is the page configuration; there needs to be some sort of storage of page and site configuration.  This storage needs to be able to tell the integration platform and supporting code things like:

  • What services/channels do I need for content?
  • What XSLT do I use for output transformation?
  • What security or authentication mechanisms are required for this page?

For this particular project, we used Oracle Universal Content Management (UCM) to provide the configuration storage, as well as storing the XSLTs used to create the output.  The client had UCM installed already, and UCM has robust SOAP APIs, so it was a good fit.  In practice, any sort of persistent storage could be used, such as a relational database, Spring context configuration, and so on.  It would be helpful to use something that doesn’t require redeployment of the application (like Spring context configurations usually do), as the idea is to allow the development of sites independently of the content delivery platform.

Once the composition of the page request is determined, the integration platform is used to collect, aggregate, transform, and assemble the resultant output.  The first step is to process the request.

Using Spring Integration, a Servlet was created to handle requests for these ‘virtual’ pages.  The Java code for the doPost method looks like this:

ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
ServiceGateway serviceGateway = (ServiceGateway) context.getBean("serviceGateway");
result = serviceGateway.getPage(requestInfo);
response.getWriter().write(result);

In the preceding code, the requestInfo variable is just a Java bean that stores a the requested page’s configuration.  The most important part of this is the page name.  In our case, the requested page is the third part of the request path; the first two are the Servlet and site respectively.  So, for a request of /serviceBus/mySite/home, the Servlet path is serviceBus, the site name is mySite, and the page is home.

The Spring context configuration for the serviceGateway bean is:

<si:gateway
  service-interface="net.avantia.bus.integration.ServiceGateway"
  id="serviceGateway"/>

And the ServiceGateway interface is a simple interface:

public interface ServiceGateway {
 @Gateway(requestChannel="page-request", replyChannel="html")
    public String getPage(RequestInfo message)
     throws MissingContentResourceException, Exception;
}

The request channel page-request is defined simply in the context configuration as:

<si:channel id="page-request"/>

None of this is very interesting though. The real key is the Message Enricher defined for the page-request channel:

<si:service-activator
  ref="bootstrapMessageEnricher"
  method="enrichMessage"
  input-channel="page-request"
  output-channel="full-request-content"/>

This bean tells Spring Integration to enrich the inbound message on the page-request channel by invoking the enrichMessage method on the bootstrapMessageEnricher bean with the supplied inbound message.  The enrichMessage method of the bootstrapMessageEnricher gets the configuration of the requested page from our configuration store (UCM in this case), and sets a number of headers in the message.  The bootstrapMessageEnricher bean’s enrichMessage method looks like this:


public Message<String> enrichMessage(Message<RequestInfo> message)  throws Exception {
  RequestInfo reqInfo = message.getPayload();
  String requestedPage = reqInfo.getRequestedPage();
  String site = reqInfo.getSiteName();
  Map queryMap = reqInfo.getQueryParams();
  String siteXmlFileName = site + "_SITE_XML";
  String siteXsltFileName = site + "_SITE_XSL";
  String siteXml = ucmContentUtil.getUcmFile(siteXmlFileName, ucmUid, ucmPwd);
  String convertedPath = convertSitePath(requestedPage);
  XPathUtil xpathUtil = new XPathUtil(siteXml);
  String pageNode = xpathUtil.getXmlFragment(convertedPath);
  if (null == pageNode) {
     throw new MissingContentResourceException("No page node found for path: '" + convertedPath + "'");
  }
  String siteXslt = ucmContentUtil.getUcmFile(siteXsltFileName, ucmUid, ucmPwd);
  String queryParamXml = wsbUtil.convertQueryParams(queryMap);
  Message<String> outMessage = MessageBuilder.withPayload(pageNode).
   setHeader("siteXml", siteXml).
   setHeader("queryParams", queryParamXml).
   setHeader("siteName", ((RequestInfo) message.getPayload()).getSiteName()).
   setHeader("requestedPage", requestedPage).setHeader("siteXslt", siteXslt).
   setHeader("servletRequest", reqInfo.getServletRequest()).
   setHeader("contextParameters", ((RequestInfo) message.getPayload()).getContextParameters().toXml()).
   build();
  return outMessage;
}

The Message Enricher takes care of figuring out the requested site, required authentication, and so on by parsing the request, finding the configuration for the requested page, and setting message headers for the requested information. The next step is to configure a Header Value Router that routes the messages to specific beans based on the message headers set by the Message Enricher:

<si:header-value-router input-channel="split-request-content" header-name="contentSrcType">
  <si:mapping value="soap" channel="soap-service-request" />
  <si:mapping value="httpGet" channel="http-get-service-request" />
  <si:mapping value="ucmGetFile" channel="ucmGetFile-request" />
  <si:mapping value="param" channel="param-request" />
  <si:mapping value="static" channel="aggregator-input" />
  <si:mapping value="http" channel="aggregator-input" />
</si:header-value-router>

The Header Value Router takes care of routing the message to various beans defined based on the header values set by the Message Enricher.  The beans defined include channels that invoke services, as well as transformers that apply XSL transformations to the messages.  The XSLTs, in this solution, generate valid HTML.

Once the routing is completed, Spring Integration sends the final message back to the serviceGateway bean, which is configured to send the message to the http reply channel.  The proxy servlet then responds to the client with the message that sits on the http reply channel.

There are a couple of design considerations that came up as part of this solution, and should certainly be evaluated before heading down this path:

  1. The solution requires content to be managed by XSLT and XML: This is not a solution for non-technical end users to manage content, like a true content management system would be.  This is a pattern for aggregating content in a SOA or cloud based environment, where your web designers are savvy in XML and XSLT and need to develop a UI for service-driven content.
  2. Form-driven content is not considered ‘first class’: HTML forms didn’t drive this design.  The architecture could certainly be modified to accomodate form driven interaction, but it was not a requirement for this solution.
  3. Invoking multiple external services for each request impacts performance: The solution has aspect-oriented caching built into it, so that invocation results can be cached on a per-service basis.  This is a subject for subsequent posts.
  4. Standard Java EE security is hard to apply to ‘virtual’ page URLs: Spring Security helps when applying custom login pages, security contexts and the like.  Spring Security was leveraged as much as possible for this solution.

In practice, this approach is better suited for delivering HTML snippets or JSON rather than full web pages.  But it is a good example of how to leverage integration techniques for more than just data message results; it gives you a way to generate user interface markup, AJAX responses, and other content markup directly from your ESB or integration platform.

Sorry, the comment form is closed at this time.