6 Creating the Client Data Model in a MAF Application

This chapter describes how to create data and service objects in the client data model of your MAF application by retrieving resources from REST services.

This chapter includes the following sections:

6.1 Introduction to the Client Data Model in a MAF Application

MAF uses the REST Service Editor to provide design-time support to connect your MAF application to REST services from where you can expose data objects. You can then create a client data model based upon these data objects within your application. In addition to retrieving data, MAF assists you in determining what data you persist on the MAF application when it is in offline mode.

A MAF application's client data model contains Java classes and associated files to represent the data model of a MAF application. MAF uses a SQLite database to store data for offline usage, and two types of Java class: data objects (also known as entity objects) and service objects (also known as entity CRUD service objects) to interact with the data. Data objects hold the data that you retrieve from the REST service(s) that your MAF application connects to. The MAF application stores and retrieves data objects in a SQLite database on the device. Service objects perform create, read, update, and delete (CRUD) actions plus other custom actions that operate on the data objects. The MAF client data model uses the persistence-mapping.xml file to store the object-relation mapping information that identifies how database tables and columns map to data objects and data objects' attributes plus how data objects and data objects' attributes map to attributes in the REST response payloads.

Figure 6-1 illustrates the runtime architecture of the MAF client data model by reference to a specific implementation in a MAF application that reads and writes department information from a REST service.

Figure 6-1 MAF Client Data Model Runtime Architecture

The surrounding text describes the image

In Figure 6-1, the service object (DepartmentService) provides the CRUD actions plus other custom actions that operate on the Department data object.

The Department data object has getter and setter methods for the department attributes (name, ID, and so on) that map to the corresponding attributes in the REST service request and response payloads. The DEPARTMENTS table in the SQLite database has columns that map to the same data object attributes. The persistence-mapping.xml file stores the information that maps the relationship between the attributes in the various locations (database table columns, Java class attributes and REST payload).

You use the REST Service Editor to create a profile for the client data model in your MAF application. See Overview of Creating a Client Data Model in a MAF Application. Once you have created the profile, you can generate it to use in the MAF application.

6.2 Overview of Creating a Client Data Model in a MAF Application

OEPE provides the REST Service Editor to generate a client data model (with all the required artefacts) for your MAF application.

Using the editor, you can create a REST service description to connect to a generic REST service or to REST services hosted on Oracle Mobile Cloud Service (MCS). Once you connect, you perform tasks to identify and retrieve the data you want to use in your application. These tasks are:

  1. Discover the REST APIs and data objects that are candidates for use in your MAF application. MAF supports the discovery of REST APIs and data objects from the following resources:

    1. REST resource URLs

    2. RAML files from Oracle Mobile Cloud Service or on the local file system

  2. Having discovered the candidate data objects for use in your MAF application, you define the REST API resources and data objects that you want to use. The REST Service Editor also provides options to create new data objects.

  3. You can inspect and modify data object attributes. Tasks you can perform include edit the attribute name, the name that appears in the REST service payload, the Java type and the database column type for each attribute in addition to choosing not to persist sensitive data on the device. You also select a key attribute. It is important that the key attribute you select be unique.

  4. Specifying parent-child relationships for data objects.

  5. Define the REST resources and associated HTTP methods to use for CRUD actions plus specify resource details such as the query and path parameters.

  6. Once you have identified the REST resources to use for CRUD actions you set the runtime behavior of your MAF application by, for example, enabling offline transactions, enabling remote read and write in the background, or showing web service errors.

Once you complete creating the CDM profile, you can generate the client data model artifacts to use in your MAF application.

6.3 Connecting to a REST Service to Create the Client Data Model

Connect to the REST service to identify the data object resources that you want to retrieve for use in the client data model of your MAF application.

You connect to the REST service (whether a generic service, or a service hosted on MCS) from the REST API page of the REST Service Editor. See:

This task is one in a series of tasks to generate a client data model in your MAF application. See Overview of Creating a Client Data Model in a MAF Application.

6.3.1 How to Connect to the REST Service to Retrieve Data Objects

Use the REST Client page of the REST Service Editor to connect to the REST resources and identify candidate data objects that you select for inclusion in the client data model.

To create a connection to a REST service:
  1. Create a MAF application. See How to Create a MAF Application.
  2. Create a REST service description making sure that it is created in the MAF application. See How to Create a REST Service Description.
  3. In the REST Client page of the editor, in the Resource area define a connection for the URL endpoint for the REST service that you want to connect to. See Specifying REST Service Connections.
  4. Enter the URI as the Address , then click Use connection and in the Manage Connection dialog, give the connection a name, as shown in Figure 6-2.
    For your convenience, specify the part of the URL endpoint that is the same for all REST resources that you want your MAF application to consume. This reduces the amount of typing that you have to do on subsequent pages of the wizard. One exception is when you specify the URL endpoint to an instance of MCS. In this latter case, the URL endpoint should end in /mobile. This allows the MAF application to use the connection for both custom API calls and MCS platform API calls.
    The connection is written into connections.xml in the assembly project under adf/META-INF.

    Note:

    Clicking the Test the URI button does not work here as you specify an incomplete URL for the URL endpoint, as illustrated in Manage Connection Dialog. Make sure that the URL endpoint does not end with a forward slash (the wizard checks this before it allows you to click Next).
  5. If the REST resources you access are secured, enter the authentication information in the appropriate pages of the wizard.
Once you connect to the REST service, you can discover the data objects in the REST service to retrieve and use in your MAF application’s client data model.

6.3.2 What You May Need to Know About the MCS Anonymous Access Key

MAF uses the MCS anonymous access key value to create the authorization header when the MAF application accesses MCS before the application has been authenticated with MCS. This is useful if, for example, you want to send a startSession MCS analytics event to MCS before your user logs in.

The access key value that you use does not have to be the anonymous key. You can use the authorization key of an MCS user defined in the user realm of your MCS mobile backend. Do this if you want to, for example, access MCS storage collections or other resources that are not accessible to anonymous users.

You do not need to prefix the access key with Basic. MAF adds or removes the Basic prefix as needed. If your MAF application needs to support dynamic MCS connections, you can specify an EL expression in the MCS Mobile Backend ID and MCS Anonymous Access Key fields. After you authenticate against MCS, MAF automatically injects the authorization header into every REST call based on the user’s login credentials. That is, MAF ignores the MCS anonymous access key value in the input field once the MAF application has been authenticated.

6.4 Discovering Candidate Data Objects for the Client Data Model

Identify the data objects that are candidates to use in the client data model of your MAF application after you connect to the REST service.

The REST Service Editor allows you to discover data objects to use in your MAF application from:

This task is one in a series of tasks to generate a client data model in your MAF application. See Overview of Creating a Client Data Model in a MAF Application.

6.4.1 How to Discover Resources and Data Objects Using a REST Resource URL

Import the REST API and its data object using the REST Service Editor.

Use the REST Client tab of the editor to import resources which MAF parses into data objects. Figure 6-3 shows the REST Client page of the REST Service Editor where you query the REST resources. For more information about using the REST Client page to import REST APIs, see Using the REST Client.

Figure 6-3 REST Request

This is described in the surrounding text

Leave the Method as GET and create the address to the REST resource URL by an appropriate query, adding to the connection you defined. For example:

  • /query/Department.findAll returns all departments

  • /entity/Department/{id}/employeeList1 returns the employees for a department, where the department is identified by the id

  • /entity/Department/{id}/employee1 returns the manager for a department, where the department is identified by the id

  • /entity/Employee/{id}/departmentList returns the departments that the employee manages, where the employee is identified by the id

Set the request header for JSON response. In the Request Details area, click Add and enter the header name and value of Accept=application/json. Then click Send Request.

If the request uses a variable, such as {id} in the examples above, a Replace Variables dialog appears. Enter a value for the variable, for example 10 for Departments.

The JSON payload is returned, and you can view it in the Rendered Content tab of the Response area so that you can examine it and ensure that it is what you expect.

Now you can click Import the REST Client information to import the REST client information.

In the Import REST Client Information dialog you can choose to import just the REST API, select Request, or the data types the API uses, select Data Type, or both, as shown in Figure 6-4.

Figure 6-4 Import REST API and Data Types

This is described in the surrounding text

Click Next, and in the Import Request page give the request a name, for example getDepartments and click Next. The Import Data Type page of the wizard displays the data types that will be imported and when you click Finish the editor shows the REST API tab with the request you imported, and the imported data types are displayed on the Data Types tab of the editor.

Return to the REST Client tab and repeat the process for the additional requests you want to use.

Figure 6-4 shows the REST API tab of the editor after a number of requests have been imported.

Figure 6-5 Requests in the REST API Tab

described in the surrounding text

Figure 6-6 shows the data types imported by the requests in the Data Types tab of the editor.

Figure 6-6 Data Types in the Editor

described in the surrounding text

6.4.2 How to Discover Data Objects Using a RAML File

Discover REST APIs and data types from Oracle Mobile Cloud Service or a RAML file.

MAF suggests data objects, parent-child relationships and CRUD resources based on the content in the RAML file. You can import RAML definition from:

  • A Mobile Cloud Service backend's API

  • A RAML file.

To import a REST API from MCS or from a local RAML file:

