RESTful service based on ADF Business Components, publishing enterprise database contents the REST way

After three introductory and exploratory articles on RestLet, RESTful Services and the creation of the latter using the former in JDeveloper 11g as well as hooking it up with ADF, it is now time to create a more serious RESTful service. A service that exposes resources from an enterprise business service. One backed by a enterprise database. One that RESTfully provides access to the Human Resource data. In other words: we will publish RESTful services for DEPT and EMP in the SCOTT schema.

It really is simple. The steps are:

  • create default ADF BC business objects for the DEPT and EMP tables; this implicitly publishes a Data Control that exposes the data collections
  •  create a new JSF page; drag the EMP table to this page and drop it as master-detail (table-table); this implicitly creates a PageDefinition that fuels the BindingContainer we need for our services
  • create and configure a servlet filter that will take care of initializing the BindingContainer on every request
  • create Resource classes for Depts, Dept, Emps and Emp; these classes get the appropriate data binding from the BindingContainer, set the current row for Dept (all resources except Depts) and Emp (only for the Emp resource) and write the XML representation of the desired data
  • attach routes for all resources using appropriate URLs (/depts, /depts/{deptno}, , /depts/{deptno}/emps,  /depts/{deptno}/emps/{empno}
  • deploy the Web Application and access the RESTfule services from a browser or other Http-client

Let’s take it one step at a time:

Create default ADF BC business objects for the DEPT and EMP tables

I assume that you are familiar with ADF Business Components and how to create a default Application Module (called HrmService in this example) with master-detail structure for ViewObjects for Dept and Emp. You will then also know that this HrmService ApplicationModule is automatically exposed as Data Control in the Data Control Palette that we can use to create Data Bindings for our application.

Create a new JSF page and an associated Page Definition

Just like we did in the previous RESTful article, we will create a dummy page – one that will absolutely never be rendered – just as a convenient (design time) way to create the Page Definition on which we will build the run time BindingContainer with the Data Bindings required by our RESTful services.

Drag the EMP table to this page and drop it as master-detail (table-table);

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way adfbcrestlet dropmasterdetailtbltbl
 

this implicitly creates a PageDefinition that fuels the BindingContainer we need for our services.

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way adfbcrestletdatabindingempdept

In XML this PageDefinition looks like this:

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                version="11.1.1.51.56" id="HrmServicePageDef"
                Package="myfirstrestfulservice.pageDefs">
  <parameters/>
  <executables>
    <iterator Binds="DeptView1" RangeSize="25"
              DataControl="HrmServiceDataControl" id="DeptView1Iterator"/>
    <iterator Binds="EmpView2" RangeSize="25"
              DataControl="HrmServiceDataControl" id="EmpView2Iterator"/>
  </executables>
  <bindings>
    <tree IterBinding="DeptView1Iterator" id="DeptView1">
      <nodeDefinition DefName="nl.amis.hrm.queries.DeptView">
        <AttrNames>
          <Item Value="Deptno"/>
          <Item Value="Dname"/>
          <Item Value="Loc"/>
        </AttrNames>
      </nodeDefinition>
    </tree>
    <tree IterBinding="EmpView2Iterator" id="EmpView2">
      <nodeDefinition DefName="nl.amis.hrm.queries.EmpView">
        <AttrNames>
          <Item Value="Empno"/>
          <Item Value="Ename"/>
          <Item Value="Job"/>
          <Item Value="Mgr"/>
          <Item Value="Hiredate"/>
          <Item Value="Sal"/>
          <Item Value="Comm"/>
          <Item Value="Deptno"/>
        </AttrNames>
      </nodeDefinition>
    </tree>
  </bindings>
</pageDefinition>
 

Note: the PageDefinition is registered in DataBindings.cpx and is to be retrieved at run time using its id:myfirstrestfulservice_HrmServicePageDef.

Create and configure a servlet filter

Instead of tasking the application with the refresh management of the BindingContainer, we create a ServletFilter that will take care of initializing the BindingContainer on every request. This filter is triggered on every request and is configured AFTER the ADFBindingFilter.

The filter gets the ADF Binding Context, retrieves the relevant BindingContainer and initializes it:

public class PrepareHrmServiceFilter implements Filter {
    private FilterConfig _filterConfig = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        _filterConfig = filterConfig;
    }

    public void destroy() {
        _filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException,
                                                   ServletException {

        // make sure the HrmService binding container is prepared when the application starts using it
        ADFContext ctx = ADFContext.getCurrent();
        OracleExpressionEvaluatorImpl elev =
            (OracleExpressionEvaluatorImpl)ctx.getExpressionEvaluator();
        HttpBindingContext data = (HttpBindingContext)elev.evaluate("${data}");
        DCBindingContainer bc =
            data.findBindingContainer("myfirstrestfulservice_HrmServicePageDef");

        bc.refresh(DCBindingContainer.PREPARE_MODEL);

        chain.doFilter(request, response);
    }
}
 

The filter configuration in web.xml – after the ADF Binding Filter!

 <filter>
  <filter-name>PrepareHrmServiceFilter</filter-name>
  <filter-class>nl.amis.rest.controller.PrepareHrmServiceFilter</filter-class>
 </filter>
 ...
 <filter-mapping>
  <filter-name>PrepareHrmServiceFilter</filter-name>
  <url-pattern>*</url-pattern>
 </filter-mapping>

Create Resource classes for Depts, Dept, Emps and Emp

Our RESTful service(s) publish four Resources: Depts (the list of Dept resources), Dept, Emps (the list of Emp resources) and Emp. For each resource, we create a class that extends the RestLet base Resource class. Each of these classes handles the GET request by implementing the represent() method. In this method, each Resource makes an XML representation of its value available.

The common logic between these classes:

a) get the appropriate data binding from the BindingContainer (either only the binding for Dept or the one for Dept and Emp)

