Introduction

Every APEX developer knows how to export and import applications using the Application Builder. And since an APEX export file is actually a SQL script, it’s also pretty obvious, that one can use SQL*Plus or SQLcl to deploy the application on the target system. So far, so good.

The export part of the process can also be automated: APEX provides the APEXExport command line utility for years now. This Java-based tool allows to build shell scripts to export APEX applications or workspaces. Such export scripts, combined with SQL*Plus invocations to import on the target system, people can build automated deployment processes. 

However …

  • Running the APEXExport utility requires either Linux/Unix access or JDBC connection details to the APEX database. That way of accessing the APEX instance is often complicated or not even supported (for instance, because of Firewalls or Cloud environments).
  • The same applies to the import part, which requires SQL*Plus access to the target database. An alternative is to use the SQLcl utility and REST Enabled SQL on the server (which uses HTTPS instead of a JDBC connection), but that feature must be enabled on the target system as well, which is often not the case.

A typical modern way of automating things is to use REST APIs – and we can do the same for APEX application exports and deployments. Operations with APEX applications then simply map on HTTP operations:

  • HTTP GET exports an application.
  • HTTP PUT imports an application.
  • HTTP DELETE deletes an application.

The good news is that such a REST API is available today. With the release of APEX 23.1, ORDS (Oracle REST Data Services) introduces a REST API to export and deploy APEX applications.

Documentation of the ORDS REST API to export and import APEX applications

Fig. 1: Documentation of the ORDS REST API to export and import APEX applications

 

Prerequisites

To use the REST APIs described in this blog post, ORDS 23.1 or higher is required. Also, when installing or configuring ORDS, make sure to activate the Database API, as described in the ORDS documentation. You can verify using the ORDS config command – the database.api.enabled property must be true.

/.../ordsdir $ dbtools/bin/ords --config config config list
 
ORDS: Release 23.2 Production on Mon Oct 09 11:34:42 2023
 
Copyright (c) 2010, 2023, Oracle.
 
Configuration:
 
  /.../ordsdir/config/
 
Database pool: default
 

Setting                   Value            Source   

-----------------------   --------------   -----------

database.api.enabled      true             Global   

db.connectionType         basic            Pool     

db.hostname               localhost        Pool     

db.password               ******           Pool-Wallet

 

If the value is false, then the Database API, and also the REST APIs to deploy APEX applications are not available. Enable as follows:

/.../ordsdir $ dbtools/bin/ords --config config config set database.api.enabled true
 
ORDS: Release 23.2 Production on Mon Oct 09 11:39:46 2023
 
Copyright (c) 2010, 2023, Oracle.
 
Configuration:
  /.../ordsdir/config/
 
The global setting named: database.api.enabled was set to: true

We will authenticate to the REST API using database user names and passwords. For this to work, also make sure that the restEnabledSql.active property is also enabled (use same steps as for the database.api.enabled property).

Also, one the database user we want to authenticate with, must be REST Enabled. One option to do that is to use APEX SQL Workshop, RESTful Services. Make sure that the Schema Access shows an Access Status of ENABLED.

A REST Enabled Schema in SQL Workshop, RESTful Services

Fig. 2: A REST Enabled Schema in SQL Workshop, RESTful Services

If the schema is not REST enabled, enable it either by clicking the Register Schema with ORDS button in APEX, or run the following PL/SQL block using SQL Developer, SQLcl or SQL*Plus while logged in as that database user.

begin
    ords.enable_schema;
end;
 
Now, you should be all set!
The database schema is REST-Enabled, and the database API as well as REST Enabled SQL in ORDS are enabled and ready to use. Let’s try our first request using the curl command line utility, and let’s use the REST Endpoint which simply shows us all the applications of a given workspace. You should be able to simply copy & paste the curl commands and adjust to your environment.
 
$ curl -X GET              \
       -u "CCZARSKI:*****" \
       http://localhost:8085/ords/cczarski/_/db-api/stable/apex/workspaces/cczarski/applications/
 
