Unlock GraphQL – ORDS on GraalVM with Docker

ORDS 23.3.0 introduced GraphQL functionality which makes it possible for a user to define complex queries on REST Enable tables and views in the database. Specific information on how to use GraphQL is in the ORDS Developer Guide. Although ORDS is known as a Java Web Application, the GraphQL implementation is primarily in JavaScript. To use the GraphQL feature, ORDS must be running on Oracle GraalVM with the JavaScript component installed.

That’s as simple as downloading the Oracle GraalVM ( for Java 11 or Java 17 ), installing the JavaScript component, and running ORDS as usual with that JVM. See GraalVM Configuration in the Installation and Configuration Guide.

Running with docker / podman / kubernetes is also straight forward but there are one or two steps that may not be intuitive. Building on concepts introduced in a previous article I’ll walk through the steps to create an image for running the latest release of ORDS on Oracle GraalVM Enterprise.

The goal is to build a docker image which is based on a GraalVM image. The build process for that image will install the required GraalVM JavaScript component and download the latest release of ORDS. The resulting image can be used to spin up a new container which is running ORDS standalone. Similar to the containers used from the above previous article, the containers will use a shared docker volume for configuration.

Getting GraalVM

GraalVM is a high-performance runtime that provides support for various programming languages and execution modes. It includes a Java Virtual Machine (JVM) that is enhanced with the GraalVM compiler, which can improve the performance of Java applications such as ORDS. GraalVM is a polyglot virtual machine that supports multiple programming languages, including JavaScript, Python and more. To use these programming languages, specific components must also be installed, and as is common, licences for their use must be agreed. The Oracle Free Use Terms and Conditions licence that ORDS is available under, and the GraalVM Free Use Terms and Conditions licence that GraalVM is available under, do not extend to these components. Although convenient Docker images are available, to run a Java application which also uses JavaScript requires that JavaScript runtime component to be installed in the image. To install that component requires an activated download token which confirms you have accepted the software licence. This article will cover getting that token, but first, which image should be used?

GraalVM Image

Oracle GraalVM Enterprise container images are published in the Oracle Container Registry and there are quite a few to choose from. The following images are available:

Image NameDescription
jdk-eeA compact image containing the GraalVM Enterprise JDK.
native-image-eeA compact image containing the GraalVM Enterprise native-image utility and JDK
enterpriseProvides the GraalVM Enterprise JDK along with the gu (Graal Updater) utility to enable installation of additional features.
nodejs-eeIncludes the Node.js runtime and the GraalVM Enterprise JDK.
Images available at container-registry.oracle.com

We will require a GraalVM Enterprise JDK to run ORDS which is a Java application but will also require the JavaScript component installed so we’ll need the Graal Updater utility too. That means the enterprise image is the one for us. In fact, we’ll use container-registry.oracle.com/graalvm/enterprise:ol8-java17 which gives us a Java 17 JDK. At the time of writing, the only supported Java versions for ORDS are Java 11 and Java 17.

GraalVM Download Token

To install the GraalVM JavaScript component requires an activated download token. This token indicates that you have accepted a licence agreement for using the component and in this article we pass it as a parameter when building the image. There are a number of ways to obtain a token, in fact just by using the Graal Updater utility ( gu ) to install a component will initiate it…

Downloading: Artifacts catalog from gds.oracle.com
Skipping ULN EE channels, no username provided.
Downloading: Component catalog from www.graalvm.org
Processing Component: Graal.js
Enter your download token and press ENTER, or press ENTER to generate a new download token.
Enter a valid download token:

However, at this stage up may not have the Graal Updater on your machine. The most convenient way on a unix based operating system with bash is the handy dandy GraalVM Download Token Generator.

