Recently Oracle has announced Application Development Platform 17.1.3 with useful new updates, enhancements and relevant announcements to a vast range of services in the Application Development portfolio, including Java Cloud, Application Container Cloud, Developer Cloud, Application Builder Cloud, Database Cloud, Exadata Express Cloud, and more.
Application Builder Cloud Service 17.1.3 comes with very interesting features including but not limited to:
- On-device mobile app development with Oracle MAX (those of you who’re familiar with Mobile Cloud Service should remember that Mobile Application Accelerator used to sit on top of Mobile Cloud Service. Now a customer who has an ABCS account leverages a visual drag&drop development tool to create both rich Web and Mobile applications).
- Connect to external REST services with pluggable Business Object Providers
- Business Logic for custom data objects – triggers validations and more
- In control availability Security at the row level Action on fields in the UI
This post covers what is available to create custom business object providers to connect to external web services and expose them as business objects. ABCS 17.1.3 has a built-in template that provides an example of how to use Application Builder APIs with no connection to real REST API. The extension I have used is a great starting point to showcase how to connect to a real REST Service for demo purposes only.
The Business Object Provider Creation
Until release 17.1.3 the Service Catalog provided connectors for connecting to Fusion Applications web services or custom ADFm REST Services.
Now you can expose an external web service as a business object by creating a custom business object provider (BOP). A custom BOP is an application extension that provides a connection to a REST web service. The sources for the BOP can be edited in the Sources tab, or you can export the sources to edit them in your local editor and import them.
After you create your custom BOP you can select it in the Service Catalog and configure the business object for your application. After a service is exposed as a business object, you can use it in your application and bind it to components in your application.
The BOP creation requires 5 key files:
- Manifest.json
- Extension Manager javascript file
- Entity Provider javascript file
- Operations Provider javascript file
- Operations implementation javascript file (REST requests) Note: this can be merged into the Operations Provider file.
A brief overview of the duties of each file is shown in this figure.
For my demo I used the Generic BOP Provider which you can dowload from https://confluence.oraclecorp.com/confluence/download/attachments/311958070/com.oracle.abcs.bop.generic.rest.zip?version=2&modificationDate=1485862327000&api=v2.
If you intend to use the following service you won’t have to edit any of the source files: https://ucf3-fap0447-gse.oracledemos.com/WorkBetterService/rest/v1/Departments
You’ll find the definition of the biz object which represents a single item in the list of Departments (of the JSON response) with all its properties in js/RESTResource.js. The other files are generic and not specific of this particular service, however they should be reviewed to be able connect to any REST service.
So let’s go through each of them:
- Extension Manager: allows user to define the behavior of the Business Object provider. The BOPExtensionManager API wraps an instance of the base ExtenionManager API and alleviates users from writing a few steps the user otherwise might have had to write. The lifecycle of extension manager and dependencies are seamlessly taken care of with the BOP implementation. The “dependencies” variable is set by the framework and the user doesn’t have to set it explicitly. In the line of code below
RESTResource.endpoint = dependencies[0].getDefinition().parameters.baseUri;
we’re reading the value of the key “baseUri” – a custom parameter representing the REST service baseURL that the user is required to provide when configuring the Service after creating the BOP Provider.
GenericRestBopExtensionManager.prototype.initialise = function(dependencies) { // set the baseUri in RESTResource RESTResource.endpoint = dependencies[0].getDefinition().parameters.baseUri; var BOPImpl = function() { this._entityProvider = new GenericRestBopEntityProvider(); this._operationProvider = new GenericRestBopOperationProvider(this._entityProvider, dependencies); }; BOPImpl.prototype.getEntityProvider = function() { return this._entityProvider; }; BOPImpl.prototype.getOperationProvider = function() { return this._operationProvider; }; BOPImpl.prototype.getId = function() { return RESTResource.getId(); }; this._bop = new BOPImpl(); return BOPRegistry.register(this._bop, this); };
- Entity Provider: create entities from the RESTResource.resources metadata. If metadata contains multiple resources, multiple entities get created here. While the Entity Provider manages all the entities that are created it is not typed to any specific type of Entity.
this._entities = []; var resources = RESTResource.getResources(); for ( var entityDetails in resources ) { var resourceName = resources[entityDetails].name; var properties = resources[entityDetails].fields; var props = []; properties.forEach(function(item) { props.push(DataModelFactory.createProperty( { id : item.name, name : item.name, type : item.type })); }); this._entities.push( DataModelFactory.createEntity( { id : RESTResource.getId() + '.' + resourceName, singularName : resourceName, pluralName : resourceName, description : resourceName + ' object representing a REST resource', properties : props })); }; EntityProvider.apply(this, arguments); }; GenericRestBopEntityProvider.prototype.getEntities = function() { return this._entities; };
- RESTResource: every business object gets created with the properties defined in RESTResource.resources[]. This is the only file of the BOP Provider Implementation that is coupled to the REST service we want to connect to, all the other files are generic and “unware” of the service specs.
var resources = [ { name : 'Departments', path : '/Departments', fields : [ { name : 'Deptid', type : PropertyType.KEY, requiredOnCreate : true // not used as of now }, { name : 'Deptname', type : PropertyType.TEXT, requiredOnCreate : true }, [.....] ] } ];
- Operations Provider: the Operation provider defines the operations the user will be able to configure on the business object.The user will find the id of the entity created previously in the EntityProvider and configure all http operations handlers possible on the REST endpoint.
var GenericRestBopOperationProvider = function(entityProvider, dependencies) { var self = this; self._operations = []; self.dependencies = dependencies; OperationProvider.apply(this, arguments); entityProvider.getEntities().forEach(function(entity) { var realEntity = Abcs.Entities().findById(entity.getId()); self._operations.push(self._getAllRecords(entity)); // GET self._operations.push(self._createRecord(entity)); // POST self._operations.push(self._updateRecord(entity)); // PATCH self._operations.push(self._deleteRecord(entity)); // DELETE }); }; AbcsLib.extend(GenericRestBopOperationProvider, OperationProvider); GenericRestBopOperationProvider.prototype.getOperations = function() { return this._operations; };
This is an example of the GET handler which retrieves the list of all Department entity instances from the REST services.
// GET handler GenericRestBopOperationProvider.prototype._getAllRecords = function( entity) { var entityName = entity.getPluralName(); var authenticator = this.getAuthenticator(); return new OperationBuilder( { name : 'Get all ' + entityName + ' records', type : Operation.Type.READ_MANY, performs : function(operationData) { return RESTCallHandler.fetchRecords(authenticator, entity, operationData); } }).description('Get all ' + entityName + ' records.').returns(entity) .paginates(Pagination.STANDARD).sortableByAll().build(); };
RESTResource.createRequestPayloadFromEntity() allows you to create json payload needed in a service request, from data rows held by your entity, transforming the data from ABCS to as needed by rest service
ABCS allows user to leverage the existing REST services authentication infrastructure to call the REST resources defined in the Business Object Provider (BOP). The BOPAuthenticators API allows user to configure security for the BOP’s. The getDefault method gets an instance of the default authenticator for invoking rest services that defines a whitelist of resources in order to make use of the built in authentication mechanism. The code snippet for that is here:
GenericRestBopOperationProvider.prototype.getAuthenticator = function() { return BOPAuthenticators.getDefault(this.dependencies, new GenericRestBopResourceProvider()); };
- REST Call Handler: handles all the HTTP operations possible on the REST endpoint – it’s where all handlers (GET,POST,PUT,DELETE) are implemented. For CRuD operations RESTResource.createRequestPayloadFromEntity() allows you to create json payload needed in a service request, from data rows held by your entity, transforming the data from ABCS to as needed by rest service
RESTCallHandler.prototype.createRecord = function(authenticator, entity, operationResult) { var data = RESTResource.createRequestPayloadFromEntity( entity, 'POST', operationResult.getData()); var url = RESTResource.endpoint + RESTResource.getResourcePath(entity.getId()); var ajaxObj = { method : 'POST', headers : { 'content-type' : 'application/vnd.oracle.adf.resourceitem+json' }, url : url, data : JSON.stringify(data), dataType : 'json' };
Service Creation / Configuration
When we’re finished creating the custom BOP we need to add the corresponding Service to the Services Catalog. Open Services in the Data Designer and click Add Service to open the Service Catalog. Select your custom BOP from the list of available connectors to services. Click Next and step through the wizard to create the business objects for the service. You’ll have to provide a Name and a Description for the Service, the REST Service base URI and the credentials to call the service if it requires one of the following authentication methods, including Basic, OAuth[User id] and OAuth[App id] its Security.
Let’s take a closer look at the Authentication tab: if your service is Public ( no authentication required, the security framework is skipped and browser tries to invoke target rest api directly). If you run into CORS issues because your service doesn’t set Access-Control-Allow–Origin response header to allow any client to access the resources set security as basic auth, enter your credentials (actually can enter any), enter password again. This is needed to avoid CORS so rest call is made from server instead of browser.
Business Objects Provided by a Service
After adding the service, the new service will be listed on the Services page and the business objects exposing the service that you added will be displayed in the list on the Business Objects page.
The Fields tab displays the current list of fields defined in the business object. The Source column indicates if the field is an external service field or a custom field. The details for fields created from a service are determined by the service, but you can edit the details of a Field or create a new Field. ABCS allows you to add more business objects for a service service.
In the Page Designer you can now add a component such as a table and bind it to the new business objects.
Appendix: how to bind a REST call to a Button
While ABCS gives us the ability to connect to external web services and expose them as business objects there are other use-cases that require us to invoke a REST service programmaticaly when the end user presses the Submit button. Here’s a custom JavaScript code snippet added to the action “receipe” of a button to create a new entity and POST it to a REST service.
Using JQuery first I read the values entered by the user through the Insert/Update form, then I create the json payload needed in the service request from my Business Object and finally I send the AJAX call.
var data = { "Name": $CustomerIdeaStatusBO.getValue('Idea_Name'), "Description": $CustomerIdeaStatusBO.getValue('Description'), "Status": $CustomerIdeaStatusBO.getValue('Status'), "IdeaType": "New_Product"}; $.ajax( { type: "POST", async: true, crossDomain: true, url: "https://apacdemo-apacdemo.mobileenv.us2.oraclecloud.com:443/mobile/custom/ideas/ideas", headers: { "content-type": "application/json", "accept": "application/json", "oracle-mobile-backend-id": "ee4d6daf-9edc-4b78-bd5f-388d532866c1", "authorization": "Basic YWxlc3NpYS5zYWNjaGlAb3JhY2xlLmNvbTphczI3ZzgzQVA=", "cache-control": "no-cache", "postman-token": "756ea57d-1835-27e6-bcf9-e93490527089" }, processData: false, data: JSON.stringify(data) }); resolve();
Thanks for reading!