RESTful CSV file

An article from earlier this year by Ulrike Schwinn titled Oracle Object Storage Access for all Oracle Databases with DBMS_CLOUD got me thinking about doing more with CSV data because it can be treated as a table through DBMS_CLOUD.CREATE_EXTERNAL_TABLE. In essence, provide secure RESTful access to the data, and even visualise that data through charts.

Very quickly, we’ll go from a uploading a Salary.csv dataset to rendering a chart of Job Titles for the highest education level and permitting RESTful queries on data…

Example chart generated on a sharable URL
Query the CSV data through ORDS RESTful interface

Salary Data

Let’s start with a CSV dataset. For this exercise we’ll use a public domain Salary by Job Title and Country from kaggle.com. The download is a ZIP archive with two files. In this case it is just the Salary.csv which will be used.

This dataset provides a comprehensive collection of salary information from various industries and regions across the globe.
Oracle Object Storage Buckets will be used for storing the Salary.csv

I’m not going into all the details of downloading the archive file, unzipping it, creating a bucket, and uploading the Salary.csv but needless to say, those are important steps to get you this stage…

Dataset is uploaded to my ‘example-bucket’

Note that the file has a URL for accessing and downloading it but that URL is not publicly accessible.

The file can be accessed at this URL, but not publicly

External Table

In the database, the DBMS_CLOUD package can create a table which has it’s data loaded from the CSV file. To get access to object storage from the database we require a credential object through DBMS_CLOUD.CREATE_CREDENTIAL. Details on achieving that are already covered in Ulrike’s Oracle Object Storage Access for all Oracle Databases with DBMS_CLOUD article and my previous article Data move mojo with ORDS 21.3.0. In fact, I’m going to reuse that DATAPUMP_CRED_NAME credential. That’s just out of convenience for me. You be you and do your own thing.

To create the SALARY table we must specify a column list with data types and sizes. That requires figuring out, based on the data and also the description on kaggle.com, what data types makes sense. Some columns are obviously numeric, but it is not always clear how big or small those columns should be.

begin 
    DBMS_CLOUD.CREATE_EXTERNAL_TABLE(table_name=>'SALARY', 
                   credential_name => 'DATAPUMP_CRED_NAME',
                   file_uri_list => 'https://frckdrxj9oco.objectstorage.eu-frankfurt-1.oci.customer-oci.com/n/frckdrxj9oco/b/example-bucket/o/Salary.csv',
                   format => json_object('type' VALUE 'CSV', 'skipheaders' VALUE '1'),
                   column_list =>'Age NUMBER(4,1),
                                  Gender varchar2(6),
                                  Education_Level NUMBER(1),
                                  Job_Title varchar2(100),
                                  Experience NUMBER(4,1),
                                  Salary NUMBER(9,2),
                                  Country varchar2(200),
                                  Race varchar2(100),
                                  Senior NUMBER(1)'
                            );
end;
/

Note that the file_uri_list parameter came directly from the object details panel in Object Storage.

Unless there’s a syntax error, creating the table will always work. You’ll only find out about problems when you run a select query. That’s when the DB attempts to read the file.

If you have done this right…select * from salary returns data from the Salary.csv file.

Chart

ORDS Database Actions has a charts and dashboard feature that we’re going to use now. Charts enable you to create charts from the database. The chart is constructed using the input SQL command. Dashboards enable you to group charts together to create reports. Let’s define a bar chart showing the number of job titles for the highest education level. So that the chart is not too busy I’m limiting the chart to only show job titles that have more than 10 occurrences.

Defining a bar chart for job titles

For simplicity my chart definition says that it is unprotected, so it is publicly available and anyone can see it with the URL. That’s ok with this scenario because the data is in the public domain anyway but please consider the protection on your charts and dashboards.

That URL can be shared and it will render in the browser like this…

Note that the data can be viewed as a chart, or as a table.

Although the chart and its data is available to anyone with the URL, the data is just the data for the chart.

No direct access to the original Salary.csv data, just aggregation from a specific query

For many folks the only way they can create a chart based on a CSV file and share it is through using a spreadsheet and sharing that, which means sharing the underlying data. In this case the data is not shared, just the chart. So far so cool. Now let’s look at sharing that data and allowing clients to query it.

