Introduction

This document provides a complete reference for the plugin programming model.

Getting Started

For a quick introduction to developing plugins please consult the getting started guide.

Plugin API Goals

  • Small learning curve: Build on existing Java SE, Java EE APIs and programming models
  • Low Coupling: Minimize external code dependencies, only requiring:
    • Java SE dependencies
    • Java EE Servlet and javax.inject dependencies
    • Minimal ORDS glue code dependencies.

These goals are met by leveraging the Java EE Servlet API and the JSR-330 Dependency Injection Framework, along with a small set of custom annotations that enable HttpServlets to be plugged in without resorting to editing a web.xml deployment descriptor.

Extension Points

Plugins can plug in additional logic at the following extension points:

  • Fulfilling HTTP requests by extending HttpServlet.
  • Manipulating HTTP requests and responses by implementing the servlet Filter interface.
  • Responding to application startup/shutdown, by implementing the Lifecycle interface.

We call these extension points, services. A service is simply any type on which another type depends. The ExtensionPoints type enumerates the available extension points.

Plugin Provider

A provider is any Java type annotated with the @Provides annotation. A provider provides one or more services. See the @Provides documentation for more information about how to express what services a provider offers.

Provider Lifecycle

Providers can have one of two life-cycles:

  • @ApplicationScoped - instantiated when the ORDS is started by an application server, destroyed when the application server stops/restarts ORDS.
  • @RequestScoped - instantiated when first required to process a HTTP request, destroyed when the request has completed processing.

By default all types annotated with @Provides are given a @RequestScoped life-cycle. This means that for each request a new instance of the type is instantiated, and that instance is destroyed once processing of the request has completed.

Since each request is processed in a single thread, this means that, @RequestScoped services:

  • Only exist in a single thread, so do not need to be thread-safe, making them easier to develop.
  • Can safely hold per request state in member variables.
  • Are instantiated and destroyed very frequently. Instantiation/destruction must be efficient.
  • Can depend on other @RequestScoped services.
  • Can depend on @ApplicationScoped services.

Since @ApplicationScoped services endure for the lifetime of ORDS application, this means that:

  • They can be accessed from multiple different threads, so they must be fully thread safe.
  • Are instantiated and destroyed very occasionally.
  • Can depend on other @ApplicationScoped services, but cannot depend on @RequestScoped services.
  • Lifecycle providers always have an @ApplicationScoped lifecycle.

About @ApplicationScoped

Avoid the use of @ApplicationScoped services, only use them when it is absolutely necessary to share state across requests. Some appropriate use-cases for @ApplicationScoped services:

  • To cache data that needs to be shared across multiple requests. For example it might be useful to cache some expensive to compute data, and it is proven that caching the data leads to a measurable performance gain, and the benefit of this performance gain outweighs the increased costs of dealing with synchronizing access safely across threads and dealing with invalidation of stale cached data. Especially when ORDS is deployed in a clustered configuration, resulting in each ORDS node’s cache being inconsistent with every other node’s cache.

  • To track global measurements, For example to calculate the total number of requests served, total number of bytes served etc.

Inappropriate use cases include:

  • Caching data because you think it might be expensive to re-compute.
  • You prefer to create one instance of the service because you think creating an instance per request is expensive/wasteful.

Rather than sharing state across threads, prefer to store global state in the backend storage, i.e. the database, where it can be reliably accessed concurrently.

Service Provider Prioritization

In many instances there may be multiple providers of a service. To assist in choosing the most appropriate provider, or to determine the order in which providers should be invoked, the @Priority annotation is provided.

Dependency Injection

The runtime uses the common dependency injection pattern to communicate what services a provider depends on. The runtime includes a JSR-330 compatible dependency injection framework. A provider enumerates it’s dependencies via it’s constructor, which must be annotated with @Inject:

@Provides /* Advertise the Foo provider to the D.I. framework */
class Foo {
 @Inject Foo(Bar bar) { /* Express the dependency on the Bar service */
  this.bar = bar;
 }
 
 public void someMethod() {
  bar.use(); /* use the dependency */
 }
 private final Bar bar;
}

Available Dependencies

The set of dependencies made available to plugins is enumerated by the AvailableDependencies type.

Servlet Extensions

If you wish to create an extension that services HTTP Requests then you must create a HttpServlet extension. The Getting Started guide contains a tutorial demonstrating all the steps required to create a HttpServlet extension. The sections below provide further detail about creating servlet extensions, in particular how to use Java annotations to describe the servlet’s metadata.

Most of the metadata can be specific to a particular HTTP method, a particular URI pattern or an entire servlet. Specific metadata is defined by annotating the corresponding Java method or by setting a property of the @PathTemplate annotation.

Servlet Lifecycle

In contrast to the lifecycle of servlets in a regular JEE application server, servlets in ORDS only exist for the duration of a HTTP request. This means each servlet instance is used in a single thread, and does not have to worry about threading considerations.

Each HTTP request gets it’s own instance of a servlet. The instance is created once it has been identified as the target of the request, and the instance is destroyed once the request has been serviced.

About the @Dispatches annotation

The @Dispatches annotation informs ORDS about what URL patterns the servlet wishes to service. The Javadoc describes in detail how to use the annotation and provides several examples. These patterns are termed Route Patterns, and have a specific syntax.

Every servlet must have exactly one @Dispatches annotation.

A @Dispatches annotation must have at least one @PathTemplate annotation. Each @PathTemplate annotation describes a URL pattern that the servlet is willing to service.

About the @PathTemplate annotation

The [@PathTemplate][path-template] annotation describes a single Route Pattern that a servlet services. In addition the @PathTemplate annotation has a number of properties that can be used to provide metadata specific to the specified Route Pattern.

About PathTemplateMatch

When a servlet wishes to determine which @PathTemplate was matched, it should use the PathTemplates#matchedTemplate(HttpServletRequest) method to retrieve the PathTemplateMatch:

@Provides
@Dispatches({
 @PathTemplate(name="collection", value="/examples/collection/"),
 @PathTemplate(name="item", value="/examples/collection/:id")})
class ExampleServlet {

 protected void doGet(HttpServletRequest req,HttpServletResponse resp) {
  PathTemplateMatch matched = this.pathTemplates.matchedTemplate(req);
  switch(matched.name()) {
   case "collection":
    // process collection pattern
    break;
   case "item::
    String id = matched.parameters().get("id");
    // process item pattern
    break;
  }
 }
}

Once the PathTemplateMatch instance has been retrieved the servlet can branch to the correct logic for handling the request, typically by examining the PathTemplateMatch#name() method.

If the Route Pattern includes any parameters, the value bound to the parameter can be retrieved via the PathTemplateMatch#parameters() method.