Neo4j: Get Json Parameters in Extentions with Spring Data Neo4j
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/