AutoREST

This will be a short section. It is as simple as:

EXEC ORDS.ENABLE_OBJECT(p_object => 'SALARY');

That’s it. The SALARY table is now accessible through a REST service at /ords/<my schema>/salary/. Moreover, the Filter Object query parameter provides a powerful search mechanism through the Salary.csv file. For example, let’s find people in the data set with more than 30 years experience: q={"experience":{"$gt":30}}

Records in the CSV can be search with relational operators!

You can’t do that so easily in a text editor 😁

See Filter Objects in the ORDS Developer Guide for more information. Of course this RESTful URL can be protected to require a authorisation for accessing it too while the chart is left unprotected.

Note that with standard REST Enabled tables you can perform DELETE, GET, POST and PUT methods but with external tables, the only functional method is GET. All others will get a HTTP 555 response with this message: ORA-30657: operation not supported on external organized table.

Conclusion

If you have followed the above steps you have opened up new ways of accessing, sharing and interrogating data in a CSV file.

To reiterate, when using an external table the data comes from files in the Cloud or from files in a directory. The data does not get loaded or copied into a table in the database. Drop in a new Salary.csv file with different data and it is immediately picked up on the next query.

The combination of Object Storage, Autonomous Database and Oracle REST Data Services provide some interesting options for doing more with CSV files. It has its limits though. To be realistic, large volumes of data and complex queries would require actually loading copies of the data into the database. For that one should consider Autonomous Database Data Studio to load the data. You can still use ORDS as above to share the data as charts or expose a RESTful interface to the data.

This article was inspired by Ulrike’s January blog post. More excellent articles on data management can be found at https://blogs.oracle.com/authors/ulrike-schwinn. Well worth a look.

ORDS 24.1.0 is faster

ORDS 24.1.0 was released today and is available for download at https://oracle.com/rest. The release notes outline the enhancements and bug fixes. Many are notable. That includes performance improvements for JSON-Relational Duality Views and AutoREST in general.

Here’s an outline of test results using ORDS 24.1.0 running in my Developer DB VM on my laptop. It’s not a blindingly fast Oracle Exadata database setup so the absolute response times are nothing to write home about but the comparison of ORDS 24.1.0 over 2 previous ORDS releases is the point of the exercise here.

Noticeable improvement in the Duality View response time statistics for ORDS 24.1.0
Similarly, a noticeable improvement in the Table response time statistics for ORDS 24.1.0

Data Shape

The data shape is the RESTful Car-Racing Example. In particular the GET of TEAM and TEAM_DV data through ORDS. Using k6 scripts for generating requests, there are 10 concurrent clients that are sending GET resource item requests 1,000 times each. The resource item identifier, team_id, is random so not the same row is returned ever time. The data set is not huge by any stretch of the imagination and does not reflect a likely production data shape. However, the purpose here is to compare changes in response times and throughput.

Authentication

Those AutoREST services provided by ORDS for the tables and duality views are protected because that’s most likely to be the situation in production. The k6 script is hard coded with client id and client secret for authentication. When the k6 test script starts the first thing performed is to get an access / bearer token which is then subsequently used for every request generated by the script. You will notice in every script run this output indicating the token that will be sent for every request.

INFO[0000] {"access_token":"u2mkjkD3lyJXDQYqgjedag","token_type":"bearer","expires_in":3600}  source=console

Checks

There is also the output for checks performed on every response to ensure the expected data is returned. For example, if the authorisation token was invalid, the response status code would not be 200.

     ✓ is status 200
     ✓ is application/json
     ✓ is requested team resource

More on the k6 scripts later. Now let’s look at the database and ORDS configuration.

Configuration

Here’s a breakdown of the Developer DB VM configuration where the database and various versions of ORDS were run for the tests. The version of both ORDS and the Java Runtime Environment have been modified so if you are not familiar with that process see Get the latest – Enhance your DB Developer VM

Database

The same Oracle Database 23 Free edition that came with the Developer DB Virtual Machine is used. The HR schema is REST Enabled and contains the Car-Racing Duality View database objects.

ORDS Settings

The default configuration settings are used. These are the same configuration settings for all versions included in the test.