For Oracle Mobile Cloud Service, you must have a connection to MCS. For more information, see About Integrating Oracle Cloud Services
  1. On the REST API tab of the REST Service Editor, click import to open the Import REST API or Data type dialog, shown in Figure 6-7.

    Figure 6-7 Importing RAML Definitions

    This image is described in the surrounding text
  2. Choose the source of the RAML definition: Mobile Backend or RAML File .
  3. Click Next and complete the wizard.
    • Mobile Backend

      • On the Backend information page you choose the backend that is associated with the API. This page also shows whether Anonymous access is supported if basic authentication is enabled.

      • If you have selected the API from the API node, choose the backend from the list of those associated with the API.

      • If you chose the API from a specific backend then that backend is displayed and you cannot change it.

    • RAML File. Choose the file containing the RAML definition from an Eclipse workspace or the file system.

  4. The RAML Preview page of the wizard allows you to choose the options to use during import of the REST API. These include:
    • The method to use if there are conflicts when you are importing a RAML definition to an existing REST Service description file.

      • Merge: Use when the existing methods should be merged with new methods from RAML definition.

      • Replace: Use when the new method should replace the existing methods.

      • Create: Use when a new method should be created with new name in case of conflicts with existing methods.

    • The resources and methods to import. The wizard shows a tree created from the information in the RAML definition in the API. By default, all resources and methods are selected. Deselect those that you do not want to import.

    • You can launch the connection wizard if you want to edit the Base URI and store the information in connections.xml. The connection wizard will be opened when you click Finish.

      The connection wizard will be populated with data fetched from the MCS API, and the connection defined in the wizard is used to create the root path of the REST Service description.

6.5 Editing Data Objects for the Client Data Model

Work with the data objects for the CDM profile.

CDM profiles in the REST Service Editor discover relationships, and for this to happen you need to make changes to the attributes on the data types. You can represent relationships between data objects by adding a new (Complex Type) attribute. The changes you make here will allow the relationships to be discovered in the Artifact Profiles page of the editor. After creating the artifact profile you may find that you need to return and continue to edit the data objects so that the relationships are discovered correctly.

Another way of specifying relationships is to add new attributes and unique index references on the data types to indicate the primary key.

This task is one in a series of tasks to generate a client data model in your MAF application. See Overview of Creating a Client Data Model in a MAF Application.

6.5.1 How to Create New Data Objects

Create data objects that only live on the mobile device and are not populated through the REST web services that your MAF application connects to.

In the Data Types page of the REST Service Editor, right-click Local Data Types and choose either Complex Type or Simple Type. Complex data types can have attributes and unique indexes. Simple data types just have a name. Name the new data type with a unique valid Java class.

MAF creates a database table in the SQLite database for each data object that you add. You can populate these database tables with data using the CRUD operations from the service object that MAF generates for the data object.

6.5.2 How to Modify Data Object Attributes

Select or modify attributes of the data objects that you have selected for inclusion in the client data model of the MAF application.

In the Data Types page of the REST Service Editor, shown in Figure 6-8, you can set or change the unique index reference used to allow the profile to understand relationships, the attribute name, data type, multiple, and whether to exclude from representation in the profile..

The runtime attributes values for persisted, required, Java name, Java type, and database type, are set in the artifact profile. See Creating the Client Data Model Artifact Profile.

It is important that the key attribute(s) be unique. The persistence runtime uses a data object cache based on the selected key attribute. If you have multiple data object instances with the same key, they will all be written to the same database table row.

Figure 6-8 Attribute Values Set in the Data Types Page

The surrounding text describes the image.

6.6 Making Relationships Discoverable

Understand how to set up parent-child relationships in the Client Data Model Profile.

CDM profiles in the REST Service Editor discover relationships from the data objects and their attributes and for this to happen you need to make changes to the attributes in the Data Type page of the editor, for example, to add new attributes and unique index references so that the parent-child relationships can be discovered in the Artifact Profiles page. As you work with the artifact profile you will need to return and continue to edit the data objects so that the relationships are discovered correctly. This section uses the example of a MAP application that creates a client data model to an HR REST service.

There are two ways that relationships can be discovered:

6.6.1 Relationships Using Web Service Calls

Understand how web service calls are used to retrieve data objects referenced by the relationships.

CDM profiles in the REST Service Editor discover relationships from the data objects and their attributes and for this to happen you need to make changes to the attributes in the Data Type page of the editor, for example, to add new attributes and unique index references so that the parent-child relationships can be discovered in the Artifact Profiles page. This section takes you through the steps involved to use web service calls to implement the relationships using the example of an HR REST service.

Payload

Figure 6-9 is an example of the JSON payload returned from a REST service using the URL http://<IP Address>:7101/HRRest/persistence/v1.0/Model-1/query/Department.findAll. The JSON payload contains information about how to access the sub objects through other web service calls, but the sub objects are not actually included in the payload, there is just link information to them. For example, http://<IP Address>:7101/HRRest/persistence/v1.0/Model-1/entity/Department/10/employee1 is the link to the manager of the department, and http://<IP Address>:7101/HRRest/persistence/v1.0/Model-1/entity/Department/10/employeeList1 is the link to the department's employees.

Figure 6-9 JSON Payload

described in the surrounding text

Data Types

Using the example of an HR REST service, you import data types by:

  • In the REST Client pate of the editor, creating a connection to http://<IP Address>:7101/HRRest/persistence/v1.0/Model-1 and executing the connection connection://HRRestConnection/query/Department.findAll.

  • Importing the REST Client information and naming the request getDepartments.

  • Then executing connection://HRRestConnection/entity/Department/{id}/employeeList1, substituting in 10 for {id} and importing the REST client information, and naming the request getEmployeesForDepartment

At this point, the Data Types page looks like Figure 6-10.

Figure 6-10 Imported Data Types in Data Types Page

described in the surrounding text

Only the department (DepartmentfindAll) and employee (EmployeeList) Data Types are going to be used in the profile and some attributes have to be added so that OEPE can find the relationships.

  • First, rename DepartmentfindAll to Departmentand EmployeeList to Employee.
  • To Department, add an employees attribute with multiple employees.  Right-click Department and choose New > Attribute.

    • Call the new attribute employees.

    • Select Multiple because the attribute has multiple employees.

    • Select Exclude from representations because this data is not part of the actual payload.

  • To Department, add an manager attribute, but this time do not check Multiple .  Also note that DepartmentfindAll has been renamed to Department and EmployeeList to Employee.

  • Create unique indices for the data types. In the example of the HR REST service:

    • For Department, use departmentId

    • For Employee, use employeeId

Relationships

For information about creating the profile in the Artifact Profiles page of the REST Service Editor, see Creating the Client Data Model Artifact Profile.

In the Artifact Profiles page:

  • Remove the unwanted data types. Select them and click Clear

    .
  • Click create relationships.  In the Choose Types dialog, leave both types selected and click OK.  This discovers the relationships based on the work done in the Data Types page by creating the new attributes, employees and manager.

There are two relationships between Department and Employee. First, the changes needed for the relationship between department and employees.

  1. Expand the profile node and the Client Data Model node and select Relationships

  2. Click create relationships.

  3. In the Choose Types dialog, specify the types to consider. In the example of the HR REST service, leave both types selected and click OK to display the relationships based on the changes already made to the data types employees and manager.

Return to the Data Types page of the editor, as shown in Figure 6-11, and add an attribute to Employee called departmentDepartmentId with the same data type as Department's departmentId attribute, json.Number. Select Exclude from representations.

Return to the Artifact Profiles page, click the relationship that has the Role name A -> B of employees.  Rename this relationship to a suitable name, such as department -> employees .  For the Foreign key in the Type B section, select departmentDepartmentId.

Figure 6-11 Defining the Types in the Relationship

described in the surrounding text

Define the accessors in the Runtime section of the Relationship panel on the Artifact Profiles page, as shown in Figure 6-12.

Figure 6-12 Runtime Options for Relationship

described in the surrounding text

In Type B resource, choose getEmployeesforDepartment from the list to be the resource for Type B data.  In this example, Type B is Employee.  In order to get the employees , CDM needs to make a web service call.  getEmployeesForDepartment is the name in the example to the request for importing the data from executing connection://HRRestConnection/entity/Department/10/employeeList1.  Because there is a variable in this request, a value provider has to be specified.  Double-click on the value provider and select departmentId as the value.

The relationship department -> employees has been defined. Now the relationship between department and manager has to be defined:

  • In the Data Types page of the REST Service Editor, create an employeeEmployeeId attribute in the Department data type.  It should have the same data types as the Employee's employeeId attribute, that is json.Number.  Select Exclude from representation

  • In the Artifact Profile page, click on the second relationship between department and manager. In the Relationship panel, as shown in Figure 6-13:

    Figure 6-13 Defining the Second Relationship

    described in the surrounding text
    • In order to have Employee as Type A, click the Reverse Cardinality button.

    • The primary key of Type A is employeeId.  For the Type B foreign key, select employeeEmployeeId.

    • Rename the Role name A->B to managedDepartments.

    • You can also rename the relationship to a suitable name, for example department -> manager.

In the REST API page of the REST Service Editor, create a GET request called getManagedDepartments to call the /entity/Employee/{id}/departmentList web service:

  • Under the entity path, create the Employee,  {id} and departmentList paths.  Set up the Output Representation to be multiple Departments and set the HTTP Header with the Content-Type set to Accept=application/json.

Figure 6-14 Defining an Accessor

described in the surrounding text

Return to the Artifact Profile page to the manager relationship.  Select getManagerForDepartment for the Type A resource (Employee) in the Runtime section, as shown in Figure 6-15.  Set up the value provider by double-clicking on it and selecting departmentId for the value.  Finally, select getManagedDepartments for the Type B data Resource and set up the value provider with the employeeId.

