This document describes the Neo4j datastore for eXtended Objects.

Introduction

As a graph database Neo4j provides very powerful capabilities to store and query highly interconnected data structures consisting of nodes and relationships between them. With release 2.0 the concept of labels has been introduced. One or more labels can be added to a single node:

create
  (a:Person:Actor)
set
  a.name="Harrison Ford"

Using labels it is possible to write comprehensive queries using Cypher:

match
  (a:Person)
where
  a.name="Harrison Ford"
return
  a.name;

If a node has a label it can be assumed that it represents some type of data which requires the presence of specific properties and relationships (e.g. property "name" for persons, "ACTED_IN" relations to movies). This implies that a Neo4j label can be represented as a Java interface and vice versa.

Person.java
@Label("Person") // The value "Person" can be omitted, in this case the class name is used
public interface Person {
  String getName();
  void setName();
}

Maven Dependencies

The Neo4j datastore for eXtended Objects is available from Maven Central and can be specified as dependency in pom.xml files:

pom.xml
<project ...>
    ...

    <properties>
        <com.buschmais.xo_version>0.4.0</com.buschmais.xo_version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.buschmais.xo</groupId>
            <artifactId>xo.neo4j</artifactId>
            <version>${com.buschmais.xo_version}</version>
        </dependency>
    </dependencies>

    ...
</project>

Bootstrapping

For a XOManagerFactory to be constructed a so called XO unit must be defined. There are two ways:

  • Using a descriptor META-INF/xo.xml

  • By using an instance of the class "com.buschmais.xo.XOUnit"

XML Descriptor

An XO descriptor is a XML file located as classpath resource under "/META-INF/xo.xml" and defines one or more XO units. Each must be uniquely identified by a name. This is similar to the persistence unit approach defined by the Java Persistence API (JPA). The following snippet shows a minimum setup:

META-INF/xo.xml
<v1:xo version="1.0" xmlns:v1="http://buschmais.com/xo/schema/v1.0">
    <xo-unit name="movies">
        <url>file://target/movies</url>
        <provider>com.buschmais.xo.neo4j.api.Neo4jXOProvider</provider>
        <types>
            <type>com.buschmais.xo.example.movies.composite.Movie</type>
            <type>com.buschmais.xo.example.movies.composite.Person</type>
            <type>com.buschmais.xo.example.movies.composite.Actor</type>
            <type>com.buschmais.xo.example.movies.composite.Directory</type>
        </types>
    </xo-unit>
</v1:xo>
url

The URL to pass to the Neo4j datastore. The following protocols are currently supported

  • "file:///C:/neo4j/movies": embedded local database using the specified directory as location for the Neo4j database

  • "http://localhost:7474/db/data": remote database over REST

  • "memory:///": non-persistent in-memory database

provider

The class name of the datastore provider, for Neo4j com.buschmais.xo.neo4j.api.Neo4jXOProvider

types

A list of all persistent interface types representing labels or relations

An XOManagerFactory instance can now be obtained as demonstrated in the following snippet:

Main.java
public class Main {

  public static void main(String[] args) {
    XOManagerFactory xmf = XO.createXOManagerFactory("movies");
    ...
    xmf.close();
  }

}

XOUnit And XOUnitBuilder

It is also possible to create a XOManagerFactory using an instance of the class com.buschmais.xo.api.XOUnit:

Main.java
public class Main {

  public static void main(String[] args) {
    XOUnit xoUnit = XOUnitBuilder.create(
      "file://target/movies", // datastore url
       Neo4jXOProvider.class, // datastore provider
       Movie.class, Person.class, Actor.class, Directory.class // persistent interface types
    ).create();
    XOManagerFactory xmf = XO.createXOManagerFactory(xoUnit);
    ...
    xmf.close();
  }

}

Note: The class XOUnitBuilder provides a fluent interface for the parameters which may be specified for an XO unit.

Mapping Persistent Types

The Neo4j database provides the following native datastore concepts:

Node

An entity, e.g. a Person, Movie, etc. A node might have labels and properties.

Relationship

A directed relation between two nodes, might have properties. The lifecycle of relation depends on the lifecycle of the nodes it connects.

The eXtended Objects datastore for Neo4j allows mapping of all these concepts to Java interfaces.

Nodes

Labeled Types

Neo4j allows adding one or more labels to a node. These labels are used by eXtended Objects to identify the corresponding Java type(s) a node is representing. Thus for each label that shall be used by the application a corresponding interface type must be created which is annotated with @Label.

Person.java
@Label
public interface Person {

String getName();
void String setName();

}

The name of the label defaults to the name of the interface, in this case Person. A specific value can be enforced by adding a value to the @Label annotation.

It can also be seen that a label usually enforces the presence of specific properties (or relations) on a node. The name of a property - starting with a lower case letter - is used to store its value in the database, this can be overwritten using @Property. The following example demonstrates explicit mappings for a label and a property:

Person.java
@Label("MyPerson")
public interface Person {

  @Property("myName")
  String getName();
  void String setName();

}

The mapping of relations will be covered later.

Inheritance Of Labels

