Integrating back-end services with WebCenter Sites using a lightweight MVC framework

Introduction

Many customers want to use Sites as a ‘thin’ rendering framework and mix web content with back-end applications. An example of such a back-end service might be a application that exposes its interface through a REST API. In this blog I explore a pattern to expose the interaction with such a API through Sites. For this I introduce a simple MVC framework that showcases the patterns.

Main Article

REST

In the exploration I show the reading of JSON responses of two different REST APIs, both publicly available. The first one is a simple time-of-day responder, the second a article listing from WordPress. The first one is a very simple response, just a JSON object with 3 name/value pairs, the WordPress response is more complex.

The general setup is that:

  • Sites, on the server-side, makes a call to the REST API
  • parses the response to an object graph
  • makes the object graph available to Sites Templates for rendering

This by itself is not difficult to implement. A simple Template and some java helper classes can accomplish the task. As soon as the integration becomes more complex and requires a more interaction with the site visitor, implementing this integration with Templates and java becomes troublesome. Quick development and fast deployments, two cornerstones of Sites value proposition, are being threatened. To overcomes this I introduce a lightweight MVC framework that makes use of Groovy for both implementing the business logic and allowing for quick deployment of code. A change in the Groovy source files will kick-off a recompilation upon request, similar to JSP files.

Lightweight MVC

The framework is embedded in Sites, so it works nicely with Sites’ caching, Template, assets and all the other core Sites’ features. The framework can be invoked from JSP and Groovy Templates and CSElements.

The framework consists of a Controller, a Model, a ControllerResolver, an annotation based dependency injection mechanism and a service Factory. In total 18 sources files:  5 interfaces, a single JSP taglib class, 2 annotation, 5 classes that implement the interfaces and some helper classes. All the sources are included in this blog for download.

As in any MVC framework there is a Controller and a Model. The Sites rendering framework is the View. The Sites servlets (Satellite and ContentServer) are the PageDispatcher. Based on the pagename request attribute is the request dispatched to a Template or CSElement. It is this Template that invokes the MVC framework and invokes the Controller. The rest of the blog assumes a reasonable understanding of common java MVC frameworks as Spring MVC or WebWork by the reader.
Groovy controller

Controller and Model

The Controller is an interface that is meant to be implemented by site developers in Groovy.

 

import COM.FutureTense.Interfaces.ICS;

public interface Controller {

Model handleRequest(ICS ics);

}

The model is an interface that look a bit like a Map and extends Iterable. It is meant as a key/value store that can easily be exposed to the View layer.

 

A sample Controller that I have created for this exercise is pretty straight forward.

class BackEnd implements Controller {

	@Inject Model model
	@Inject JsonClient client

	@Override
	public Model handleRequest(ICS ics) {
		Map<String, ?> s = client.getHTTP("http://time.jsontest.com/", new JsonHandler());
		client.end(ics);
		model.putAll(s);

		return model;
	}
}

As you can see can a Controller return a Model, returning null is also legal.

import java.util.Map;
import java.util.Set;

public interface Model extends Iterable<Map.Entry<String, Object>> {
/**
*
* @see java.util.Map#size()
*/
int size();

/**
*
* @see java.util.Map#isEmpty()
*/
boolean isEmpty();

/**
* @see java.util.Map#containsKey(java.lang.Object)
*/
boolean containsKey(Object key);

/**
* @see java.util.Map#get(java.lang.Object)
*/
Object get(Object key);

/**
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
Object put(String key, Object value);

/**
* @see java.util.Map#remove(java.lang.Object)
*/
Object remove(Object key);

/**
* @see java.util.Map#putAll(java.util.Map)
*/
void putAll(Map<? extends String, ? extends Object> m);

/**
*
* @see java.util.Map#clear()
*/
void clear();

/**
* @see java.util.Map#keySet()
*/
Set<String> keySet();

}

The Model is implemented by the DefaultModel class, that delegates to a java.util.HashMap. It is the job of the implementor of the Controller to populate the Model correctly. A further evolution of Model might be a revision that includes the View name to call, in Sites’ case this migth be the layout Template. It is also legal for the controller to return this or a Model that allows for lazy loading of data if that is more performant or applicable for your application.

The third interface that is declared is the ControllerResolver. The ControllerResolver is looking up the Controller by name. The GroovyControllerResolver implementation creates the Controller from a Groovy script file and configures the Controller for runtime execution. This configuration is done through annotations and a Factory.

import COM.FutureTense.Interfaces.ICS;

public interface ControllerResolver {

    Controller getController(ICS ics, String name);

}

 

 

Dependency Injection

The dependency injection part of the framework consist of a Injector and Factory interface and two annotations. The annotations are @Inject and @Producer. The @Inject annotation is used on the Controller to declare the services that need to be injected. These services are are created by a Factory. In this Groovy based framework a couple of groovy scrips are used to implement the factory methods for the services. These methods need to be annotated with the @Producer annotation.