Figure 6-15 Establishing the

described in the surrounding text

6.6.2 Relationships in the JSON Payload

Understand how to define relationships for JSON payloads that contain subobjects within the payload.

CDM profiles in the REST Service Editor discover relationships, and for this to happen you need to make changes to the attributes on the data types. You can represent relationships between data objects by adding a new (Complex Type) attribute. The changes you make here will allow the relationships to be discovered in the Artifact Profiles page of the editor. After creating the artifact profile you may find that you need to return and continue to edit the data objects so that the relationships are discovered correctly.

Another way of specifying relationships is to add new attributes and unique index references on the data types to indicate the primary key.

This section takes you through the steps involved to implement the relationships in the JSON payload, using the example of OEPE's oracle.eclipse.tools.testserver2.Server2, found in annex/maf-offline/plugins/oracle.eclipse.tools.test.rest.cdm/src.server2, started on port 4646.

Payload

For http://<IP Address>:4646/departments, the web service returns a JSON payload like the one shown in Figure 6-16.

employees and manager are contained within the JSON payload of departments.

Figure 6-16 JSON Payload Using Subobjects

described in the surrounding text

Data Types

In this example, on the REST Client page of the REST Service Editor, create a create a connection for http://<IP Address>:4646.  After executing connection://OEPE4646/departments, import the REST Client information.  Then execute connection://OEPE4646/employees and import the REST Client information.

The Data Types page looks like Figure 6-17.

  • Rename Departments to Department and Employees to Employee.

  • Create unique indices on the Department and Employee data types.

Figure 6-17 Data Types from JSON Payload

described in the surrounding text

Relationships

Once the data types have been set up, the next step is to create the relationships:

  • In the Artifact Profiles tab of the REST Service Editor and create a new Client Data Model profile.

  • Expand the nodes and select Data Types.

  • Select Department and select Persisted.

  • Select Employee and select Persisted.

  • Select the Relationships node and in the details panel click create relationships. In the Choose Types dialog, leave both types selected andclick OK. This discovers the employees and manager relationships between the Department and Employee.

  • Click on the employees relationship and give it a suitable name, for example department -> employees .    For the Foreign key in the Type B section , select departmentId.  The Role name B -> A has also been renamed to "myDepartment."

  • For the Foreign key in the Type B section , select departmentId.

  • Rename the Role name B -> A, for example, to myDepartment.

Figure 6-18 JSON Payload Relationships

described in the surrounding text

To set up the rest of the accessor information, go to the Runtime section.  To specify how to get the Type B data (the Employees), select Included in Type A payloadin the Type B data section.  The Parent data type attribute is employees as shown in the payload.

Figure 6-19 Runtime

described in the surrounding text

Now, set up the relationship between the Department and its manager by setting up a way for the runtime to link them.  In the Data Types page of the REST Service Editor, create a managerId in the Department data type.  It should have the same date type as the Employee's id attribute, that is, json.String.  Select Exclude from representations.

In the Artifact Profile page of the editor, click on the manager relationship.  In order to have Employee as Type A, click on the Reverse Cardinality button.  For the Type B Foreign Key, select managerId.  You can also rename the relationship manager -> departmentor another suitable name. Also, rename the Role name A -> B to managedDepartment.

Figure 6-20 Defining the Types in the Relationship

described in the surrounding text

Now specify the runtime information by defining how to get the Employee that manages the department by executing connection://OEPE4646/employees/{empId} and importing the REST Client information. Alternatively, create the data manually. The REST API page looks like Figure 6-21.

Figure 6-21 REST API Page from JSON Payload

described in the surrounding text

Go to the Artifact Profiles page of the editor. In the Runtime section for the manager relationship, select getEmployeeById for the Type A resource.  Because getEmployeeById has a variable, set up the value provider.  Double click the default value provider and select managerId for the value. The runtime section looks like Figure 6-22.

Figure 6-22 Runtime section of Profile

described in the surrounding text

6.7 Creating the Client Data Model Artifact Profile

The artifact profile defines how the REST API and data types are used to generate the CDM artifacts used in the MAF application.

After you have imported the REST API and data types, defined the resources, and established the relationships between data objects, you create the CDM profile.

For information about relationships, see Making Relationships Discoverable.

In the Artifact Profiles page of the REST Service Editor, shown in Figure 6-23, right-click Profiles and choose New. A new client data model profile is created with the default name CDMProfile1 and using the data objects you have been working with. Change the name to one suitable for your application.

Figure 6-23 Artifact Profiles in the REST Service Editor

described in the surrounding text

6.7.1 Defining CRUD REST Resources

Specify CRUD actions to enable the MAF application end user to edit the data objects that the MAF application retrieves from the REST services it connects to.

This task is one in a series of tasks to generate a client data model in your MAF application. For more information, see Overview of Creating a Client Data Model in a MAF Application.

You define REST requests and runtime options for the CRUD operations in the Artifact Profile page of the REST Service Editor .Figure 6-24 shows the panel where these are specified.

Figure 6-24 Defining the CRUD REST Resources for the Client Data Model

The surrounding text describes the image.

The REST Service Editor populates the Artifact Profiles page of the editor from the content of the REST API page and the Data Types page. The types of resource are:

  • A POST resource creates a resource

  • A PUT or PATCH resource updates or merge a resource

  • A DELETE resource to delete a resource

Select the CRUD action then click browse next to Update Resource. Choose the data object from the list. The actions available are:

  • Canonical Resource Request, which is useful in a situation where you have a data object with many attributes, and you only want to bring in all the attributes once a specific data object instance has been specified. If you specify the Canonical Trigger Attribute, MAF generates code in the data object class that automatically invokes the canonical REST resource when the value of the attribute is retrieved through the getter method. If you specify the Canonical Trigger Attribute, MAF generates code in the data object class that automatically invokes the canonical REST resource when the value of the attribute is retrieved through the getter method.

    For example, if a Department data object’s Find All Resource returns a list of department IDs and names to display on a list page and you select a department to go to the detail page which shows all department attributes, then we can set the Canonical Trigger Attribute to locationId, the getLocationId method invokes when you navigate to the detail page. MAF has generated code inside the getLocationId method to automatically invoke the canonical REST resource.

    Note:

    If you set the Canonical Resource Request with its canonical trigger attribute, then there is an issue when you generate code. The generated Java class for the Data Object will have a call to EntityUtils, but will not have the import for this class. Use the Organize Imports feature (part of the Save actions for the Java Editor in the Preferences dialog) to add the missing import.

  • Create Resource Request, used to produce an instance where the data object state is new. If this is not specified, the merge resource request is used instead.

  • Delete Resource Request, used to delete an existing instance.

  • Find All Request which returns all instances.

    If you used sample resource URLs to discover your data objects, the Find All Resource defaults to the sample resource. While the defaults are usually accurate you might need to change the values of the resource and/or HTTP method if your REST services do not follow best practice.

    If you select the Delete Local Rows checkbox and the GET resource is used as Find All resource, then all rows in the table created for the corresponding data object are deleted after the REST call is made and before the REST response is processed. This is useful to ensure that any obsolete rows that are no longer included in the GET response payload do not remain visible in the application just because that data was downloaded before. If you select the Delete Local Rows checkbox and the GET resource was defined in as a relationship to retrieve child data objects, then all those child rows will be removed just before the REST call is executed.

  • Merge Resource Request which updates an existing instance.

  • Quick Search Request Useful for large data sets. For a large data set you typically do not want to execute a Find All Resource that returns all instances as this may cause your application to run out of memory. In such a situation, define a quick search facility in the user interface that returns only the instances that match the search criterion. With a smaller data set, use the Find All Resource to return all instances at once. Execute a quick search filter directly against the on-device SQLite database. Performance is faster as no web service is invoked.

  • Update Resource Request used to update an eixisting instance. If this is not specified, the merge resource request is used.

The service object, generated for each data object that has at least one REST resource specified, includes a save[DataObjectName] method. When you invoke the save[DataObjectName] method MAF decides based on the data object state which resource to call: Create, Update, or Merge. MAF makes its decision as follows:

  • If the data object state is new, MAF calls Create resource. If the Create resource is not specified, MAF calls the Merge resource. If neither resource is specified, the MAF application will not make a REST call.

  • If the data object state is not new, MAF calls the Merge resource. If Merge resource is not specified, MAF calls the Update resource. If neither resource is specified, the MAF application will not make a REST call. The data object state is automatically set to New when you create a new data object through the data control Create operation. If you programmatically create a new data object instance using a subclass of oracle.maf.api.cdm.persistence.model.Entity, call setIsNewEntity(true).

6.7.2 How to Specify Query and Path Parameters

Choose how query and path parameters specified for CRUD operations or relationships in a CDM profile are specified at runtime.

Figure 6-25 Specifying Query and Path Parameters

The surrounding text describes the image.

When a MAF application executes the REST resource, it automatically populates the resource query and path parameter values based on the Value Provider you choose:

  • DataObjectAttribute: Populates the parameter with the value of a data object attribute. When using this value provider, you need to choose a value from the Data Object Attribute drop-down list. The data object whose attributes are displayed in the drop-down list are determined by the usage of the resource. For example, the above resource returns the employees within a department, so it is assumed you want to select an attribute from the department data object to set the context for the employees list.

  • LiteralValue: Populates the parameter with a literal value specified in the Value column.

  • ELExpression: Populates the parameter with a value obtained by evaluating an EL Expression. You specify the EL Expression in the Value column. You can use EL Expressions with any scope (applicationScope, pageFlowScope, viewScope, deviceScope, preferenceScope) but it is your own responsibility that the expression is valid in the execution context of the REST service call. Remember that when making transactions in offline mode, the REST service will be invoked later and the EL expression context will then be determined by the task flow and page that triggers the data synchronization.

  • SearchValue: Populates the parameter with the value of the quick search value entered in the user interface. You will typically use this value provider only with the Quick Search Resource.

