Extension Points

spmallette edited this page Aug 17, 2012 · 14 revisions

Extension points refer to the places from which Rexster can be extended and are defined by the @ExtensionDefinition annotation which has an extensionPoint parameter. This parameter must be defined on any method to be exposed as an extension on Rexster. The value of the extensionPoint parameter is a selection from the ExtensionPoint enumeration. The following extension point options are available:

extension point uri extends from
GRAPH http://{base}/graphs/{graph}
EDGE http://{base}/graphs/{graph}/edges/{id]
VERTEX http://{base}/graphs/{graph}/vertices/{id}

By default, the @ExtensionDefinition assumes that the ExtensionResponse will return an entity of type application/json. To override that value, simply supply the revised mime-type to the produces parameter, as in:

@ExtensionDefinition(extensionPoint = ExtensionPoint.VERTEX, produces = MediaType.APPLICATION_XML)
@ExtensionDescriptor(description = "Turn a vertex into XML")
public ExtensionResponse doVertexToXml(@RexsterContext Vertex vertex) {
    String xml = "<vertex><id>" + vertex.getId().toString() + "</id></vertex>";
    return new ExtensionResponse(Response.ok(xml).build());
}

The code above is taken from the ProducesXmlExtension in the Sample Kibbles project.

When the extension method produces JSON, Rexster tries to add some standard attributes to the response. It will look to add the version and queryTime as in:

{
  "some-key":"some-value",
  "other-key":"other-value",
  "version":"*.*",
  "queryTime":24.940088
}

To override this default behavior, Rexster can be instructed to not add these values to JSON responses by setting the tryIncludeRexsterAttributes on the ExtensionDefinition to false. As stated earlier, this value is true by default. In the event that the produces value is set to something other than application/json, Rexster will behave as though the value of tryIncludeRexsterAttributes is false.

The ExtensionDefinition provides control over the HTTP method on the request that it will respond to through the method parameter:

@ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH, method = HttpMethod.GET)

In the above example, the specification of HttpMethod.GET makes it so that Rexster will only relay GET based requests to this method. The HttpMethod enumeration has the full range of standard HTTP methods: GET, PUT, POST, DELETE, HEAD, and OPTIONS. It is up to the Extension developer to ensure that the returned response meets HTTP standards as necessary (ie. HEAD will not return and entity body). By default the value of method is set to HttpMethod.ANY which means that all requests, regardless of the HTTP method, will be funneled through to the Extension.

It’s important to note that when implementing methods other than GET or POST that you should also consider implementation of the OPTIONS method. Depending upon your environment, HTTP Access Control may come into play and prevent advanced methods like DELETE and PUT from working properly. Typically, this scenario occurs when cross-site HTTP requests are made and requests to the extension are met with an error like:

Method DELETE is not allowed by Access-Control-Allow-Methods

By implementing OPTIONS, the pre-flight check made by some browsers will succeed and allow the advanced methods to be serviced. The ExtensionResponse helper class has a static method called availableOptions to help construct the required response.

Extensions and Transactions

By default, Rexster will auto-commit a transaction with success if the extension method returns a non-error ExtensionResponse. If the ExtensionResponse is an error or the extension throws an untrapped exception it will auto-commit with failure.

To turn off this behavior, set the autoCommitTransaction value on the ExtensionDefinition to false. When set to false, the extension method itself is responsible for committing transaction, though untrapped exceptions will still commit with failure.

Types of Extensions

There are multiple types of extensions. The type of extension is driven by a combination of the settings in the ExtensionDefinition.

type defined by
Root An ExtensionDefinition defined with an ExtensionPoint of GRAPH, VERTEX or EDGE with no path.
Path An ExtensionDefinition defined with an ExtensionPoint of GRAPH, VERTEX or EDGE and a specified path.

Root Extensions

Consider the following code snippet from the SimpleRootExtension extension class in the Sample Kibbles project:

ExtensionNaming(name = SimpleRootExtension.EXTENSION_NAME, namespace = SimpleRootExtension.EXTENSION_NAMESPACE)
public class SimpleRootExtension extends AbstractRexsterExtension {

    public static final String EXTENSION_NAME = "simple-root";
    public static final String EXTENSION_NAMESPACE = "tp";

    @ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH)
    @ExtensionDescriptor(description = "Do something on graph")
    public ExtensionResponse doWorkOnGraph(@RexsterContext Graph graph) {
        return toStringIt(graph);
    }
}

will serve at:

http://{base}/graphs/{graph}/tp/simple-root

When defining an ExtensionDefinition in this way, it is referred to as a “root extension” where the extension serves from the root of the ExtensionPoint. A single root extension can be defined for a single extension point within a method, namespace and extension class. Therefore, the following extension would not be valid:

ExtensionNaming(name = SimpleRootExtension.EXTENSION_NAME, namespace = SimpleRootExtension.EXTENSION_NAMESPACE)
public class SimpleRootExtension extends AbstractRexsterExtension {

    public static final String EXTENSION_NAME = "simple-root";
    public static final String EXTENSION_NAMESPACE = "tp";

    @ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH)
    @ExtensionDescriptor(description = "Do something on the graph")
    public ExtensionResponse doWorkOnGraph(@RexsterContext Graph graph) {
        return toStringIt(graph);
    }

    @ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH)
    @ExtensionDescriptor(description = "Do some more stuff on the graph")
    public ExtensionResponse doWorkSomeNonsenseWorkOnGraph(@RexsterContext Graph graph) {
        // DON'T INCLUDE TWO METHODS THAT EXTEND FROM THE SAME EXTENSIONPOINT
        // UNLESS SPECIFYING A PATH EXTENSION (described below)
        return toStringIt(graph.getVertex(1));
    }
}

While Rexster does not complain if it comes across the above scenario, it may not behave as expected. Rexster will simply choose the first and best method match to the request.

Path Extensions

In addition to root extensions, there are also path extensions. Path extensions allow an extension to have multiple service methods exposed from the same namespace, extension name and extension point. Consider the following code snippet from the SimplePathExtension extension class in Sample Kibbles project:

@ExtensionNaming(name = SimplePathExtension.EXTENSION_NAME, namespace = SimplePathExtension.EXTENSION_NAMESPACE)
public class SimplePathExtension extends AbstractRexsterExtension {
    public static final String EXTENSION_NAME = "simple-path";
    public static final String EXTENSION_NAMESPACE = "tp";

    @ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH, path = "some-work")
    @ExtensionDescriptor(description = "Do something on the graph")
    public ExtensionResponse doSomeWorkOnGraph(@RexsterContext Graph graph) {
        return toStringIt(graph, "some");
    }

    @ExtensionDefinition(extensionPoint = ExtensionPoint.GRAPH, path = "other-work")
    @ExtensionDescriptor(description = "Do more stuff on the graph")
    public ExtensionResponse doOtherWorkOnGraph(@RexsterContext Graph graph){
        return toStringIt(graph, "other");
    }
}

Note that there are two ExtensionDefinition annotations on two different methods that both specify the ExtensionPoint.GRAPH. It was previously established in the root extension section that this specification is not permitted, however, the difference here is the usage of the path parameter on the annotation. The value of the path parameter helps to identify the method to be exposed on the extension, such that Rexster will serve those methods on the following URIs:

http://{base}/graphs/{graph}/tp/simple-path/some-work
http://{base}/graphs/{graph}/tp/simple-path/other-work

Like the root extension, the path extension will behave inconsistenly if there is more than one method on the extension that has the same namespace, extension name, extension point, method and path. Rexster will always serve the first method to match the request.