b) get the relevant parameters from the request, such as the resource identifier (deptno and empno) and the start and maxRows parameters

For example the code from Class EmpsResource:

    JUControlBinding deptsBinding;
    String deptno;
    JUControlBinding empsBinding;
    int startRow = 0;
    int maxRows = -1;

In the constructor for EmpsResource: the class has to get the DeptsBinding to set its current row to the row identified by {deptno}.

...
        deptsBinding =
                (JUControlBinding)RestfulApplication.getHrmService().findCtrlBinding("DeptView1");
        empsBinding =
                (JUControlBinding)RestfulApplication.getHrmService().findCtrlBinding("EmpView2");

        deptno = (String)getRequest().getAttributes().get("deptno");

        String startRowParam =
            request.getResourceRef().getQueryAsForm().getFirstValue("start");
        if (startRowParam != null) {
            startRow = Integer.parseInt(startRowParam);
        } else {
            startRow = 0;
        }
        String maxRowsParam =
            request.getResourceRef().getQueryAsForm().getFirstValue("maxRows");
        if (maxRowsParam != null) {
            maxRows = Integer.parseInt(maxRowsParam);
        } else {
            maxRows = -1;
        }
...

It retrieves the deptno from the request as well as other relevant parameters.

Note the getHrmService() on the RestfulApplication class. It is implemented like this:

    public static DCBindingContainer getHrmService() {
        ADFContext ctx = ADFContext.getCurrent();
        OracleExpressionEvaluatorImpl elev =
            (OracleExpressionEvaluatorImpl)ctx.getExpressionEvaluator();
        HttpBindingContext data = (HttpBindingContext)elev.evaluate("${data}");
        DCBindingContainer bc =
            data.findBindingContainer("myfirstrestfulservice_HrmServicePageDef");
        // the servlet filter took care of preparing the binding container
        return bc;
    }

c) set the current row for Dept (all resources except Depts) and Emp (only for the Emp resource)

d) and write the XML representation of the desired data

Again from EmpsResource, the represent() method:

    public Representation represent(Variant variant) throws ResourceException {
        deptsBinding.getIteratorBinding().setCurrentRowWithKeyValue(deptno);
        // Generate the right representation according to its media type.
        if (MediaType.TEXT_XML.equals(variant.getMediaType())) {
            try {
                DomRepresentation representation =
                    RestfulApplication.createDomRepresentationForRows("employees",
                                                                      empsBinding.getRowIterator(),
                                                                      maxRows,
                                                                      startRow,
                                                                      false);
                return representation;
            } catch (IOException e) {
            }
        }
        return null;
    }

For the last step – the creation of XML – ,the classes make use of a nice little utility method on the Row interface: writeXML(). We can call this method to get an XML representation of the Row and possibly its detail graph (traversing the graph via ViewLinks). Since the Node returned from this method lives in its own Document, we have to import the node into the Document we are preparing for the DomRepresentation:

In code:

  Node node = row.writeXML(0, row.XML_OPT_LIMIT_RANGE);
  Node importedRow = d.importNode(node, true);
  r.appendChild(importedRow);