Note:

When you use a query parameter with a literal value (rather than a variable) for a method then the query parameter is not generated in persistence-mapping.xml when that method is used in a profile. For example, if you have a query parameter for a particular method such as technician=~ and this method is used in the profile, the method in persistence-mapping.xml is not generated. Instead, you should use the query parameter technician={techId} where techId is a variable name. When the method is used in th eprofile, you will see a variable value provider from the CRUD Operations node of the profile and you can set up a LITERAL_VALUE value provider with a value of ~.

6.7.3 How to Add Custom Resources

A custom resource is a REST call you make that does not map to a find, create, update or delete action that you specified on the CRUD Resources page.

You can add a custom resource in the Artifact Profiles page of the REST Service Editor. Right-click Custom Operations and choose New > Custom Operation.

Figure 6-26 Adding a Custom Resource

The surrounding text describes the image

In the service class that MAF generates for the data object, a method will be added with the name you specified in the Name field. Calling this method invokes the REST resource. Custom methods are also included in the data synchronization mechanism of the MAF client data model, so, if you call the custom method in offline mode, it will be registered as a pending synchronization action and the REST call will be executed when the MAF application is online again.

6.7.4 Setting Attribute Values in the Profile

Run-time values for attributes are set in the artifact profile of the REST Service Editor.

In the Artifact Profile page , shown in Figure 6-27, you can set attribute values used by the profile, required, name in payload, Java type and database column type of each attribute as desired. For an attribute carrying sensitive data you can choose to not persist it, which causes the attribute value to be null when the application runs in offline mode.

When using sample resources to identify the candidate data objects, you usually need to modify the Java type for the attributes as they typically show up as java.lang.String. Some attributes may show up as java.math.BigDecimal when the payload value is not enclosed in double quotes. You can change the Java types using the class picker, and the database column type automatically updates based on the Java type you select. Typical type changes include changing numeric attributes from String or BigDecimal to Long or Integer.

If you used a RAML file to discover the data objects, the default attribute type is usually correct and does not need modification.

Figure 6-27 Attribute Values Set in the Artifact Profiles Page

The surrounding text describes the image.

6.7.5 Setting Runtime Options for the Client Data Model

Set runtime options for the MAF application that you generate from the REST Service Editor.

This task is one in a series of tasks to generate a client data model in your MAF application. See Figure 6-28.

Figure 6-28 Setting Runtime Options for a MAF Application

Description of Figure 6-28 follows
Description of "Figure 6-28 Setting Runtime Options for a MAF Application"

Configure the fields as follows:

  • Read in Background: If selected, the GET requests (typically the Find All, Find and Canonical resources) specified for the data object execute in a background thread. It is a good idea to select this checkbox as it enhances the end user's perception of the MAF application’s performance. MAF first queries the SQLite database and show these results immediately on the screen (assuming you persisted the data object). REST calls will be made in the background without blocking the user interface. Once the MAF application receives the REST response, it automatically updates the user interface.

  • Write in Background: If selected, non-GET requests (typically the Create, Update, and Merge resources) specified for this data object execute in a background thread. You will usually leave this checkbox selected as it enhances the end user's perception of the MAF application’s performance. After triggering a REST call through, for example, a Save button, the end user can continue to use the application. The user interface is not blocked for the duration of the REST call. Again, if the REST response includes some attributes with server-updated values, the MAF application automatically refreshes the user interface.

  • Auto Query: If selected, MAF automatically queries the on-device database for all rows and/or call resource specified by Find All Resource when the service object class for the data object is initialized. This is convenient when building your AMX pages using the bean data control that you create for the service object class. The bean data control exposes a collection element that you can drag and drop onto an AMX page to, for example, create a list view or form view. When auto-query is selected, this collection element returns all data objects. It initially returns what is present in the SQLite database and refreshes with the remote collection once execution of the Find All Resource completes. If you clear this checkbox, you will need to execute a finder method in your task flow before navigating to the AMX page. Otherwise the AMX page will show no data.

  • Generate Primary Key: If selected, the MAF application automatically generates a primary key when a new data object is inserted into SQLite database when the primary key attribute value is still null. This feature only works when the primary key attribute is a numeric attribute. MAF queries the SQLite database for the current maximum value and increments this value with 1.

  • Enable Offline Transactions: If selected, write REST calls can be invoked while the MAF application is offline. MAF registers the transaction (create, update, remove or a custom action) as a pending data synchronization action. Once the MAF application comes online, MAF synchronizes these actions and executes the associated REST calls. Note that when the MAF application is online, and the REST call fails because the server is not available or the server throws some error when invoking the REST resource, the transaction is also registered as a pending synchronization action. MAF retries execution the next time the MAF application makes a REST call. If this checkbox is cleared, an error message appears to the end user when the MAF application is offline or when the REST call fails.

  • Show Web Service Errors: If selected, any REST Call failure shows an error message popup in the user interface. This is a useful setting during development so you can see errors. We recommend you clear this checkbox when publishing your MAF application in production. You typically do not want to show technical details about REST call failures to end users.

  • Remote Persistence Manager: Register your own remote persistence manager. This is useful if you want to extend the default behavior of the MAF remote persistence manager.

    Note:

    For MCS connections, set the Remote Persistence Manager to oracle.maf.api.cdm.persistence.manager.MCSPersistenceManager.
  • Local Persistence Manager: Register your own local persistence manager. This is useful if you want to extend the default behavior of the MAF local persistence manager.

  • Payload Date Format and Payload Time Format: Specify the Java date/time pattern to convert date attribute string values in the payload to a java.util.Date instance, date format should be specified for use in the JSON payload (for example, yyyy-MM-dd'T'HH:mm:ssZ).

6.8 Generating the Client Data Model

Generate the client data model profile artifacts to use in the MAF application.

This task is one in a series of tasks to generate a client data model in your MAF application. For more information, see Overview of Creating a Client Data Model in a MAF Application.

Once you have created the CDM artifact profile, you can generate it to create the FARs to use in your application.

Ensure that Configure MAF Application is selected for Client Data Model of the profile.

Figure 6-29shows how to select Generate Profile Artifacts from the context menu of the profile.

Figure 6-29 Generating the CDM Profile

The surrounding text describes the image

When you choose Generate Profile Artifacts the following happens:

  • The confirm changes dialog appears, where you can confirm some or all of the changes to be made. See Figure 6-30.

    Figure 6-30 Confirm the Changes Before Generation

    The surrounding text describes the image
  • If the application is in a dirty state, a dialog appears asking you to confirm that it should be saved.

  • The CDM FARs are generated and appear in the application project, as shown in Figure 6-31.

    Figure 6-31 Generated FARs in MAF Application Editor

    The surrounding text describes the image

6.9 Editing the Client Data Model in a MAF Application

Describes how to modify a previously-generated client data model in a MAF application.

MAF supports iterative development of the client data model in a MAF application. You can extend and refine the client data model after its initial creation.

The Configure MAF Application option on the Artifact Profiles page of the REST Service Editor, shown in Figure 6-32, controls whether changes made to the profile are reflected in the generated artifacts in the application. Deselect it while you are refining the profile, then when you are finished, select it and generate the profile.

Figure 6-32 Control Generating Artifacts

described in the surrounding text

6.10 Accessing the SQLite Database Using the MAF Client Data Model DBPersistenceManager

The MAF client data model provides an API (oracle.maf.api.cdm.persistence.manager.DBPersistenceManager) to access the on-device SQLite database within your MAF application.

The MAF client data model delegates all interaction with the SQLite database to DBPersistenceManager. Use DBPersistenceManager as an alternative to writing the low-level JDBC statements described in How to Connect to the Database. For more information about the role of DBPersistenceManager in the client data model, see Introduction to the Client Data Model in a MAF Application. For information about the DBPersistenceManager and the methods it exposes, see the Java API Reference for Oracle Mobile Application Framework.

The following examples demonstrate how you get an instance of DBPersistenceManager, perform some search operations and other operation on data objects (insert, update, and remove). Apart from using the constructor as shown in the following example, you can also get an instance of the DBPersistenceManager by calling getLocalPersistenceManager() from an instance of a service object. This returns an instance of DBPersistenceManager or a subclass if you registered a custom local persistence manager in the Artifacts Profile page of the REST Service Editor, described in Setting Runtime Options for the Client Data Model.

/* Use the default constructor to get an instance of DBPersistenceManager.*/
DBPersistenceManager pm = new DBPersistenceManager();

/* findAll takes a class or class name as an argument and returns a list of all data objects.*/
List<Department> departments = pm.findAll(Department.class);


/* The overloaded find method returns a filtered list of data objects.
 *
 * There are various ways to specify your filter conditions, as demonstrated by the following examples.
 *
 * A quick search method that returns all employees where at least one of the String
 * attributes (firstName, lastName, emailAddress, and so on) starts with "king" (case insensitive).
 * This translates to use of the SQL LIKE operator where the search value is suffixed with %. */
List<Employee> emps = pm.find(Employee.class, "king");


