Consuming a REST service from your ADF 12.2.1 application

5

With the release of ADF 12.2.1 in the fall of 2015, Oracle finally added support for declaratively consuming a RESTful web service that responds in JSON format. Until then, processing JSON from a rest web service was only possible using Java.
The ability to consume and process the web service response declaratively enables fast implementation in an application that required this, and in this post, I will show how you can use this feature, with the help of a demo application.

The RESTful web service to consume

I will not go into the details of the definition of REST web services or JSON, but start immediately with an example. For this example I will use a service, exposed from the Google Geocoding API. The service can be invoked by an HTTP request, and responds with JSON.
The URL to be invoked is: http://maps.googleapis.com/maps/api/geocode/json
The service request takes one parameter, named “address”. The address can contain any address you can imagine, and when you invoke the service, the response will contain the geographical longitude and latitude of this location, and the postal code, if available. For the demo application I set the retrieval of the postal code as the goal.

Let us see how a typical response looks like, by invoking the service from the browser and putting the following request in the address bar of it:
http://maps.googleapis.com/maps/api/geocode/json?address=”Edisonbaan 15 Nieuwegein”

The browser will execute a GET request and the the result will be:

{
“results” : [
{
“address_components” : [
{
“long_name” : “15”,
“short_name” : “15”,
“types” : [ “street_number” ]
},
{
“long_name” : “Edisonbaan”,
“short_name” : “Edisonbaan”,
“types” : [ “route” ]
},
{
“long_name” : “Nieuwegein”,
“short_name” : “Nieuwegein”,
“types” : [ “locality”, “political” ]
},
{
“long_name” : “Nieuwegein”,
“short_name” : “Nieuwegein”,
“types” : [ “administrative_area_level_2”, “political” ]
},
{
“long_name” : “Utrecht”,
“short_name” : “UT”,
“types” : [ “administrative_area_level_1”, “political” ]
},
{
“long_name” : “Nederland”,
“short_name” : “NL”,
“types” : [ “country”, “political” ]
},
{
“long_name” : “3439 MN”,
“short_name” : “3439 MN”,
“types” : [ “postal_code” ]
}
],
“formatted_address” : “Edisonbaan 15, 3439 MN Nieuwegein, Nederland”,
“geometry” : {
“location” : {
“lat” : 52.0334908,
“lng” : 5.099067900000001
},
“location_type” : “ROOFTOP”,
“viewport” : {
“northeast” : {
“lat” : 52.03483978029151,
“lng” : 5.100416880291503
},
“southwest” : {
“lat” : 52.03214181970851,
“lng” : 5.097718919708499
}
}
},
“place_id” : “ChIJ2X-AAEpkxkcRnIHiNNbIzHM”,
“types” : [ “street_address” ]
}
],
“status” : “OK”
}

In general, when you develop a client for a RESTful web service, you will probably prefer to use a tool like Postman, which lets you invoke other operations than the GET of the browser, but for this example we only use the GET request, and therefore the browser is sufficient.

From the response, we can read now the latitude and longitude of our address, and also the postal code, which is in line 36.

Creating the data control

Now, let us see how we can perform this call and process the response from ADF. In a new ADF application, we choose to create a Web Service Data Control (SOAP/REST) from the gallery:

Creating a new Web Service Data Control

1. Creating a new Web Service Data Control

After clicking “OK”, we enter the Web Service Data Control wizard, where we give the data control a name, choose REST and the type of data control. The first option is to use an ADF based REST service, which is one that is based on ADF Business Components. You can see examples of this in this post.
For consuming the Geocode web service, however, we will need to use the second option: Generic Data Control.

2. Creating Web Service Data Control wizard, step 1 of 5

2. Creating Web Service Data Control wizard, step 1 of 5

With the green plus we can create a new REST connection from here.

3. Creating REST connection

3. Creating REST connection

We fill in a name for the REST connection and enter the base URI, but without the last part of it: “json”. We leave that part for some next step. Note that, when you click the “Test Connection” button, you will see an error, but this is not fatal. We can go on with the wizard, so, after clicking OK we get into step 2 of the wizard, which we can skip, because we don’t implement any authentication here.
So we get into part three of the wizard, where we are supposed to enter a “Resource Path”.