Here d is the document in the domRepresentation and r is the root element ("departments" or "employees").

With this little bit of logic, we can quickly create a generic method that builds an XML document for any RowIterator. Note that writeXML() can be called with an XSL stylesheet to create a more fitting representation of the data. The generic method:

    public static DomRepresentation createDomRepresentationForRows(String rootElement,
                                                                   RowIterator ri,
                                                                   int maxRows,
                                                                   int startRow,
                                                                   boolean singleRow) throws IOException {
        DomRepresentation representation =
            new DomRepresentation(MediaType.TEXT_XML);
        Document d = representation.getDocument();
        Element r = d.createElement(rootElement);
        d.appendChild(r);
        Row[] rows;
        if (singleRow) { // indicates a single row representation
            rows = new Row[] { ri.getCurrentRow() };
        } else {
            ri.reset();
            ri.setRangeSize(maxRows);
            ri.setRangeStart(startRow);
            rows = ri.getAllRowsInRange();
        }
        for (Row row : rows) {
            Node node = row.writeXML(0, row.XML_OPT_LIMIT_RANGE);
            Node importedRow = d.importNode(node, true);
            r.appendChild(importedRow);
        }

        d.normalizeDocument();
        return representation;
    }

Note that when this method is called with the boolean parameter singleRow set to true, it relies on the caller to have set the current row for the RowIterator.

Attach routes for all resources using appropriate URLs

In class RestfulApplication, all routes are set up, all URLs that lead to resources. The URLs our service will expose: (/depts, /depts/{deptno}, , /depts/{deptno}/emps,  /depts/{deptno}/emps/{empno}. These are configured with this code:

    public synchronized Restlet createRoot() {
        // Create a router Restlet that defines routes.
        Router router = new Router(getContext());

        // Defines a route for the resource "list of departments"
        router.attach("/depts", DeptsResource.class);
        // Defines a route for the resource "department)"
        router.attach("/depts/{deptno}", DeptResource.class);
        // Defines a route for the resource "list of employees (within current department)"
        router.attach("/depts/{deptno}/emps", EmpsResource.class);
        // Defines a route for the resource "list of employees (within current department)"
        router.attach("/emps", EmpsResource.class);

        return router;
    }

 

Deploy the Web Application and access the RESTful services from a browser or other Http-client

With all the above in place, we can deploy the Web Application and start accessing the RESTful URLs for retrieving Dept and Emp data.

A list of all departments:

url: /depts

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbased021

The details for a specific department:

url: /depts/{deptno}

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbased022

A list of employees in Department 20 – url: /depts/20/emps

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbased020

Improving the XML representation of our data

Until now we have solely relied on the default XML structure generated by the writeXML() method. While that gets the data quickly out of our system, you may desire a more object or consumer oriented format that is somewhat farther removed from the underlying data model. If we want a different XML format than the default provided to us by writeXML(), we can define an XSL-T stylesheet that transforms the XML created by writeXML into a more fitting structure.

First I have created a somewhat sloppy but functional utility method that takes a string and turns it into a XSL-T Stylesheet. Note: the string better contain correct XML syntax or else unspecified things go wrong:

    public static XSLStylesheet getXslFromString(String xslcontent) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // Turn on validation, and turn off namespaces
        factory.setValidating(true);
        factory.setNamespaceAware(false);
        DocumentBuilder builder = null;
        try {
            builder = factory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
        }
        InputSource is = new InputSource(new StringReader(xslcontent));
        Document doc = null;
        try {
            doc = builder.parse(is);
        } catch (SAXException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
        }
        XSLStylesheet xsl = null;
        try {
            xsl = new XSLProcessor().newXSLStylesheet((XMLDocument)doc);
        } catch (XSLException e) {
        }
        return xsl;
    }