A sample of such a service can be a service that reads asset data, or a REST client that connects to a back-end API.

import COM.FutureTense.Interfaces.ICS;

public interface Injector {

    void inject(ICS ics, Object object) throws Exception;

}

The Injector is implemented in GroovyInjector. This GroovyInjector also creates the AnnotationFactory that implements the Factory. The GroovyInjector makes use of the helper AnnotationInjector to wire the Controller with the services.

public interface Factory {

    public  T getObject(String name, Class fieldType);

}

The GroovyInjector makes use of serveral Factories. The BaseFactory is used for some obvious services and data structures: the Model is created here, and the session. Other Factories can be implemented in Groovy in WEB-INF/lw/ObjectFactory.groovy and WEB-INF/lw/<sitename>ObjectFactory.groovy. This gives a extendable and programmer controlled way of instantiating services, even in a multi-tenant way. It is possible for instance to differentiate factory methods per site in a predictable way.

The services are cached on the ICS scope so services can be reused during the execution of the request. Here a some samples the show the API. Three variants are possibe for factory methods, no arguments, a  single ICS argument and a ICS and Factory argument variant.

 

@Producer static Zoo makeZoo(){
    Zoo zoo = new Foo();
}

@Producer static Too makeToo(ICS ics){
    return new Too(ics,zoo);
}

@Producer static Foo makeFoo(ICS ics, Factory factory) {
    Zoo zoo = factory.getObject(null, Zoo.class); 
    return new Foo(ics,zoo);
}

Producers can also have a name so they can be wired to the named Inject annotation in case mulitple distinct services are needed.

The Groovy factory can also make use of classes defined in Groovy scripts. As long as the scripts are placed in WEB-INF/lw-groovy they will be compiled and used. There is one caveat. Because of the use of a different classloader to load the Controller and the Factories from the web application class loader, can these Groovy classes not be exposed to Templates unless the implement an interface or extend a class that is defined in the web application class loader. In most cases this means that the interface must be in a jar file in WEB-INF/lib or in the application server’s classpath. As long as the groovy classes are only used by the Controller, this restriction does not apply.

Calling and booting

The MVC framework can be called from either Groovy or JSP. The ControllerExecutor and ControllerTag are the vehicles to interact with the MVC framework.

A Groovy element is typically used as a outer wrapper as the main controller for the page.

import oracle.ateam.fatwire.sites.lightweightcontroller.controller.ControllerExecutor

ControllerExecutor.executePageController(ics, "controller/BackEnd");

Below is an example on how to interact with the framework from a JSP Template.

<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld"
%><%@ taglib prefix="ics" uri="futuretense_cs/ics.tld"
%><%@ taglib prefix="lw" uri="http://oracle.com/ateam/sites/tags/lightweight"
%><%@ taglib prefix="render" uri="futuretense_cs/render.tld"
%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%><cs:ftcs><!DOCTYPE html>
<lw:controller controller="controller/WordPress"/>

<c:forEach var="post" items="${posts}">
<div>
<a href="${post.URL}">${post.title}</a>
<div style="background-color: LightGray">${post.excerpt}</div>
<div>${post.content}</div>
</div>
<br/>
</c:forEach>

</cs:ftcs>

In the Model that is created by the controller is only exposed to the JSP page context in case of the JSP Template. The Groovy variant exposes the model to the request scope, so they can be used by all the Templates. Best practise is to call these Templates either as an element (not as pagelet or embedded) or the Wrapper and the Template that reads the model data must boeth be uncached. The latter uncached Wrapper/Template contstruct will not work with Remote Satellite Server, as each pagelet it its own request, losing the Model data on subsequent requests. Please withstand the temptation to add the Model to the session to overcome this limitation as this is very bad practise.

 

The last feature of the framework is booting. For this a ServletContextListener is implemented in GroovyControllerContextListener. This constructs the main classes and makes them available to the Templates and CSElements.

<listener>
<listener-class>oracle.ateam.fatwire.sites.lightweightcontroller.groovy.GroovyControllerContextListener</listener-class>
</listener>

Back-end REST samples

Now that I have gone through the moving parts of the framework it is time to come to the integration part with the two sample REST APIs.

For this I have implemented two java classes, a HTTP client that makes use of Apache’s httpcomponents to make HTTP requests and a response parser:

  • JsonClient
  • JsonHandler

The JsonHandler is a httpcomponent RequestHandler that makes use of Jackson to read the JSON response and create an object graph. As we have seen in the sample controller above, it is the Controller that makes use of these two classes. Other REST APIs might come with their own Java clients so some of the low level work is then hidden from the developer. As I wanted to showcase the interaction patterns I did not want to abstract to far away from HTTP.

I’ll go over three variants

a Template controller for a simple service

a Template controller for a more complex return

a Wrapper/Template controller construct

