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.

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

Crafting your ORDS plugin the Maven way

This article provides a step-by-step guide to setting up an Oracle REST Data Services (ORDS) plugin using Maven. It covers the installation of two ORDS jars into the local Maven repository, the creation of the project structure, the configuration of the plugin, and the building and deployment of the plugin. This guide will help developers get started quickly with ORDS plugin development where Maven is already used in their software development infrastructure.

  1. Introduction
  2. Step 1: Install ORDS Jars Into the Local Maven Repository
  3. Step 2: Set Up the Project Structure
  4. Step 3: Create Configuration Files and Source
  5. Step 4: Build and Deploy the Plugin
  6. Step 5: Verify the Plugin
  7. Conclusion

Introduction

ORDS provides a plugin framework which makes it possible to extend the functionality and introduce custom behaviour. Creating a plugin can be a daunting task, especially if you’re not familiar with ORDS or the underlying technologies. Fortunately, there are some great examples out there to get you started, including the ORDS YAML plugin example. However, the plugin example projects are Ant-based, so if you’re looking to use Maven for your plugin project, you’ll need to take a few extra steps.

These steps have Maven as a prerequisite and it is assumed your familiarity with Maven is the reason you’re here, reading this article. If you need more information on Maven then start with What is Maven?

Step 1: Install ORDS Jars Into the Local Maven Repository

An ORDS plugin has a dependency on two jars that are distributed with ORDS. The ords-plugin-api jar provides all the interfaces, annotations and base classes described in the ORDS Java API which are the building blocks for your plugin implementation. The ords-plugin-apt jar provides the annotation processing support required when building your plugin. These two jar files can be found in the examples/plugins/lib/ directory of ORDS distribution.

First, you’ll need to install the two ORDS jars into your local Maven repository. To install the jars, you’ll need to use the command line. Navigate to the directory where the ORDS was extracted to, and then run the following two commands:

> mvn install:install-file \
  -DgroupId=oracle.dbtools.ords \
  -DartifactId=ords-plugin-api \
  -Dversion=22.4.4 \
  -Dpackaging=jar \
  -Dfile=examples/plugins/lib/ords-plugin-api-22.4.4.041.1526.jar  \
  -DgeneratePom=true  

> mvn install:install-file \
  -DgroupId=oracle.dbtools.ords \
  -DartifactId=ords-plugin-apt \
  -Dversion=22.4.4 \
  -Dpackaging=jar \
  -Dfile=examples/plugins/lib/ords-plugin-apt-22.4.4.041.1526.jar  \
  -DgeneratePom=true

That will put two artefacts in your local maven repository both as version 22.4.4. That is the version of ORDS that this article refers to but you can change the version number to be reflect the version of ORDS you are using.

Step 2: Set Up the Project Structure

This guide is based on the ORDS YAML plugin example and will use the source PluginYaml.java from that article. The plugin can modify the application/json response which would normally be returned by ORDS and that response in text/yaml format instead.

For simplicity we’ll use maven-archetype-quickstart as outlined in Maven in 5 Minutes to create the project structure. The focus here is on creating an example, so that term makes sense as the group identifier. You should use your existing Maven artefact nomenclature for identifiers. In the directory where you typically have your Maven projects run the following command:

> mvn archetype:generate \
    -DgroupId=example \
    -DartifactId=ords-yaml-plugin \
    -DarchetypeArtifactId=maven-archetype-quickstart \
    -DarchetypeVersion=1.4 \
    -DinteractiveMode=false


[INFO] Scanning for projects...
[INFO] 
[INFO] ---------< org.apache.maven:standalone-pom >----------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] -----------------------[ pom ]------------------------
[INFO] 
...
[INFO] ------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------
[INFO] Total time:  4.779 s
[INFO] Finished at: 2023-03-27T23:18:25+01:00
[INFO] ------------------------------------------------------

You should now have an ords-yaml-plugin directory which looks like this:

ords-yaml-plugin
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- example
    |           `-- App.java
    `-- test
        `-- java
            `-- example
                `-- AppTest.java

The generated App.java and AppTest.java files can be removed or ignored. They serve no purpose but to provide placeholders in the source directory. Change directory into ords-yaml-plugin and verify the project builds.

> cd ords-yaml-plugin 
> mvn clean package
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------< example:ords-yaml-plugin >-------------
[INFO] Building ords-yaml-plugin 1.0-SNAPSHOT
[INFO] -----------------------[ jar ]------------------------
...
[INFO] ------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------
[INFO] Total time:  2.755 s
[INFO] Finished at: 2023-03-27T23:39:42+01:00
[INFO] ------------------------------------------------------

Once that completes you should see target/ords-yaml-plugin-1.0-SNAPSHOT.jar file has been produced. Let’s make it interesting by adding the PluginYaml.java to the project.

Step 3: Create Configuration Files and Source

To compile the ORDS plugin there are classpath dependencies that must be met. Modify the ords-yaml-plug project pom.xml and put these dependencies just after the <dependencies> element at line #21.

    <dependency>
      <groupId>oracle.dbtools.ords</groupId>
      <artifactId>ords-plugin-api</artifactId>
      <version>22.4.4</version>
    </dependency>
    <dependency>
      <groupId>oracle.dbtools.ords</groupId>
      <artifactId>ords-plugin-apt</artifactId>
      <version>22.4.4</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>jakarta.inject</groupId>
        <artifactId>jakarta.inject-api</artifactId>
        <version>2.0.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.14.2</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-yaml</artifactId>
        <version>2.14.2</version>
    </dependency>

Most of those dependencies will be satisfied by the artefacts already available in the central Maven repository and you have addressed the remaining two dependencies by putting the ORDS plugin api and apt jars in your local repository earlier.

Download the source for the plugin class into the source package directory created when the Maven project was created.

> curl --output src/main/java/example/PluginYaml.java \
       https://raw.githubusercontent.com/pobalopalous/pobalopalous/main/example/PluginYaml.java

That PluginYaml.java example was written for an earlier version of ORDS which used the javax.inject library. ORDS 22.4.4 uses the jakarta.inject library. So edit the java source and replace the import statement at line #15

import javax.inject.Inject;

with

import jakarta.inject.Inject;

With that minor change in place you are now ready for the next step: build and deploy the plugin.

Step 4: Build and Deploy the Plugin

It’s as simple as running this on the command line:

> mvn clean package
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< example:ords-yaml-plugin >----------------------
[INFO] Building ords-yaml-plugin 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ ords-yaml-plugin ---
[INFO] Deleting /development/github/ords-yaml-plugin/target
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ ords-yaml-plugin ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /development/github/ords-yaml-plugin/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ ords-yaml-plugin ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /development/github/ords-yaml-plugin/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ ords-yaml-plugin ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /development/github/ords-yaml-plugin/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ ords-yaml-plugin ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /development/github/ords-yaml-plugin/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ ords-yaml-plugin ---
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running example.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.041 s - in example.AppTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ ords-yaml-plugin ---
[INFO] Building jar: /development/github/ords-yaml-plugin/target/ords-yaml-plugin-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.580 s
[INFO] Finished at: 2023-03-28T00:34:29+01:00
[INFO] ------------------------------------------------------------------------

Now your target/ords-yaml-plugin-1.0-SNAPSHOT.jar file really does have the capability of doing something interesting but it must be put in the lib/ext/ directory of your ORDS installation directory first. Many of the runtime dependencies will be met by the jars distributed with ORDS. However, this particular plugin requires certain YAML related jars to also be in the runtime classpath: snakeyaml and jackson-dataformat-yaml. These jars will also have to be copied to the lib/ext/ directory.

The jackson-dataformat-yaml artefact will be in your local Maven repository because it was pulled in for this ords-yaml-plugin project and that can just be copied over. However, for snakeyaml you may have to download that file.

Let’s assume the directory that ORDS was extracted to can be referred to by the environment variable $ORDS_HOME.

To download the snakeyaml jar so that it is included in the ORDS runtime classpath run this command:

> curl -o $ORDS_HOME/lib/ext/snakeyaml-1.33.jar \
https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.33/snakeyaml-1.33.jar

To copy the jackson-dataformat-yaml jar so that it is included in the ORDS runtime classpath run this command:

> cp ~/.m2/repository/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.14.2/jackson-dataformat-yaml-2.14.2.jar \
   $ORDS_HOME/lib/ext/ 

Unless you want to change the versions of these jars used, you only need to do this once. Every time you build a new ords-yaml-plugin jar you will have to copy it to the ORDS library extension directory. To copy the jar file you built, run this command while in your ords-yaml-plugin project directory:

> cp target/ords-yaml-plugin-1.0-SNAPSHOT.jar \
   $ORDS_HOME/lib/ext/ 

Your ORDS directory should look like this:

ords-dist-22.4.4.041.1526
|-- FUTC.txt
|-- bin
|-- doc
|-- examples
|-- icons
|-- index.html
`-- lib
    |-- ext
    |   `-- README
    |   `-- jackson-dataformat-yaml-2.14.2.jar
    |   `-- ords-yaml-plugin-1.0-SNAPSHOT.jar
    |   `-- snakeyaml-1.33.jar
|-- license.txt
|-- linux-support
|-- ords.war
|-- scripts

Now just start ORDS in standalone mode with a configuration directory setup to use your preferred database.

> $ORDS_HOME/bin/ords --config /path/to/config serve
ORDS: Release 22.4 Production on Tue Mar 28 00:14:06 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /path/to/config/

2023-03-28T00:14:06.522Z INFO        HTTP and HTTP/2 cleartext listening on host: 0.0.0.0 port: 8080
...
2023-03-28T00:14:23.618Z INFO        

Mapped local pools from /path/to/config/databases:
  /ords/                              => default                        => VALID     


2023-03-28T00:14:23.662Z 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 11.0.13+10-LTS-370

ORDS started up without issue so you plugin is effectively deployed and ready to work.

Step 5: Verify the Plugin

It’s time to confirm that the PluginYaml class does in fact transform the application/json response to text/yaml when the client requests that format. Send a request to ORDS that would normally return application/json but indicate that text/yaml is preferred. In my database I have the HR schema, and it’s EMPLOYEES table, REST Enabled and accessible at http://localhost:8080/ords/hr/employees/. The plugin is looking for Accepts header in the request to see if it should transform the response.

> curl -i -H "Accepts: text/yaml" 'http://localhost:8080/ords/hr/employees/?limit=2'
HTTP/1.1 200 OK
Content-Type: text/yaml
ETag: "PJjqS1hHizmvT8/WpBBub/3wIZ3HcdY6w/hNFD279DyLK4Prvk+q7BlnmHeKEfnQu7Ek7gGqeT86ltVBQcky6Q=="
Transfer-Encoding: chunked

---
items:
- employee_id: 100
  first_name: "Steven"
  last_name: "King"
  email: "SKING"
  phone_number: "515.123.4567"
  hire_date: "1987-06-17T00:00:00Z"
  job_id: "AD_PRES"
  salary: 24000
  commission_pct: null
  manager_id: null
  department_id: 90
  links:
  - rel: "self"
    href: "http://localhost:8080/ords/hr/employees/100"
- employee_id: 101
  first_name: "Neena"
  last_name: "Kochhar"
  email: "NKOCHHAR"
  phone_number: "515.123.4568"
  hire_date: "1989-09-21T00:00:00Z"
  job_id: "AD_VP"
  salary: 17000
  commission_pct: null
  manager_id: 100
  department_id: 90
  links:
  - rel: "self"
    href: "http://localhost:8080/ords/hr/employees/101"
hasMore: true
limit: 2
offset: 0
count: 2
links:
- rel: "self"
  href: "http://localhost:8080/ords/hr/employees/"
- rel: "edit"
  href: "http://localhost:8080/ords/hr/employees/"
- rel: "describedby"
  href: "http://localhost:8080/ords/hr/metadata-catalog/employees/"
- rel: "first"
  href: "http://localhost:8080/ords/hr/employees/?limit=2"
- rel: "next"
  href: "http://localhost:8080/ords/hr/employees/?offset=2&limit=2"

Conclusion

By following the steps in this article you have used Maven to compile and package an ORDS plugin. There were a few once of steps to get dependencies addressed but you now have a Maven project structure for developing more extensions to the existing ORDS behaviour.

If you’re looking to build an ORDS plugin, Apache Maven is a great choice because it provides a uniform build system and there is a huge range of Integrated Development Environments and Source Control Systems that work with Maven. Give it a try today and see how it can help you take your development efforts to the next level.