Plugins provide an easy way to extend the Neo4j REST API with new functionality, without the need to invent your own API. Think of plugins as server-side scripts that can add functions for retrieving and manipulating nodes, relationships, paths, properties or indices.
Tip
Want full control over your API and are willing to put in the effort? Neo4j server also provides hooks for unmanaged extensions based on JAX-RS. |
The needed classes reside in the org.neo4j:server-api jar file.
See the linked page for downloads and instructions on how to include it using dependency management.
For Maven projects, add the Server API dependencies in your pom.xml
:
<dependency> <groupId>org.neo4j</groupId> <artifactId>server-api</artifactId> <version>3.1.0-SNAPSHOT</version> </dependency>
To create a plugin, your code must inherit from the ServerPlugin class. Your plugin should also:
-
ensure that it can produce an (Iterable of)
Node
,Relationship
orPath
, any Java primitive orString
or an instance of aorg.neo4j.server.rest.repr.Representation
- specify parameters,
- specify a point of extension and of course
- contain the application logic.
-
make sure that the discovery point type in the
@PluginTarget
and the@Source
parameter are of the same type.
Note
If your plugin class has any constructors defined it must also have a no-arguments constructor defined. |
An example of a plugin which augments the database (as opposed to nodes or relationships) follows:
Get all nodes or relationships plugin
@Description( "An extension to the Neo4j Server for getting all nodes or relationships" ) public class GetAll extends ServerPlugin { @Name( "get_all_nodes" ) @Description( "Get all nodes from the Neo4j graph database" ) @PluginTarget( GraphDatabaseService.class ) public Iterable<Node> getAllNodes( @Source GraphDatabaseService graphDb ) { ArrayList<Node> nodes = new ArrayList<>(); try (Transaction tx = graphDb.beginTx()) { for ( Node node : graphDb.getAllNodes() ) { nodes.add( node ); } tx.success(); } return nodes; } @Description( "Get all relationships from the Neo4j graph database" ) @PluginTarget( GraphDatabaseService.class ) public Iterable<Relationship> getAllRelationships( @Source GraphDatabaseService graphDb ) { List<Relationship> rels = new ArrayList<>(); try (Transaction tx = graphDb.beginTx()) { for ( Relationship rel : graphDb.getAllRelationships() ) { rels.add( rel ); } tx.success(); } return rels; } }
The full source code is found here: GetAll.java
Find the shortest path between two nodes plugin
public class ShortestPath extends ServerPlugin { @Description( "Find the shortest path between two nodes." ) @PluginTarget( Node.class ) public Iterable<Path> shortestPath( @Source Node source, @Description( "The node to find the shortest path to." ) @Parameter( name = "target" ) Node target, @Description( "The relationship types to follow when searching for the shortest path(s). " + "Order is insignificant, if omitted all types are followed." ) @Parameter( name = "types", optional = true ) String[] types, @Description( "The maximum path length to search for, default value (if omitted) is 4." ) @Parameter( name = "depth", optional = true ) Integer depth ) { PathExpander<?> expander; List<Path> paths = new ArrayList<>(); if ( types == null ) { expander = PathExpanders.allTypesAndDirections(); } else { PathExpanderBuilder expanderBuilder = PathExpanderBuilder.empty(); for ( int i = 0; i < types.length; i++ ) { expanderBuilder = expanderBuilder.add( RelationshipType.withName( types[i] ) ); } expander = expanderBuilder.build(); } try (Transaction tx = source.getGraphDatabase().beginTx()) { PathFinder<Path> shortestPath = GraphAlgoFactory.shortestPath( expander, depth == null ? 4 : depth.intValue() ); for ( Path path : shortestPath.findAllPaths( source, target ) ) { paths.add( path ); } tx.success(); } return paths; } }
The full source code is found here: ShortestPath.java
To deploy the code, simply compile it into a .jar file and place it onto the server classpath (which by convention is the plugins directory under the Neo4j server home directory).
The .jar file must include the file META-INF/services/org.neo4j.server.plugins.ServerPlugin with the fully qualified name of the implementation class. This is an example with multiple entries, each on a separate line:
org.neo4j.examples.server.plugins.DepthTwo org.neo4j.examples.server.plugins.GetAll org.neo4j.examples.server.plugins.ShortestPath
The code above makes an extension visible in the database representation (via the @PluginTarget
annotation)
whenever it is served from the Neo4j Server.
Simply changing the @PluginTarget
parameter to Node.class
or Relationship.class
allows us to target those parts of the data model should we wish.
The functionality extensions provided by the plugin are automatically advertised in representations on the wire.
For example, clients can discover the extension implemented by the above plugin easily by examining the representations
they receive as responses from the server, e.g. by performing a GET
on the default database URI:
curl -v http://localhost:7474/db/data/
The response to the GET
request will contain (by default) a JSON container that itself contains a container
called "extensions" where the available plugins are listed.
In the following case, we only have the GetAll
plugin registered with the server, so only its extension functionality is available.
Extension names will be automatically assigned, based on method names, if not specifically specified using the @Name
annotation.
{ "extensions-info" : "http://localhost:7474/db/data/ext", "node" : "http://localhost:7474/db/data/node", "node_index" : "http://localhost:7474/db/data/index/node", "relationship_index" : "http://localhost:7474/db/data/index/relationship", "reference_node" : "http://localhost:7474/db/data/node/0", "extensions_info" : "http://localhost:7474/db/data/ext", "extensions" : { "GetAll" : { "get_all_nodes" : "http://localhost:7474/db/data/ext/GetAll/graphdb/get_all_nodes", "get_all_relationships" : "http://localhost:7474/db/data/ext/GetAll/graphdb/getAllRelationships" } }
Performing a GET
on one of the two extension URIs gives back the meta information about the service:
curl http://localhost:7474/db/data/ext/GetAll/graphdb/get_all_nodes
{ "extends" : "graphdb", "description" : "Get all nodes from the Neo4j graph database", "name" : "get_all_nodes", "parameters" : [ ] }
To use it, just POST
to this URL, with parameters as specified in the description and encoded as JSON data content.
For example for calling the shortest path
extension (URI gotten from a GET
to http://localhost:7474/db/data/node/123):
curl -X POST http://localhost:7474/db/data/ext/ShortestPath/node/123/shortestPath \ -H "Content-Type: application/json" \ -d '{"target":"http://localhost:7474/db/data/node/456", "depth":"5"}'
If everything is OK a response code 200
and a list of zero or more items will be returned.
If nothing is returned (null returned from extension) an empty result and response code 204
will be returned.
If the extension throws an exception response code 500
and a detailed error message is returned.
Extensions that do any kind of database operation will have to manage their own transactions, i.e. transactions aren’t managed automatically. Note that the results of traversals or execution of graph algorithms should be exhausted inside the transaction before returning the result.
Through this model, any plugin can naturally fit into the general hypermedia scheme that Neo4j exposes — meaning that clients can still take advantage of abstractions like Nodes, Relationships and Paths with a straightforward upgrade path as servers are enriched with plugins (old clients don’t break).