This method can be invoked by Resource classes to get a stylesheet to pass to createDomRepresentationForRows that we have extended with XSLT support:

    public static DomRepresentation createDomRepresentationForRows(String rootElement,
                                                                   RowIterator ri,
                                                                   int maxRows,
                                                                   int startRow,
                                                                   boolean singleRow,
                                                                   XSLStylesheet xsl) throws IOException {
        DomRepresentation representation =
...
        for (Row row : rows) {
            Node node = null;
            if (xsl != null)
                node = row.writeXML(0, row.XML_OPT_LIMIT_RANGE, xsl);
            else
                node = row.writeXML(0, row.XML_OPT_LIMIT_RANGE);

            Node importedRow = d.importNode(node, true);
...

For example in DeptsResource in method represent():

 DomRepresentation representation =
                   RestfulApplication.createDomRepresentationForRows("departments",
                                                                      deptsBinding.getRowIterator(),
                                                                      maxRows,
                                                                      startRow,
                                                                      false,
                                                                      getXsl());

The getXsl() method is implemented in this same class:

    private XSLStylesheet getXsl() {
        String xslString =
            "<?xml version=\"1.0\" encoding=\"windows-1252\" ?>\n" +
            "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n" +
            "  <xsl:template match=\"/DeptViewRow\">\n" +
            "        <Department deptno=\"{Deptno}\">\n" +
            "          <Name><xsl:value-of  select=\"Dname\" /></Name>\n" +
            "          <Location><xsl:value-of  select=\"Loc\" /></Location>\n" +
            "        </Department>\n" +
            "  </xsl:template>" +
            "</xsl:stylesheet>\n";

        return RestfulApplication.getXslFromString(xslString);
    }

With this code in place, the data is presented in our browser like this:

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbased023

Provide Query Services

For real enterprise services, it is a fairly common requirement to be able to perform queries on the resources. Rather than returning an unfiltered list of all resources a service consumer wants to be able to specify conditions for the resources to satisfy. The URL pattern that could be used for such queries – and here the REST theory is somewhat unclear – could be something like:

URL like /depts?show=BOSTON or /depts?show=BOSTON&show=DALLAS

With the following bit of code, we can get hold of all query strings that were passed in.

  String[] queryParam =
    request.getResourceRef().getQueryAsForm().getValuesArray("show");

Alternatively, one could use:

/depts?query=BOSTON+DALLAS

or

/depts?loc=BOSTON+DALLAS&dname=A%25

(note: %25 is the escape character for %)

How we interpret these values is a different matter altogether: do we apply the values to one specific attribute or to all? And do we AND the criteria together or OR them? (show departments that are both in BOSTON AND DALLAS (not likely) or that are in one of the two LOCs. Or do we show departments that are called BOSTON or located in BOSTON and departments that are called (dname equals) DALLAS or located in DALLAS?

I have not been able to establish the common wisdom of this. It depends on the situation I suppose. ORring the criteria together instead of ANDing seems an obvious choice. Deciding which attributes to apply the criteria against is matter of choice.

 

Add ViewCriteria DeptViewGlobalSearchCriteria and Bind Parameter searchCriteria to DeptView:

using the ViewCriteria editor it is easy to create a ViewCriteria that returns only departments for which the Dname or the Loc starts with the same characters as the searchCriteria bind-parameter – compared in an case insensitive manner.

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbased024

Create two new methods in the DeptViewObjectImpl class – one for applying the ViewCriteria and one for unapplying all ViewCriteria:

    public void applyDeptViewGlobalSearchCriteria(String searchCriteriaValue) {
      ViewCriteria vc = getViewCriteria("DeptViewGlobalSearchCriteria");
      setsearchCriteria(searchCriteriaValue);
      applyViewCriteria(vc);
      executeQuery();
    }

    public void unapplyAllViewCriteria() {
      setApplyViewCriteriaNames(null);
      executeQuery();
    }

Publish these two methods on the Client Interface for the ViewObject –

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbased027

This makes these methods available as operations on the Data Control palette that can be bound to the application:

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbased028

In the DeptsResource, we need to check for the query parameters. If they are present, we need to get hold of the ActionBinding and execute it with the proper value for the searchCriteria parameter.

 

Now we can execute queries from within the browser by manipulating the URL.

Here the simplest form: with a URL of /depts?show=DALLAS we instruct the service to give us a list of all Dept resources that satisfy the search criteria DALLAS. How exactly the service applies that criteria, we do not exactly know:

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbasedsimplecriteria

The next example: search criterium is just A: /depts?show=A

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbasedstartswitha

 

And finally the use of a Wildcard: the % is the match zero, one or more characters wildcard in the SQL Like operation. The URL encoding of % is %25. With a URL of /depts?show=%25N we tell the service to accept %N as our search criteria. The service itself adds % to end, so we are looking for Departments whose name or loc is like %N% – or whose or loc contain an N character:

RESTful service based on ADF Business Components, publishing enterprise database contents the REST way restfuladfbcbasedwildcard

Resources

Download the JDeveloper 11g Application for this article: restfulservicesonadfbc.zip.

2 Comments

  1. Marco Gralike October 16, 2008