A labeled type can extend from one or more labeled types.

Actor.java
@Label
public interface Actor extends Person {
}

In this case a node created using the type Actor would be labeled with both Person and Actor. This way of combining types is referred to as static composition.

Template Types

There might be situations where the same properties or relations shall be re-used between various labels. In this case template types can be used, these are just interfaces specifying properties and relations which shall be shared. The following example demonstrates how the property name of the labeled type Person is extracted to a template type:

Named.java
public interface Named {

  String getName();
  void setName(String name);

}
Person.java
@Label
public interface Person extends Named {
}

Relations

Unidirectional Relations

A node can directly reference other nodes using relation properties. A property of a labeled type or template type is treated as such if it references another labeled type or a collection thereof.

Movie.java
@Label
public interface Movie {

  String getTitle();
  void setTitle();

}
Actor.java
@Label
public interface Actor extends Person {

  List<Movie> getActedIn();

}

If no further mapping information is provided an outgoing unidirectional relation using the fully capitalized name of the property is assumed. The name may be specified using the @Relation annotation with the desired value. Furthermore using one of the annotations @Outgoing or @Incoming the direction of the relation can be specified.

Actor.java
@Label
public interface Actor extends Person {

  @Relation("ACTED_IN")
  @Outgoing
  List<Movie> getActedIn();

}

Note on multi-valued relations (i.e. collections):

  • Only the following types are supported: java.util.Collection, java.util.List or java.util.Set.

  • It is recommend to only specify the getter method of the property, as add or remove operations can be performed using the corresponding collection methods

  • The provided java.util.Set implementation ensures uniqueness of the relation to the referenced node, if this is not necessary java.util.List should be prefered for faster add-operations.

Bidirectional Qualified Relations

Relations in many case shall be accessible from both directions. One possible way is to use two independent unidirectional relations which map to the same relation type; one of them annotated with @Outgoing, the other with @Incoming. There are some problems with this approach:

  • it is not explicitly visible that the two relation properties are mapped to the same type

  • renaming of the type or of one the properties might break the mapping

The recommended way is to use an annotation which qualifies the relation and holds the mapping information at a single point:

ActedIn.java
@Relation
@Retention(RUNTIME)
public @interface ActedIn {
}
Actor.java
@Label
public interface Actor extends Person {

  @ActedIn
  @Outgoing
  List<Movie> getActedIn();

}
Movie.java
@Label
public interface Movie {

  String getTitle();
  void setTitle();

  @ActedIn
  @Incoming
  List<Actors> getActors();

}

Typed Relations With Properties

If a relation between two nodes shall have properties a dedicated type must be declared. It must contain two properties returning the types of referenced types which are annotated with @Incoming and @Outgoing:

Directed.java
@Relation
public interface Directed {

  @Outgoing
  Director getDirector();

  @Incoming
  Movie getMovie();

  Calendar getFrom();
  void setFrom(Calendar from);

  Calendar getUntil();
  void setUntil(Calender until);

}
Director.java
@Label
public interface Director extends Person {

  List<Directed> getDirected();

}
Movie.java
@Label
public interface Movie {

  String getTitle();
  void setTitle();

  List<Directed> getDirected();

  ...
}

Note: If the typed relation references the same labeled type at both ends then the according properties of the latter must also be annotated with @Outgoing and @Incoming:

Directed.java
@Relation
public interface References {

  @Outgoing
  Movie getReferencing();

  @Incoming
  Movie getReferenced();

  int getMinute();
  void setMinute(int minute);

  int getSecond()
  void setSecond(int second);
}
Movie.java
@Label
public interface Movie {

  @Outgoing
  List<References> getReferenced();

  @Incoming
  List<References> getReferencedBy();

  ...
}

Typed relations may also be constructed using Template Types, i.e. types which define commonly used Properties.

Dynamic Properties

Labeled types or relation types may also define methods which execute a query on invocation and return the result:

Movie.java
@Label
public interface Movie {

  @ResultOf
  @Cypher("match (m:Movie) where m.title={title} return m");
  Result<Movie> getMoviesByTitle(@Parameter("title") String title);

  @ResultOf
  @Cypher("match (a:Actor)-[:ACTED_IN]->(m:Movie) where id(m)={this} return count(a)");
  Long getActorCount();

  ...
}

Transient Properties

Properties of entities or relations can be declared as transient, i.e. they may be used at runtime but will not be stored in the database:

Person.java
@Label
public interface Person {

  @Transient
  String getName();
  void setName();

}

User defined methods

It can be useful to provide a custom implementation of a method which has direct access to the underlying datatypes. This can be achieved using @ImplementedBy.

Person.java
@Label
public interface Person {

  @ImplementedBy(SetNameMethod.class)
  String setName(String firstName, String lastName);

}
SetNameMethod.java

public class SetNameMethod implements ProxyMethod<Node> {

 @Override
    public Object invoke(Node node, Object instance, Object[] args) {
      String firstName = (String) args[0];
      String lastName = (String) args[1];
      String fullName = firstName + " " + lastName;
      node.setProperty("name", fullName);
      return fullName;
    }

}