Default, out of the box, configuration that came with the Developer DB VM

Java Runtime Environment

The virtual machine was originally packaged with Oracle Java 11.0.19 but it is easy to change that. In this virtual machine Oracle GraalVM for Java 17 is used.

It is straight forward to download another JRE distribution and create a symbolic link to it from your $ORDS_HOME/jre directory. The ORDS shell script will use that Java Runtime Environment irrespective of the $PATH or $JAVA_HOME environment variable.

[oracle@localhost ~]$ /home/oracle/ords/jre/bin/java --version
java 17.0.9 2023-10-17 LTS
Java(TM) SE Runtime Environment Oracle GraalVM 17.0.9+11.1 (build 17.0.9+11-LTS-jvmci-23.0-b21)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.9+11.1 (build 17.0.9+11-LTS-jvmci-23.0-b21, mixed mode, sharing)

That is the setup for every test run. Startup a specific version of ORDS in standalone mode using the same configuration folder, and JRE, for each ORDS version.

  • Start ORDS in standalone mode: /home/oracle/ords-23.3.0/bin/ords serve
  • Start the test run: k6 run team_dv.js
  • Gather the results
  • Start the test run: k6 run team.js
  • Gather the results
  • Shutdown ORDS

Repeat for /home/oracle/ords-23.4.0/ and /home/oracle/ords-24.1.0/ to cover those releases too. Not sure how to get your Developer DB VM to have multiple versions of ORDS? Take a look at Get the latest – Enhance your DB Developer VM.

Results

The test runs involve sending requests to GET a random team record, either as a Team Duality View query or query directly on the Team Table that the duality view uses.

Results – ORDS 23.3.0

ORDS 23.3.0 AutoREST Duality View

Duality View row GET using ORDS 23.3.0
iteration_duration:
avg=474.61ms
min=77.88ms
med=469.71ms
max=1.79s
p(90)=671.22ms
p(95)=741.16ms

throughput 20.917985/s

ORDS 23.3.0 AutoREST Table

Table row GET using ORDS 23.3.0
iteration_duration:
avg=130.91ms
min=18.3ms
med=132.64ms
max=390.02ms
p(90)=190.82ms
p(95)=212.67ms

throughput: 75.742323/s

Results – ORDS 23.4.0

ORDS 23.4.0 AutoREST Duality View

Duality View row GET using ORDS 23.4.0
iteration_duration: 
avg=451.33ms
min=76.64ms
med=450.22ms
max=1.22s
p(90)=642.21ms
p(95)=696.75ms

throughput 22.055704/s

ORDS 23.4.0 AutoREST Table

Table row GET using ORDS 23.4.0
iteration_duration: 
avg=163.61ms
min=19.53ms
med=160.07ms
max=1s
p(90)=241.68ms
p(95)=270.31ms

throughput: 60.703901/s

Results – ORDS 24.1.0

Now for the test run with the latest version of ORDS.

Oracle REST Data Services version : 24.1.0.r0960841
Oracle REST Data Services server info: jetty/10.0.20
Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 17.0.9+11-LTS-jvmci-23.0-b21

ORDS 24.1.0 AutoREST Duality View

Duality View row GET with ORDS 24.1.0
iteration_duration:
avg=134.29ms
min=9.97ms
med=90.28ms
max=1.41s
p(90)=261.21ms
p(95)=522.78ms

throughput: 73.321615/s

ORDS 24.1.0 AutoREST Table

Table row GET with ORDS 24.1.0
iteration_duration:
avg=88.72ms
min=11.99ms
med=84.26ms
max=619.03ms
p(90)=134.83ms
p(95)=153.84ms

throughput: 111.435388/s

K6 Scripts

The scripts are quite simple. There are effectively only 3 files involved and they are all available at this github gist: https://gist.github.com/pobalopalous/dcf572abfce1b18f7f49168918231765

  • ords_auth.js provides a function for getting a bearer token
  • team.js tests the AutoREST endpoint for TEAM table and uses a function imported from ords_auth.js to get an access token. It is hardcoded with a client_id and client_secret and randomly gets team data based on their team_id.
  • team_dv.js tests the AutoREST endpoint for TEAM_DV duality view and uses a function imported from ords_auth.js to get an access token. It is hardcoded with a client_id and client_secret and randomly gets team data based on their team_id.