/* A search method which allows you to specify the search attributes. It returns all employees where at least
 * one of the attributes passed in as list in the third argument starts with "king" (case insensitive). */
List<String> searchAttrs = new ArrayList<String>();
searchAttrs.add("firstName");
searchAttrs.add("lastName");
List<Employee> emps = pm.find(Employee.class, "king", searchAttrs);


/* This is a search method which allows you to specify separate values for each
 * attribute you want to search on, it will return all employees where the firstName
 * equals "Steven" and the lastName equals "King". The query is case sensitive.
 */
Map<String, String> searchAttrs = new HashMap<String, String>();
searchAttrs.put("firstName", "Steven");
searchAttrs.put("lastName", "King");
List<Employee> emps = pm.find(Employee.class, searchAttrs);


/* The findByKey method returns one data object based on its primary key.
 * It first checks the data object cache. If the department with id 10 does not exist in the cache, it queries
 * the database. If you do not want to check the cache, add a third boolean argument checkEntityCache, as demonstrated
 * by the second example
 */
Department dep = (Department) pm.findByKey(Department.class, new Object[] { 10 });
Department dep = (Department) pm.findByKey(Department.class, new Object[] { 10 }, false);


/* Insert, updates and remove a row for a data object instance.
 * MAF automatically commits the change if the second argument is true. If false, you
 * need to call pm.commit() later.
 * MAF generates the primary key attribute for you when calling insertEntity if you selected
 * the Generate Primary Key checkbox.
 */
pm.insertEntity(department, true);
pm.updateEntity(department, true);
pm.removeEntity(department, true);

Using Custom SQL Statements

You can specify custom SQL statements if the above data object-based APIs do not provide an option to execute the SQL statement you need. We distinguish between SQL SELECT and SQL DML statements.

Use code as demonstrated in the following example if the result of your SELECT statement needs to be converted to a data object or a data object list.

DBPersistenceManager pm = new DBPersistenceManager();
ClassMappingDescriptor descriptor = ClassMappingDescriptor.getInstance(Dealer.class);
StringBuffer sql = pm.getSqlSelectFromPart(descriptor);
sql.append(" WHERE SALES_ACCOUNT_ID not in (SELECT DEALER_ID FROM PRIORITY_ASSIGNMENT WHERE PRIORITY_ID=" +
         priority.getId() + ")");
sql = pm.constructOrderByClause(sql, descriptor);
ResultSet set = pm.executeSqlSelect(sql.toString(), null);
List<Dealer> dealerList = pm.createEntitiesFromResultSet(set, descriptor.getAttributeMappingsDirect());

Since the result must be converted to data object list, the SELECT clause should include all the columns, and the FROM clause should use the table name that corresponds to the data object. By using the getSqlSelectFromPart convenience method, the SELECT and FROM clause will be automatically created for you using the data object class descriptor from the persistence-mapping.xml file. You can then append your custom WHERE clause to the SQL statement. You can also append your custom ORDER BY clause, or, if you just want to use the default order by as registered with the data object class descriptor you can use the convenience method constructOrderByClause as in the example above. Once you have constructed your SQL SELECT statement, you can execute it using the executeSqlSelect method which returns a JDBC RowSet object. You then convert the JDBC RowSet to a data object list using the createEntitiesFromResultSet method.

For all SQL SELECT results that cannot be converted to a data object list, you can define the whole SQL statement, and also process the JDBC RowSet. The following is an example of an aggregate query:

DBPersistenceManager pm = new DBPersistenceManager();
String sql = "SELECT AVG(SALARY) FROM EMPLOYEE";
ResultSet set = pm.executeSqlSelect(sql, null);
try {
  set.first();
  int averageSalary = set.getInt(1);
} 
catch (SQLException e) {
  sLog.severe("Error executing SQL statement: "+e.getLocalizedMessage());
}

For single-row insert, update or delete you will typically use the data object-based API described above. However, if you want to insert, update or delete multiple rows at once, you can use the executeSqlDml method. Here is an example where we increase the salary of all clerks by 10%.

DBPersistenceManager pm = new DBPersistenceManager();
String sql = "UPDATE EMPLOYEE SET SALARY = SALARY * 1.1 WHERE JOB_ID='CLERK'";
pm.executeSqlDml(sql, null, true);

If the third doCommit argument is true, the batch update will be automatically committed. If you set it to false, you need to call pm.commit() later.

6.11 Defining a Custom Resource

Describes how to add custom REST resources in your MAF application’s client data model that do not map directly to the standard CRUD resources supported by the MAF client data model.

The Artifact Profiles page of the REST Service Editor allows you to add custom resources. When you do this, MAF generates an additional method with the same name as your custom resource into your service object with the following signature:

public void doSomething(Department department) {
  invokeCustomMethod(department, "doSomething");
}

The advantage of this approach is that it is fast and easy to implement. If your MAF application is offline or the REST call fails due to a server error, the custom resource action will be registered as a pending data synchronization action. MAF makes the call later on when the MAF application returns to online mode. In other words, it is handled in the same ways as standard CRUD transactions in offline mode, as described in Synchronizing Offline Transactions from a MAF Application.

The disadvantage of this approach is that it limits you in how you supply query and path parameters, and how you provide the request payload (this can only be the serialized data object). The value of query and path parameters can be defined declaratively using the options described in How to Specify Query and Path Parameters. If you need complete flexibility in how you construct your URI path with query and path parameters, it is better to go for the programmatic approach and code your REST call in Java.

You can invoke any REST resource using the invokeRestService method on the remote persistence manager, which is either the RestJSONPersistenceManager, or if you connect to Oracle Mobile Cloud Service (MCS), the MCSPersistenceManager. For more information about these classes and methods, see Java API Reference for Oracle Mobile Application Framework.

You could also use the RESTServiceAdapter, described in Creating a Rest Service Adapter to Access Web Services. However, the invokeRestService method has the following advantages:

  • One line of code makes the REST call

  • It handles all responses with HTML status code in 200-299 range successfully. No exception is thrown for status codes 201-299, as is the case with the RESTServiceAdapter.

  • If you have enabled web service logging, you can easily view the REST call details.

  • If connecting to MCS, you do not need to specify the Oracle-Mobile-Backend-Id and (anonymous) Authorization header parameters.

The invokeRestService method has the following signature:

public String invokeRestService(String connectionName, String requestType, String requestUri, 
                                              String payload, Map<String, String> headerParamMap, int retryLimit, boolean secured)

Note:

The last argument (secured) is not used. It included for backward compatibility.

You typically add a method to your service object in which you invoke the invokeRestService method. You can then make the REST call from the user interface by dragging and dropping the method onto your AMX page. Here is a sample method that makes such a REST call:

public void invokeSomeRestResource(String pathParamValue,String queryParamValue) {

  if (isOnline()) {
      RestJSONPersistenceManager rpm = new RestJSONPersistenceManager();      
      String uri = "/someResourcePath/"+pathParamValue+"?someQueryParam="+queryParamValue;
      String result = rpm.invokeRestService("MyRESTConn", "GET", uri, null, null, 0, false);
    // do something with the result
  }    
}

If you want to execute the REST call in the background, the code looks as follows:

public void invokeSomeRestResource(String pathParamValue,String queryParamValue) {
  TaskExecutor.getInstance().execute(true, () -> {
    if (isOnline())
    {
      RestJSONPersistenceManager rpm = new RestJSONPersistenceManager();      
      String uri = "/someResourcePath/"+pathParamValue+"?someQueryParam="+queryParamValue;
      String result = rpm.invokeRestService("MyRESTConn", "GET", uri, null, null, 0, false);
      // do something with the result
    }    
  });
}

If the response should be converted to a list of entities and stored in SQLite database, like the standard Find All resource, use the handleReadResponse method on the remote persistence manager do all this for you. This is the signature of this method:

public <E extends Entity> List<E> handleReadResponse(String jsonResponse, Class entityClass, 
                                                     String collectionElementName, String rowElementName,          
                                                     List<BindParamInfo> parentBindParamInfos, boolean deleteAllRows)

The collectionElementName and rowElementName arguments map to the Payload List Element Name and Payload Row Element Name that we specify for standard GET resources. See Defining CRUD REST Resources for an explanation on how to set the values of these arguments based on the structure of the response payload. If the response returns an array as top-level object, you need to specify root as the value for collectionElementName.

You can leave the parentBindParamInfos argument null, and the deleteAllRows argument is obsolete (It is included for backwards compatibility). If you want to delete all local rows prior to processing the response payload, then you need to add the code to do so yourself.

The following code sample illustrates how the above example can be extended to process the response payload into a list of entities:

public void invokeSomeRestResource(String pathParamValue,String queryParamValue) {
  if (isOnline()) {
      RestJSONPersistenceManager rpm = new RestJSONPersistenceManager();      
      String uri = "/someResourcePath/"+pathParamValue+"?someQueryParam="+queryParamValue;
      String result = rpm.invokeRestService("MyRESTConn", "GET", uri, null, null, 0, false);
      List<Employee> emps = rpm.handleReadResponse(result, Employee.class, "root", null, null, false);
      setEntityList(emps);
  }    
}

6.12 Executing Custom Logic After CRUD REST Calls

Describes the methods that MAF generates in service classes to execute CRUD operations against the local and/or remote persistence manager(s). MAF generates these methods from the Artifact Profiles page of the REST Service Editor.