4. Creating Web Service Data Control setp 3 of 5

4. Creating Web Service Data Control setp 3 of 5

With the green plus we can create a new resoruce path, which we assign to the the last part of our base URI, that we have left out in the previous step: “/json”. For the data format, we choose JSON and select the “GET” method and give it a name: “get” for example. Then we click next to go to step 4 of the wizard.

5. Creating Web Service Data Control step 4 of 5

5. Creating Web Service Data Control step 4 of 5

Here we see the method that we have just created “get”. After we click on it, we can choose either a schema file or a sample. Since we have no schema file available, we choose “Parse from Sample Code” and paste the response we got earlier into the box. Further on, we add the address URL parameter.
Now we click “Next” and this will bring us to the last screen of the wizard, where we can test the data control, and this should give us the “Connection Successful” message, like below.

6. Creating Web Service Data Control step 5 of 5

6. Creating Web Service Data Control step 5 of 5

Now our data control is ready to use. Let us see how it looks like:

Geocode data control

Geocode data control

We see the “get”-method that we have defined, which takes one parameter: “address”. The method returns a status and a “results”-collection, which on its turn contains the “address_components”-collection where one of its members will hold the postal code that we are looking for.

Creating a page declaratively

Let’s make a simple page, where we will just display the list of address components, declaratively. First we drag the “get” method from the data controls section onto the page, where we choose “ADF Parameter Form” from the context menu. We give the input field the label “Address” so it will be clear what needs to be entered here. The submit button will get the label “get” by default, but we can change that of course.
After that, we drag the “address_components”-collection onto the page, and choose “ADF Table”, which we will make read only.
One more thing we need to do is to set the PartialTriggers property on the table to the button, so that the response will be displayed immediately, and when we run the page, the result will be as follows:

Test Page on REST service, created declaratively

Test Page on REST service, created declaratively

Now we enter some address in the address field, for instance “Edisonbaan 15 Nieuwegein” and press the “get” button. The result will be:

Test Page with the service call executed

Test Page with the service call executed

So, as you can see, we have the postal code that we were looking for, in the last row of the table.

We can conclude from this, that it is easy to make a simple page based on a RESTful service. But what if we want to display just the postal code in an extra field?
This can be done, but with some Java code, as I will show below.

Reading data from the response programmatically

We will now add an extra input field, that we will put in disabled mode, that will contain the postal code, as retrieved from the Geocode service.
First we drag the input text component from the component palette onto the page and give it the appropriate label. For the value property, we invoke the Expression Builder, which will lets us create a managed bean, when we select the ADF Managed Beans node.

Creating managed bean

Creating managed bean

We call this bean the geocodeBean and give it view scope.

After that we can create the property for postal code and set this property on the value of our input field. We also add the button to the PartialTriggers property.

Creating postal code property

Creating postal code property

Then we change the get button’s action listener property to a new managed bean method, called “findPostalCode”.

Creating new action listener for button

Creating new action listener for button

Then we add some code to this method:

    public void findPostalCode(ActionEvent actionEvent) {
        BindingContext bindingContext = BindingContext.getCurrent();
        BindingContainer bindings = bindingContext.getCurrentBindingsEntry();
        OperationBinding operationBinding = bindings.getOperationBinding("get");
        operationBinding.execute();

        DCIteratorBinding addressComponentsIterator = 
             ((DCBindingContainer) bindings).findIteratorBinding("address_componentsIterator");
        addressComponentsIterator.setRangeSize(-1);
        Row[] rows = addressComponentsIterator.getAllRowsInRange();

        if (rows != null) {
            Optional postalCode = 
            Arrays.stream(rows)
            .map(e -> (DCDataRow) e)
            .map(e -> (Map) e.getDataProvider())
            .filter(map -> ((List) map.get("types")).contains("postal_code"))
            .map(e -> (String) e.get("long_name"))
            .map(e -> {return (e == null ? "Nothing found" : e);})
            .findFirst()
            ;
          postalCode.ifPresent(this::setPostalCode);
        }
    }