Do it yourself

With the RESTful Car-Racing Example data you should be able to run the same k6 scripts for testing your environment. If you have not done so already install k6 and download the team.js and team_dv.js script files. Just note that the CLIENT_ID and CLIENT_SECRET values will have to change for you database.

const ORDS_CLIENT_ID = 'ymDCZv5ePvte2kN8seOygw..';
const ORDS_CLIENT_SECRET = 'alU5L4Xp5GXOoT-qo9v6UQ..';

It is explained in the RESTful Car-Racing Example article how to get this information from the database. If your schema or port number is different, you’ll have to modify those too.

The upshot from my test runs is approximately 3 times improvement on throughput per second for JSON-Relational Duality Views and 2 times improvement for the throughput per second for a table. Your results may show different absolute response times but I expect you will see a similar improvement ratio when comparing with previous versions of ORDS.

Reply in the comments with your own findings. I look forward to hearing from you.

Invalid SNI – What is it and how to fix it

Everyone was happy. The web application uptime and response rates have been well within the Service Level Agreements for months. Uptake of the published REST Services was on the rise. Both internally and externally, more and more systems were consuming the services and the three ORDS instances behind an NGINX load balancer took it all in their stride. No long running queries or inefficient PL/SQL code locking DB resources. Everything humming along like a finely tuned engine. Then it came time to upgrade…

The ORDS product team have been operating to the usual cadence of one major release every 3 months. Although not ancient, our system is a few releases behind and not only are there interesting new features that are worth exploring but there’s always the security fixes to recent critical 3rd party Common Vulnerabilities and Exposures ( CVE ) that justifies being at the most recent stable release.

Upgrade time comes around so the test environment is setup to run through the ORDS upgrade process before applying the same to production. Then this happens for all the requests: HTTP ERROR 400 Invalid SNI

But this was working before I upgraded !

What is my SNI and how did it get invalid?

The same web server could be hosting multiple sites. In the case of ORDS, the same ORDS instance could have multiple domain name entries that route to it. Server Name Indication ( SNI ) is an extension to the Transport Layer Security (TLS) networking protocol for a client ( such as your browser ) to indicate to the server ( ORDS running standalone mode for example ) which site it wishes to talk to. To put it simply, SNI is used at the start of the secure connection handshake between client and server. The client sends a Host header in the request to indicate what site it is requesting and the server goes looking for its certificate for that server name. See https://datatracker.ietf.org/doc/html/rfc4366#section-3.1 for more details on the role of Server Name Indication in keeping communication secure.

You’ll notice from the stack trace and the error page that the Invalid SNI response does not look like a standard ORDS error page. It is in fact generated by the Eclipse Jetty embedded in ORDS when running in standalone mode.

org.eclipse.jetty.http.BadMessageException: 400: Invalid SNI
A typical ORDS generated error page. It looks a lot different from the Invalid SNI error page!

It is a feature, not a bug – Jetty 10

When running ORDS in standalone mode an embedded Eclipse Jetty server is used. Before ORDS 22.3.0 that was Jetty 9 but since that release ORDS has been using Jetty 10 and for very good reason: more secure. As mentioned in the second paragraph at the start of this article there a fixes and optimisation that are worth making the upgrade for. Jetty 10 addressed some issues in the TLS handshake. Not least Better handling for wrong SNI #5379. Beforehand it didn’t really matter what the client sent in the Host header, Jetty would return any certificate it had.

What is in that certificate anyway?

The certificate, self-signed or otherwise, holds some important information about the server and is essential in establishing trust. To see the contents of the certificate, use the keytool utility in your Java distribution.

keytool -printcert -file self-signed.pem 
Owner: CN=localhost
Issuer: CN=localhost
Serial number: 5e90f747912dd350
Valid from: Thu Feb 08 17:55:19 GMT 2024 until: Fri Feb 07 17:55:19 GMT 2025
Certificate fingerprints:
	 SHA1: FB:F2:E7:30:B5:3F:D1:8B:AC:D0:8E:C3:49:15:3B:B2:75:F1:6E:AD
	 SHA256: 54:B1:4E:6E:92:DC:7F:88:E8:66:6B:69:91:C9:E1:01:CB:69:97:4A:B7:24:BA:F9:A0:52:AC:F3:C3:15:AB:39
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 3072-bit RSA key
Version: 3