A department service class, for example, will have methods like findAllDepartment, saveDepartment and removeDepartment. These methods indirectly make the corresponding REST calls if you configured these calls in the REST editor. So, if you want to execute custom logic based on a REST response, you might be inclined to add your logic at the end of these methods. However, this will not work if you execute these REST calls in the background, which might be the case when you select the Remote Read in Background and/or Remote Write in Background checkboxes in the Runtime Options page. In this scenario, your custom logic would already be executed while the REST call is still in progress in a separate background thread.

You need to follow a different approach to make sure your custom logic executes after the REST call completes in the background. If you need to add custom logic after a read REST call (GET), you can override one of the following methods in your service class:

  • executeRemoteFindAll

  • executeRemoteFindAllInParent

  • executeGetCanonical

Add your custom logic after the call to super, as demonstrated in the following example that overrides executeRemoteFindAll:

@Override
protected List<Department> executeRemoteFindAll()
{
  // call super to get the new list of departments from REST call
  List<Department> result =  super.executeRemoteFindAll();
  // do some custom logic here
  ...

  // return department list
  return result;
}

If you need to execute custom logic after a write REST call (POST, PUT, PATCH, DELETE), create a subclass of the remote persistence manager, and override one of the following methods:

  • insertEntity

  • updateEntity

  • mergeEntity

  • removeEntity

Find the remote persistence manager class that you need to subclass. In the Artifact Profiles page, shown in Figure 6-33, select the correct data object, and copy the class name from the Remote Persistence Manager field. Create a new Java class that subclasses from this class and override one or more of the methods listed above. To ensure that the MAF application runtime uses your custom remote persistence manager, enter the fully qualified class name of your subclass in the Remote Persistence Manager field shown in Figure 6-33. For information about invoking the Edit Persistence Mapping dialog to access the Runtime Options page, see Editing the Client Data Model in a MAF Application.

Figure 6-33 Remote Persistence Manager

The surrounding text describes the image.

You need to subclass the remote persistence manager for write methods because of the offline transactions supported by MAF. In offline mode, REST calls will not be executed but will instead be registered as pending synchronization action(s). When online again, the MAF application executes the REST call directly using your remote persistence manager subclass, bypassing the service class.

If you need to execute custom logic based on the raw response from the REST call, before MAF has done any processing, override the following method in the remote persistence manager:

public String invokeRestService(String connectionName, String requestType, String requestUri, 
                                String payload, Map111String,String222 headerParamMap, int retryLimit, boolean secured)

This method handles all REST calls in MAF and returns the response of the REST call. After calling super, you can also call getLastResponseStatus() and getLastResponseHeaders() to get more information about the response.

6.13 Getting Programmatic Access to Service Objects

Describes how to get programmatic access to an instance of a service object.

You typically create a data control for your service objects to create the user interface of your MAF application using drag and drop from the Data Controls panel. If you want to access a service object from a managed bean or a lifecycle listener method, create an instance of your service object as follows:

DepartmentService service = new DepartmentService(); 

Where DepartmentService extends from oracle.maf.impl.cdm.persistence.service.EntityCRUDService.

This triggers a REST call if you have selected Auto Query in the Runtime Options page, as described in Setting Runtime Options for the Client Data Model, and you specified a value for Find All Resource , as described in Defining CRUD REST Resources. If you do not want to perform this automatic query for the instance you create programmatically, use the following constructor that takes autoQuery as a boolean argument:

DepartmentService service = new DepartmentService(false); 

If you do want to perform an automatic query, write code as follows:

DepartmentService service = new DepartmentService(); 
List<Department> deps = service.getDepartment();
// do something with the departments

This will not work reliably if the REST call executes in the background which happens when the Remote Read in Background checkbox is selected in the Runtime Options page. In that case, the getDepartment() method call only returns the departments stored in the local database (if any), as the REST call executes in a background thread. So, the safest way is to use the following constructor:

public DepartmentService(boolean doRemoteReadInBackground, boolean doRemoteWriteInBackground)
{
  super(false);
  setDoRemoteReadInBackground(doRemoteReadInBackground);
  setDoRemoteWriteInBackground(doRemoteWriteInBackground);
}

This constructor calls super with the autoQuery argument set to false, and will set the remoteReadInBackground and remoteWriteInBackground properties as specified in your constructor call. In other words, using this constructor you can get an instance that ignores the selections made in the Runtime Options page (and stored in the persistence-mapping.xml file).

DepartmentService service = new DepartmentService(false,false); 
// get latest department list from server by making synchronous REST call
service.findAllDepartmentRemote; 
// get a handle in the department list
List<Department> deps = service.getDepartment();
// do something with the department list

In managed bean code, you might need access to the service object instance used by the data control. Do this using the following convenience method:

DepartmentService service = (DepartmentService) EntityUtils.getEntityCRUDService(Department.class);

This method looks up a data control instance by the name of your service object class. Be aware though that if you use this method without the data control being instantiated yet (that is, used on an AMX page), it creates a new instance using the default constructor which might trigger an unwanted REST call as explained previously. A data control instance lives in the context of an application feature. If you have used the same data control in the AMX pages of two different application features, you will have two instances of the underlying service object. The application feature context in which you execute the getEntityCRUDService method determines which instance returns.

Move as much logic as possible into your service class to reduce the need to get a handle on the data control instance in your managed bean code. If you evaluate lots of value binding and method binding expressions, and subsequently execute lots of these method bindings, you might want to rethink your coding strategy.

Finally, if you only need access to the local SQLite database in your custom Java code and do not require any REST calls to be made, you do not have to create a service object instance. Instead, use an instance of oracle.maf.api.cdm.persistence.manager.DBPersistenceManager to query or manipulate data objects. For more information, see the Java API Reference for Oracle Mobile Application Framework.

6.14 Understanding Usage of the Primary Key

Every data object must have a primary key to ensure that rows in the SQLite database can be uniquely identified. MAF also uses the primary key to minimize data object creation in the data object cache and to prevent the creation of multiple instances of the data object.

Use the following method to retrieve a specific data object instance from the cache:

EntityCache.getInstance().findByUID(Class entityClass, Object[] key)

Use the following method from the DBPersistenceManager class to retrieve a data object from the cache or from the database if the data object has not been cached:

findByKey(Class entityClass, Object[] key)

The primary key can be a composite key consisting of multiple attributes/columns. This is why the key argument in the above methods takes an object array. If the primary key is a single numeric attribute, MAF automatically generates a primary key if you select the Generate Primary Key checkbox in the Runtime Options page described in Setting Runtime Options for the Client Data Model. MAF queries the SQLite database for the current maximum value and increments this value by 1 when the DBPersistenceManager’s insertEntity method is called, either by the framework, or by your custom code.

You can also generate a primary key yourself, by using the following method:

EntityUtils.generatePrimaryKey(Entity entity, int increment)

Note:

The latter method only works if you selected the Generate Primary Key checkbox for the data object as it checks this flag in the persistence-mapping.xml file.

Understanding MAF Management of Server-Derived Primary Key Values

The primary key value that you create or generate for new data object instances can be regarded as a temporary primary key. Obviously, this primary key is not guaranteed to be unique at the server side. Typically, when you invoke a REST service that inserts the data object to the remote server, the remote server generates a truly unique primary key.

If the REST call that inserts the data object returns the data object with the new server-derived values in the response, then the MAF client data model automatically updates the temporary primary key with the server-derived primary key. The MAF client data model deletes the database row with the temporary primary key and inserts a new row with the server-derived key. It also updates the database object cache.

Make sure that the Create Resource endpoint returns the full data object in the response to benefit from this MAF client data model feature.

SQLite Auto-Increment Functionality

Avoid using SQLite’s auto-increment functionality because MAF uses the primary key to identify instances in the data object cache and to update existing rows in the SQLite database when the MAF application fetches the up-to-date data from the server. For this reason, it is better to specify the primary key using one or more data object attributes that are included in the payload coming from the remote server rather than SQLite’s auto-increment functionality.

It is fine to use SQLite’s auto-increment functionality for data objects where no existing data objects instances are retrieved from the server. That is, where no GET resource called to load data into the SQLite database.

For more information about SQLite’s auto-increment functionality, see SQLite’s documentation.

6.15 Using Filtered Entity Lists

Describes the methods that the MAF client data model generates to facilitate the creation of filtered lists in the UI of your MAF application.

MAF generates a getter method in the data object’s CRUD service class that returns a collection of the data object instances. An employee data object’s service class (EmployeeService) has, for example, the following method:

public List<Employee> getEmployee() {   
  return getEntityList(); 
}  

The EntityCRUDService superclass stores the returned list in a private member variable (private EntityList<E> entityList). This list contains all data object instances retrieved from the local SQLite database and/or from a call to the Find All REST resource if you selected the Auto Query checkbox in the Runtime Options page, as described in Setting Runtime Options for the Client Data Model.

To filter this list based on a quick search field in the user interface, use the find[entityName] method that the data object CRUD service class also generates. For example, the following method is generated for an employee data object.

public void findEmployee(String searchValue) {
  super.find(searchValue);
}

Drag and drop the method from the Data Controls panel onto your AMX page to render a search field and a command button to invoke the find[entityName] method. When the end user taps the command button to invoke the find[entityName] method, the find[entityName] method delegates to the find method in the DBPersistenceManager class. For more information about the DBPersistenceManager class, see Accessing the SQLite Database Using the MAF Client Data Model DBPersistenceManager and Java API Reference for Oracle Mobile Application Framework. The result returned by the find method is stored as the new content of the entityList variable. A data change event is send to the user interface to refresh the list correctly after the end user clicks the command button.

