Saturday, June 7, 2014

Create RESTful services on top of ADF Business Components


Introduction

Creating SOAP Services on top of ADF Model is for a long time quite easy and convenient. In JDeveloper: Open an ApplicationModule goto WebService Tab and create a Service Interface by exposing some given ViewObject Instances or custom Service methods. See http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52adf-1735897.html (Consume Early, Consume Often) for more details and great explanation or take a look in the official Oracle documentation http://docs.oracle.com/cd/E23943_01/web.1111/b31974/bcextservices.htm#CJAJGIEB (11 Integrating Service-Enabled Application Modules)

Options to create RESTful Services on top ob ADF BC Model

Creating a RESTful Service Fassade on top of existing ADF Business Components Model is not that straightforward. From different „announcements" we know that Oracle plans to generate RESTful Services ADF BC SDO (Service Data Object) in the future. But for now we need a custom solution.

Exploring various possibilities I came up with the following


Note that the Service Facade is optional but recommended in terms of „separation of concerns", „service virtualization", „clean code", etc. Watch the youtube ADF Insider Essentials for the detailed explanation of the Service Facade Pattern.

Going from top to bottom in the diagram we have the following options
a) ADF BC -> SDO Service Interface -> WebService  -> SOAP 
b) ADF BC -> SDO Service Interface -> Inject EJB in REST-Resource -> REST
c) ADF BC -> Use AM Instance programmatically in REST-Resource ->REST
d) this one is planned for some 12.1.3+ release (and in future will be the default option to expose RESTful Services for a given ADF BC Model).

In this post I am covering case b. I did not find a sample on the web yet. So I give it a try cause it looks like THE pragmatic approach so far.

Howto

For the example I am using JDeveloper 12.1.2 and the corresponding Runtim ADF 12.1.2. In order to understand the next steps I assume you know the basics of ADF and JDeveloper.

1. Create ADF BC SDO SOAP Service for an Application Module:
Open the AM in „Overview", goto "Web Service" Tab. Now open the dialog to create the service interface.

Choose the desired VO-Instances and apply you changes.

JDeveloper should generate some files now. If you take a look in the *ServiceImpl.java Class you will notice that  it is exposed as Stateless Session EJB. This is great!

Because from now on you can use the EJB in a REST-Resource to expose it as RESTful-Service. That's what we are planning to do next.

But before this step test your generated (SOAP-based) service first: Make sure you do not ran into the „StackOverflowException" because the SDO Service will try to traverse the ViewLinks recursively. So the following exception might be the one you will run into.

To fix this, open the corresponding ViewLink and uncheck the property „Generate Property in SDO"


2.  Next: Create a new Project for the RESTful Service
Use the right project template from the NEW-Wizard

Open the NEW-Wizard again and create a RESTful service from new

Deselect the Checkbox in front of GET-Method. We are going to do that later in Sourcecode.

Finish. => This should generate a Java class and configure the project with the Jersey Library.
For the sample to complete the project needs on more adjustment in the project properties. 
- One more Library (Context and Dependency Injection CDI)
and a dependency to the Model-Project because the RESTfulWebService project need access to the application module.

Thats all for the project setup.

3. Add some classes, annotations and the methods you want to expose

import javax.ejb.EJB;

import javax.inject.Singleton;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

import oracle.jbo.service.errors.ServiceException;

@Singleton
@Path("api")
public class HrRESTResource {
   
    @EJB
    HRAppModuleService serviceBean;
   
    public HrRESTResource() {
    }
   
    /**
     * getEmployeesView1: generated method. Do not modify.
     */
    @GET
    @Path("emp/{empId}")
    @Produces(value = { "application/json", "application/xml" })
    public EmpResult getEmployeesView1(@PathParam("empId") Integer employeeId) throws ServiceException {
       
        EmployeesViewSDO empSDO = serviceBean.getEmployeesView1(employeeId);
       
        EmpResult result = new EmpResult();
        result.setEmployee(empSDO);
       
        return result;
    }
   
    @GET
    @Path("depts")
    @Produces(value = { "application/json", "application/xml" })
    public DeptResult findAllDepartments() throws ServiceException {
        DeptResult result = new DeptResult();
        result.setDepartmentList(serviceBean.findDepartmentsView1(null, null));
       
        return result;
       
    }
}

Important note: For what ever reason you need to annotate the REST-Resource with @javax.inject.Singleton. Otherwise the EJB-Injection won't work.

Just reuse the SDO-Entity-classes (eg. EmployeesViewSDO), but wrap them inside a custom „Result"-class which should be annotated with @XmlRootElement (JAX-B Standard). 

@XmlRootElement

public class DeptResult {
    public DeptResult() {
        super();
    }
   
    @XmlElement(name="departments")
    private List<DepartmentsViewSDO> departmentList;

    public void setDepartmentList(List<DepartmentsViewSDO> departmentList) {
        this.departmentList = departmentList;
    }

    public List<DepartmentsViewSDO> getDepartmentList() {
        return departmentList;
    }
}
See attached sample application for the whole source code.

4. Run an see the RESTful service in action

As a result we will get the proper JSON structure


if changing the accept- Header to: application/xml we will get XML-Result. 

This is pretty cool. Because we can reuse the generated ADF BC SDO SOAP Service Layer as EJB for the REST Fassade.

Download sample application, based on Version ADF 12.1.2: enpit.sample.adf12.restadfbc-jdev1212.zip

More Information



7 comments:

  1. Hello,

    Thanks for this nice doc. Note, the link to the sample seems to be broken.
    Can you pls fix that?

    Thanks,
    Peter

    ReplyDelete
  2. Hi pondrejk,

    the download link works for me: https://app.box.com/s/zoc6ivzsm7ihmpy7idh6
    (Tested with Chrome Version 36.0.1985.125 on Mac OS X 10.9.4)

    Regards,
    Andreas

    ReplyDelete
  3. Hi Andreas,
    Can you please elaborate on the third approach.

    Thanks,
    Santhosh

    ReplyDelete
  4. I am getting following error mentioned in the link while testing the service:
    http://docs.oracle.com/cd/E17904_01/apirefs.1111/e14397/J2EE.html#BEA-160091

    ReplyDelete
  5. Hi v sai santhosh Chada,

    the third approach is based on the programmatic usage of Application Modules.

    So you can use the Configuration.createRootApplicationModule(String, String) method to create an ApplicationModule instance which you can use to serve your REST resource.
    Do not forget to release the AM instance after using it: Configuration.releaseRootApplicationModule(ApplicationModule, boolean)

    Hope that helps / guides you in the right direction.

    Regards Andreas

    ReplyDelete
  6. Hi Andreas,
    Thanks for the post.
    Could you please share how did you deploy this app on standalone wls.
    I tried to create war file, added all model project dependencies and I could see all the EOs, VOs, AMs etc in the war file but it gives error classNotFound for Configuration class.
    Did you deploy ear or war?
    Also please share the steps to create the ear/war.
    Great thanks.

    Best regards,
    Saurabh

    ReplyDelete
  7. Hello Saurabh,

    you can download the sample application where you will see the used deployment profiles. The creation of war/ears are common tasks which I am not going to describe in detail. sorry for that.

    Please take a look at the documentation: for example here: http://docs.oracle.com/cd/E23943_01/web.1111/e16272/deploy_java_ee_app.htm#ADFJE442

    Best Regards,
    Andreas

    ReplyDelete