The above output shows that the Owner of this certificate has a Common Name ( CN ) value localhost which is the default standalone.https.host configuration setting value.

What to do about it

Now that you know the root cause how do you go about resolving it? It is as simple as ensuring the Common Name ( CN ) or Subject Alternative Name ( SAN ) field in the certificate matches what the client is sending the in Host header of the request. For more information on having more than one hostname in a single certificate see a previous article: Beyond the Common Name: Secure multiple domains with a single self-signed certificate

ORDS will generate a self-signed certificate if necessary when it is started in standalone mode and configured for HTTPS. In other words, the standalone.https.port is specified in the global configuration settings or --secure option is used in the ords serve command. If no certificate exists at the configured standalone.https.cert location, ORDS will generate the self-signed certificate and key file.

The Common Name used for the self-signed certificate is taken from the standalone.https.host configuration setting. If not set, the value is localhost. Traditional certificate practices often rely solely on the Common Name (CN) field. That’s the approach taken by ORDS by default when generating the self-signed certificate.

This can sometimes catch people out when they start up ORDS in secure standalone mode the first time. The self-signed certificate is generated but they may not have specified the standalone.https.host configuration setting and when they do later, they still get HTTP 400 Invalid SNI responses. That’s because the self-signed certificate is already generated so no matter what the standalone.https.host configuration setting says, the certificate will not be generated again.

Simple steps

The simplest course of action is to rename the self-signed certificate and key files and restart ORDS. It will not find the files and therefore will attempt to generate new self-signed certificates.

In summary, make sure the standalone.https.host configuration setting matches the Host header value that clients will send for requests being routed to your ORDS standalone server.

Oracle APM agent generates NullPointerException

April 2024 Update !
ORDS 24.1.0 no longer has this Java Agent / Classloader issue.
Use ORDS 24.1.0 or a later version.

When using Oracle APM Java Agent with ORDS, it crashes with a NullPointerException at startup. Not just when running in standalone mode, if your JVM is configured to use the Oracle APM Java Agent, you could encounter this issue with any command.

~/Downloads/ords-23.2.3.242.1937/bin/ords config list  
Oracle APM Agent: Starting APM Agent [premain]
Oracle APM Agent: Wrapper: version, hybrid
Oracle APM Agent: [DirectoryLocation] initialized on classloader [null]
Oracle APM - temp log directory is /var/folders/hq/6cg5drc54c3f371r9v65c8qm0000gn/T//
Oracle APM Agent: Parsing instrumentation directives
Oracle APM Agent: Loading directives from [built-in.directives]
Oracle APM Agent: Loading directives from [/~/work/ora_apm/oracle-apm-agent/config/1.8.3326/DirectivesConfig.acml]
Oracle APM Agent: Parsed a total of [116] directives
Oracle APM Agent: Initializing AgentInstance
Oracle APM Agent: Initialized AgentInstance
Oracle APM Agent: Started [premain] Agent
null
java.lang.NullPointerException
	at java.base/java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011)
	at java.base/java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1541)
	at java.base/java.lang.ClassLoader.getClassLoadingLock(ClassLoader.java:667)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:591)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:575)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	at oracle.dbtools.launcher.executable.jar.ExecutableJarEntrypoint.invoke(ExecutableJarEntrypoint.java:42)
	at oracle.dbtools.launcher.executable.jar.ExecutableJarEntrypoint.main(ExecutableJarEntrypoint.java:64)

This was one of the reasons that a previous article on using Oracle APM with ORDS focused on using Apache Tomcat rather then ORDS standalone.

When running standalone, ORDS uses a built-in embedded jetty server for HTTP(s) service handling. The ords.war file contains a META-INF/MANIFEST.MF which has the following 2 lines used in standalone mode:

Main-Class: oracle.dbtools.launcher.executable.jar.ExecutableJarEntrypoint 
Executable-Jar-Main-Class: oracle.dbtools.cmdline.CommandLine

