I want to write a extention to provide REST API for client to add a new user to Neo4j database, which I also use Spring Data Neo4j to OGM. Client just sends a josn post to the extention, and just get a simple status result.
1. Create a maven project in eclipse with pom.xml to manage related dependencies
2. Create Modle
package com.fadeinfadeout.modle; import java.util.HashSet; import java.util.Set; import javax.xml.bind.annotation.XmlRootElement; import org.codehaus.jackson.annotate.JsonAutoDetect; import org.neo4j.graphdb.Direction; import org.springframework.data.neo4j.annotation.GraphId; import org.springframework.data.neo4j.annotation.Indexed; import org.springframework.data.neo4j.annotation.NodeEntity; import org.springframework.data.neo4j.annotation.RelatedTo; @NodeEntity @XmlRootElement @JsonAutoDetect public class User { private String name; @Indexed(unique = true) private String userId; private String phoneNumber; @GraphId private Long nodeId; @RelatedTo(type = "IS_FRIEND_OF", direction = Direction.BOTH) private Set<User> friends; public User() { } public User(String userId, String name, String phoneNumber) { this.userId = userId; this.name = name; this.phoneNumber = phoneNumber; } public Long getNodeId() { return nodeId; } public void setNodeId(Long nodeId) { this.nodeId = nodeId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } public Set<User> getFriends() { return friends; } public void setFriends(Set<User> friends) { this.friends = friends; } public void addFriends(User friend) { if (friends == null) friends = new HashSet<User>(); friends.add(friend); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (nodeId != null ? !nodeId.equals(user.nodeId) : user.nodeId != null) return false; return true; } @Override public int hashCode() { return nodeId != null ? nodeId.hashCode() : 0; } public String toString(){ return "userId="+this.userId +"\tname="+this.name+"\tpN="+this.phoneNumber; } }
In order to using auto json wired in jersey(which is a Restful framework embeded in Neo4j to provide Remote access), you should anotate the mode class with
@XmlRootElement @JsonAutoDetect
and add dependencies in pom.xml
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> <version>1.9</version> <exclusions> <exclusion> <artifactId>jackson-core-asl</artifactId> <groupId>org.codehaus.jackson</groupId> </exclusion> <exclusion> <artifactId>jackson-jaxrs</artifactId> <groupId>org.codehaus.jackson</groupId> </exclusion> <exclusion> <artifactId>jersey-core</artifactId> <groupId>com.sun.jersey</groupId> </exclusion> </exclusions> </dependency>
3. Create a spring data neo4j repository to operate User
package com.fadeinfadeout.repository; import org.springframework.data.neo4j.annotation.Query; import org.springframework.data.neo4j.repository.GraphRepository; import com.fadeinfadeout.modle.User; public interface UserRepository extends GraphRepository<User> { @Query(value = "match (n)-[r:IS_FRIEND_OF]-(friend)-[r2:IS_FRIEND_OF]-(fof) " + "where n.userId) = {0} " + "return distinct fof") Iterable<User> getFriendsOfFriends(String userId); }
4. Inorder to use SDN in Neo4j unmanaged extentions, We should inite SDN with SpringPluginInitializer
package com.fadeinfadeout.plugins; import java.util.Collection; import java.util.logging.Logger; import org.apache.commons.configuration.Configuration; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.server.plugins.Injectable; import org.springframework.data.neo4j.server.SpringPluginInitializer; import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.util.Assert; import com.fadeinfadeout.repository.UserRepository; public class FadeinFadeoutNeo4jPluginInitializer extends SpringPluginInitializer{ private static final Logger logger = Logger.getLogger(FadeinFadeoutNeo4jPluginInitializer.class.getName()); @SuppressWarnings("unchecked") public FadeinFadeoutNeo4jPluginInitializer() { super(new String[] { "META-INF/spring/application-context.xml" }, expose("neo4jTemplate", Neo4jTemplate.class), expose("userRepository", UserRepository.class)); } }
This class is aimed to initialize SDN and expose Beans which could be used in extentions
The full-qualified class name should be placed in file org.neo4j.server.plugins.PluginLifecycle which located at
src/main/resources/MATA-INF/services dir. When start neo4j server, neo4j will pick the file and scan its contnets, then initialize related classes.
5.Create a Unmanaged Extentions to provide clients User operation API
package com.fadeinfadeout.unmanagedext; import java.util.logging.Logger; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import com.fadeinfadeout.modle.User; import com.fadeinfadeout.plugins.FadeinFadeoutNeo4jPluginInitializer; import com.fadeinfadeout.repository.UserRepository; @Path("/user") public class UserCtrollerUnmanagedExt { private static final String ERROR_MSG = "{Result:[error]}"; private static final String OK_MSG = "{Result:[OK]}"; private static final Logger logger = Logger.getLogger(UserCtrollerUnmanagedExt.class.getName()); @Context private UserRepository userRepo; @Path("/addUser") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response addUser(User user) { String userId = null; String name = null; String phoneNumber = null; userId = user.getUserId(); name = user.getName(); phoneNumber = user.getPhoneNumber(); logger.info("Json user paramerters : " + user.toString()); if (userId == null || name == null || phoneNumber == null) { return Response.status(Status.NOT_ACCEPTABLE).entity(ERROR_MSG).build(); } // User newUser = new User(userId, name, phoneNumber); userRepo.saveOnly(user); return Response.status(Status.OK).entity(OK_MSG).build(); } }
To let neo4j knows the extention, you should configure it in conf/neo4j-server.properties
org.neo4j.server.thirdparty_jaxrs_classes=com.fadeinfadeout=/fifo
Your client can use the following path to access the extention
curl -i -H 'content-type: application/json' -X POST -d '{"userId":"1","name":"zhaohj","phoneNumber":"18190752251"}' htt:7474/fifo/user/addUser
6. Integrate SDN in Neo4j, you should configure dependencies in pom.xml. the following sinppets are some important dependencies
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-neo4j-aspects</artifactId> <version>${neo4j.springdata.version}</version> <exclusions> <exclusion> <groupId>org.neo4j</groupId> <artifactId>neo4j-kernel</artifactId> </exclusion> <exclusion> <groupId>org.neo4j</groupId> <artifactId>neo4j</artifactId> </exclusion> <exclusion> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </exclusion> <exclusion> <groupId>org.aspectj</groupId> <artifactId>*</artifactId> </exclusion> <exclusion> <artifactId>geronimo-jta_1.1_spec</artifactId> <groupId>org.apache.geronimo.specs</groupId> </exclusion> </exclusions> </dependency>
Note:
A:Due to transitive dependices, some jars are provided by Neo4j in lib and system/lib dir. So, you should exclude these jars from pom.xml
B:Because many spring jars include some meta data file in META-INF/ dir, when you build the final jar, you should merge these meta file using maven shade plugin, otherwise some schema Handler can't found when start neo4j server
<build> <pluginManagement> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.tooling</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.factories</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer" /> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> </plugin> </plugins> </build>
The configure SDN, you should put the SDN configuration file application-context.conf in src/main/resoureces/MATA-INF/spring
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:neo4j="http://www.springframework.org/schema/data/neo4j" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd"> <context:annotation-config /> <context:spring-configured /> <context:component-scan base-package="com.fadeinfadeout"> </context:component-scan> <neo4j:config id="graphDatabaseService" storeDirectory="/home/zhaohj/hadoop/neo4j-community-2.1.7/data/graph.db" base-package="com.fadeinfadeout.modle" graphDatabaseService="graphDatabaseService" /> <!-- Instructs Spring Data where to look for repository implementations --> <neo4j:repositories base-package="com.fadeinfadeout.repository" /> <tx:annotation-driven mode="aspectj" /> </beans>
The final project dirs likes
References
https://jersey.java.net/documentation/1.9/json.html
https://inserpio.wordpress.com/2014/04/30/extending-the-neo4j-server-with-spring-data-neo4j/
http://neo4j.com/docs/stable/server-unmanaged-extensions.html
https://github.com/AtomRain/neo4j-extensions
http://docs.spring.io/spring-data/neo4j/docs/3.0.0.RELEASE/reference/html/reference_neo4j-server.html
http://examples.javacodegeeks.com/enterprise-java/rest/jersey/json-example-with-jersey-jackson/