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:
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.
With the green plus we can create a new REST connection from here.
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”.
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.
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.
Now our data control is ready to use. Let us see how it looks like:
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:
Now we enter some address in the address field, for instance “Edisonbaan 15 Nieuwegein” and press the “get” button. The result will be:
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.
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.
Then we change the get button’s action listener property to a new managed bean method, called “findPostalCode”.
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.
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.
is there any post method example ?
Hi,
I get error Caused by: java.net.UnknownHostException: maps.googleapis.com while running page.
Any Idea????
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!
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
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????