The first line is used by by the JVM to launch the main method in class ExecutableJarEntrypoint. Inside this main method ORDS is trying to read the second line. This is done via a mechanism like what is shown below:

ClassLoader cl = Thread.currentThread().getContextClassLoader();
URL url = cl.getResource("META-INF/MANIFEST.MF");

This works fine when running standalone since there is only 1 single war/jar file and therefore only a single manifest. However, when running with the APM Agent there are now 2 war/jar files and also 2 manifests in the classpath. In this case, the getResource call returns the MANIFEST.MF from the agent jar file instead of the MANIFEST.MF within ords.war file. This manifest from the APM agent does not have any Executable-Jar-Main-Class which makes ORDS throw the NullPointerException!

Workaround

Until this is addressed in ORDS java code, a simple workaround to allow the Oracle APM java agent monitor the standalone ORDS env is to simply edit the MANIFEST.MF file inside the agent to add a line like this:

Executable-Jar-Main-Class: oracle.dbtools.cmdline.CommandLine

This is an approach provided to me by the Oracle APM team.

Since a runtime environment may not have the jar command to unpack the agent jar, they were kind enough to write a python script to edit the agent jar for this and repackage everything properly afterwards.

import zipfile
import io
import shutil
# Specify the path to the APM Agent jar file
jar_filename = '/opt/oracle/product/oracle-apm-agent/bootstrap/ApmAgent.jar'
file_to_edit = 'META-INF/MANIFEST.MF'
new_line = 'Executable-Jar-Main-Class: oracle.dbtools.cmdline.CommandLine\n'
# Create a temporary file to hold the modified JAR content
temp_jar_filename = 'temp_modified.jar'
# Open the original JAR file in read mode
with zipfile.ZipFile(jar_filename, 'r') as original_jar:
    # Create a new JAR file in write mode
    with zipfile.ZipFile(temp_jar_filename, 'w') as temp_jar:
        for item in original_jar.infolist():
            # Copy all files from the original JAR to the new JAR
            if item.filename != file_to_edit:
                temp_jar.writestr(item, original_jar.read(item))
        # Read the contents of the file to be edited
        with original_jar.open(file_to_edit) as file:
            content = file.read().decode('utf-8')
        # Append the new line to the content
        modified_content = content + new_line
        # Write the modified content to the new JAR
        temp_jar.writestr(file_to_edit, modified_content.encode('utf-8'))
# Replace the original JAR with the modified JAR
shutil.move(temp_jar_filename, jar_filename)
print('New line appended and old file removed successfully.')

Change the jar_filename variable to the location of your ApmAgent.jar and save the above as ords_apm.py. Then run it..

>python3 ords_apm.py

New line appended and old file removed successfully.

Now you can use the Oracle APM Java Agent with your ORDS command line.

> ~/Downloads/ords-23.2.3.242.1937/bin/ords config list  
Oracle APM Agent: Starting APM Agent [premain]
Oracle APM Agent: Wrapper: version, hybrid
Oracle APM Agent: [DirectoryLocation] initialized on classloader [null]
Oracle APM - temp log directory is /var/folders/hq/6cg5drc54c3f371r9v65c8qm0000gn/T//
Oracle APM Agent: Parsing instrumentation directives
Oracle APM Agent: Loading directives from [built-in.directives]
Oracle APM Agent: Loading directives from [~/work/ora_apm/oracle-apm-agent/config/1.8.3326/DirectivesConfig.acml]
Oracle APM Agent: Parsed a total of [116] directives
Oracle APM Agent: Initializing AgentInstance
Oracle APM Agent: Initialized AgentInstance
Oracle APM Agent: Started [premain] Agent

ORDS: Release 23.2 Production on Thu Oct 05 19:47:01 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /path/to/config/

Database pool: default

Setting                  Value                                          Source     
----------------------   --------------------------------------------   -----------
database.api.enabled     true                                           Global     
db.hostname              localhost                                      Pool       
db.password              ******                                         Pool Wallet
db.port                  2193                                           Pool       
db.servicename           DB193P                                         Pool       
db.username              ORDS_PUBLIC_USER                               Pool       
feature.sdw              false                                          Pool       
jdbc.MaxLimit            100                                            Pool       
plsql.gateway.mode       disabled                                       Pool       
restEnabledSql.active    true                                           Pool       

