Wednesday, March 18, 2015

MAF 2.1 : Mobile Front End for Live Mobile Hacking with an OFM 12c red stack - Part I

During the closing keynote at the Oracle Fusion Middleware Forum XX in Budapest my former colleague Lucas Jellema and I delivered a live development demo. It was great fun to do. During this demo I developed an Oracle MAF frontend on top of Oracle SOA Suite 12c REST-JSON services. All in all it was a good session that was exactly delivered on-time with no errors. I think the demo gods where with us.
In this post I describe some of the details regarding the Oracle MAF part of this demo. To be more specific, how the MAF application is able to work with the REST-JSON Services. In a later post I will go into the details of how to user interface was built. The details of the SOA suite 12c Backend are described by Lucas at the AMIS Technology blog in the following posts:

Preparing ,

Implementing part - 1  and

Implementing part – 2



Before I start with the details I best describe the case first. We decided to create a tablet app that can be used by flight attendants to browse flight details, change the flight status (from boarding to boarding complete for instance), and browse the passenger list. Also, in case a customer wants to file a complaint for some reason, a flight attendant can do this using the app. For all these purposes the SOA Suite 12c backend exposes the necessary RESTJ-SON Services.



To create a MAF application that is able to consume these services I first need to create Connections to these services, next I need to transform the JSON Object to Java objects and finally I need to

Setting up Connections

In order to “talk” to the backend REST-JSON Services I need to connect to them by means of a REST connection. This can be easily created in JDeveloper. Simply create a connection that points to the URL endpoint of the REST-JSON Service.



I used SOAP-UI to mock the services. This helps a lot, especially because Lucas and I are not really connected all the time. This way he could work on the backend and I on the mobile front end.

The JSON Object
When I call the Service it returns a JSON Object that contains all information regarding a specific Flight. The JSON Object looks like the one below. This is the format and structure that Lucas and I agreed on.

1:  {  
2:    "FlightCode":  {  
3:     "CarrierCode": "KL",  
4:     "FlightNumber": "34"  
5:    },  
6:    "FlightStatus": "boarding",  
7:    "FlightDate": "2015-03-07T+01:00",  
8:    "Departure":  {  
9:     "Time": "2015-03-07T09:50:00+01:00",  
10:     "AirportCode": "AMS",  
11:     "AirportName": "Schiphol Airport",  
12:     "City": "Amsterdam",  
13:     "Country": "NL"  
14:    },  
15:    "Destination":  {  
16:     "Time": "2015-03-07T11:55:00-08:00",  
17:     "AirportCode": "SFO",  
18:     "AirportName": "San Francisco International Airport",  
19:     "City": "San Francisco",  
20:     "Country": "US"  
21:    }  
22:  }  

Working with the JSON Object

To work with the JSON Object in the MAF app, when data is read to or from REST services, some additional processing is required to translate the JSON Object (which in fact is a String) to and from Java objects. This proces is called (de-) serialisation.

All of the classes that I created to work with JSON in this MAF app are displayed in the following class Hierarchy. I will discus them bottom up and for clarity in the remaining part of this post I will explain how to work with the FlightDetail Object. That is the one in the middle (top to bottom) of the class hierarchy diagram.


The RestCallerUtil Class

The RestCallerUtil class wraps the MAF calls to the REST service, thus further simplifying the REST interaction, avoiding you to write unnecessary boilerplate code over and over again.
The RestCallerUtil class contains some utility methods such as invokeREAD() (line 12). This methode takes the RequestURI of the service call as an argument and then calls the invokeRequest() method. The InvokeRequest() method finally calls the REST Service (line 54), returning the response of the service call. The MAF Framework has a RestServiceAdapter class that facilitates calling REST Services. to call a REST Service, simply create an instance of that class, set the connection name to the connection that was created earlier (Line 35), set the Request type (Line 37), set the RequestURI (Line 43) and finally call the service (Line 54). The request URI identifies the actual resource that we need. This will be explained later.

The way the RestCallerUtil class is setup enables you to simply add more and more service calls by adding more and more of these simple methods. The method that actually invokes the service call does not change.