The first one is a very simple service. It returns the time of the day in three name/values pairs. The JSP Template that can be called anywhere on the page, is invoking the BackEnd controller and rendering the model. This is straight forward.

%><cs:ftcs><lw:controller controller="controller/BackEnd"/>

time:${time}<br/>
milliseconds_since_epoch: ${milliseconds_since_epoch}<br/>
date: ${date}<br/>

</cs:ftcs>

And the Controller, placed in WEB-INF/lw-groovy/controller/BackEnd.groovy, as per convention.

class BackEnd implements Controller {

@Inject Model model
@Inject JsonClient client

@Override
public Model handleRequest(ICS ics) {
Map<String, ?> s = client.getHTTP("http://time.jsontest.com/", new JsonHandler());
client.end(ics);
model.putAll(s);

return model;
}
}

The second example is a bit more complex. The constuct is still that of a pagelet controller, but the response is more complex and needs to be iterated over. As you can see could the ‘num’ argument be picked up from eithe the request or a render:calltemplate argument.

package controller

import oracle.ateam.fatwire.sites.lightweightcontroller.controller.Controller
import oracle.ateam.fatwire.sites.lightweightcontroller.controller.Model
import oracle.ateam.fatwire.sites.lightweightcontroller.http.JsonClient
import oracle.ateam.fatwire.sites.lightweightcontroller.http.JsonHandler
import oracle.ateam.fatwire.sites.lightweightcontroller.injection.Inject
import COM.FutureTense.Interfaces.ICS

class WordPress implements Controller {
    @Inject Model model
    @Inject JsonClient client
    @Override
    public Model handleRequest(ICS ics) {
        int num = 3; //ics.GetVar("num");
        Map<String, ?> s = client.getHTTP("http://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/?number="+num,
            new JsonHandler());
        client.end(ics);
        model.putAll(s);
        return model;
    }
}
<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld"
%><%@ taglib prefix="ics" uri="futuretense_cs/ics.tld"
%><%@ taglib prefix="lw" uri="http://oracle.com/ateam/sites/tags/lightweight"
%><%@ taglib prefix="render" uri="futuretense_cs/render.tld"
%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%><cs:ftcs><!DOCTYPE html>
<lw:controller controller="controller/WordPress"/>

<c:forEach var="post" items="${posts}">
    <div>
        <a href="${post.URL}">${post.title}</a>
        <div style="background-color: LightGray">${post.excerpt}</div>
        <div>${post.content}</div>
    </div>
    <br/>
</c:forEach>

</cs:ftcs>

The third variant is a Wrapper/Template construct. This gives the ability for the controller to have a lot of control over the page execution. It could change the LayoutTemplate to call, it could signal HTTP error codes to the browser, it could redirect the browser after interacting with the back-end etc. In this sample is the LayoutTemplate and the controller name are hard coded within the Wrapper. Other variants could pick up the controller name and Layout name from an ics variable, or have the Controller call the Template directly. In that latter case the controller would also need to copy the Model data (if needed) to the request, or maybe use this to set up the arguments for the render calltemplate call. As you can see is the same backEnd Controller reused.

import oracle.ateam.fatwire.sites.lightweightcontroller.controller.ControllerExecutor
ControllerExecutor.executePageController(ics, "controller/BackEnd");
ics.CallElement("Page/LightweightLayout",null);
<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld"
%><%@ taglib prefix="ics" uri="futuretense_cs/ics.tld"
%><%@ taglib prefix="render" uri="futuretense_cs/render.tld"
%><cs:ftcs>
<ics:if condition='<%=ics.GetVar("tid")!=null%>'><ics:then><render:logdep cid='<%=ics.GetVar("tid")%>' c="Template"/></ics:then></ics:if>

date: ${date}<br/>
time: ${time}<br/>
milliseconds_since_epoch: ${milliseconds_since_epoch}<br/>

</cs:ftcs>

I hope that this blog gives some insight in the possibilities around lightweight back-end integration with WebCenter Stes.

sites-lightweight-controller source code download

Comments

  1. Code patterns such as that of this blog article are not expected to be”plug and play” but rather the expectation is for developers such as yourself to take the ideas presented herein and then extend them. To the extent that your extensions can be discussed publicly on this blog, then of course we can offer advice or make adjustments/enhancements to our blog posts — and if your extensions/ideas warrant it we might even petition engineering to consider “productizing” them. That being said, there is nothing in this “framework” that is illegal, prohibited, or could be considered a customization to the core product. As such, use of this pattern would be supported like any other client-specific code implementation. It should be noted that other clients have gone live with similar Sites MCV patterns in the past.

  2. Vishvajeet Saraf says:

    Still waiting for my earlier question – Will this framework be supported by Oracle ? In case of an issue in the source code provided … Please confirm.

  3. Vishvajeet Saraf says:

    Will this framework be supported by Oracle ? In case of an issue in the source code provided … Please confirm.

Add Your Comment