Here you can see some Java 8 style code to iterate over the address components and look for the component that has a types array that contains the text “postal_code”.

We can now make the table with address components invisible, but we need to keep the binding available.

Let us first run the page to see if it works and after that look more closely to the code.

We run the page, type in the address and after pressing the “get” button we see the postal code appear in the field below.

Page with postal code after REST call has been executed

Page with postal code after REST call has been executed

In the code you see that the “get”-method is executed and after that the rows from the address-components iterator are caught into a Row[] array.
If we cast each Row to DCDataRow, we are able to get the underlying data provider with the method “getDataProvider()”. This will give us either a LinkedHashMap for an object, or an ArrayList for an array.

By creating a Java stream from this array, we are able to filter and map its elements using lambda expressions, and retrieve the element that we are looking for.

Conclusion

Let me conclude that Oracle made it easy in the ADF 12.2.1 release to consume a REST web service that produces JSON, declaratively.
With the help of the binding layer or ADF Model, we can also process the data programmatically with Java, without having to parse JSON, because the data is automatically converted into Java object structures.

You can download the demo application here.

About Author

Edward Orlowski is active in IT since 1996, and has a lot of experience with Oracle development tools. Currently, his main interest is ADF, but he also has a thorough knowledge of SQL, PL/SQL and the RDBMS.

5 Comments

  1. Hi,
    While following the same steps in consuming the specific rest service, I get the following record as soon as I run the page.

    Caused by: oracle.adf.model.adapter.AdapterException: JBO-29114 ADFContext is not setup to process messages for this exception. Use the exception stack trace and error code to investigate the root cause of this exception. Root cause error code is DCA-29000. Error message parameters are {0=java.lang.NullPointerException, 1=null}
    at oracle.adf.model.adapter.rest.RestURLDCDefinition.getStructure(RestURLDCDefinition.java:259)
    at oracle.adf.model.adapter.AbstractImpl.getDefinitionInternal(AbstractImpl.java:458)
    at oracle.adf.model.adapter.rest.RestURLDataControl.getDefinition(RestURLDataControl.java:214)
    at oracle.adf.model.bean.DCBeanDataControl.getDefinition(DCBeanDataControl.java:1011)
    at oracle.adf.model.generic.StructureDefImpl.(StructureDefImpl.java:137)
    at oracle.adf.model.bean.DCBeanDataControl.initDCProperties(DCBeanDataControl.java:330)
    at oracle.adf.model.bean.DCBeanDataControl.(DCBeanDataControl.java:262)
    at oracle.adf.model.adapter.AdapterDCService.(AdapterDCService.java:90)
    at oracle.adf.model.adapter.DataControlFactoryImpl.createDataControl(DataControlFactoryImpl.java:284)
    at oracle.adf.model.adapter.DataControlFactoryImpl.createSession(DataControlFactoryImpl.java:211)
    … 131 more
    Caused by: java.lang.NullPointerException
    at java.io.Reader.(Reader.java:78)
    at java.io.InputStreamReader.(InputStreamReader.java:72)
    at oracle.adf.model.adapter.dataformat.json.SchemaStore.getReader(SchemaStore.java:310)
    at oracle.adf.model.adapter.dataformat.json.JSONSchemaHandler.setJsonSchema(JSONSchemaHandler.java:142)
    at oracle.adfinternal.model.adapter.JSONChildOperation.getMethodDefInternal(ChildOperation.java:1715)
    at oracle.adfinternal.model.adapter.ChildOperation.getMethodDef(ChildOperation.java:520)
    at oracle.adf.model.adapter.rest.RestURLDCDefinition.getStructure(RestURLDCDefinition.java:244)
    … 140 more

    Any ideas?
    Thank you!

  2. Hi Edward,

    I looking at creating data controls for a REST API which is secured using basic auth. I did select Authentication Type while creating REST Connection, but I still get a 401 Unauthorized error on accessing the WS. Also where do I configure the headers needed for the GET, POST calls?

    Thanks for the help,
    Sapna

  3. Hi,

    Thanks for this article and this is very helpful for us.

    Is there any way to create a data control in ADF application using rest services which has pagination along with query param????

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.