Conclusion

It really is as simple as that. Note that a similar workaround might work with other java agents which encounter a NullPointerException.

How to check if ORDS is running

Oracle REST Data Services (ORDS) provides web services for an Oracle Database and allows for access to the database over the web. In a recent YouTube video I showed how to use ORDS in the new Oracle Developer DB Virtual Machine and briefly mentioned how to find the running ORDS process. Let’s go into that in more detail. In this article, we will discuss how to check if ORDS is running in a standalone ORDS deployments as well as deployments on Apache Tomcat and Oracle WebLogic Server.

  1. Checking ORDS on Standalone Deployments
  2. Checking ORDS on Apache Tomcat
  3. Checking ORDS on Oracle WebLogic Server
  4. What about Windows systems?
  5. Conclusion

Checking ORDS on Standalone Deployments

On a unix based system, the simplest way to check if ORDS is running is to use the jps command. This command will list all Java process that are currently running on the system. It is important to note that in order for jps to work, the JDK must be installed and the PATH environment variable must be set correctly. The jps command accepts a number of options and the most useful in this case are

  • -m to display the arguments passed to the main method
  • -l to display the full path name to the ords.war location. Pass that through a grep for ords to find the ords specific java process.
  • -v displays the arguments passed to the JVM.
jps -mlv | grep ords
8545 /home/oracle/ords/ords.war --config /home/oracle/ords_config serve --secure --port 443 -Doracle.dbtools.cmdline.home=/home/oracle/ords -Duser.language=en -Duser.region=US -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dnashorn.args=--no-deprecation-warning -Doracle.dbtools.cmdline.ShellCommand=ords -Duser.timezone=UTC

If ORDS is running in standalone mode, you should see a process with ords.war in the path. If this process is listed, then ORDS is running. Note that due to the -v option you also see the Java option oracle.dbtools.cmdline.home used. That should be the folder that the ords.war is found in and should also be the distribution directory. In the above example, the distribution directory is /home/oracle/ords/ and the ORDS command used to start ORDS was actually /home/oracle/ords/bin/ords --config /home/oracle/ords_config serve --secure --port 443

Another approach is to use the ps command. This command will list all processes currently running on the system. To check if ORDS is running, use the command ps -ef | grep ords.war. If you see a process with ords.war as the command, then ORDS is running.

[oracle@localhost ~]$ ps -ef | grep ords.war
oracle      8545    8516  2 21:13 pts/1    00:00:49 /home/oracle/java/jdk-11.0.17/bin/java -Doracle.dbtools.cmdline.home=/home/oracle/ords -Duser.language=en -Duser.region=US -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dnashorn.args=--no-deprecation-warning -Doracle.dbtools.cmdline.ShellCommand=ords -Duser.timezone=UTC -jar /home/oracle/ords/ords.war --config /home/oracle/ords_config serve --secure --port 443
oracle      9554    3521  0 21:42 pts/0    00:00:00 grep --color=auto ords.war

You’ll also see similar information as with the jps command but also the java executable used at start up too. Note that access to run ps is not always guaranteed and it main not be available for the user you are logged in as.

One note about the command line arguments one can see for the running Java processes. It can be seen by other users. This is one of the reasons that ORDS does not accept a password as a command line option. That password would be visible to anyone able to list the running processes. Either through jps or ps.

Checking ORDS on Apache Tomcat

If it’s enabled, use the Tomcat manager web application, or at the very least the catalina log files, to confirm that ORDS web application is deployed and started. If not, then jps on the command line is the next logical choice.

When ORDS is deployed on Apache Tomcat, you can use the jps command to check if the server is running and get further information about where the ords.war might be deployed. Run jps -mlv as before but this time grep for the catalina package.

jps -mlv | grep catalina
88 org.apache.catalina.startup.Bootstrap start --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Dconfig.url=/u01/oracle/properties/config -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp

The above indicates that catalina.base is /usr/local/tomcat and most people use the automatic web application deployment by placing the ords.war in the $CATALINA_BASE/webapps/ directory.

