Consume a REST Service in Oracle MAF

INTRODUCTION

REST-JSON is the recommended technology of choice for consuming web services in Oracle MAF. In this blog, we will build a MAF application that displays weather forecast for a chosen city. I am using the excellent OpenWeatherMap api to demonstrate consuming a rest service in a MAF mobile application. The application will allow you to select from a predefined list of cities and display the weather forecast in 3 three hour increments as supported by the api. When the application is completed and run in a simulator or device, this is what you should see

Screenshot_20170905-130232.png

SOLUTION

To consume a rest service we have to:
1. Create a new REST connection in Jdeveloper and add it to your application.
2. A Java class that creates a RestServiceAdapter and invokes the service.
3. Java classes that model the web service return object(s).

Create a new connection for your rest service as shown below:
restConnection.PNG

restConnectionDetails.PNG

Next, create a new java class for the RestServiceAdapter utility. In this case, we will be retrieving forecast data, so a simple get method should suffice. The sample code available for download does show examples of other rest verbs like update, put and delete.
The RestServiceAdapter api usage is shown below:

        RestServiceAdapterFactory factory = RestServiceAdapterFactory.newFactory();
        RestServiceAdapter restServiceAdapter = factory.createRestServiceAdapter();
        restServiceAdapter.clearRequestProperties();
        restServiceAdapter.setConnectionName(connectionName);
        restServiceAdapter.setRequestMethod(requestType);
        restServiceAdapter.setRetryLimit(0);
        restServiceAdapter.setRequestURI(requestURI);
        restServiceAdapter.addRequestProperty("Content-Type", "application/json");
        String response = "";
        String postDataStr = "";
        
            if (postData != null) {
                JSONObject jsonObj = (JSONObject) JSONBeanSerializationHelper.toJSON(postData);
                removeNullsAndTypeFromJSON(jsonObj);
                postDataStr = jsonObj.toString();
            }

            response = restServiceAdapter.send(postDataStr); 

You can call this method using a simple cover method like:

    public String invokeFind(String requestURI, Object postData, String authHeader, Boolean isToken) throws Exception {
        return invokeRestService(RestServiceAdapter.REQUEST_TYPE_GET, requestURI, postData);
    }

The rest service forecast json should now be inspected to model our java classes. Take a look at this JSON:

{
    "cod": "200",
    "message": 0.0899,
    "cnt": 40,
    "list": [
        {
            "dt": 1504040400,
            "main": {
                "temp": 65.1,
                "temp_min": 65.1,
                "temp_max": 65.41,
                "pressure": 1021.77,
                "sea_level": 1028.29,
                "grnd_level": 1021.77,
                "humidity": 100,
                "temp_kf": -0.17
            },
            "weather": [
                {
                    "id": 501,
                    "main": "Rain",
                    "description": "moderate rain",
                    "icon": "10d"
                }
            ],
            "clouds": {
                "all": 100
            },
            "wind": {
                "speed": 10.67,
                "deg": 42.501
            },
            "rain": {
                "3h": 5.2
            },
            "sys": {
                "pod": "d"
            },
            "dt_txt": "2017-08-29 21:00:00"
        },

This JSON describes a main container that has two internal parameters called cod and message that do not concern us. The cnt attribute will contain the number of forecast objects contained in the main “List” attribute. You can model the main container as the “AllForecast” java class.

public class AllForecast
{

    private String cod;
    private Float message;
    private Integer cnt;
    private OneForecast [] list;
    private PropertyChangeSupport _propertyChangeSupport = new PropertyChangeSupport(this);

    public AllForecast() {
        super();
    }

Each item in the list attribute contains an hourly forecast for the city code the service was invoked for. The “Forecast” java class can be modeled as:

public class OneForecast

{
    private Integer dt;
    private MainForecast main;
    private Weather [] weather;
    private Clouds clouds;
    private Wind  wind;
    private Sys sys;
    private String dt_txt;
    
    private String  formattedUnixDate;
    private Weather weatherOne;
    private String imageURL;
    private PropertyChangeSupport _propertyChangeSupport = new PropertyChangeSupport(this);
    final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm");

    public OneForecast() {
        super();
    }

You can find the java classes for the remaining objects in the Forecast in the attached sample code. Once all the return JSON has been modeled as java classes, all that needs to be done is code the api call in your managed bean. Create a managed bean in the main adfc-mobile-config in application scope. This class will contain our api call like:

private List<Forecast> forecast = new ArrayList<Forecast>();
    public boolean getBForecast() {
        String response = null;
        //4355843- gaither
        //4360369 - laurel
        //4349159 - bowie
        try {           
                response = restServiceUtil.invokeFind("/forecast?id="+this.getSelectedCity()+"&APPID=<YOUR_API_KEY_FOR_OPENWEATHERMAP>&units=imperial", null, null, true);
                
                AllForecast allForeCast = (AllForecast) JSONBeanSerializationHelper.fromJSON(AllForecast.class, response);
                this.setForecast(Arrays.asList(allForeCast.getList()));
                
                return true;            
        } catch (AdfInvocationException ex) {
            if (AdfInvocationException.CATEGORY_WEBSERVICE.compareTo(ex.getErrorCategory()) == 0) {
                throw new RuntimeException("Error with the server. Please try later.");
            }
        } catch (Exception e) {
            throw new AdfException(e.getMessage(), AdfException.ERROR);
        }
        return false;
    }

Note the call to JSONBeanSerializationHelper maf api to easily convert the JSON response to our java class AllForecast. Remember to sign up for an account on the OpenWeatherMap site for the api key that’s required for this to work.
Now, the bean attribute “forecast” can be used in the amx page to display the weather data.

<amx:listView var="row" showMoreStrategy="autoScroll" bufferStrategy="viewport"
                                              value="#{weatherBean.forecast}" id="lv1" >                                        
	<amx:listItem id="li1" >
		  <amx:tableLayout id="tl1" width="100%">
			<amx:rowLayout id="rl1">
			  <amx:cellFormat id="cf2" width="55%">
						<amx:panelGroupLayout id="pgl12" layout="vertical" halign="center">
							  <amx:outputText value="#{row.formattedUnixDate}" id="ot8" />
							  <amx:outputText value="Current: #{row.main.temp}" id="ot4" />
							  <amx:outputText value="Min.: #{row.main.temp_min}" id="ot7"  />
							  <amx:outputText value="Min.: #{row.main.temp_max}" id="ot6" />
							  <amx:outputText value="Humidity: #{row.main.humidity}" id="ot5" />
						 </amx:panelGroupLayout>
			  </amx:cellFormat>
			
			  <amx:cellFormat id="cf1" width="45%">
				<amx:panelGroupLayout id="pgl3" layout="vertical" halign="center">
							<amx:image id="i1" source="#{row.imageURL}"/>
							  <amx:outputText value="#{row.weatherOne.main}" id="ot12" />
							  <amx:outputText value="#{row.weatherOne.description}" id="ot15" />
				 </amx:panelGroupLayout>
			  </amx:cellFormat>
			</amx:rowLayout>
		  </amx:tableLayout>
	</amx:listItem>
</amx:listView>

Again, note that I coded the list view manually on the amx page. You can easily convert the AllForecast class to a data control and drop the forecast list to create an amx listview.

As an added bonus, I have added a drop down on the page to allow the user to select a city and display the associated weather forecast. You will have to find the appropriate city codes from the openweathermap site to add your cities to this page. You can find this in the attached sample code.

Starting with MAF 2.4, the AMPA library has been officially folded into the framework as “Client Data Model (CDM)”. You can use this api to call the rest service. This API has a jdeveloper wizard that helps you add the service and model the java classes. I find the JDeveloper wizard a bit too complex for use and makes some assumptions on your usecase. As of now, I still prefer the RestServiceAdapter approach for the flexibility it offers. I will try to document CDM in an upcoming blog.

DOWNLOAD

WeatherApp.zip
 

REFERENCES

Oracle MAF Developer Guide: Using Web Services in a MAF Application
OpenWeatherMap API: 5 day forecast API

vivek
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Gamal Badr

Thank you so much.This demo is not covered anywhere else on the net.