You can also write a custom method to filter entity lists. The following example filters a list of employees to only clerks.

public void filterClerks()
{
  DBPersistenceManager pm = new DBPersistenceManager();
  Map<String,String> searchAttrs = new HashMap<String,String>();
  searchAttrs.put("jobId","CLERK");
  List<Employee> clerks = pm.find(Employee.class,searchAttrs); 
  setEntityList(clerks);
}

The call to setEntityList updates the entityList variable and sends the required data change events to the user interface once setEntityList is invoked.

Note:

It does not matter whether this method is invoked through some UI action or through Java code in a background thread.

Assume, for example, that the user interface of your application needs to show multiple filtered lists at the same time. You want to show, for example, a list of employees and a list of clerks. In this scenario, the filterClerks method cannot update the entityList member variable because entityList already shows all employees in the user interface. The solution is to add a getter method to return the list of clerks. This adds a clerks collection attribute to the EmployeeService data control that you can drag and drop onto AMX pages to show the list of clerks. The following code example illustrates such a getter method:

public List<Employee> getClerks()
{
  DBPersistenceManager pm = new DBPersistenceManager();
  Map<String,String> searchAttrs = new HashMap<String,String>();
  searchAttrs.put("jobId","CLERK");
  List<Employee> clerks = pm.find(Employee.class,searchAttrs); 
  return clerks;
}

@Override
protected void setEntityList(List<Employee> entityList)
{
  super.setEntityList(entityList);
  // we also need to refresh the clerks list
  getPropertyChangeSupport().firePropertyChange("clerks", null, getClerks());
  getProviderChangeSupport().fireProviderRefresh("clerks");      
}

The previous example overrides the setEntityList method to handle the situation where we retrieve the latest list of employees in the background by calling the Find All REST resource. When a MAF application executes a Find All REST call in the background, it calls setEntityList when the response is returned and the database has been updated with the latest set of data object instances included in the response payload. In our example, the user interface shows both a list of all employees and a list of clerks. We want both lists updated when the REST call completes. Overriding the setEntityList method ensures that the getClerks method is executed again once the latest set of employees has been retrieved from the REST service and ensures that data change events are sent to refresh the clerks list.

6.16 Using the CDM in a MAF Application

Use the generated CDM artifacts in a MAF application.

When you generate a CDM profile, it creates the artifacts needed to incorporate the REST service into the MAF application. It creates:

  • The Java data and service objects

  • persistence-mapping.xml

  • mobile-persistence-config.properties

  • The SQL DDL statements

  • And some managed beans and configuring FARs

In the MAF Application Editor, you can see the generated features in the Registered Features node, as shown in .

Figure 6-34 CDM Features

described in the surrounding text

To incorporate features from the client data model:

  • Create a task flow and AMX pages. For more information, see Creating MAF AMX Pages and MAF Task Flows.

  • With an AMX page open in the editor, add data controls from the client data model by dragging them from the Data tab in the Palette, as shown below.

    described in the surrounding text

6.17 Synchronizing Offline Transactions from a MAF Application

MAF applications allow end users to perform transactions when the application is in offline mode. MAF performs a data synchronization action for each transaction when the application is next in online mode.

A transaction in this context is a method invocation on a service object that results in a REST call to create, modify or delete data. If you invoke a service object’s method, such as, saveDepartment(Department), MAF checks if there is a corresponding REST resource to call. If there is no corresponding REST resource, MAF invokes the method against the on-device SQLite database if applicable. If there is a corresponding REST resource to call, MAF creates a data synchronization action. The data synchronization action holds the type of resource to execute (Insert, Update, Remove, or a custom action) and all data of the data object instance which the transaction requires.

If the MAF application is in online mode, MAF starts the synchronization action and invokes the corresponding REST resource. If the MAF application is in offline mode and you enabled offline transactions for the data object, MAF registers the data synchronization action and stores it as a pending synchronization action. MAF synchronizes the action when the MAF application is next in online mode. MAF preserves the exact sequence in which transactions are committed when synchronizing.

You enable offline transactions for a data object by selecting the Enable Offline Transactions checkbox, as described in Setting Runtime Options for the Client Data Model. If you disable offline transactions for a data object, MAF throws a “Device is offline” exception if an end user attempts to perform an offline transaction on the data object. Prevent this exception by disabling the UI controls when the MAF application is in offline mode so that end users cannot create a transaction that throws the exception.

MAF stores data synchronization actions in the PENDING_SYNCH_ACTIONS table of the SQLite database. The data object’s SQLite database table does not store information related to data synchronization actions. Assume, for example, an end user deletes a department when the MAF application is in offline mode. This action removes the corresponding row from the DEPARTMENTS table in the SQLite database and a corresponding data synchronization action is added to the PENDING_SYNCH_ACTIONS table. When the MAF application is next in online mode, the REST action associated with the data synchronization action of deleting the department is performed. This ensures that the local SQLite database tables reflect the latest transactions performed by end users regardless of whether the associated REST calls have been performed or not.

When a MAF application returns to online mode from offline mode, MAF waits for the application to invoke a REST call. When this event occurs, MAF synchronizes the pending data synchronization actions before processing the REST call. This synchronization makes sure that the subsequent REST call(s) and responses to the MAF application do not use obsolete data. You can explicitly invoke this automatic synchronization when the MAF application returns to online mode, even if there are no REST calls to trigger the automatic synchronization by invoking the synchronize(boolean) method from any service object in your MAF application. The oracle.maf.impl.cdm.persistence.service.EntityCRUDService class, from which all service object classes extend provides the synchronize(boolean) method. Invoking this method once from any service object class performs an automatic synchronization for all pending data synchronization actions in the MAF application. The boolean argument for synchronize determines whether synchronization happens in the background (true) or in the foreground (false). Although not recommended, you can disable the default behavior of MAF applications to synchronize pending synchronization actions before invoking a REST call. See How to View Pending Synchronization Actions.

MAF applications cannot detect if data synchronization conflicts occur when a MAF application returns to online mode and synchronizes data. Assume, for example, that an end user of your MAF application updates a department when the MAF application is in offline mode. Elsewhere, another user of a web application that accesses the same data set modifies the same department information. MAF cannot detect this latter change. When the MAF application returns online, MAF attempts to synchronize the changes in the PENDING_SYNCH_ACTIONS table. To resolve and work around the issue just described, you need to identify and resolve data synchronization conflicts at the location where all applications (mobile, web, and so on) access the data set that your MAF application accesses.

When MAF tries to synchronize pending synchronization actions by calling the corresponding REST resource, the REST call may return an error response for an action because, for example, the server is down. If this happens, MAF keeps the data synchronization action in the PENDING_SYNCH_ACTIONS table. It also updates the action with the timestamp of the synchronization attempt and the synchronization error. MAF continues processing the remaining data synchronization actions in the table despite the failure of one or more actions. MAF retries these pending data synchronization actions the next time it performs synchronization. You can expose these pending synchronization actions to end users so they can view and make a decision on what do with actions that remain to be synchronized. See How to View Pending Synchronization Actions. You can also write custom logic to execute in your MAF application on completion of a synchronization action. You can, for example, write code that executes in response to failure to synchronize. See How to Add Custom Logic to Handle Failed Synchronization Actions.

6.17.1 How to View Pending Synchronization Actions

Add the DataSyncFeature.jar feature archive to your MAF application to display an application feature where end users can view and remove pending synchronization actions.

Once added, your MAF application includes an application feature that includes a menu to view pending synchronization actions. End users can tap each pending synchronization action to view more detail and make a decision to remove the action or leave it for MAF to re-attempt to synchronize it.Figure 6-35 shows a composite image of the menu entry and the Pending Sync Actions screen in a MAF application where this feature archive has been added.

Figure 6-35 Viewing Pending Sync Actions

The surrounding text describes the text.

6.17.2 How to Add Custom Logic to Handle Failed Synchronization Actions

Write custom code that executes in your MAF application once data synchronization completes if you want to handle failed synchronization actions programmatically.

Perform the following steps to write Java code in and register it in your MAF application:

  1. Create a Java class that extends from oracle.maf.impl.cdm.persistence.service.DataSynchManager.

  2. Override the dataSynchFinished method to add custom logic after the data synchronization action(s) complete.

    protected void dataSynchFinished(java.util.List<DataSynchAction> succeededDataSynchActions, 
                                             java.util.List<DataSynchAction> failedDataSynchActions)
    

    The dataSynchFinished method has two arguments: a list of successful synchronization actions and a list of failed synchronization actions. You can use this method to, for example, warn end users that one or more transactions failed and will be re-tried later, or you can inform them that all pending data synchronization actions have been processed successfully.

    Add the Java code to implement your synchronization policy to this method. The following simple example informs the user about the number of successful and failed synchronization actions:

    package application.model.service;
    
    import java.util.List;    
    import oracle.adfmf.framework.exception.AdfException;
    import oracle.maf.impl.cdm.util.MessageUtils;
    import oracle.maf.api.cdm.persistence.service.DataSynchAction;
    import oracle.maf.impl.cdm.persistence.service.DataSynchManager;
    
    public class MyDataSynchManager extends DataSynchManager {
    
      public DataSynchManager() {
        super();
      }
    
      @Override
      protected void dataSynchFinished(List<DataSynchAction> succeededDataSynchActions,
                                       List<DataSynchAction> failedDataSynchActions)  {
        int ok = succeededDataSynchActions.size();
        int fails = failedDataSynchActions.size();
        int total = ok + fails;
        MessageUtils.handleMessage(AdfException.INFO,
           total + " data synch actions completed. Successful: " + ok + ", Failed: " + fails);
      }
    }
    
    
  3. Register your class in your application’s mobile-persistence-config.properties file, as demonstrated by the following example:

    datasync.manager.class=application.model.service.MyDataSynchManager

    The mobile-persistence-config.properties file is in ApplicationRootDirectory/ApplicationController/src/META-INF/ folder.