1:  package com.fhacust.app.mobile.utils;  
2:  import java.util.logging.Level;  
3:  import oracle.adfmf.dc.ws.rest.RestServiceAdapter;  
4:  import oracle.adfmf.framework.api.Model;  
5:  import oracle.adfmf.util.Utility;  
6:  import oracle.adfmf.util.logging.Trace;  
7:  public class RestCallerUtil {  
8:    public RestCallerUtil() {  
9:      super();  
10:    }  
11:    //GET  
12:    public String invokeREAD(String requestURI){  
13:      return this.invokeRestRequest(RestServiceAdapter.REQUEST_TYPE_GET, requestURI, "");  
14:    }  
15:    //POST  
16:    public String invokeUPDATE(String requestURI, String payload){  
17:      return this.invokeRestRequest(RestServiceAdapter.REQUEST_TYPE_POST, requestURI, payload);  
18:    }  
19:    public String invokeUPDATEcust(String requestURI, String payload){  
20:      return this.invokeRestRequestCust(RestServiceAdapter.REQUEST_TYPE_POST, requestURI, payload);  
21:    }  
22:    /**  
23:     * Method that handles the boilerplate code for obtaining and configuring a service   
24:     * adapter instance.   
25:     *   
26:     * @param httpMethod GET, POST, PUT, DELETE  
27:     * @param requestURI the URI to appends to the base REST URL. URIs are expected to start with "/"  
28:     * @return  
29:     */  
30:    private String invokeRestRequest(String httpMethod, String requestURI, String payload){  
31:      String restPayload = "";  
32:      RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();  
33:      restServiceAdapter.clearRequestProperties();  
34:      //set URL connection defined for this sample.   
35:      restServiceAdapter.setConnectionName("FlightsREST");  
36:      //set GET, POST, DELETE, PUT  
37:      restServiceAdapter.setRequestType(httpMethod);  
38:      //this sample uses JSON only. Thus the media type can be hard-coded in this class  
39:      //the content-type tells the server what format the incoming payload has  
40:      restServiceAdapter.addRequestProperty("Content-Type", "application/json");  
41:      //the accept header indicates the expected payload fromat to the server  
42:      restServiceAdapter.addRequestProperty("Accept", "application/json; charset=UTF-8");  
43:      restServiceAdapter.setRequestURI(requestURI);      
44:      restServiceAdapter.setRetryLimit(0);    
45:      Trace.log(Utility.ApplicationLogger,Level.INFO, this.getClass(),"invokeRestRequest", requestURI);  
46:      //variable holding the response  
47:      String response = "";  
48:      //set payload if there is payload passed with the request  
49:      if(payload != null){    
50:         //send with empty payload  
51:         restPayload = payload;  
52:      }  
53:      try {  
54:        response = (String) restServiceAdapter.send(restPayload);  
55:      } catch (Exception e) {  
56:        //log error  
57:        Trace.log("REST_JSON",Level.SEVERE, this.getClass(),"invokeRestRequest", e.getLocalizedMessage());  
58:      }  
59:      Trace.log(Utility.ApplicationLogger,Level.INFO, this.getClass(),"response= ", response);  
60:      return response;  
61:    }  


The FlightCustURI class contains methods that return the request URIs for a specific REST request like GetFlightsByNumber, PostFlightStatusUpdate and PostCustomerComplaint.
For each and every resource that we want to use, this class defines a static String holding the exact URI. In order to write this class it is necessary to know the URI's. Let's look at the REST Service call for FlightDetails, or more specific, the details of a given flight. That call looks like this :

http:///FlyingHigh/FlightService/FlightService/flights/KL34

The part in bold is the endpoint that was defined in the REST connection that was setup earlier. The part /flights/KL34  is the URI that we are looking for. The flightnumber, in this case KL34, is dynamic and changes if we need info for a different flight. Now based on this information we can create the FlightCustURI class and add the getFlightsByNumberURI method. This method takes the flightNumber as an argument and returns the exact URI that we need to call the service (Line 7).

1:  package com.fhacust.app.mobile.uri;  
2:  public class FlightCustURIs {  
3:    private static final String FLIGHTS_URI = "/flights";  
4:    private static final String CUSTOMER_URI = "/complaints";    
5:    //Flights URI  
6:    public static String GetAllFlightsURI(){ return FLIGHTS_URI; }  
7:    public static String GetFlightsByNumberURI(String flightNumber){ return FLIGHTS_URI+"/"+flightNumber;}  
8:    public static String GetPassengersinFlight(String flightNumber){ return FLIGHTS_URI+"/"+flightNumber+"/passengerlist";}  
9:    public static String PostFlightStatusUpdateURI(){ return FLIGHTS_URI;}  
10:    //customer URI  
11:    public static String PostCustomerComplaintURI(){return CUSTOMER_URI;}  
12:  }  

From JSON to Java

Now that the infrastructure for calling the REST-JSON Service is setup, I must make sure that I can work with the JSON Object in my MAF app. I use a couple of classes that help me to convert the JSON Object to my Java Objects. In this section I will explain how I get from the JSON Object for FlightDetails to my Java Object that represents the Flight Details. In this scenario I use the following 3 classes:
  • JsonObjectToFlightDetailsObject (Translates JSON String to Java Object)
  • FlightDetailsObject (Mimics the JSON payload for FlightDetail)
  • FlightDetailsEntity (Definition of FlightDetails Object)

All of these team up to take care of the JSON conversion.

1:  package com.fhacust.app.mobile.json.helper;  
2:  import com.fhacust.app.mobile.entities.FlightDetailsEntity;  
3:  import java.util.logging.Level;  
4:  import oracle.adfmf.framework.api.JSONBeanSerializationHelper;  
5:  import oracle.adfmf.util.logging.Trace;  
6:  public class JsonObjectToFlightDetailsObject {  
7:    public JsonObjectToFlightDetailsObject() {  
8:      super();  
9:    }  
10:    public static FlightDetailsEntity getFlightsObject(String jsonObjectAsString) {  
11:      FlightDetailsEntity flightsResult = null;  
12:      //object that serializes the JSON payload into the Java object  
13:      JSONBeanSerializationHelper jbsh = new JSONBeanSerializationHelper();  
14:      try {  
15:        flightsResult = (FlightDetailsEntity) jbsh.fromJSON(FlightDetailsEntity.class, jsonObjectAsString);  
16:      } catch (Exception e) {  
17:        Trace.log("JSONArray_to_JavaArray", Level.SEVERE, JsonObjectToFlightDetailsObject.class, "getFlightsObject",  
18:             "Parsing of REST response failed: " + e.getLocalizedMessage());  
19:      }  
20:      return flightsResult;  
21:    }  
22:  }  

The FlightDetailsObject is used to translate the JSON Object to the Java FlightDetailsEntity Object in one go. Note that this FlightDetailsObject  class is not really necessary in this specific case because the JSON Object is not an array of objects. However if the JSON object that is returned by the service call is an actual Array of objects, the FlightDetailsObject can be used to work with the array. For our current example it now has only setters and getters for the flightDetails object of type FlightDetailsEntity. In case of an array I would change that to FlightDetailsEntity[].

1:  package com.fhacust.app.mobile.json.dao;  
2:  import com.fhacust.app.mobile.entities.FlightDetailsEntity;  
3:  public class FlightDetailsObject {  
4:    /**  
5:     * class that mimics the structure of the JSON payload for the flights request  
6:     *  
7:     */  
8:    private FlightDetailsEntity flightDetails = null;  
9:    public FlightDetailsObject() {  
10:      super();  
11:    }  
12:    public void setFlightDetails(FlightDetailsEntity flightDetails) {  
13:      this.flightDetails = flightDetails;  
14:    }  
15:    public FlightDetailsEntity getFlightDetails() {  
16:      return flightDetails;  
17:    }  
18:  }  


Finally I use class called FlightDetailsEntity which is the detailed implementation of the FlightDetails object with all the necessary getters and setters.

1:  package com.fhacust.app.mobile.entities;  
2:  import oracle.adfmf.java.beans.PropertyChangeListener;  
3:  import oracle.adfmf.java.beans.PropertyChangeSupport;  
4:  public class FlightDetailsEntity {  
5:    private FlightCodeEntity flightcode;  
6:    private String flightDate;  
7:    private String flightStatus;  
8:    private SlotEntity departure;  
9:    private SlotEntity destination;  
10:    public FlightDetailsEntity() {  
11:      super();  
12:    }  
13:    here go getters and setters  

And the FlightCodeEntity class that is used to hold FlightNumber and CarrierCode.

1:  package com.fhacust.app.mobile.entities;  
2:  import oracle.adfmf.java.beans.PropertyChangeListener;  
3:  import oracle.adfmf.java.beans.PropertyChangeSupport;  
4:  public class FlightCodeEntity {  
5:    private String CarrierCode;  
6:    private String FlightNumber;  
7:    public FlightCodeEntity() {  
8:      super();  
9:    }  
10:   here go getters and setters  

Note that I created two other groups of these classes (FlightPassengerList and the CustomerComplaint) that enable me to work with these REST-JSON services as well.

Calling the REST Services from the MAF app

So far I created everything that I need in order to call the REST-JSON Service and to deserialise the result of that service call. While preparing this demo Lucas and I decided to actually have 4 services that the MAF UI needs. These are the following services.
  • findFlightByNumber
  • getFlightPassengers
  • updateFlightStatus
  • createCustomerComplaint
In order to actually call these services, using all the infrastructure code that was created in the first part of this post, I now create a JavaClass that I call FlightPassengersDC. This class holds all the methods for all of the service calls that I need. Because I only discuss the flightDetails part in this post, I will not post all the code here. It can be downloaded from GIT (link is provided at the bottom of this post) but I will show the part up till findFlightByNumber().

All that I need to call the service is a simple method ( getFlightDetails() ) uses the RequestURI (Line 34) to get the correct URI,  the RestCallerUtil (Line 36) to call the service and finally the JsonObjectToFlightDetailsObject.getFlightsObject() (Line 37) to deserialise the result to Java Objects.


1:  ackage com.fhacust.app.mobile.datacontrol;  
2:  import com.fhacust.app.mobile.entities.CustomerComplaintEntity;  
3:  import com.fhacust.app.mobile.entities.DepartureDestinationEntity;  
4:  import com.fhacust.app.mobile.entities.FlightCodeEntity;  
5:  import com.fhacust.app.mobile.entities.FlightDetailsEntity;  
6:  import com.fhacust.app.mobile.entities.FlightPassengerListEntity;  
7:  import com.fhacust.app.mobile.entities.PassengerEntity;  
8:  import com.fhacust.app.mobile.json.helper.CustomerComplaintToJson;  
9:  import com.fhacust.app.mobile.json.helper.FlightDetailsEntityToJson;  
10:  import com.fhacust.app.mobile.json.helper.JsonObjectToFlightDetailsObject;  
11:  import com.fhacust.app.mobile.json.helper.JsonObjectToFlightPassengerListObject;  
12:  import com.fhacust.app.mobile.uri.FlightCustURIs;  
13:  import com.fhacust.app.mobile.utils.RestCallerUtil;  
14:  import oracle.adfmf.java.beans.ProviderChangeListener;  
15:  import oracle.adfmf.java.beans.ProviderChangeSupport;  
16:  public class FlightPassengersDC {  
17:    String flightNumber ="KL34";  
18:    //Details for a selected flight.   
19:    private FlightDetailsEntity flightDetails = null;  
20:    //all Passengerrs. We fecth the passnegers only once as this is not expected to  
21:    //change in the context of the flight  
22:    private FlightPassengerListEntity flightPassengers = null;  
23:    public FlightPassengersDC() {  
24:      super();  
25:    }   
26:    public void findFlightByNumber(String flightNr){  
27:      if (flightNr!=null&&flightNr!=""){  
28:        this.flightNumber=flightNr;  
29:      }  
30:      getFlightDetails();  
31:    }  
32:    public FlightDetailsEntity getFlightDetails() {  
33:      if(flightDetails == null){   
34:        String restURI = FlightCustURIs.GetFlightsByNumberURI(this.flightNumber);  
35:        RestCallerUtil rcu = new RestCallerUtil();  
36:        String jsonObjectAsString = rcu.invokeREAD(restURI);  
37:        FlightDetailsEntity flights = JsonObjectToFlightDetailsObject.getFlightsObject(jsonObjectAsString);  
38:        flightDetails = flights;  
39:      }  
40:      return flightDetails;  
41:    }  
42:  more here below .........  


That is all. Now I can call the services and translate the payload to and from Java Objects. It is time to expose the data to the UI. This is done by using a Data control.

From Rest Service to MAF UI: The datacontrol

The datacontrol can be created from the FlightPassengersDC Class. Data controls in MAF expose the service data model to the mobile client developer. I can create a datacontrol based on this class, and the datacontrol will expose all the methods from the class as operations, and all .

  


From here on it is easy to create AMX pages that display flightDetails and Passengers. The easiest way is to simply drag and drop from the datacontrol on to an AMX page. The code in GIT shows the end result of you would do it like this. For the live demo that was all that time permitted.

In a next post I will describe how I created a more fancy User Interface.

Resources


The code for the MAF app can be downloaded from GIT.

No comments: