Introduction

This document describes how to develop and deploy plugins that integrate with the Oracle REST Data Services (ORDS) runtime.

Further Information

For a more a more detailed reference, consult the developer guide.

Examples

Below are some examples that give a flavour of the programming model.

Hello World

The Hello World example below demonstrates the basics of creating a request handler plugin:

@Dispatches(@PathTemplate("/hello"))
@Provides                            
public class HelloWorld extends HttpServlet {
 public doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
 	response.setContentType("text/plain");
 	response.getWriter().println("Hello World");
 }
}
  • Create a class that sub-classes HttpServlet.
  • Advertise the class to the Dependency Injection framework via the @Provides annotation
  • Advertise the class to the request dispatching framework via the @Dispatches annotation
  • Advertise the request path that the class responds to via the @PathTemplate annotation
  • Override the HttpServlet doGet () method to provide the logic of the handler.

Injecting Dependencies

A plugin can express it’s dependencies on external APIs using the @Inject annotation on its constructor.

@Dispatches(@PathTemplate("/uses-logging"))
@Provides                            
public class UsesLogging extends HttpServlet {

 @Inject
 public UsesLogging(Logger log) {
 	this.log = log;
 }   
 public doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
    log.fine("received request:\n" + request.toString());
 	response.setContentType("text/plain");
 	response.getWriter().println("Hello World");
 	log.fine("processed request");
 }

 private final Logger log;
}
  • Before instantiating the UsesLogging type, the DI framework looks for a constructor annotated with @Inject. It then examines the arguments of the constructor and attempts to resolve an implementation of the specified type. Once all dependencies have been resolved, the DI framework invokes the Constructor with the required arguments.
  • In this case, the servlet uses the Logger service to log some debugging information.
  • The set of services that can be injected is documented in the AvailableDependencies enum.

Tutorial

About the Tutorial Files

All of the files referenced in this tutorial are included in the product distribution under the examples/plugins folder:

${ORDS_HOME}
|-- examples
     |-- plugins
          |-- lib
          |-- plugin-demo
              |-- src

where ${ORDS_HOME} is the location where the product distribution was unzipped.

Prepare Environment

Consult the instructions in the Oracle REST Services Documentation to install and configure ORDS.

Enable database schema

To make a database schema accessible to ORDS, it must be explicitly enabled. To demonstrate this we will create a new schema and enable it:

$ sqlplus /nolog
SQL> CONNECT SYS as SYSDBA
Enter password: SYS_password
SQL> create user TEST_SCHEMA identified by TEST_SCHEMA_password;
SQL> grant connect, resource to TEST_SCHEMA;
SQL> DISCONNECT
SQL> CONNECT TEST_SCHEMA
Enter password: TEST_SCHEMA_password
SQL> execute ords.enable_schema;
SQL> commit;

Note: Replace TEST_SCHEMA_password with your own choice of password.

Verify schema is enabled

To verify if a schema is enabled for ORDS access, check the USER_ORDS_SCHEMAS view:

SQL> select * from USER_ORDS_SCHEMAS;

ID PARSING_SCHEMA TYPE      PATTERN
-- -------------- --------- -----------
 2 TEST_SCHEMA    BASE_PATH test_schema

Creating a Plugin

In this guide we will walk through building and deploying the plugin-demo plugin that queries the database to determine the current database user and echo that information in the response. This example uses Apache ANT to manage the build process.

Prerequisities

  • JDK 1.8 or later.
  • Apache Ant 1.8.2 or later.
  • Installed and configured ORDS as detailed above.

About the Plugin project folder structure

The plugin-demo source files are located within the ${ORDS_HOME} folder at:

${ORDS_HOME}/examples/plugins/plugin-demo

The folder contains the following:

  • src folder which contains:
    • PluginDemo.java - the Java source code of the plugin.
  • build.xml - ANT build script, which compiles and packages the source code
  • built - folder generated by build.xml which will contain the packaged plugin: plugin-demo.jar.

Located in the parent folder is a lib/ folder which holds the .jar files required to compile plugins.

Required Libraries

To compile a plugin the following libraries must be in the classpath:

Required Library Locations

Each of the required jars is included in the product distribution in the ${ORDS_HOME}/examples/plugins/lib folder.

About plugin-api.jar

This library provides the glue code - such as @Dispatches - to weave the plugin into the runtime.

About plugin-apt.jar

This library provides the annotation processor that makes classes annotated with @Provides discoverable.

About javax.inject.jar

This library provides the JSR-330 API types such as @Inject.

About servlet-api-3.1.0.jar

This library provides the Java Servlet 3.1.0 API types, such as HttpServlet.

About ojdbc11.jar

This library is optional and is only required if the plugin needs to access the Oracle JDBC Extension APIs such as OracleConnection.

About PluginDemo.java

The source code of the plugin is reproduced below, following that is a point by point explanation of the source code.

package example;

import java.io.IOException;
import java.sql.*;
import jakarta.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import oracle.dbtools.plugin.api.di.annotations.Provides;
import oracle.dbtools.plugin.api.http.annotations.*;
import oracle.dbtools.plugin.api.routes.*;

/**
 * This example plugin {@link HttpServlet} demonstrates:
 * <ul>
 * <li>Using the injected {@link Connection} to query the database.</li>
 * <li>Using the injected {@link PathTemplates} service to decode the parameters
 * of the servlet's {@link PathTemplateMatch}.</li>
 * </ul>
 *
 * <h4>Testing the Servlet</h4> Invoke the servlet with the following URL:
 *
 * <pre>
 *  http://<i>server</i>/ords/<i>schema</i>/demos/plugin?who=<i>somebody</i>
 * </pre>
 *
 * where:
 * <ul>
 * <li><i>server</i> is the hostname and port of the server.</li>
 * <li><i>schema</i> is the name of the REST enabled database schema.</li>
 * <li><i>somebody</i> is any value you wish, e.g. a person's name.</li>
 * <ul>
 * For example:
 *
 * <pre>
 *  http://localhost:8080/ords/test_schema/demos/plugin?who=Scott
 * </pre>
 *
 * @author cdivilly
 *
 */
@Provides
@Dispatches(@PathTemplate("/demos/plugin"))
class PluginDemo extends HttpServlet {
  @Inject
  PluginDemo(Connection conn, PathTemplates pathTemplates) {
    this.conn = conn;
    this.pathTemplates = pathTemplates;
  }

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {

    PathTemplateMatch match = pathTemplates
                                .matchedTemplate(request);
    try {
      /* retrieve 'who' query parameter */
      String who = match.parameters().get("who");
      who = null == who ? "anonymous" : who;

      /* execute database query */
      PreparedStatement ps = conn
       .prepareStatement("select sys_context('USERENV','CURRENT_USER') from dual");

      ResultSet rs = ps.executeQuery();
      rs.next();

      /* determine the database user */
      String user = rs.getString(1);

      /* Print the greeting */
      response.getWriter().println(user + " says hello to: " + who);
      rs.close();
      ps.close();
    } catch (SQLException e) {
      throw new ServletException(e);
    }
  }

  private final Connection    conn;
  private final PathTemplates pathTemplates;
}
  • The PluginDemo type is annotated with @Provides to indicate that it offers a service, in this case it offers the HttpServlet service.
  • The type is also annotated with the @Dispatches and @PathTemplate annotations which indicate the request URL pattern that the servlet listens for.
  • The PluginDemo constructor is annotated with @Inject to indicate that this constructor should be used by the dependency injection framework to instantiate instances of the type.
  • The arguments of the constructor indicate the types that PluginDemo depends on:
    • The first argument indicates that a database Connection is required, ORDS connects this connection instance to a database schema, which schema is determined by the request URL to database schema mapping rules.
    • The second argument is the PathTemplates service which provides a method for determining the PathTemplate bound to a request.
  • PluginDemo overrides the doGet() method to indicate that it supports the GET HTTP method.
  • It uses the PathTemplates service to determine the PathTemplateMatch bound to the request.
  • It decodes the PathTemplate parameters, and extracts the value of the who parameter.
  • The Connection instance is queried to determine the identity of the current database user.
  • Finally the message showing the database user and the value of the who parameter is printed in the response.

Build the Plugin

In the plugin-demo folder type the following command:

$ ant

The source code will be compiled and packaged into an archive named built/plugin-demo.jar

Package the Plugin

Copy plugin-demo.jar to extension library:

$  cp built/plugin-demo.jar ${ORDS_HOME}/lib/ext/

Test the Plugin

Start ords in standalone mode.

$ cd ${ORDS_HOME}/bin
$ ords --config config_dir serve

About Request URLs

The plugin we have built needs a database connection to function. Therefore ORDS must be able to figure out what database and schema to connect to. ORDS does this by examine the Request URL and mapping the URL to the appropriate database pool and database schema.

If ORDS cannot determine a mapping, then it will report a 404 Not Found status for the request URL.

Configuring a database schema mapping

To map a database schema to a URL, you must enable the schema to be accessed by ORDS, as we did in the section titled ‘Enable Database Schema’.

Try the Request URL

With ORDS running in standalone mode on localhost, try the following URL:

http://localhost:8080/ords/test_schema/demos/plugin?who=Scott

The browser should display the following text:

TEST_SCHEMA says hello to: Scott
  • The /test_schema portion of the request URL maps the request to the TEST_SCHEMA database schema.
  • A Connection instance connected to the TEST_SCHEMA schema is injected into the PluginDemo instance.
  • PluginDemo queries the Connection to determine the current user and decodes the {who} parameter bound to the request URL, and uses this information to construct the message displayed above.