6.17.3 What You May Need to Know About Disabling Automatic Synchronization

Automatic synchronization of transactions from a MAF application can be disabled.

To disable automatic synchronization of transactions from a MAF application:

  1. Create a new abstract Java class that extends the oracle.maf.impl.cdm.persistence.service.EntityCRUDService

  2. Override the synchronize method and comment out the call to super.synchronize so the method performs no execution

  3. Modify your service object classes to extend from the just-created subclass instead of extending from EntityCRUDService

If you disable automatic synchronization, configure your MAF application so that end users can explicitly start a synchronization action. Use the following statement to explicitly trigger data synchronization:

new DataSynchService().synchronize(true); 

The boolean argument determines whether the synchronization happens in the background (true) or in the foreground (false). The DataSynchService class is located in the following package: oracle.maf.api.cdm.persistence.service. For more information, see Java API Reference for Oracle Mobile Application Framework.

We do not recommend disabling automatic synchronization of transactions as it can lead to out-of-date data in your application and a confusing user experience. The following example use case for a user (John) illustrates this point. John performs the following actions in his application:

  • Gets the latest list of departments when starting the application in online mode

  • Modifies the name of department 10 in offline mode

  • Removes department 20 in offline mode

  • Creates a new department 280 in offline mode

  • Leaves the application

  • John starts the application again in online mode and the latest list of departments is retrieved from the server while the 3 pending sync actions are not yet processed

  • John will now see the old department name of department 10

  • John will now see department 20 again although he already removed it

  • If you (the MAF application developer) selected the Delete Local Rows checkbox for the Find All Resource for department data object, the new department (280) that John created disappears again.

Only when John manually starts a synchronization action, will he see the latest data again including the changes he made in offline mode once he refreshes the department list again with the latest data from the server (through a user interface control or by restarting the application).

6.18 Understanding the Client Data Model’s Support for Data Change Events

Describes the APIs that the MAF client data model uses to refresh the user interface of MAF applications in response to data change events in the underlying data collection.

The data and service classes that the MAF client data model generates provide ready-to-use support for both types of change events (property and provider) that MAF supports. This is because the Entity and EntityCRUDService classes that these types of classes extend from both extend from the ChangeEventSupportable class. For more information about the data change events that MAF supports, see Working with Data Change Events.

If you want to send a data change event from your data or service class, call the corresponding getter method to get an instance to send your change event:

  • getPropertyChangeSupport()

  • getProviderChangeSupport()

Always use the above getter methods rather than including your own instance of propertyChangeSupport or providerChangeSupport in a data or service class. This avoids breaking the built-in runtime code that the MAF client data model uses to refresh the user interface of your MAF application.

By default, each service class has a getter method that returns a list of data objects. A DepartmentService class, for example, includes the following generated method:

public List<Department> getDepartment() {
  return getEntityList();
}

When your MAF application makes a REST call to get the up-to-date list of departments, MAF automatically refreshes the user interface by invoking the setEntityList method from the EntityCRUDService superclass of the service class. This method calls another generated method in the service class (getEntityListName) to find out which property name to use in the fireProviderRefresh method call. The following example shows the generated getEntityListName method for a DepartmentService class:

protected String getEntityListName() {
  return "department";
}

The above implementation means that:

  • If you write custom logic to change the content of a department list, you can call setEntityList to refresh the user interface with content changes.

  • If you rename the generated method getDepartment to the more appropriate plural name getDepartments, you also need to change the method getEntityListName to return "departments" instead of "department". If you do not, the standard MAF client data model refresh code will no longer work.

  • If you have added your own getter list methods to the service class to provide multiple filtered views on your set of data object instances, you can override the setEntityList method to make sure your filtered lists also refresh in the user interface when a REST call completes. See Using Filtered Entity Lists.

Refresh Forms in Response to Data Change Events

To refresh the user interface in a form layout where only one data object instance displays, you need to use property change events because provider change events only refresh list views. While each data class includes the PropertyChangeSupport instance to send a property change event as explained above, the generated setter methods in your data object instances do not send change events by default. You are free to add this code yourself, as long as you use the getPropertyChangeSupport() method:

public void setName(String name)
{
  String oldValue = this.name;
  this.name = name;
  getPropertyChangeSupport().firePropertyChange("name", oldValue, name);;
}

If you want to refresh all properties (attributes) of a data object instance, you can also use the MAF convenience method EntityUtils.refreshEntity. This takes a data object instance as its only argument.

Send Data Changes in a Background Thread

When sending data change events in a background thread, you need to flush these data change events to the user interface layer by calling AdfmfJavaUtilities.flushDataChangeEvent. Furthermore, MAF requires you to use the MafExecutorService to prevent deadlocks when sending data change events in a background thread. The following example demonstrates how you use the execute method from the MafExecutorService to send data change events and flush the events once complete. The following example uses a Java 8 Lambda expression to pass in the code.

public void setName(String name) {
  String oldValue = this.name;
  this.name = name;
  if (AdfmfJavaUtilities.isBackgroundThread()) {
    MafExecutorService.execute(() ->
       {
         getPropertyChangeSupport().firePropertyChange("name", oldValue, name);
         AdfmfJavaUtilities.flushDataChangeEvent();                     

       });        
  }
  else {
    getPropertyChangeSupport().firePropertyChange("name", oldValue, name);        
  }
}

As an alternative to writing the above code for each attribute you want to refresh, you can instead use the refreshUI method provided by the MAF client data model’s Entity class to send property change events. This method takes a list of attributes for which you want to send data change events, as the following example demonstrates:

      List<String> attrs = new ArrayList<String>();
      attrs.add("name");
      attrs.add("managerId");
      refreshUI(attrs);

The refreshUI method uses the previously-described MafExecutorService and flushes the data change events when running in a background thread.

Refresh the User Interface with Data Changes from a Child Data Collection

By default, the MAF client data model does not refresh a user interface with data changes to a child data collection when a MAF application makes a REST call that returns the parent data collection. This default behavior optimizes the performance of your application as it prevents a child data object being read from the database and loaded into memory when the page that triggers the REST call might only display the parent data object. You can extend a refresh to include a child data collection by overriding the refreshUI method from the Entity class in your data object’s class. The following example demonstrates how you might do this in a Department class where you want to refresh a child data collection of employees:

  @Override
  public void refreshUI(List attrsToRefresh)
  {
    getProviderChangeSupport().fireProviderRefresh("employees");
    super.refreshUI(attrsToRefresh);
  }

6.19 Forcing Offline Mode in a MAF Application

MAF allows you to configure a MAF application as if the device it runs on is offline when it is connected to the Internet.

This can be helpful to prevent REST calls over slow network connections. For example, you might use it to prevent REST calls over 3G connections but allow them over Wi-Fi connections.

Use the following method to force offline mode:

new ConnectivityBean().setForceOffline(true);

Note:

While you create a new instance of ConnectivityBean, the value of forceOffline flag is saved in a static variable that is shared across all instances.

This allows you to use the ConnectivityBean class as a managed bean and force/unforce offline mode from the user interface. This might be helpful for demos where you want to show data synchronization capabilities of MAF. To do this, define the managed bean in the adfc-mobile-config.xml file as follows:

<managed-bean id="__3">
  <managed-bean-name>Connectivity</managed-bean-name>
  <managed-bean-class>oracle.maf.api.cdm.controller.bean.ConnectivityBean</managed-bean-class>
  <managed-bean-scope>application</managed-bean-scope>
</managed-bean>

You can then add a "force offline" toggle option in your AMX pages as follows:

<amx:commandLink id="menFo" text="#{Connectivity.forceOffline ? 'Unforce offline' : 'Force offline'}">
    <amx:setPropertyListener id="menfospl" from="#{!Connectivity.forceOffline}" to="#{Connectivity.forceOffline}"/>
</amx:commandLink>

CDM generates a menu entry that displays this option, as shown in Figure 6-36.

Figure 6-36 Force Offline Menu Entry from MAF User Interface Generator

The surrounding text describes the image.

If you want to force offline mode based on the strength of the network connection, use the Cordova network plugin which is pre-installed with MAF to set up a JavaScript callback handler that calls the ConnectivityBean.forceOffline method.

6.20 Using a Visual Indicator for Running Background Tasks

Describes how to render a visual indicator to end users to let them known that their MAF application is performing background tasks.

MAF makes all background REST calls through a thread pool that maintains a Boolean flag to return True if the MAF application is executing at least one task in the thread pool. Use the following EL expression to access the value of this Boolean flag:

#{applicationScope.maf_bgtask_running}

CDM generates the following entry in the AMX pages it creates to display the visual indicator, shown in Figure 6-37, when the MAF application processes tasks in the background.

  <amx:image id="bgRunImg" source="/images/reloading.gif" inlineStyle="margin-right:5px;" 
            rendered="#{applicationScope.maf_bgtask_running}"/>

Figure 6-37 Visual Indicator for Background Tasks from MAF User Interface Generator

The surrounding text describes the image