{
   "items" : [
      {
         "last_updated_on" : "2023-10-09T09:45:33Z",
         "application_group" : null,
         "links" : [
            {
               "rel" : "self",
            }
         ],
         "availability_status" : "Available with Developer Toolbar",
         "last_updated_by" : "CCZARSKI",
         "application_alias" : "DEMO-APP",
         "application_name" : "Demo App",
         "application_id" : 100,
         "workspace" : "CCZARSKI",
         "application_owner" : "CCZARSKI",
         "page_count" : 3
      },
      {
         "last_updated_on" : "2023-07-25T16:01:08Z",
         "availability_status" : "Available with Developer Toolbar",
         "links" : [
            {
               "rel" : "self"
            }
         ],
         "application_group" : null,
         "application_name" : "test APEX-7309",
         "application_alias" : "TEST-APEX-7309",
         "last_updated_by" : "CCZARSKI",
         "page_count" : 7,
         :

 

Authentication is done using a database user and password. The database user (schema) must be REST Enabled. After authentication, we will be able to access applications in all workspaces, which have that very database schema assigned. The request path of the URL is built as outlined in the following table:

 
Path Meaning
ords/ ORDS context root. Determined when ORDS is installed.
cczarski/

The schema alias often equals the schema name and can be looked up in APEX SQL Workshop, RESTful Services on the overview screen under Schema Alias (see Screenshot X above).

_/db-api/stable/

Use current stable version of the Database REST API.

workspace/cczarski/applications/

Return a list of all applications in the CCZARSKI workspace. Note that the database user we authenticated with could be assigned to multiple workspaces, so it’s required to specify the workspace here again.

 

Exporting an application via REST

As we’re able to access the Database REST API now, we can perform the first export operation. Let’s export application 100, and we want to have the “classic” APEX export format, the SQL file.

$ curl -X GET             \
       -u "CCZARSKI:****" \
       http://localhost:8085/ords/cczarski/_/db-api/stable/apex/applications/100?export_format=SQL_SCRIPT > f100.sql
 
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  707k    0  707k    0     0  2702k      0 --:--:-- --:--:-- --:--:-- 2702k
 
$ cat f100.sql
 
prompt --application/set_environment
set define off verify off feedback off
whenever sqlerror exit sql.sqlcode rollback
--------------------------------------------------------------------------------
--
-- Oracle APEX export file
--
-- You should run this script using a SQL client connected to the database as
-- the owner (parsing schema) of the application or as a database user with the
-- APEX_ADMINISTRATOR_ROLE role.
--
-- This export file has been automatically generated. Modifying this file is n
:
 
 

The /apex/applications/{application ID} endpoint returns “an application”. This endpoint can return three different kinds of output.

  • If the export_format Query String parameter is omitted, only a JSON with the most important application attributes is returned.
  • If SQL_SCRIPT is provided as the export_format parameter, then an application export is performed and a single SQL file is returned. 
  • If SQL_ZIP is passed as the export_format, then the application export is split, zipped up and a ZIP file is returned. The ZIP file contains one single file for each application component, and is the preferred format to check into version control systems like Git or SVN.

The API documentation for the /apex/applications/{application ID} endpoint lists even more Query String parameters. For instance, to request an export with “Original IDs” (which is important to get the same internal ID values as that application was imported with before), the request would look as follows:

$ curl -X GET             \
       -u "CCZARSKI:****" \
 
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  707k    0  707k    0     0  2702k      0 --:--:-- --:--:-- --:--:-- 2702k
 
But what happens if the application does not exist, or if the requested application is in another workspace, to which the database user we authenticated with has no access?
Let’s try this out: We can request to export application 4000, which is the APEX application builder itself. This application is obviously not contained in our own workspace, and consequently the import request returns an error message.
 
curl -X GET             \
     -u "CCZARSKI:apex" \
 
{
    "code": "BadRequest",
    "title": "Bad Request",
    "message": "An error occurred when processing the request. The error message is ORA-20987: APEX - Security Group ID
               (your workspace identity) is invalid. - Contact your application administrator.\n. Please review the request before trying again.",
    "type": "tag:oracle.com,2020:error/BadRequest",
    :
 

Importing an application using REST

The REST endpoint to import an application looks a bit different. The database user we are authenticating with, can be assigned to multiple workspaces, so we need to specify the workspace to import the application into. Also, when importing an application using the APEX Builder, one can decide to create a new application from the import file, or to replace an existing one. The REST Import provides the same options. Any import is a PUT request to the /apex/workspaces/{workspace_name}/applications/{application_id} endpoint.

Thus the following request imports the application in the file f100.sql (which we exported before) into the CCZARSKI workspace, as an application with ID 14711. Whether the import creates a new application or replaces an existing one, solely depends on the application ID being used in the URL endpoint.

curl -X PUT                            \
     -u "CCZARSKI:****"                \
     -H "Content-Type:application/sql" \
     --data-binary @f100.sql           \
     "http://localhost:8085/ords/cczarski/_/db-api/stable/apex/workspaces/cczarski/applications/14711
 
{
   "items" : [
      {
         "application_name" : "Demo App",
         "last_updated_by" : "CCZARSKI",
         "workspace" : "CCZARSKI",
         "application_alias" : "DEMO-APP14711",
         "application_group" : null,
         "application_owner" : "CCZARSKI",
         "application_id" : 14711,
         "page_count" : 3,
         :
      }
   ]
}
 
When logging into the APEX workspace, we can see that our new application Demo App (14711) is available now.

APEX Application Builder after application 14711 was deployed using the REST API

Fig. 3: APEX Application Builder after application 14711 was deployed using the REST API
 
Attempting to import into a workspace which the authenticated database user has no privileges on, leads to an error message:
 
curl -X PUT                            \
     -u "CCZARSKI:****"                \
     -H "Content-Type:application/sql" \
     --data-binary @f100.sql           \
     "http://localhost:8085/ords/cczarski/_/db-api/stable/apex/workspaces/other-workspace/applications/14711
 
{
    "code": "BadRequest",
    "title": "Bad Request",
    "message": "An error occurred when processing the request. The error message is ORA-20987: APEX - Invalid workspace ID. -
                Contact your application administrator.\n. Please review the request before trying again.",
    "type": "tag:oracle.com,2020:error/BadRequest",
    "instance": "tag:oracle.com,2020:ecid/w9XnLgLGTXrCwUgMBRl3rg",
    :
 

Deleting an Application using REST

We can also remove an application using the Database REST API. Obviously, we have to perform a DELETE request, and the endpoint is /apex/workspaces/{workspace_name}/applications/{application_id}. As the DELETE endpoint does only return the HTTP-204 (“No Content”) status code, and no request body, we’ll use the -v curl switch to see that information in the console output.

$ curl -v                 \
       -X DELETE          \
       -u "CCZARSKI:****" \
 
* TCP_NODELAY set
* Connected to localhost (::1) port 8085 (#0)
* Server auth using Basic with user 'CCZARSKI'
:
< HTTP/1.1 204 No Content
< X-ORDS_DEBUG: true
< X-Frame-Options: SAMEORIGIN
<
* Connection #0 to host localhost left intact
 
If we check the App Builder now, it should show that the application has been removed. Attempts to delete a non-existing application, or an application in a different workspace will receive an error response.
 

Individual Application Components

The REST API also allows to export individual components of an application: We will use the same endpoint as we used to export the full application. However, now we have to perform a POST request, and the request body will contain the list of components to export. 

$ curl -X POST                                                         \
       -u "CCZARSKI:****"                                              \
       -H "Content-Type: application/json"                             \
       -d '{"components": ["PAGE:1","PAGE:2","LOV:3592518833461236"]}' \
 
 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  5587    0  5529  100    58  49810    522 --:--:-- --:--:-- --:--:-- 50333
 
$ unzip -l f100_components.zip
 
Archive:  f100_components.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
     1098  10-09-2023 15:19   f100/application/set_environment.sql
     1575  10-09-2023 15:19   f100/application/shared_components/user_interface/lovs/lov_rest.sql
      393  10-09-2023 15:19   f100/application/pages/delete_00001.sql
     1124  10-09-2023 15:19   f100/application/pages/page_00001.sql
      393  10-09-2023 15:19   f100/application/pages/delete_00002.sql
     5304  10-09-2023 15:19   f100/application/pages/page_00002.sql
      228  10-09-2023 15:19   f100/application/end_environment.sql
      304  10-09-2023 15:19   f100/install_component.sql
---------                     -------
    10419                     8 files

For the component export, the request body must be a JSON object, which contains the components member. That components member is an array of strings – each string consists of the component type to be exported, followed by a colon (:), and then the component ID. The pretty-printed JSON request body looks as follows:

{
    "components": [
                    "PAGE:1",
                    "PAGE:2",
                    "LOV:3592518833461236"
                  ]
}
 

The APEX dictionary view APEX_APPL_EXPORT_COMPS allows to query all exportable components of an application, as shown in the following screenshot:

The APEX_APPL_EXPORT_COMPS shows all exportable components of an application

Fig. 4: The APEX_APPL_EXPORT_COMPS shows all exportable components of an application

 

So, after looking up the component types and IDs in the APEX_APPL_EXPORT_COMPS view, we can construct the JSON array of components to be exported and POST that JSON to the export REST API, as shown above.

Importing components is similar to importing a full application. We’re executing a PUT request to the /apex/workspaces/{workspace_name}/applications/{application_id} endpoint; and the request body contains the ZIP or SQL file containing the application components to import. 

       
curl -X PUT                            \
     -u "CCZARSKI:****"                \
     -H "Content-Type:application/zip" \
     --data-binary @f100_components.zip  \
 
{
   "items" : [
      {
         "application_name" : "Demo App",
         "last_updated_by" : "CCZARSKI",
         "workspace" : "CCZARSKI",
         "application_alias" : "DEMO-APP14711",
         "application_group" : null,
         "application_owner" : "CCZARSKI",
         "application_id" : 14711,
         "page_count" : 3,
         :
      }
   ]
}
 

 

As with APEX Application Builder, single components can only be imported into the same application as they were exported from. Importing components into a different application is not supported.

However, the REST API will only throw errors for a component import if the target application is not present or the database user has no privileges to import components into it. Components which cannot be imported into the target application (because they were exported from another application, or because they’re referencing non-existent other components) will silently be ignored.

Using the REST API from APEX: APEX_WEB_SERVICE

A REST API can be used with any client application. Instead of curl, we can use APEX_WEB_SERVICE; and get application exports from another APEX instance!

A PL/SQL block to get an export of an application from an APEX server is very simple and looks as follows. This block references page items for the actual REST Endpoint URL and the application to export. APEX_WEB_SERVICE.MAKE_REST_REQUEST_B performs the actual REST request and the result is stored in the BLOB variable. In this example, that BLOB is simply downloaded, but anything can be done: can be stored into a table or pushed to another REST API.

declare
    l_export apex_data_export.t_export;
begin
    l_export.content_blob := apex_web_service.make_rest_request_b(
                                 p_url           =>    rtrim( :P1_REST_ENDPOINT_URL, '/')
                                                    || '/_/db-api/stable/apex/applications/'
                                                    || :P1_APP_ID
                                                    || '?export_format=SQL_SCRIPT',
                                 p_http_method   => 'GET',
                                 p_username      => 'CCZARSKI',
                                 p_password      => 'apex' );
 
    if apex_web_service.g_status_code != 200 then
        apex_error.add_error(
            p_message => 'Export attempt failed with HTTP-' || apex_web_service.g_status_code,
            p_display_location => apex_error.c_on_error_page );
    else
        l_export.mime_type := 'application/sql';
        l_export.file_name := 'f' || :P1_APP_ID || '.sql';
 
        apex_data_export.download(
            p_export              => l_export,
            p_content_disposition => 'attachment' );
     
    end if;
end;
 

Summary

The new REST APIs in ORDS 23.1 and higher can make the deployment automation for APEX applications way simpler than it was before. Neither Oracle Client nor JDBC driver is needed any more in order to perform an automated export; neither SQL*Plus nor SQLcl is required for an automated import. All these things can be done with the REST client of choice, and all through standard HTTP. Moving APEX applications from on-premises to the Cloud (potentially passing several firewalls) becomes as easy as a simple curl invocation.

Additionally, regular exports to check in APEX applications into version control can benefit from these REST APIs. Simply export as a ZIP and then check into the version control of your choice. As all of this is standards-based, such integration can be done using shell scripts, Hudson or Jenkins job or any other programming language. REST APIs are the way to go for exporting and deploying APEX applications.