bash <(curl -sL https://get.graalvm.org/ee-token)

That will run a script to generate a token and initiate the licence agreement process. In this scenario I just copy the token string rather than persisting it.

About to request a download token for GraalVM Enterprise Edition...
Please provide an email address and review Oracle's Privacy Policy at https://www.oracle.com/legal/privacy/privacy-policy.html.
Enter a valid email address: pobalopalous@ordsexample.org
Your new download token is: 'NzY4MDYwYTk4ZjIyMTZmNTgzYWE3NzMwNGFkOGJiMmIwZTVlYTU3MmI3YThjZmY3NzExYzFlOWQxNjgyNTUwZGNiNzUxNmNlODIxNTkwYjM0MjA4NzkwNDVhNzUx'
Please check your email inbox and accept the license to activate your download token.
Would you like to persist this token in '/Users/pobalopalous/.gu/config' on this machine? (y/N): N
Download token not persisted.

Now I have an 88 character token but it has not been activated. Next, I must check my email for a link to accept the licence.

Email for activating the download token
Click that “Accept license and verity download token” button

The link will take you through the Oracle SSO sign-on process if you’re not already logged in. Keep the download token value safe. It will be baked into your docker image so do not go publishing the image out on the internet, in other words … distributing the software, because the token can be linked back to your Oracle SSO account.

Get Building

Now that an activated download token is at hand we can get on with building the docker image. The Dockerfile is very similar to the one used in Get started with Oracle REST Data Services (ORDS) and Docker but has a couple of extra lines for installing the JavaScript component.

#
# Defines a docker image, based on the Oracle JDK image, to run Oracle REST Data Services. During the image building 
# process the most recent version of ORDS will be automatically downloaded and extracted.
#
# Volumes for configuration and lib/ext are defined.
#
# docker run -p 8080:8080 -v ords-config:/opt/ords-config/ -v ords-lib-ext:/opt/ords/latest/lib/ext ords-latest/graaljdk
#
# See https://peterobrien.blog/ for more information and examples.
#
FROM container-registry.oracle.com/graalvm/enterprise:ol8-java17
MAINTAINER Peter O'Brien

# Get an Oracle Graal EE Download token and provide that value when building the image.
# See https://github.com/graalvm/graalvm-jdk-downloader/blob/main/README.md#set-up-a-download-token-for-graalvm-enterprise-edition-installations
# docker build  --build-arg="GRAAL_TOKEN=Get your own token" --tag ords-latest/graaljdk .
# Note that token will be baked into the image.
ARG GRAAL_TOKEN
ENV GRAAL_EE_DOWNLOAD_TOKEN=$GRAAL_TOKEN
RUN gu install --auto-yes --non-interactive js

ENV LATEST=/opt/ords-latest/
ENV CONFIG=/opt/ords-config/
WORKDIR $LATEST
ADD https://download.oracle.com/otn_software/java/ords/ords-latest.zip $LATEST
RUN jar xf ords-latest.zip; rm ords-latest.zip; chmod +x bin/ords
VOLUME $LATEST/lib/ext/ $CONFIG
EXPOSE 8080
EXPOSE 8443
WORKDIR $CONFIG
ENTRYPOINT ["/opt/ords-latest/bin/ords"]
CMD ["serve"]

When running docker build with this Dockerfile the download token must be provided as an argument. Otherwise there will be a build failure similar to this:

Step 3/13 : RUN gu install --auto-yes --non-interactive js
 ---> Running in 86da6cdbc24f
Downloading: Artifacts catalog from gds.oracle.com
Skipping ULN EE channels, no username provided.
Downloading: Component catalog from www.graalvm.org
Processing Component: Graal.js
Enter your download token and press ENTER, or press ENTER to generate a new download token.
Enter a valid download token:The command '/bin/sh -c gu install --auto-yes --non-interactive js' returned a non-zero code: 5

Convenient Dockerfile

To make things even easier, I’ve created a Dockerfile that you can use and the only thing you have to change is the token value on the command line:

docker build  --build-arg="GRAAL_TOKEN=NzY4MDYwYTk4ZjIyMTZmNTgzYWE3NzMwNGFkOGJiMmIwZTVlYTU3MmI3YThjZmY3NzExYzFlOWQxNjgyNTUwZGNiNzUxNmNlODIxNTkwYjM0MjA4NzkwNDVhNzUx" --tag ords-latest/graaljdk https://gist.githubusercontent.com/pobalopalous/a8c78d45c69fdb40763c913e2bda82c6/raw/4b57a40639385085e51dae21dec4b3fec7b1708c/

Let’s break those docker build command arguments down..

--build-arg="GRAAL_TOKEN=NzY4MDYwYTk4ZjIyMTZmNTgzYWE3NzMwNGFkOGJiMmIwZTVlYTU3MmI3YThjZmY3NzExYzFlOWQxNjgyNTUwZGNiNzUxNmNlODIxNTkwYjM0MjA4NzkwNDVhNzUx"

This specifies the GRAAL_TOKEN build argument that the Dockerfile will use to set the GRAAL_EE_DOWNLOAD_TOKEN environment variable which is used by the gu install js command when building the image.

--tag ords-latest/graaljdk 

The image that we build is going to be called ords-latest/graaljdk. That will differentiate it from the ords-latest/oraclejdk image we created in Get started with Oracle REST Data Services (ORDS) and Docker.

https://gist.githubusercontent.com/pobalopalous/a8c78d45c69fdb40763c913e2bda82c6/raw/4b57a40639385085e51dae21dec4b3fec7b1708c/

That’s the URL for the Dockerfile build context I have created for you so you don’t have to copy the text locally. Isn’t that nice?

Run that with your download token and you’ll see something like this…

Downloading build context from remote url: https://gist.githubusercontent.com/pobalopalous/a8c78d45c69fdb40763c913e2bda82c6/raw/4b57a40Downloading build context from remote url: https://gist.githubusercontent.com/pobalopalous/a8c78d45c69fdb40763c913e2bda82c6/raw/4b57a40639385085e51dae21dec4b3fec7b1708c/ORDS_Latest_GraalVM_Dockerfile [==================================================>]  1.371kB/1.371kB
Sending build context to Docker daemon  3.072kB
Step 1/16 : FROM container-registry.oracle.com/graalvm/enterprise:ol8-java17
 ---> 64dca1a5fa2a
Step 2/16 : MAINTAINER Peter O'Brien
 ---> Running in 33db11ef151e
Removing intermediate container 33db11ef151e
 ---> d38b7a7f3b67
Step 3/16 : ARG GRAAL_TOKEN
 ---> Running in 7613b2d725f7
Removing intermediate container 7613b2d725f7
 ---> f67809364f83
Step 4/16 : ENV GRAAL_EE_DOWNLOAD_TOKEN=$GRAAL_TOKEN
 ---> Running in fa3dc864e3b5
Removing intermediate container fa3dc864e3b5
 ---> 5fb687d736d1
Step 5/16 : RUN gu install --auto-yes --non-interactive js
 ---> Running in 9fb627218998
Downloading: Artifacts catalog from gds.oracle.com
Skipping ULN EE channels, no username provided.
Downloading: Component catalog from www.graalvm.org
Processing Component: Graal.js
Downloading: Component js: Graal.js from gds.oracle.com
Installing new component: Graal.js (org.graalvm.js, version 22.3.3)
Refreshed alternative links in /usr/bin/
Removing intermediate container 9fb627218998
 ---> ecef6d97d8db
Step 6/16 : ENV LATEST=/opt/ords-latest/
 ---> Running in f661804977a7
Removing intermediate container f661804977a7
 ---> 1005a3e3690b
Step 7/16 : ENV CONFIG=/opt/ords-config/
 ---> Running in 50901e104315
Removing intermediate container 50901e104315
 ---> 8a376e3151ee
Step 8/16 : WORKDIR $LATEST
 ---> Running in bd9810fe29f9
Removing intermediate container bd9810fe29f9
 ---> c18a57d2a8db
Step 9/16 : ADD https://download.oracle.com/otn_software/java/ords/ords-latest.zip $LATEST
Downloading [==================================================>]  114.1MB/114.1MB
 ---> 3cec2554cd2b
Step 10/16 : RUN jar xf ords-latest.zip; rm ords-latest.zip; chmod +x bin/ords
 ---> Running in 50a97beac418
Removing intermediate container 50a97beac418
 ---> 3f9fff9bded5
Step 11/16 : VOLUME $LATEST/lib/ext/ $CONFIG
 ---> Running in 0276fc517e42
Removing intermediate container 0276fc517e42
 ---> 5a8b5e5947e6
Step 12/16 : EXPOSE 8080
 ---> Running in c4dbf726988b
Removing intermediate container c4dbf726988b
 ---> c2ad8e7b94d3
Step 13/16 : EXPOSE 8443
 ---> Running in da46a9b873a3
Removing intermediate container da46a9b873a3
 ---> 5619e6b04ece
Step 14/16 : WORKDIR $CONFIG
 ---> Running in d5959c70115b
Removing intermediate container d5959c70115b
 ---> 440d4e54a4ce
Step 15/16 : ENTRYPOINT ["/opt/ords-latest/bin/ords"]
 ---> Running in 4452ce4de261
Removing intermediate container 4452ce4de261
 ---> f23c5cf67824
Step 16/16 : CMD ["serve"]
 ---> Running in 9f9f69dce7ae
Removing intermediate container 9f9f69dce7ae
 ---> ab2e0a2981e0
Successfully built ab2e0a2981e0
Successfully tagged ords-latest/graaljdk:latest

If you have made it this far, you now have an ords-latest/graaljdk image ready to be put into action.

Now RUN !

This is the part we’ve been waiting for. Remember that just like in Get started with Oracle REST Data Services (ORDS) and Docker the database already has ORDS installed and the configuration directory is a docker volume called ords-adb-config which gets mapped to /opt/ords-config/ in the container.

docker run --detach --rm --name ords-latest-8080 \
             -p 8080:8080 \
             -v ords-adb-config:/opt/ords-config/ \
             ords-latest/graaljdk

That will run in the background so you can use the docker logs command to check the output: docker logs -f ords-latest-8080

ORDS: Release 23.3 Production on Fri Nov 17 13:32:11 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /opt/ords-config/

2023-11-17T13:32:11.890Z INFO        HTTP and HTTP/2 cleartext listening on host: 0.0.0.0 port: 8080
...
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.vendor.version=GraalVM EE 22.3.3
...
2023-11-17T13:32:27.906Z INFO        

Mapped local pools from /opt/ords-config/databases:
  /ords/                              => default                        => VALID     


2023-11-17T13:32:28.233Z INFO        Oracle REST Data Services initialized
Oracle REST Data Services version : 23.3.0.r2891830
Oracle REST Data Services server info: jetty/10.0.17
Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 17.0.8+9-LTS-jvmci-22.3-b21

The GraphQL implementation in ORDS is based on REST Enabled tables and views. Therefore, to get any sensible use of GraphQL with ORDS one must first have a REST Enabled schema with some REST Enabled tables or views. Thankfully I already have that in my database so I can dive right in…

GraphQL in action with ORDS on GraalVM

Over to you

To sum it up, this article explained how to create a Docker image using GraalVM, specifically focusing on adding the JavaScript component needed for ORDS GraphQL. The image build process involves installing the GraalVM JavaScript part and downloading the latest ORDS release, resulting in a Docker image. Before doing so, you had to get and activate a download token so that the JavaScript component could be installed in the image. This image, when used, makes it easy to start a standalone ORDS container. You now have a straightforward solution for using ORDS GraphQL in a GraalVM environment that is ready for production.

To explore all the powerful querying options available to you with ORDS GraphQL see the relevant chapter in the ORDS Developer Guide.

Beyond the Common Name: Secure multiple domains with a single self-signed certificate

Oracle REST Data Services (ORDS) will generate a self-signed automatically when starting for the first time in standalone mode. That is, if it has not already been configured with an existing cert and key. 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. The upshot is that you are limited to one certificate for one domain. In this article, we explore the untapped potential of self-signed certificates with Subject Alternative Name (SAN) extension, enabling you to secure multiple domains efficiently

Understanding Self-Signed Certificates

Self-signed certificates are created and signed by the same entity without involving a trusted third-party Certificate Authority (CA). They provide encryption and authentication capabilities, although they are not initially recognised by most browsers and operating systems. Self-signed certificates can be manually trusted or imported, making them a viable option for specific use cases.

Subject Alternative Name (SAN)

SAN is an extension within the X.509 certificate standard that allows multiple domain names to be associated with a single certificate. Unlike the limited scope of the Common Name field, SAN enables you to secure multiple domains or subdomains with one certificate, simplifying management and reducing costs.

Benefits of Self-Signed Certificates with SAN

  • Enhanced Flexibility: Self-signed certificates with SAN offer the flexibility to secure numerous domains or subdomains, accommodating complex web architectures and diverse configurations.
  • Cost-Effectiveness: By leveraging self-signed certificates, you can save costs associated with purchasing individual certificates for each domain.
  • Internal Environments: Self-signed certificates with SAN are especially valuable for securing internal networks, intranets, or testing environments that don’t require public trust.
  • Rapid Deployment: Generating and deploying self-signed certificates with SAN is a swift and straightforward process, making it ideal for time-sensitive projects.

Considerations and Best Practices:

  • Trust Management: Users must manually trust self-signed certificates, so clear instructions or documentation on certificate importation are essential.
  • Certificate Lifecycle: Although self-signed certificates have no defined expiration period, regular rotation is recommended to uphold security best practices.
  • Public-Facing Websites: For public-facing websites, obtaining trusted certificates from recognised CAs is crucial to establish trust with visitors and avoid browser warnings.
  • Hybrid Approach: Consider combining self-signed certificates with SAN for internal domains and trusted CA certificates for public-facing domains to strike a balance between cost-efficiency and public trust.
  • Load Balancers: For public-facing websites, use a load balancer in front of your ORDS instance(s). That way you can aim for high availability but also configure single Common Name certs for public-facing services.

Generate your self-signed SAN cert…

Try saying that fast three times 😀

There’s no command in ORDS to generate self-signed certificate with a Subject Alternative Name ( SAN ) field. To achieve this we’ll use the openssl utility which is most likely already available on your machine. The example openssl command below can be executed where the ORDS configuration directory is the working directory. The output is two files with the same name as what ORDS uses as the default standalone.https.cert.key and standalone.https.cert configuration settings respectively.

openssl req -x509 -nodes -days 365 \
       -newkey rsa:2048 \
       -keyout global/standalone/self-signed.key \
       -out global/standalone/self-signed.pem \
       -subj "/CN=localhost" \
       -addext "subjectAltName = DNS:vanity.example.com, DNS:myhost.example

In the above example the self-signed certificate is generated with localhost in the Common Name ( CN ) field. This is the same as the certificate that would normally be generated by ORDS. However, that certificate also has a subjectAltName field listing two DNS hostnames: vanity.example.com and myhost.example. Start up ORDS and your traffic to the server will be secured for both domains. Note that you will have to look after your DNS network routing so that the domain names you choose route to the machine you are running ORDS on.

That certificate and key could also be used with your load balancer to accept HTTPS traffic too. As mentioned earlier, this is really only suitable for internal environments where you can have your users manually trust the certificate.

Conclusion

Ditching the limitations of the Common Name field, self-signed certificates with Subject Alternative Name (SAN) extension enable you to secure multiple domains with ease. While self-signed certificates may not be suitable for public-facing websites, these certificates provide numerous benefits for internal environments or specific use cases. By using self-signed certificates with SAN in your ORDS instance, you can fortify your infrastructure security while efficiently managing multiple domains, all without breaking the bank.

Oracle Developer DB Virtual Machine – a closer look

The recently announced Oracle Developer DB Virtual Machine comes with ORDS 23.1.0 and the new Oracle 23c Database Free. In this video I walk through starting the virtual machine, restarting ORDS to use HTTPS, accessing ORDS from the host machine as well as stopping and restarting the virtual machine. The new “converged database” has a number of new features that ORDS works with. One that is not widely known is the Property Graph and it is demonstrated in this video.

Links that are mentioned in the video

Developer DB VM: https://www.oracle.com/database/technologies/databaseappdev-vm.html

Jeff Smith’s Announcement: https://www.thatjeffsmith.com/archive/2023/04/oracle-database-23c-free-developer-release-on-virtualbox/

Information about Property Graph: https://blogs.oracle.com/database/post/operational-property-graphs-in-oracle-database-23c-free-developer-release

Get started with Oracle REST Data Services (ORDS) and Docker

  1. Overview
  2. Autonomous Database
    1. Oracle Content Delivery Network
  3. ORDS Latest on Docker
    1. Dockerfile for ORDS Entrypoint
  4. Docker volume for ORDS configuration
    1. Configuration for Customer Managed ORDS
  5. Start it up!
    1. Verify
  6. Conclusion

Overview

Welcome to the third instalment of my series on using Oracle REST Data Services (ORDS), NGINX, Docker, SSL and Autonomous Database! In this article, I will show you how to quickly get started using ORDS and Docker. Together we will walk through the basics of building the Docker image, storing configuration in a Docker volume, running multiple ORDS instances and balancing the load using NGINX. With the help of this guide, you will be able to have a load balanced Customer Managed ORDS with Autonomous Database up and running in no time. To recap on the previous articles:

  • Load Balancing ORDS with NGINX introduced the concept of load balancing and the most basic of configurations to get started with NGINX running in docker. That was entirely using HTTP as the transport protocol.
  • HTTPS Load Balance: NGINX & ORDS took that a step further by using a self signed certificate so that the traffic between client and server was over the more secure HTTPS protocol. That was with ORDS instances running on port 8080 and 8090.

Autonomous Database – hosted and managed for free

Autonomous Database

In this article the ORDS instances will be running in Docker and sharing a configuration for an Autonomous Database hosted on Oracle Cloud Infrastructure Free Tier resources. The prerequisite for this article is an understanding of Installing and Configuring Customer Managed ORDS on Autonomous Database. The database has ORDS and APEX already installed. However, the credentials for ORDS Runtime user and PLSQL Gateway user are not known so the ords install adb command instruction will be used to create and configure additional users in the database to be used by our new ORDS instances.

Oracle Content Delivery Network

In the previous article we had the APEX images in the global/doc_root directory. It is much easier to not have to configure an ORDS instance to serve those static files and to use the Oracle Content Deliver Network instead. One should note that by default, the APEX installation in the Autonomous Database does not use the Oracle CDN for the APEX static resources.  So if you have not done so already, use Oracle CDN for the APEX images. The URL to use will depend on the version of APEX in use. At the time of writing, that is APEX 22.2.0. Once you have made this change the next APEX upgrade will keep the IMAGE_PREFIX parameter in synch. See
https://support.oracle.com/epmos/faces/DocumentDisplay?id=2817084.1 and https://blogs.oracle.com/apex/post/running-customer-managed-ords-on-autonomous-database-heres-how-to-get-ready-for-apex-211-upgrade for more information on using Oracle CDN with APEX

begin
  apex_instance_admin.set_parameter(
  p_parameter => 'IMAGE_PREFIX',
  p_value => 'https://static.oracle.com/cdn/apex/22.2.0/' );
  commit;
end;

ORDS Latest on Docker

As shown in the previous article it is already straight forward to use ORDS from the command line to configure and run in standalone mode. In doing so, you are satisfying the most fundamental requirement for ORDS by providing a supported Java Runtime Environment for it to run in. Running ORDS in Docker takes care of that dependancy and provides a consistent structure. For your convenience, I have defined a Dockerfile to create an image with the latest version of ORDS built in. It does require the JDK 17 image from Oracle Container Registry jdk repository. To use images from the Oracle Container Registry you must first sign in using your Oracle Account to accept the license agreement for the Oracle image. Once you have accepted the licence, follow the installation instructions on the page to login and pull the jdk:17 image:

> docker login container-registry.oracle.com
Username: <Oracle Account Username>
Password: <Oracle Account Password>
Login successful.

> docker pull container-registry.oracle.com/java/jdk:17
17: Pulling from java/jdk
0b93191bf088: Pull complete 
f5a748ad7565: Pull complete 
004350aa024a: Pull complete 
Digest: sha256:6ca4abe688e437a2189e54e42fc8325ed9d7230286f61bfb0199b8e693423f70
Status: Downloaded newer image for container-registry.oracle.com/java/jdk:17
container-registry.oracle.com/java/jdk:17

That will pull into your local Docker repository the most recent Oracle JDK 17 build.

Dockerfile for ORDS Entrypoint

The configuration is quite simple. A couple of folders are exposed for providing configuration and library extensions. That configuration directory is essential but in the majority of cases, customers do not have custom extensions so the lib/ext folder will not be used in this article. Similarly, although the Dockerfile specifies that both port 8080 and port 8443 should be exposed, we will only be using port 8080 for HTTP traffic in this article. It is NGINX that will be terminating the HTTPS traffic before routing upstream to our ORDS instances.

The Dockerfile we’ll use to create the ORDS image is available at ORDS_Latest_Dockerfile. Contents listed below.

#
# Defines a docker image, based on the Oracle JDK image, to run Oracle REST Data Services. During the image building 
# process the most recent version of ORDS will be automatically downloaded and extracted.
#
# Volumes for configuration and lib/ext are defined.
#
# docker run -p 8080:8080 -v ords-adb-config:/opt/ords-config/ -v ords-lib-ext:/opt/ords/latest/lib/ext ords-latest/oraclejdk
#
# See https://peterobrien.blog/ for more information and examples.
#
FROM container-registry.oracle.com/java/jdk:17
MAINTAINER Peter O'Brien
ENV LATEST=/opt/ords-latest/
ENV CONFIG=/opt/ords-config/
WORKDIR $LATEST
ADD https://download.oracle.com/otn_software/java/ords/ords-latest.zip $LATEST
RUN jar xf ords-latest.zip; rm ords-latest.zip; chmod +x bin/ords
VOLUME $LATEST/lib/ext/ $CONFIG
EXPOSE 8080
EXPOSE 8443
WORKDIR $CONFIG
ENTRYPOINT ["/opt/ords-latest/bin/ords"]
CMD ["serve"]

To use the above Dockerfile and build an image locally called ords-latest/oraclejdk use the following command

> docker build --tag ords-latest/oraclejdk \
https://gist.githubusercontent.com/pobalopalous/fc6ab4ee777f6b7f32a400e920df682d/raw/ORDS_Latest_Dockerfile

Downloading build context from remote url: https://gist.githubusercontent.com/pobalopalous/fc6ab4ee777f6b7f32a400e920df682d/raw/ORDS_Latest_Dockerfile [===============Downloading build context from remote url: https://gist.githubusercontent.com/pobalopalous/fc6ab4ee777f6b7f32a400e920df682d/raw/ORDS_Latest_Dockerfile [==================================================>]     878B/878B
Downloading build context from remote url: https://gist.githubusercontent.com/pobalopalous/fc6ab4ee777f6b7f32a400e920df682d/raw/ORDS_Latest_Dockerfile [==================================================>]     878B/878B
Sending build context to Docker daemon   2.56kB
Step 1/13 : FROM container-registry.oracle.com/java/jdk:17
 ---> 4945318567e9
Step 2/13 : MAINTAINER Peter O'Brien
 ---> Using cache
 ---> 1bb5b3ea1d92
Step 3/13 : ENV LATEST=/opt/ords-latest/
 ---> Using cache
 ---> 4798e9cbc8d1
Step 4/13 : ENV CONFIG=/opt/ords-config/
 ---> Using cache
 ---> a1f6e0bf441c
Step 5/13 : WORKDIR $LATEST
 ---> Using cache
 ---> 1b961db4ee2d
Step 6/13 : ADD https://download.oracle.com/otn_software/java/ords/ords-latest.zip $LATEST
Downloading [==================================================>]  94.62MB/94.62MB
 ---> Using cache
 ---> f6d009ada2f1
Step 7/13 : RUN jar xf ords-latest.zip; rm ords-latest.zip; chmod +x bin/ords
 ---> Using cache
 ---> f6d20c737486
Step 8/13 : VOLUME $LATEST/lib/ext/ $CONFIG
 ---> Using cache
 ---> fde34609973e
Step 9/13 : EXPOSE 8080
 ---> Using cache
 ---> 77933cb86baa
Step 10/13 : EXPOSE 8443
 ---> Using cache
 ---> 094fc3d8332b
Step 11/13 : WORKDIR $CONFIG
 ---> Using cache
 ---> 2d1b41e2c6f0
Step 12/13 : ENTRYPOINT ["/opt/ords-latest/bin/ords"]
 ---> Using cache
 ---> 9974ac45526d
Step 13/13 : CMD ["serve"]
 ---> Using cache
 ---> 4cbe74b80bb5
Successfully built 4cbe74b80bb5
Successfully tagged ords-latest/oraclejdk:latest

You now have an image in your local Docker repository ready to run. Note that the base image is an Oracle JDK 17 one. You can of course change that to something else. At the time of writing, only Oracle JDK 11 and 17 are supported Java Runtime Environments for ORDS.

Docker volume for ORDS configuration

Now it’s time to start putting the ORDS configuration together. In the previous article I outlined a configuration folder structure which was defined on the host computer file system. We are deviating from that in two ways. First, as outlined above, we will not have any APEX images in the global/doc_root directory because we are using the Oracle CDN with APEX in the hosted Autonomous Database. Second, we’re using a Docker volume, rather than the local filesystem, to store all the configuration.

Docker volumes are an ideal way to persist data generated by and used by Docker containers. They provide several benefits, such as:

  • Data isolation: Docker volumes are independent of the underlying filesystem, which ensures that the data persists even if the container is moved to a different host.
  • Easy deployment: Docker volumes can be shared across multiple containers and hosts, making it easy to deploy applications in different environments.
  • Data security: Docker volumes are stored outside the container, so they are not affected by any changes within the container. This ensures that your data remains secure and consistent.
  • Performance: Docker volumes are stored on the host system, which can be faster than using shared storage. This can improve the performance of your containers.

The first configuration item for a Customer Managed ORDS on Autonomous Database is the wallet and getting that wallet zip file into the Docker volume involves a few steps that may not be intuitive if you are not familiar with Docker volumes. You see, to copy a file into a Docker volume, one must do that through a running container, but before we have a running container, we must first create the volume.

> docker volume create ords-adb-config
ords-adb-config

Let’s assume you have downloaded your Autonomous Database wallet zip file to your ~/Downloads directory. For example: ~/Downloads/Wallet_DB202301101106.zip. We’re going to put it in the ords-adb-config volume as /opt/ords-config/Wallet_Autonomous.zip but first we must start a container to use it.

> docker run --detach --rm --name ords-latest \
             -v ords-adb-config:/opt/ords-config/ \
             ords-latest/oraclejdk

Note that we’re not mapping to any ports and once we’re finished with this container it will be removed. Let’s copy that wallet zip file. We know the name of the container is ords-latest because that’s the name we gave in the docker run command. Your wallet file name will be different but we’re going to copy it to /opt/ords-config/Wallet_Autonomous.zip to keep things simple for subsequent commands. If you are going to have multiple pools, you will have to have distinct filenames.

> docker cp ~/Downloads/Wallet_DB202301101106.zip \
            ords-latest:/opt/ords-config/Wallet_Autonomous.zip

That ords-latest container is no longer required. It only came into existence to allow you to copy the zip file. When you stop the container it should be removed automatically.

> docker stop ords-latest

Configuration for Customer Managed ORDS

The wallet zip file is a good start but now it’s time to run through the Customer Managed ORDS with Autonomous Database install step which will create additional users in the database and store the necessary pool settings in the ords-adb-config Docker volume. We’re going to use the non-interactive silent installation so will have to provide the passwords for the existing ADMIN user, and the two users to create. Referring back to the ORDS documentation, the ords install adb command is…

ords install adb --admin-user <DATABASE USER> \
                 --db-user <DATABASE USER> \
                 --gateway-user <DATABASE USER>
                 --wallet <PATH TO ZIP FILE>
                 --wallet-service-name <NET SERVICE NAME>
                 --feature-sdw <BOOLEAN>
                 --feature-db-api <BOOLEAN> \
                 --feature-rest-enabled-sql <BOOLEAN> \
                 --password-stdin < adbs_passwords.txt

Let’s create that file with the passwords to use. We can delete it once the ords install adb command completes. Create the adbs_passwords.txt file with three passwords on each line:

<PASSWORD FOR admin-user>
<PASSWORD FOR db-user>
<PASSWORD FOR gateway-user>

In my case the adbs_passwords.txt file looks like this:

MyADMIN_password_1s_a_s@cret
K@@PThe!RuntimeUserPr1vate
G@teWayUs3r!IsHidden

With my passwords file I can pass all these details in one command as I run it in Docker. Note that the entire command line also specifies -i which instructs the docker engine to use standard input ( STDIN ) for the container.

> docker run -i -v ords-adb-config:/opt/ords-config/ \
ords-latest/oraclejdk \
install adb \
--admin-user ADMIN \
--db-user ORDS_PUBLIC_USER2 \
--gateway-user ORDS_PLSQL_GATEWAY2 \
--wallet /opt/ords-config/Wallet_Autonomous.zip \
--wallet-service-name db202301101106_low \
--feature-sdw true \
--feature-db-api true \
--feature-rest-enabled-sql true \
--password-stdin < adbs_passwords.txt

ORDS: Release 22.4 Production on Mon Mar 06 09:52:30 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
/opt/ords-config/

Oracle REST Data Services - Non-Interactive Customer Managed ORDS for Autonomous Database
Connecting to Autonomous database user: ADMIN TNS Service: db202301101106_low
Retrieving information
Checking Autonomous database user: ORDS_PLSQL_GATEWAY2 TNS Service: db202301101106_low
The setting named: db.wallet.zip.path was set to: /opt/ords-config/Wallet_Autonomous.zip in configuration: default
The setting named: db.wallet.zip.service was set to: db202301101106_low in configuration: default
The setting named: db.username was set to: ORDS_PUBLIC_USER2 in configuration: default
The setting named: db.password was set to: ****** in configuration: default
The setting named: plsql.gateway.mode was set to: proxied in configuration: default
The setting named: feature.sdw was set to: true in configuration: default
The global setting named: database.api.enabled was set to: true
The setting named: restEnabledSql.active was set to: true in configuration: default
The setting named: security.requestValidationFunction was set to: ords_util.authorize_plsql_gateway in configuration: default
2023-03-06T09:52:38.256Z INFO Connecting to Autonomous database user: ADMIN TNS Service: db202301101106_low
------------------------------------------------------------
Date : 06 Mar 2023 09:52:38
Release : Oracle REST Data Services 22.4.4.r0411526

Database : Oracle Database 19c Enterprise Edition
DB Version : 19.18.0.1.0
------------------------------------------------------------
Container Name: C4TOSECRETNQ2JA_DB202301101106
------------------------------------------------------------

[*** script: ords_runtime_user.sql]

PL/SQL procedure successfully completed.

2023-03-06T09:52:42.532Z INFO ... Verifying Autonomous Database runtime user
[*** script: ords_gateway_user.sql]

PL/SQL procedure successfully completed.

2023-03-06T09:52:43.674Z INFO ... Verifying Autonomous Database gateway user
2023-03-06T09:52:43.675Z INFO Completed configuring for Customer Managed Oracle REST Data Services version 22.4.4.r0411526. Elapsed time: 00:00:05.407

[*** Info: Completed configuring for Customer Managed Oracle REST Data Services version 22.4.4.r0411526. Elapsed time: 00:00:05.407
]
2023-03-06T09:52:43.720Z INFO To run in standalone mode, use the ords serve command:
2023-03-06T09:52:43.723Z INFO ords --config /opt/ords-config serve
2023-03-06T09:52:43.723Z INFO Visit the ORDS Documentation to access tutorials, developer guides and more to help you get started with the new ORDS Command Line Interface (http://oracle.com/rest).

Note that because the Docker entrypoint for the image that we built earlier was specified as /opt/ords-latest/bin/ords which means we can run the ords command line with any supported commands and arguments.

Don’t forget to rm adbs_passwords.txt. You do not need it anymore.

In summary, we’ve just told ORDS to use the wallet zip file and the ADMIN credentials to connect to the hosted service, create some users and persist configuration details on the ords-adb-config volume. The docker container exits because the command is complete. You can see the ORDS configuration by running the ords config list command.

> docker run -v ords-adb-config:/opt/ords-config/ \
             ords-latest/oraclejdk config list

ORDS: Release 22.4 Production on Mon Mar 06 19:07:27 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /opt/ords-config/

Database pool: default

Setting                              Value                                    Source     
----------------------------------   --------------------------------------   -----------
database.api.enabled                 true                                     Global     
db.password                          ******                                   Pool Wallet
db.username                          ORDS_PUBLIC_USER2                        Pool       
db.wallet.zip.path                   /opt/ords-config/Wallet_Autonomous.zip   Pool       
db.wallet.zip.service                db202301101106_low                       Pool       
feature.sdw                          true                                     Pool       
plsql.gateway.mode                   proxied                                  Pool       
restEnabledSql.active                true                                     Pool       
security.requestValidationFunction   ords_util.authorize_plsql_gateway        Pool       

No doubt you will remember this from the previous article about HTTPS and NGINX with ORDS. There’s one more configuration setting to address. That’s to tell ORDS what header key / value pair to use to trust that the request was received by a load balancer over HTTPS even though ORDS is receiving traffic over HTTP.

docker run -v ords-adb-config:/opt/ords-config/ \
  ords-latest/oraclejdk \
  config set security.httpsHeaderCheck "X-Forwarded-Proto: https"

At this point we have a Docker volume ords-adb-config which has all the configuration settings necessary to run one or more Customer Managed ORDS with Autonomous Database instances as we see fit.

Start it up!

From the previous article you have a NGINX configuration that you have running in Docker to talk to two ORDS instances listening on port 8080 and 8090. Now let’s replace those ORDS instances with ones running in Docker with the above ords-adb-config Docker volume. You can leave the NGINX container running but if you have not done so already, shutdown those ORDS instances.

Up until now, we have not specified a container name when running ORDS in Docker. For convenience, we’ll refer to the container listening on port 8080 as ords-latest-8080 and the other one as ords-latest-8090.

> docker run --detach --rm --name ords-latest-8080 \
             -p 8080:8080 \
             -v ords-adb-config:/opt/ords-config/ \
             ords-latest/oraclejdk
9e0d8ec541bc5c360c7e156153cfd8f6437d61ab2d4f627c887f03d7384a56e6

> docker run --detach --rm --name ords-latest-8090 \
             -p 8090:8080 \
             -v ords-adb-config:/opt/ords-config/ \
             ords-latest/oraclejdk
7a36de7fb14e54710181c43caa6fb2aa9dfdf013f5afa32405378da61a9a13e0

Verify

To check that they are up and running have a look at the process list.

> docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED        STATUS        PORTS                                                                      NAMES
2c11ababaf1b   ords-latest/oraclejdk   "/opt/ords-latest/bi…"   4 hours ago    Up 4 hours    8443/tcp, 0.0.0.0:8090->8080/tcp, :::8090->8080/tcp                        ords-latest-8090
7fd8c821be64   nginx                   "/docker-entrypoint.…"   6 hours ago    Up 6 hours    0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   optimistic_kilby
9e0d8ec541bc   30e6e561dc7d            "/opt/ords-latest/bi…"   6 hours ago    Up 6 hours    0.0.0.0:8080->8080/tcp, :::8080->8080/tcp                                  ords-latest-8080

Also use the docker logs command to keep track of the activity and status. We’ve given specific names for the two ORDS containers so we can refer to them directly,

> docker logs -f ords-latest-8080

ORDS: Release 22.4 Production on Mon Mar 06 13:48:57 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /opt/ords-config/

2023-03-06T13:48:58.335Z INFO        HTTP and HTTP/2 cleartext listening on host: 0.0.0.0 port: 8080
2023-03-06T13:48:58.389Z INFO        Disabling document root because the specified folder does not exist: /opt/ords-config/global/doc_root
2023-03-06T13:49:07.009Z INFO        Configuration properties for: |default|lo|
...
Mapped local pools from /opt/ords-config/databases:
  /ords/                              => default                        => VALID     


2023-03-06T13:49:14.790Z INFO        Oracle REST Data Services initialized
Oracle REST Data Services version : 22.4.4.r0411526
Oracle REST Data Services server info: jetty/10.0.12
Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 17.0.6+9-LTS-190
> docker logs -f ords-latest-8090      

ORDS: Release 22.4 Production on Mon Mar 06 13:56:22 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /opt/ords-config/

2023-03-06T13:56:23.011Z INFO        HTTP and HTTP/2 cleartext listening on host: 0.0.0.0 port: 8080
2023-03-06T13:56:23.066Z INFO        Disabling document root because the specified folder does not exist: /opt/ords-config/global/doc_root
2023-03-06T13:56:32.683Z INFO        Configuration properties for: |default|lo|
...
Mapped local pools from /opt/ords-config/databases:
  /ords/                              => default                        => VALID     


2023-03-06T13:56:32.683Z INFO        Oracle REST Data Services initialized
Oracle REST Data Services version : 22.4.4.r0411526
Oracle REST Data Services server info: jetty/10.0.12
Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 17.0.6+9-LTS-190

As a reminder, to check the logs for the NGINX container you’ll have to specify the container name that was allocated at runtime. In my case it is optimistic_kilby.

> docker logs -f optimistic_kilby
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
 to: 192.168.5.2:8080 {GET / HTTP/1.1} upstream_response_time 0.155 request_time 0.155
172.17.0.1 - - [06/Mar/2023:13:52:58 +0000] "GET /ords/ HTTP/1.1" 301 169 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
 to: 192.168.5.2:8090 {GET /ords/ HTTP/1.1} upstream_response_time 2.356 request_time 2.356
 to: 192.168.5.2:8080 {GET /ords/f?p=4550:1:117375695883225::::: HTTP/1.1} upstream_response_time 2.101 request_time 2.101
 to: 192.168.5.2:8090 {GET / HTTP/1.1} upstream_response_time 0.006 request_time 0.006
172.17.0.1 - - [06/Mar/2023:13:53:03 +0000] "GET /ords/ HTTP/1.1" 301 169 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
 to: 192.168.5.2:8080 {GET /ords/ HTTP/1.1} upstream_response_time 2.045 request_time 2.045

From the NGINX logs you can see that traffic is being alternated between the ORDS instance listening on port 8080 and 8090.

As before, the request goes over HTTPS through NGINX and routed upstream to an ORDS instance.

You can stop a container and restart it to confirm the failover works as before.

Conclusion

Building on the previous articles you now have both NGINX and ORDS running in Docker and using an Autonomous Database. This is still effectively a development / proof of concept environment because the DNS entry and SSL certificate are not properly setup to operate seamlessly. The nginx.conf is hardcoded with two upstream ORDS instances to use and the containers are using two specific ports on the host machine. In the next article we’ll look at using docker compose so that we have more flexibility around this.

Using the Dockerfile from this article you have created an ORDS image which can be used to run ORDS commands and update your configuration in ords-adbs-config. As an additional exercise you can look into increasing pool size (jdbc.MaxLimit) and doing a rolling restart of the two ORDS docker containers to pick up that configuration change.

Leave a comment and let me know how you get on.

ORDS Standalone vs Tomcat vs WebLogic

When it comes to deploying Oracle REST Data Services (ORDS), there are three main options to consider: Standalone, Apache Tomcat, and Oracle WebLogic Server. Each has its own advantages and drawbacks, so it’s important to understand the differences between them before choosing a deployment option.

ORDS Standalone

ORDS Standalone is the simplest deployment option and requires no external application servers. In fact, once you have ORDS, you have all you need to get started. It uses the Eclipse Jetty server embedded in ORDS, which is suitable for development, testing and production environments. It’s easy to set up and manage. It’s simplicity makes it ideal for smaller workloads but can also scale for high availability and greater throughput when a load balancer is put in front multiple ORDS standalone instances.

To run ORDS in standalone mode: ords --config /path/to/config/ serve

One should note that although using ORDS with Apache Tomcat or Oracle WebLogic Server is supported, quite often the diagnosis process for any support issues will involve verifying if the issue also occurs with ORDS standalone.

As mentioned in a previous article about Application Process Monitoring, when running in standalone mode, ORDS loads the jars from the ords.war into memory and some Java Agents which modify jars to instrument classes at the byte level can interfere with that classloading process. Examples of Java Agents which can not be used when ORDS standalone mode is used: Oracle APM Java Agent, DynaTrace Java Monitoring. However, there is a workaround to have ORDS working with Oracle APM Java Agent which you should be aware of.

Pros:

  • Easy to set up and manage. Get started straight away!
  • Can generate a convenient self-signed certificate for HTTPS.
  • Suitable for development and testing.
  • Ideal for a variety of workloads.
  • ORDS configuration of embedded Jetty server optimised for REST services.
  • Jetty configuration is extensible using XML files.

Cons:

  • Limited integration with identity and authorisation management systems.
  • Requires a load balancer for high availability.
  • Does not work with some Java Agents.
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 DynaTrace, Oracle APM or any other Java Agent.

ORDS Deployment to Tomcat

Apache Tomcat powers numerous large-scale, mission-critical web applications across a diverse range of industries and organisations. Chances are that your organisation is already running at least one Apache Tomcat servlet container. Tomcat is a popular open-source web server that is well-suited to ORDS.

It’s easy to set up, and deploying ORDS to Apache Tomcat is as simple as ords --config /path/to/config/ war $CATALINA_HOME/webapps/ords.war

Of course, that’s if your Tomcat configuration has auto deployment enabled, which is the default setting. Similar to ORDS standalone, Tomcat is configured to be reasonably secure for most use cases by default. Also, similar to ORDS standalone, for high availability a load balancer / reverse proxy must be configured to route to the servers.

Pros:

  • Integration with identity and authorisation management systems, such as Active Directory, OpenID Connect, through container managed security.
  • Suitable for production workloads.
  • Easy to get started with: install Apache Tomcat, start it, generate the ords web application, done.
  • Free.

Cons:

  • Involves an additional server.
  • More complex to manage than ORDS Standalone for clustering – see Tomcat Cluster documentation. Moreover, since ORDS is stateless, session serialisation, which is a common characteristic of web server clustering, that aspect of most clustered systems is not required.

ORDS Deployment to WebLogic

Oracle WebLogic Server is a unified and extensible platform for developing, deploying and running enterprise applications, such as Java, for on-premises and in the cloud. ORDS deployment to WebLogic is a very robust option. Weblogic is a powerful and reliable application server and provides advanced features such as clustering and load balancing. It’s suitable for large-scale production workloads. However, provisioning and configuring an Oracle Weblogic domain can be complicated.

The steps for deploying ORDS to WebLogic are involved and it’s best to refer to ORDS documentation.

Pros:

  • Provides advanced features such as clustering and load balancing
  • Suitable for large-scale production workloads
  • Robust and reliable
  • Integration with identity and authorisation management systems, such as Active Directory, OpenID Connect, through container managed security.

Cons:

  • Requires an Oracle WebLogic application server licence for production
  • Complex to set up and manage but is fairly standard for an enterprise grade application server

Decision Factors

Ultimately, the deployment option that’s best for you depends on the complexity of your integration with other systems. What your organisation already uses, and has support in place for, is also an important factor. ORDS standalone is ideal for getting started, developing and testing new services before deployment to product. It’s also suitable for production workloads. What it misses out of the box is integration with identity and authorisation management systems. That’s essentially the gap that deployment on Tomcat and Weblogic addresses. In all three cases, each mode is suitable for large-scale production workloads with appropriate load balancing in place.


Post Publishing Edits:

February 20th 2023 - Added text about some Java Agents not working with ORDS standalone.
February 25th 2023 - Added text pointing out that ORDS does not retain session state.
October 5th 2023 - Added text about workaround for Oracle APM Java Agent NullPointerException.
April 11th 2024 - Added text stating that ORDS 24.1.0 has a fix for the Java Agent/Classloader exception.