ls -la /usr/local/tomcat/webapps/
total 92384
drwxr-xr-x 1 root root       62 Apr 11 06:40 .
drwxr-xr-x 1 root root       77 Apr 11 06:40 ..
drwxr-x--- 5 root root       67 Apr 11 05:38 ords
-rw-r--r-- 1 root root 94599760 Apr 10 19:45 ords.war
drwxr-xr-x 3 root root       45 Apr 11 05:38 ROOT

That directory listing shows the ords.war file and that it has been exploded by Tomcat into the ords directory. It’s not a guarantee that ORDS was deployed successfully, one would have to check the Tomcat logs, but it is a good indicator.

Checking ORDS on Oracle WebLogic Server

If it’s enabled, use the WebLogic console or WLST to confirm that ORDS web application is deployed and started.

Similar to Apache Tomcat, the Oracle WebLogic Server is a Java application so yet again, it is the jps utility that is the starting point to get information on the server(s) running and their configuration.

jps -mvl | grep weblogic
80 weblogic.Server -Djava.security.egd=file:/dev/./urandom -Dlaunch.use.env.classpath=true -Dweblogic.Name=AdminServer -Djava.security.policy=/u01/oracle/wlserver/server/lib/weblogic.policy -Dconfig.url=/u01/oracle/properties/config -Djava.system.class.loader=com.oracle.classloader.weblogic.LaunchClassLoader -javaagent:/u01/oracle/wlserver/server/lib/debugpatch-agent.jar -da -Dwls.home=/u01/oracle/wlserver/server -Dweblogic.home=/u01/oracle/wlserver/server

The output indicates that the weblogic.home is /u01/oracle/wlserver/server. Unlike with Tomcat, one is not going to find a straight forward web application deployment directory under the weblogic.home. There’s a little more digging required to check if a server is configured to deploy ORDS and we’ll use the weblogic.Name parameter ( AdminServer in this case) to when digging deeper. The weblogic.home just indicates where the server java application is being executed from. The server runtime configuration is being picked up separately. In the majority of installations that will be in a related user_projects/domains directory found two levels up. For the above WLS instance that would be /u01/oracle/user_projects/domains/.

That directory may have the configuration for multiple domains but quite often there is just one: base_domain. From there you can get a little more information on how the server identified by the above weblogic.Name. The domain configuration is persisted in /u01/oracle/user_projects/domains/base_domain/config/config.xml and you can see there is an app-deployment configuration targetting the AdminServer.

<?xml version="1.0" encoding="UTF-8"?>
<domain xsi:schemaLocation="http://xmlns.oracle.com/weblogic/security/wls http://xmlns.oracle.com/weblogic/security/wls/1.0/wls.xsd http://xmlns.oracle.com/weblogic/domain http://xmlns.oracle.com/weblogic/1.0/domain.xsd http://xmlns.oracle.com/weblogic/security http://xmlns.oracle.com/weblogic/1.0/security.xsd http://xmlns.oracle.com/weblogic/security/xacml http://xmlns.oracle.com/weblogic/security/xacml/1.0/xacml.xsd" xmlns="http://xmlns.oracle.com/weblogic/domain" xmlns:sec="http://xmlns.oracle.com/weblogic/security" xmlns:wls="http://xmlns.oracle.com/weblogic/security/wls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <name>base_domain</name>
  <domain-version>14.1.1.0.0</domain-version>
...
  <server>
    <name>AdminServer</name>
    <ssl>
      <name>AdminServer</name>
      <enabled>true</enabled>
    </ssl>
    <listen-address/>
  </server>
...
  <app-deployment>
    <name>ords</name>
    <target>AdminServer</target>
    <source-path>/u01/oracle/ords/ords.war</source-path>
    <staging-mode>nostage</staging-mode>
  </app-deployment>
...
</domain>

If you see this app-deployment configuration in place it is not a complete guarantee that ORDS successfully deployed when the server started but it is a good indication.

What about Windows systems?

The syntax for path separates might be different but the same jps utility options apply and the relative file locations will also be the same for standalone, Tomcat and WebLogic.

Conclusion

In summary, the jps utility, also known as the Java Virtual Machine Process Status Tool, that comes with your Java SE distribution is your friend. Just from the command line, it can guide you in discovering more about the ORDS instance running on your machine irrespective of deployment mode.