Merge branch 'master' of github.com:SpringSource/spring-data-graph

This commit is contained in:
Andres Taylor
2011-04-01 09:40:48 +02:00
53 changed files with 1280 additions and 804 deletions

View File

@@ -42,7 +42,6 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>

View File

@@ -1,8 +1,6 @@
package org.neo4j.kernel;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.kernel.Config;
import org.neo4j.kernel.IdGeneratorFactory;
import org.neo4j.kernel.impl.core.*;
import org.neo4j.kernel.impl.nioneo.store.FileSystemAbstraction;
import org.neo4j.kernel.impl.nioneo.store.StoreId;
@@ -48,7 +46,7 @@ public class RestConfig extends Config {
}
private static class NullTransactionManager implements TransactionManager {
private static final Transaction TRANSACTION = new Transaction() {
private static final Transaction NULL_JAVA_TRANSACTION = new Transaction() {
@Override
public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException, SecurityException, SystemException {
@@ -102,7 +100,7 @@ public class RestConfig extends Config {
@Override
public Transaction getTransaction() throws SystemException {
return TRANSACTION;
return NULL_JAVA_TRANSACTION;
}
@Override
@@ -127,7 +125,7 @@ public class RestConfig extends Config {
@Override
public Transaction suspend() throws SystemException {
return TRANSACTION;
return NULL_JAVA_TRANSACTION;
}
}
}
}

View File

@@ -1,39 +0,0 @@
package org.neo4j.rest.graphdb;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
/**
* @author mh
* @since 25.01.11
*/
public class GraphDatabaseFactory {
public static GraphDatabaseService databaseFor(String url) {
return databaseFor( url, null,null );
}
public static GraphDatabaseService databaseFor(String url, String username, String password) {
if (url.startsWith( "http://" ) || url.startsWith( "https://" )) {
return new RestGraphDatabase( toURI( url ), username,password );
}
String path=url;
if (url.startsWith( "file:" )) {
path = toURI( url ).getPath();
}
File file = new File( path );
if (!file.isDirectory()) file=file.getParentFile();
return new EmbeddedGraphDatabase( file.getAbsolutePath() );
}
private static URI toURI( String uri ) {
try {
return new URI(uri);
} catch ( URISyntaxException e ) {
throw new RuntimeException( "Error using URI "+uri, e);
}
}
}

View File

@@ -1,6 +1,7 @@
package org.neo4j.rest.graphdb;
import com.sun.jersey.api.client.ClientResponse;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.helpers.collection.IterableWrapper;
@@ -154,7 +155,11 @@ public class RestEntity implements PropertyContainer {
return getClass().equals( o.getClass() ) && getId() == ( (RestEntity) o ).getId();
}
public RestGraphDatabase getGraphDatabase() {
public GraphDatabaseService getGraphDatabase() {
throw new UnsupportedOperationException("No GraphDatabaseService semantics for the REST-API");
}
public RestGraphDatabase getRestGraphDatabase() {
return graphDatabase;
}

View File

@@ -4,18 +4,22 @@ import com.sun.jersey.api.client.ClientResponse;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.event.KernelEventHandler;
import org.neo4j.graphdb.event.TransactionEventHandler;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.kernel.AbstractGraphDatabase;
import org.neo4j.kernel.Config;
import org.neo4j.kernel.RestConfig;
import org.neo4j.rest.graphdb.index.RestIndexManager;
import org.springframework.data.graph.core.GraphDatabase;
import org.springframework.data.graph.core.Property;
import javax.ws.rs.core.Response.Status;
import java.io.Serializable;
import java.net.URI;
import java.util.Map;
public class RestGraphDatabase extends AbstractGraphDatabase {
public class RestGraphDatabase extends AbstractGraphDatabase implements GraphDatabase {
private RestRequest restRequest;
private long propertyRefetchTimeInMillis = 1000;
@@ -28,86 +32,62 @@ public class RestGraphDatabase extends AbstractGraphDatabase {
restRequest = new RestRequest( uri, user, password );
}
public Transaction beginTx() {
return new Transaction() {
public void success() {
}
public void finish() {
}
public void failure() {
}
};
@Override
public Node getNodeById(long id) {
ClientResponse response = restRequest.get("node/" + id);
if ( restRequest.statusIs(response, Status.NOT_FOUND) ) {
throw new NotFoundException( "" + id );
}
return new RestNode( restRequest.toMap(response), this );
}
public <T> TransactionEventHandler<T> registerTransactionEventHandler( TransactionEventHandler<T> tTransactionEventHandler ) {
throw new UnsupportedOperationException();
}
public <T> TransactionEventHandler<T> unregisterTransactionEventHandler( TransactionEventHandler<T> tTransactionEventHandler ) {
throw new UnsupportedOperationException();
}
public KernelEventHandler registerKernelEventHandler( KernelEventHandler kernelEventHandler ) {
throw new UnsupportedOperationException();
}
public KernelEventHandler unregisterKernelEventHandler( KernelEventHandler kernelEventHandler ) {
throw new UnsupportedOperationException();
}
public IndexManager index() {
return new RestIndexManager( restRequest, this );
}
public Node createNode() {
ClientResponse response = restRequest.post( "node", null );
if ( restRequest.statusOtherThan( response, Status.CREATED ) ) {
@Override
public Node createNode(Property... props) {
ClientResponse response = restRequest.post("node", null);
if ( restRequest.statusOtherThan(response, Status.CREATED) ) {
throw new RuntimeException( "" + response.getStatus() );
}
return new RestNode( response.getLocation(), this );
}
public boolean enableRemoteShell() {
throw new UnsupportedOperationException();
}
public boolean enableRemoteShell( Map<String, Serializable> config ) {
throw new UnsupportedOperationException();
}
public Iterable<Node> getAllNodes() {
throw new UnsupportedOperationException();
}
public Node getNodeById( long id ) {
ClientResponse response = restRequest.get( "node/" + id );
if ( restRequest.statusIs( response, Status.NOT_FOUND ) ) {
throw new NotFoundException( "" + id );
}
return new RestNode( restRequest.toMap( response ), this );
}
public Node getReferenceNode() {
Map<?, ?> map = restRequest.toMap( restRequest.get( "" ) );
return new RestNode( (String) map.get( "reference_node" ), this );
}
public Relationship getRelationshipById( long id ) {
ClientResponse response = restRequest.get( "relationship/" + id );
@Override
public Relationship getRelationshipById(long id) {
ClientResponse response = restRequest.get("relationship/" + id);
if ( restRequest.statusIs( response, Status.NOT_FOUND ) ) {
throw new NotFoundException( "" + id );
}
return new RestRelationship( restRequest.toMap( response ), this );
}
public Iterable<RelationshipType> getRelationshipTypes() {
throw new UnsupportedOperationException();
@Override
public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Property... props) {
Relationship relationship = startNode.createRelationshipTo(endNode, type);
return relationship;
}
public void shutdown() {
@Override
public <T extends PropertyContainer> Index<T> getIndex(String indexName) {
return (Index<T>) index().forNodes(indexName); // todo
}
@Override
public <T extends PropertyContainer> Index<T> createIndex(Class<T> type, String indexName, boolean fullText) {
return (Index<T>) index().forNodes(indexName); // todo
}
@Override
public TraversalDescription createTraversalDescription() {
return new RestTraversal();
}
public RestIndexManager index() {
return new RestIndexManager( restRequest, this );
}
@Override
public Node getReferenceNode() {
Map<?, ?> map = restRequest.toMap( restRequest.get( "" ) );
return new RestNode( (String) map.get( "reference_node" ), this );
}
public RestRequest getRestRequest() {
@@ -117,9 +97,77 @@ public class RestGraphDatabase extends AbstractGraphDatabase {
public long getPropertyRefetchTimeInMillis() {
return propertyRefetchTimeInMillis;
}
@Override
public Node createNode() {
return createNode((Property[])null);
}
@Override
public Iterable<Node> getAllNodes() {
throw new UnsupportedOperationException();
}
@Override
public Iterable<RelationshipType> getRelationshipTypes() {
throw new UnsupportedOperationException();
}
@Override
public void shutdown() {
}
@Override
public boolean enableRemoteShell() {
return false;
}
@Override
public boolean enableRemoteShell(Map<String, Serializable> initialProperties) {
return false;
}
@Override
public Transaction beginTx() {
return new Transaction() {
@Override
public void failure() {
}
@Override
public void success() {
}
@Override
public void finish() {
}
};
}
@Override
public <T> TransactionEventHandler<T> registerTransactionEventHandler(TransactionEventHandler<T> handler) {
return handler;
}
@Override
public <T> TransactionEventHandler<T> unregisterTransactionEventHandler(TransactionEventHandler<T> handler) {
return handler;
}
@Override
public KernelEventHandler registerKernelEventHandler(KernelEventHandler handler) {
return handler;
}
@Override
public KernelEventHandler unregisterKernelEventHandler(KernelEventHandler handler) {
return handler;
}
@Override
public String getStoreDir() {
return restRequest.getUri().toString();
return getRestRequest().getUri().toString();
}
@Override

View File

@@ -35,7 +35,7 @@ public class RestNode extends RestEntity implements Node {
if ( restRequest.statusOtherThan( response, Status.CREATED ) ) {
throw new RuntimeException( "" + response.getStatus() );
}
return new RestRelationship( response.getLocation(), getGraphDatabase() );
return new RestRelationship( response.getLocation(), getRestGraphDatabase() );
}
public Iterable<Relationship> getRelationships() {
@@ -48,7 +48,7 @@ public class RestNode extends RestEntity implements Node {
(Collection<Object>) restRequest.toEntity( response ) ) {
@Override
protected Relationship underlyingObjectToObject( Object data ) {
return new RestRelationship( (Map<?, ?>) data, getGraphDatabase() );
return new RestRelationship( (Map<?, ?>) data, getRestGraphDatabase() );
}
};
}

View File

@@ -44,7 +44,7 @@ public class RestRelationship extends RestEntity implements Relationship {
}
private RestNode node( String uri ) {
return new RestNode( uri, getGraphDatabase() );
return new RestNode( uri, getRestGraphDatabase() );
}
public Node getStartNode() {

View File

@@ -207,7 +207,7 @@ public class RestTraversal implements RestTraversalDescription
{
throw new RuntimeException( String.format( "Unexpected traversal result, %s instead of collection", col != null ? col.getClass() : null ) );
}
return new RestTraverser( (Collection)col, restNode.getGraphDatabase() );
return new RestTraverser( (Collection)col, restNode.getRestGraphDatabase() );
}
public static RestTraversalDescription description()

View File

@@ -22,13 +22,13 @@ public class RestRelationshipIndex extends RestIndex<Relationship> implements Re
return Relationship.class;
}
public void remove(Relationship entity, String key) {
throw new UnsupportedOperationException();
}
public void remove(Relationship entity) {
throw new UnsupportedOperationException();
}
// public void remove(Relationship entity, String key) {
// throw new UnsupportedOperationException();
// }
//
// public void remove(Relationship entity) {
// throw new UnsupportedOperationException();
// }
protected Relationship createEntity( Map<?, ?> item ) {
return new RestRelationship( (Map<?, ?>) item, restGraphDatabase );

View File

@@ -1,23 +1,13 @@
package org.neo4j.rest.graphdb;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import org.apache.log4j.BasicConfigurator;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.server.AddressResolver;
import org.neo4j.server.NeoServerWithEmbeddedWebServer;
import org.neo4j.server.modules.RESTApiModule;
import org.neo4j.server.modules.ThirdPartyJAXRSModule;
import org.neo4j.server.startup.healthcheck.StartupHealthCheck;
import org.neo4j.server.web.Jetty6WebServer;
import java.net.URI;
import java.net.URISyntaxException;
@@ -25,7 +15,7 @@ import java.util.Iterator;
public class RestTestBase {
protected GraphDatabaseService graphDb;
protected RestGraphDatabase graphDb;
private static final String HOSTNAME = "localhost";
private static final int PORT = 7473;
private static LocalTestServer neoServer = new LocalTestServer(HOSTNAME,PORT).withPropertiesFile("test-db.properties");
@@ -47,11 +37,6 @@ public class RestTestBase {
neoServer.cleanDb();
}
@After
public void tearDown() throws Exception {
graphDb.shutdown();
}
@AfterClass
public static void shutdownDb() {
neoServer.stop();
@@ -61,7 +46,7 @@ public class RestTestBase {
protected Relationship relationship() {
Iterator<Relationship> it = node().getRelationships(Direction.OUTGOING).iterator();
if (it.hasNext()) return it.next();
return node().createRelationshipTo(graphDb.createNode(), Type.TEST);
return node().createRelationshipTo(graphDb.createNode(null), Type.TEST);
}
protected Node node() {

View File

@@ -24,20 +24,20 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestEntityPropertyValidationTest extends EntityPropertyValidationTest {
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
}

View File

@@ -5,8 +5,7 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.neo4j.rest.graphdb.RestTestBase;
import org.springframework.data.graph.neo4j.support.FinderTest;
import org.springframework.data.graph.neo4j.support.IndexTest;
import org.springframework.data.graph.neo4j.support.GraphRepositoryTest;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -22,22 +21,22 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml",
"classpath:RestTest-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestFinderTest extends FinderTest {
public class RestFinderTest extends GraphRepositoryTest {
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
}

View File

@@ -27,20 +27,20 @@ import java.net.NoRouteToHostException;
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestNodeEntityRelationshipTest extends NodeEntityRelationshipTest {
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
}

View File

@@ -6,7 +6,6 @@ import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.neo4j.rest.graphdb.RestTestBase;
import org.springframework.data.graph.neo4j.support.ProjectionTest;
import org.springframework.data.graph.neo4j.support.RelationshipEntityTest;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -24,20 +23,20 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestProjectionTest extends ProjectionTest {
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
}

View File

@@ -24,20 +24,20 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestPropertyTest extends PropertyTest {
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
}

View File

@@ -24,20 +24,20 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestRelationshipEntityTest extends RelationshipEntityTest {
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@Before
public void cleanDb() {
RestTestBase.cleanDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
}

View File

@@ -0,0 +1,82 @@
package org.springframework.data.graph.core;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.traversal.TraversalDescription;
/**
* @author mh
* @since 29.03.11
*/
public interface GraphDatabase {
/**
* @return the reference node of the underlying graph database
*/
Node getReferenceNode();
/**
* @param id node id
* @return the requested node of the underlying graph database
* @throws org.neo4j.graphdb.NotFoundException
*/
Node getNodeById(long id);
/**
* Transactionally creates the node, sets the properties (if any).
* Two shortcut means of providing the properties (very short with static imports)
* <code>graphDatabase.createNode(PropertyMap._("name","value"));</code>
* <code>graphDatabase.createNode(PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop");</code>
*
* @param props properties to be set at node creation might be null
* @return the newly created node
*/
Node createNode(Property... props);
/**
* @param id relationship id
* @return the requested relationship of the underlying graph database
* @throws org.neo4j.graphdb.NotFoundException
*/
Relationship getRelationshipById(long id);
/**
* Transactionally creates the relationship, sets the properties (if any) and indexes the given fielss (if any)
* Two shortcut means of providing the properties (very short with static imports)
* <code>graphDatabase.createRelationship(from,to,TYPE, PropertyMap._("name","value"));</code>
* <code>graphDatabase.createRelationship(from,to,TYPE, PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop");</code>
*
*
* @param startNode start-node of relationship
* @param endNode end-node of relationship
* @param type relationship type, might by an enum implementing RelationshipType or a DynamicRelationshipType.withName("name")
* @param props optional initial properties
* @return the newly created relationship
*/
Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Property... props);
/**
* @param indexName existing index name, not null
* @return existing index {@link Index}
* @throws IllegalArgumentException if the index doesn't exist
*/
<T extends PropertyContainer> Index<T> getIndex(String indexName);
/**
* creates a index
* @param type type of index requested - either Node.class or Relationship.class
* @param indexName, not null
* @param fullText true if a fulltext queryable index is needed, false for exact match
* @return node index {@link Index}
*/
<T extends PropertyContainer> Index<T> createIndex(Class<T> type, String indexName, boolean fullText);
/**
* @return a TraversalDescription as starting point for defining a traversal
*/
TraversalDescription createTraversalDescription();
}

View File

@@ -0,0 +1,89 @@
package org.springframework.data.graph.core;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.data.graph.neo4j.support.DelegatingGraphDatabase;
import javax.annotation.PreDestroy;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URI;
/**
* @author mh
* @since 25.01.11
*/
public class GraphDatabaseFactory implements FactoryBean<GraphDatabase> {
private String storeLocation;
private String userName;
private String password;
protected GraphDatabase graphDatabase;
public String getStoreLocation() {
return storeLocation;
}
public void setStoreLocation(String storeLocation) {
this.storeLocation = storeLocation;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private GraphDatabase databaseFor(String url, String username, String password) throws Exception {
if (url.startsWith( "http://" ) || url.startsWith( "https://" )) {
return createRestGraphDatabase(url, username, password);
}
String path=url;
if (url.startsWith( "file:" )) {
path = new URI(url).getPath();
}
File file = new File( path );
// if (!file.isDirectory()) file=file.getParentFile();
return new DelegatingGraphDatabase(new EmbeddedGraphDatabase(file.getAbsolutePath()));
}
private GraphDatabase createRestGraphDatabase(String url, String username, String password) throws Exception {
Class<?> restGraphDatabaseClass = Class.forName("org.neo4j.rest.graphdb.RestGraphDatabase");
Constructor<?> constructor = restGraphDatabaseClass.getConstructor(URI.class, String.class, String.class);
return (GraphDatabase) constructor.newInstance(new URI(url), username,password );
}
@Override
public GraphDatabase getObject() throws Exception {
if (graphDatabase==null) graphDatabase = databaseFor(storeLocation, userName, password);
return graphDatabase;
}
@PreDestroy
public void shutdown() {
if (graphDatabase instanceof DelegatingGraphDatabase) {
((DelegatingGraphDatabase)graphDatabase).shutdown();
}
}
@Override
public Class<?> getObjectType() {
return GraphDatabaseService.class;
}
@Override
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,48 @@
package org.springframework.data.graph.core;
import java.util.Map;
/**
* @author mh
* @since 31.03.11
*/
public class Property {
public final String name;
public final Object value;
public Property(String name, Object value) {
this.name = name;
this.value = value;
}
public static Property _(String name, Object value) {
return new Property(name, value);
}
public static Property[] _(String name1, Object value1, String name2, Object value2) {
return new Property[]{new Property(name1, value1), new Property(name2, value2)};
}
public static Property[] _(String name1, Object value1, String name2, Object value2, String name3, Object value3) {
return new Property[]{new Property(name1, value1), new Property(name2, value2), new Property(name3, value3)};
}
public static Property[] _(Object... nameValuePairs) {
if (nameValuePairs.length % 2 != 0)
throw new IllegalArgumentException("there must be an even number of name value pairs");
Property[] result = new Property[nameValuePairs.length / 2];
for (int i = 0; i < result.length; i++) {
result[i] = new Property(nameValuePairs[i * 2].toString(), nameValuePairs[i * 2 + 1]);
}
return result;
}
public static Property[] _(Map<String, Object> properties) {
Property[] result = new Property[properties.size()];
int i = 0;
for (Map.Entry<String, Object> entry : properties.entrySet()) {
result[i++] = new Property(entry.getKey(), entry.getValue());
}
return result;
}
}

View File

@@ -31,9 +31,10 @@ import org.neo4j.graphdb.PropertyContainer;
*/
public interface TypeRepresentationStrategy<S extends PropertyContainer, T extends GraphBacked<S>> {
/**
* callback on entity creation for setting up type representation
* @param state
* @param type
* Callback for setting up and/or storing type information after creation.
*
* @param state Backing state of entity being created
* @param type Type of entity being created
*/
void postEntityCreation(S state, Class<? extends T> type);
@@ -57,10 +58,12 @@ public interface TypeRepresentationStrategy<S extends PropertyContainer, T exten
<U extends T> Class<U> getJavaType(S state);
/**
* callback for lifecycle management before node entity removal
* @param entity
* Callback for cleaning up type information before removal. If state does not have any
* state associated, doesn't do anything.
*
* @param state Backing state of entity being removed
*/
void preEntityRemoval(T entity);
void preEntityRemoval(S state);
/**
* Instantiate the entity given its state. The type of the entity is inferred by the strategy

View File

@@ -16,10 +16,7 @@
package org.springframework.data.graph.neo4j.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.*;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
@@ -31,7 +28,7 @@ import java.util.Set;
* @author Michael Hunger
* @since 11.09.2010
*/
public abstract class AbstractNodeRelationshipFieldAccessor<ENTITY extends GraphBacked,STATE,TARGET extends GraphBacked,TSTATE> implements FieldAccessor<ENTITY> {
public abstract class AbstractNodeRelationshipFieldAccessor<ENTITY extends GraphBacked,STATE extends PropertyContainer,TARGET extends GraphBacked,TSTATE extends PropertyContainer> implements FieldAccessor<ENTITY> {
protected final RelationshipType type;
protected final Direction direction;
protected final Class<? extends TARGET> relatedType;

View File

@@ -46,7 +46,7 @@ public class ConvertingNodePropertyFieldAccessorFactory implements FieldAccessor
@Override
public FieldAccessor<GraphBacked<PropertyContainer>> forField(final Field field) {
return new ConvertingNodePropertyFieldAccessor(field, conversionService);
return new ConvertingNodePropertyFieldAccessor(conversionService, DelegatingFieldAccessorFactory.getNeo4jPropertyName(field),field.getType());
}
private boolean isSerializableField(final Field field) {
@@ -67,8 +67,8 @@ public class ConvertingNodePropertyFieldAccessorFactory implements FieldAccessor
public static class ConvertingNodePropertyFieldAccessor extends PropertyFieldAccessorFactory.PropertyFieldAccessor {
private final ConversionService conversionService;
public ConvertingNodePropertyFieldAccessor(final Field field, final ConversionService conversionService) {
super(field, conversionService);
public ConvertingNodePropertyFieldAccessor(ConversionService conversionService, String propertyName, Class fieldType) {
super(conversionService,propertyName,fieldType);
this.conversionService = conversionService;
}
@@ -88,7 +88,7 @@ public class ConvertingNodePropertyFieldAccessorFactory implements FieldAccessor
}
private Object deserializePropertyValue(final Object value) {
return conversionService.convert(value, field.getType());
return conversionService.convert(value, fieldType);
}
}

View File

@@ -43,7 +43,7 @@ public class PropertyFieldAccessorFactory implements FieldAccessorFactory<GraphB
@Override
public FieldAccessor<GraphBacked<PropertyContainer>> forField(final Field field) {
return new PropertyFieldAccessor(field,conversionService);
return new PropertyFieldAccessor(conversionService,DelegatingFieldAccessorFactory.getNeo4jPropertyName(field),field.getType());
}
private boolean isNeo4jPropertyType(final Class<?> fieldType) {
@@ -57,12 +57,14 @@ public class PropertyFieldAccessorFactory implements FieldAccessorFactory<GraphB
}
public static class PropertyFieldAccessor implements FieldAccessor<GraphBacked<PropertyContainer>> {
protected final Field field;
private final ConversionService conversionService;
protected final String propertyName;
protected final Class<?> fieldType;
public PropertyFieldAccessor(final Field field, ConversionService conversionService) {
this.field = field;
public PropertyFieldAccessor(ConversionService conversionService, String propertyName, Class fieldType) {
this.conversionService = conversionService;
this.propertyName = propertyName;
this.fieldType = fieldType;
}
@Override
@@ -74,9 +76,9 @@ public class PropertyFieldAccessorFactory implements FieldAccessorFactory<GraphB
public Object setValue(final GraphBacked<PropertyContainer> graphBacked, final Object newVal) {
final PropertyContainer propertyContainer = graphBacked.getPersistentState();
if (newVal==null) {
propertyContainer.removeProperty(getPropertyName());
propertyContainer.removeProperty(propertyName);
} else {
propertyContainer.setProperty(getPropertyName(), newVal);
propertyContainer.setProperty(propertyName, newVal);
}
return newVal;
}
@@ -87,17 +89,16 @@ public class PropertyFieldAccessorFactory implements FieldAccessorFactory<GraphB
}
protected Object doGetValue(final GraphBacked<PropertyContainer> graphBacked) {
Class<?> type = field.getType();
Object value = graphBacked.getPersistentState().getProperty(getPropertyName(), getDefaultValue(type));
if (value == null || type.isInstance(value)) return value;
if (conversionService!=null) {
return conversionService.convert(value,type);
PropertyContainer element = graphBacked.getPersistentState();
if (element.hasProperty(propertyName)) {
Object value = element.getProperty(propertyName);
if (value == null || fieldType.isInstance(value)) return value;
if (conversionService!=null) {
return conversionService.convert(value, fieldType);
}
return value;
}
return value;
}
private String getPropertyName() {
return DelegatingFieldAccessorFactory.getNeo4jPropertyName(field);
return getDefaultValue(fieldType);
}
private Object getDefaultValue(final Class<?> type) {

View File

@@ -0,0 +1,115 @@
package org.springframework.data.graph.neo4j.support;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.index.impl.lucene.LuceneIndexImplementation;
import org.neo4j.kernel.AbstractGraphDatabase;
import org.neo4j.kernel.Traversal;
import org.springframework.data.graph.core.GraphDatabase;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.Property;
import org.springframework.data.graph.core.RelationshipBacked;
import java.util.Map;
/**
* @author mh
* @since 29.03.11
*/
public class DelegatingGraphDatabase implements GraphDatabase {
protected AbstractGraphDatabase delegate;
public DelegatingGraphDatabase(final AbstractGraphDatabase delegate) {
this.delegate = delegate;
}
@Override
public Node getReferenceNode() {
return delegate.getReferenceNode();
}
@Override
public Node getNodeById(long id) {
return delegate.getNodeById(id);
}
@Override
public Node createNode(Property... props) {
return setProperties(delegate.createNode(), props);
}
private <T extends PropertyContainer> T setProperties(T primitive, Property... properties) {
assert primitive != null;
if (properties==null || properties.length==0) return primitive;
for (Property prop : properties) {
if (prop.value==null) {
primitive.removeProperty(prop.name);
} else {
primitive.setProperty(prop.name, prop.value);
}
}
return primitive;
}
@Override
public Relationship getRelationshipById(long id) {
return delegate.getRelationshipById(id);
}
@Override
public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Property... props) {
return setProperties(startNode.createRelationshipTo(endNode,type),props);
}
@Override
public <T extends PropertyContainer> Index<T> getIndex(String indexName) {
IndexManager indexManager = delegate.index();
if (indexManager.existsForNodes(indexName)) return (Index<T>) indexManager.forNodes(indexName);
if (indexManager.existsForRelationships(indexName)) return (Index<T>) indexManager.forRelationships(indexName);
throw new IllegalArgumentException("Index "+indexName+" does not exist.");
}
// TODO handle existing indexes
@Override
public <T extends PropertyContainer> Index<T> createIndex(Class<T> type, String indexName, boolean fullText) {
IndexManager indexManager = delegate.index();
if (isNode(type)) {
if (indexManager.existsForNodes(indexName))
return (Index<T>) checkAndGetExistingIndex(indexName, fullText, indexManager.forNodes(indexName));
return (Index<T>) indexManager.forNodes(indexName, indexConfigFor(fullText));
} else {
if (indexManager.existsForRelationships(indexName))
return (Index<T>) checkAndGetExistingIndex(indexName, fullText, indexManager.forRelationships(indexName));
return (Index<T>) indexManager.forRelationships(indexName, indexConfigFor(fullText));
}
}
public boolean isNode(Class<? extends PropertyContainer> type) {
if (type.equals(Node.class)) return true;
if (type.equals(Relationship.class)) return false;
throw new IllegalArgumentException("Unknown Graph Primitive, neither Node nor Relationship"+type);
}
private <T extends PropertyContainer> Index<T> checkAndGetExistingIndex(final String indexName, boolean fullText, final Index<T> index) {
Map<String, String> existingConfig = delegate.index().getConfiguration(index);
Map<String, String> config = indexConfigFor(fullText);
if (config.equals(existingConfig)) return index;
throw new IllegalArgumentException("Setup for index "+indexName+" does not match "+(fullText ? "fulltext":"exact"));
}
private Map<String, String> indexConfigFor(boolean fullText) {
return fullText ? LuceneIndexImplementation.FULLTEXT_CONFIG : LuceneIndexImplementation.EXACT_CONFIG;
}
@Override
public TraversalDescription createTraversalDescription() {
return Traversal.description();
}
public void shutdown() {
delegate.shutdown();
}
}

View File

@@ -44,19 +44,206 @@ import java.util.Map;
*/
public class GraphDatabaseContext {
private static final Log log = LogFactory.getLog(GraphDatabaseContext.class);
public static final String DEFAULT_NODE_INDEX_NAME = "node";
public static final String DEFAULT_RELATIONSHIP_INDEX_NAME = "relationship";
private GraphDatabaseService graphDatabaseService;
private ConversionService conversionService;
private Validator validator;
private NodeTypeRepresentationStrategy nodeTypeRepresentationStrategy;
private RelationshipTypeRepresentationStrategy relationshipTypeRepresentationStrategy;
private Validator validator;
private final static Log log = LogFactory.getLog(GraphDatabaseContext.class);
public <S extends PropertyContainer, T extends GraphBacked<S>> Index<S> getIndex(Class<T> type) {
return getIndex(type, null);
}
public <S extends PropertyContainer, T extends GraphBacked<S>> Index<S> getIndex(Class<T> type, String indexName) {
return getIndex(type, indexName, false);
}
public <S extends PropertyContainer, T extends GraphBacked<S>> Index<S> getIndex(Class<T> type, String indexName, boolean fullText) {
if (indexName==null) indexName = Indexed.Name.get(type);
Map<String, String> config = fullText ? LuceneIndexImplementation.FULLTEXT_CONFIG : null;
if (NodeBacked.class.isAssignableFrom(type)) return (Index<S>) getIndexManager().forNodes(indexName, config);
if (RelationshipBacked.class.isAssignableFrom(type)) return (Index<S>) getIndexManager().forRelationships(indexName, config);
throw new IllegalArgumentException("Wrong index type supplied: " + type);
}
/**
* @return true if a transaction manager is available and a transaction is currently running
*/
public boolean transactionIsRunning() {
try {
return getTxManager().getStatus() != Status.STATUS_NO_TRANSACTION;
} catch (SystemException e) {
log.error("Error accessing TransactionManager", e);
return false;
}
}
public <T extends GraphBacked<? extends PropertyContainer>> Iterable<T> findAll(final Class<T> entityClass) {
return getTypeRepresentationStrategy(entityClass).findAll(entityClass);
}
public <T extends GraphBacked<? extends PropertyContainer>> long count(final Class<T> entityClass) {
return getTypeRepresentationStrategy(entityClass).count(entityClass);
}
public <S extends PropertyContainer, T extends GraphBacked<S>> T createEntityFromStoredType(S state) {
return getTypeRepresentationStrategy(state).createEntity(state);
}
public <S extends PropertyContainer, T extends GraphBacked<S>> T createEntityFromState(S state, Class<T> type) {
if (state==null) throw new IllegalArgumentException("state has to be either a Node or Relationship, not null");
return getTypeRepresentationStrategy(state, type).createEntity(state, type);
}
public <S extends PropertyContainer, T extends GraphBacked<S>> T projectTo(GraphBacked<S> entity, Class<T> targetType) {
S state = entity.getPersistentState();
return getTypeRepresentationStrategy(state, targetType).projectEntity(state, targetType);
}
public <S extends PropertyContainer, T extends GraphBacked<S>> void postEntityCreation(S node, Class<T> entityClass) {
getTypeRepresentationStrategy(node, entityClass).postEntityCreation(node, entityClass);
}
public void removeNodeEntity(NodeBacked entity) {
Node node = entity.getPersistentState();
if (node == null) return;
nodeTypeRepresentationStrategy.preEntityRemoval(node);
for (Relationship relationship : node.getRelationships()) {
removeRelationship(relationship);
}
removeFromIndexes(node);
node.delete();
}
public void removeRelationshipEntity(RelationshipBacked entity) {
Relationship relationship = entity.getPersistentState();
if (relationship == null) return;
removeRelationship(relationship);
}
private void removeRelationship(Relationship relationship) {
relationshipTypeRepresentationStrategy.preEntityRemoval(relationship);
removeFromIndexes(relationship);
relationship.delete();
}
private void removeFromIndexes(Node node) {
IndexManager indexManager = getIndexManager();
for (String indexName : indexManager.nodeIndexNames()) {
indexManager.forNodes(indexName).remove(node);
}
}
private void removeFromIndexes(Relationship relationship) {
IndexManager indexManager = getIndexManager();
for (String indexName : indexManager.relationshipIndexNames()) {
indexManager.forRelationships(indexName).remove(relationship);
}
}
private IndexManager getIndexManager() {
return graphDatabaseService.index();
}
@SuppressWarnings("unchecked")
private <T extends GraphBacked<? extends PropertyContainer>>
TypeRepresentationStrategy<?, T> getTypeRepresentationStrategy(Class<T> type) {
if (NodeBacked.class.isAssignableFrom(type)) {
return (TypeRepresentationStrategy<?, T>) nodeTypeRepresentationStrategy;
} else if (RelationshipBacked.class.isAssignableFrom(type)) {
return (TypeRepresentationStrategy<?, T>) relationshipTypeRepresentationStrategy;
}
throw new IllegalArgumentException("Type is not NodeBacked nor RelationshipBacked.");
}
@SuppressWarnings("unchecked")
private <S extends PropertyContainer, T extends GraphBacked<S>>
TypeRepresentationStrategy<S, T> getTypeRepresentationStrategy(S state, Class<T> type) {
if (state instanceof Node && NodeBacked.class.isAssignableFrom(type)) {
return (TypeRepresentationStrategy<S, T>) nodeTypeRepresentationStrategy;
} else if (state instanceof Relationship && RelationshipBacked.class.isAssignableFrom(type)) {
return (TypeRepresentationStrategy<S, T>) relationshipTypeRepresentationStrategy;
}
throw new IllegalArgumentException("Type is not NodeBacked nor RelationshipBacked.");
}
@SuppressWarnings("unchecked")
private <S extends PropertyContainer, T extends GraphBacked<S>>
TypeRepresentationStrategy<S, T> getTypeRepresentationStrategy(S state) {
if (state instanceof Node) {
return (TypeRepresentationStrategy<S, T>) nodeTypeRepresentationStrategy;
} else if (state instanceof Relationship) {
return (TypeRepresentationStrategy<S, T>) relationshipTypeRepresentationStrategy;
}
throw new IllegalArgumentException("Type is not NodeBacked nor RelationshipBacked.");
}
/**
* @return Neo4j Transaction manager
*/
public TransactionManager getTxManager() {
return ((AbstractGraphDatabase) graphDatabaseService).getConfig().getTxModule().getTxManager();
}
/**
* Delegates to {@link GraphDatabaseService}
*/
public Node createNode() {
return graphDatabaseService.createNode();
}
/**
* Delegates to {@link GraphDatabaseService}
*/
public Node getNodeById(final long nodeId) {
return graphDatabaseService.getNodeById(nodeId);
}
/**
* Delegates to {@link GraphDatabaseService}
*/
public Node getReferenceNode() {
return graphDatabaseService.getReferenceNode();
}
/**
* Delegates to {@link GraphDatabaseService}
*/
public Iterable<? extends Node> getAllNodes() {
return graphDatabaseService.getAllNodes();
}
/**
* Delegates to {@link GraphDatabaseService}
*/
public Transaction beginTx() {
return graphDatabaseService.beginTx();
}
/**
* Delegates to {@link GraphDatabaseService}
*/
public Relationship getRelationshipById(final long id) {
return graphDatabaseService.getRelationshipById(id);
}
public GraphDatabaseService getGraphDatabaseService() {
return graphDatabaseService;
@@ -90,230 +277,6 @@ public class GraphDatabaseContext {
this.conversionService = conversionService;
}
public Node createNode() {
return graphDatabaseService.createNode();
}
/**
* @param relationship to remove from indexes and to delete
*/
private void removeRelationship(Relationship relationship) {
removeFromIndexes(relationship);
relationship.delete();
}
/**
* @param relationship to be removed from all indexes, all properties are removed from all indexes
*/
private void removeFromIndexes(Relationship relationship) {
IndexManager indexManager = graphDatabaseService.index();
for (String indexName : getIndexManager().relationshipIndexNames()) {
indexManager.forRelationships(indexName).remove(relationship);
}
}
/**
* removes the entity by cleaning the relationships first and then removing the node
* it removes all of them from all indexes in advance
* the entity and relationship are still accessible after removal but before transaction commit
* but all modifications will throw an exception
* @param entity to remove
*/
// TODO: What about connected relationship entities?
public void removeNodeEntity(NodeBacked entity) {
Node node = entity.getPersistentState();
if (node==null) return;
nodeTypeRepresentationStrategy.preEntityRemoval(entity);
for (Relationship relationship : node.getRelationships()) {
removeRelationship(relationship);
}
removeFromIndexes(node);
node.delete();
}
public void removeRelationshipEntity(RelationshipBacked entity) {
Relationship relationship = entity.getPersistentState();
if (relationship==null) return;
removeRelationship(relationship);
}
/**
* @param node to be removed from all indexes, all properties of the node are removed from all indexes
*/
private void removeFromIndexes(Node node) {
IndexManager indexManager = graphDatabaseService.index();
for (String indexName : getIndexManager().nodeIndexNames()) {
indexManager.forNodes(indexName).remove(node);
}
}
/**
* Creates either a node or relationship entity by delegating the creation to the appropriate @{link EntityInstantiator}
* @param state Node or Relationship
* @param type target entity type
* @return an instance of the entity type
*/
public <S, T extends GraphBacked> T createEntityFromState(final S state, final Class<T> type) {
if (state==null) throw new IllegalArgumentException("state has to be either a Node or Relationship, not null");
if (state instanceof Node && NodeBacked.class.isAssignableFrom(type))
return (T) nodeTypeRepresentationStrategy.createEntity((Node) state, (Class<NodeBacked>) type);
// return (T) graphEntityInstantiator.createEntityFromState((Node) state, typeRepresentationStrategy.confirmType((Node)state, (Class<? extends NodeBacked>)type));
else
return (T) relationshipTypeRepresentationStrategy.createEntity((Relationship) state, (Class<RelationshipBacked>) type);
// return (T) relationshipEntityInstantiator.createEntityFromState((Relationship) state, (Class<? extends RelationshipBacked>) type);
}
private IndexManager getIndexManager() {
return graphDatabaseService.index();
}
/**
* @param indexName or null, "node" is assumed if null
* @return node index {@link Index}
*/
public <T extends PropertyContainer,N extends GraphBacked<T>> Index<T> getIndex(Class<N> type, String indexName) {
if (indexName==null) indexName = Indexed.Name.get(type);
if (NodeBacked.class.isAssignableFrom(type)) return (Index<T>) getIndexManager().forNodes(indexName);
if (RelationshipBacked.class.isAssignableFrom(type)) return (Index<T>) getIndexManager().forRelationships(indexName);
throw new IllegalArgumentException("Wrong index type supplied "+type);
}
/**
* @param type type of index requested - either Node.class or Relationship.class
* @param indexName or null, "node" is assumed if null
* @param fullText true if a fulltext queryable index is needed, false for exact match
* @return node index {@link Index}
*/
public <T extends PropertyContainer,N extends GraphBacked<T>> Index<T> getIndex(Class<N> type, String indexName, boolean fullText) {
if (indexName==null) indexName = Indexed.Name.get(type);
Map<String, String> config = fullText ? LuceneIndexImplementation.FULLTEXT_CONFIG : LuceneIndexImplementation.EXACT_CONFIG;
if (NodeBacked.class.isAssignableFrom(type)) return (Index<T>) getIndexManager().forNodes(indexName, config);
if (RelationshipBacked.class.isAssignableFrom(type)) return (Index<T>) getIndexManager().forRelationships(indexName, config);
throw new IllegalArgumentException("Wrong index type supplied "+type);
}
/**
* @param nodeId
* @return Node
* @throws NotFoundException
*/
public Node getNodeById(final long nodeId) {
return graphDatabaseService.getNodeById(nodeId);
}
/**
* delegates to the configured @{link TypeRepresentationStrategy} for after entity creation operations
* @param node
* @param entityClass
*/
public void postEntityCreation(Node node, final Class<? extends NodeBacked> entityClass) {
nodeTypeRepresentationStrategy.postEntityCreation(node, entityClass);
}
/**
* delegates to the configured @{link TypeRepresentationStrategy} for after entity creation operations
* @param relationship
* @param entityClass
*/
public void postEntityCreation(Relationship relationship, final Class<? extends RelationshipBacked> entityClass) {
relationshipTypeRepresentationStrategy.postEntityCreation(relationship, entityClass);
}
/**
* delegates to the configured @{link TypeRepresentationStrategy} to iterate over all instances of this type
* @param clazz type of entity
* @param <T>
* @return
* TODO inheritance handling
*/
public <T extends GraphBacked> Iterable<T> findAll(final Class<T> clazz) {
if (!checkIsNodeBacked(clazz)) throw new UnsupportedOperationException("No support for relationships");
return (Iterable<T>) nodeTypeRepresentationStrategy.findAll((Class<NodeBacked>)clazz);
}
/**
* class base check for nodebacked subclasses
*/
private boolean checkIsNodeBacked(Class<?> clazz) {
return NodeBacked.class.isAssignableFrom(clazz);
}
/**
* delegates to the configured @{link TypeRepresentationStrategy} for a count of all instances of this type
* @param entityClass
* @return count of all instances
*/
public long count(final Class<? extends GraphBacked> entityClass) {
if (!checkIsNodeBacked(entityClass)) throw new UnsupportedOperationException("No support for relationships");
return nodeTypeRepresentationStrategy.count((Class<NodeBacked>)entityClass);
}
/**
* delegates to the configured @{link TypeRepresentationStrategy} to lookup the type information for the given node
* @param node
* @param <T>
* @return entity type of the node
* @throws IllegalStateException for nodes that are not instance backing nodes of a known type
*/
public <T extends NodeBacked> Class<T> getJavaType(final Node node) {
return nodeTypeRepresentationStrategy.getJavaType(node);
}
/**
* @return reference node of the graph database
*/
public Node getReferenceNode() {
return graphDatabaseService.getReferenceNode();
}
/**
* @return Neo4j Transaction manager
*/
public TransactionManager getTxManager() {
return ((AbstractGraphDatabase) graphDatabaseService).getConfig().getTxModule().getTxManager();
}
/**
* @return true if a transaction manager is available and a transaction is currently running
*/
public boolean transactionIsRunning() {
try {
return getTxManager().getStatus() != Status.STATUS_NO_TRANSACTION;
} catch (SystemException e) {
log.error("Error accessing TransactionManager", e);
return false;
}
}
/**
* delegates to @{link GraphDatabaseService}
*/
public Iterable<? extends Node> getAllNodes() {
return graphDatabaseService.getAllNodes();
}
/**
* delegates to @{link GraphDatabaseService}
*/
public Transaction beginTx() {
return graphDatabaseService.beginTx();
}
/**
* delegates to @{link GraphDatabaseService}
*/
public Relationship getRelationshipById(final long id) {
return graphDatabaseService.getRelationshipById(id);
}
public <T extends GraphBacked> T projectTo(GraphBacked entity, Class<T> targetType) {
final Object state = entity.getPersistentState();
if (state instanceof Node)
return (T) nodeTypeRepresentationStrategy.projectEntity((Node) state, (Class<NodeBacked>) targetType);
else
return (T) relationshipTypeRepresentationStrategy.projectEntity((Relationship) state, (Class<RelationshipBacked>) targetType);
}
public Validator getValidator() {
return validator;
}
@@ -322,8 +285,7 @@ public class GraphDatabaseContext {
this.validator = validatorFactory;
}
public <T extends NodeBacked> T createEntityFromStoredType(Node node) {
return nodeTypeRepresentationStrategy.createEntity(node);
}
}

View File

@@ -84,7 +84,6 @@ public class IndexingNodeTypeRepresentationStrategy implements NodeTypeRepresent
return count;
}
@Override
public Class<? extends NodeBacked> getJavaType(Node node) {
if (node == null) throw new IllegalArgumentException("Node is null");
@@ -112,8 +111,8 @@ public class IndexingNodeTypeRepresentationStrategy implements NodeTypeRepresent
}
@Override
public void preEntityRemoval(NodeBacked entity) {
getNodeTypesIndex().remove(entity.getPersistentState());
public void preEntityRemoval(Node state) {
getNodeTypesIndex().remove(state);
}
@Override

View File

@@ -116,10 +116,11 @@ public class IndexingRelationshipTypeRepresentationStrategy implements Relations
}
}
@Override
public void preEntityRemoval(RelationshipBacked entity) {
getRelTypesIndex().remove(entity.getPersistentState());
}
public void preEntityRemoval(Relationship state) {
getRelTypesIndex().remove(state);
}
@Override
@SuppressWarnings("unchecked")

View File

@@ -0,0 +1,46 @@
package org.springframework.data.graph.neo4j.support;
import org.neo4j.graphdb.Node;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.NodeTypeRepresentationStrategy;
public class NoopNodeTypeRepresentationStrategy implements NodeTypeRepresentationStrategy {
@Override
public void postEntityCreation(Node state, Class<? extends NodeBacked> type) {
}
@Override
public <U extends NodeBacked> Iterable<U> findAll(Class<U> clazz) {
throw new UnsupportedOperationException("findAll not supported.");
}
@Override
public long count(Class<? extends NodeBacked> entityClass) {
throw new UnsupportedOperationException("count not supported.");
}
@Override
public void preEntityRemoval(Node state) {
}
@Override
public Class<? extends NodeBacked> getJavaType(Node state) {
throw new UnsupportedOperationException("getJavaType not supported.");
}
@Override
public <U extends NodeBacked> U createEntity(Node state) {
throw new UnsupportedOperationException("Creation with stored type not supported.");
}
@Override
public <U extends NodeBacked> U createEntity(Node state, Class<U> type) {
return projectEntity(state, type);
}
@Override
public <U extends NodeBacked> U projectEntity(Node state, Class<U> type) {
return null;
}
}

View File

@@ -0,0 +1,46 @@
package org.springframework.data.graph.neo4j.support;
import org.neo4j.graphdb.Relationship;
import org.springframework.data.graph.core.RelationshipBacked;
import org.springframework.data.graph.core.RelationshipTypeRepresentationStrategy;
public class NoopRelationshipTypeRepresentationStrategy implements RelationshipTypeRepresentationStrategy {
@Override
public void postEntityCreation(Relationship state, Class<? extends RelationshipBacked> type) {
}
@Override
public <U extends RelationshipBacked> Iterable<U> findAll(Class<U> clazz) {
throw new UnsupportedOperationException("findAll not supported.");
}
@Override
public long count(Class<? extends RelationshipBacked> entityClass) {
throw new UnsupportedOperationException("count not supported.");
}
@Override
public void preEntityRemoval(Relationship state) {
}
@Override
public Class<? extends RelationshipBacked> getJavaType(Relationship state) {
throw new UnsupportedOperationException("getJavaType not supported.");
}
@Override
public <U extends RelationshipBacked> U createEntity(Relationship state) {
throw new UnsupportedOperationException("Creation with stored type not supported.");
}
@Override
public <U extends RelationshipBacked> U createEntity(Relationship state, Class<U> type) {
return projectEntity(state, type);
}
@Override
public <U extends RelationshipBacked> U projectEntity(Relationship state, Class<U> type) {
return null;
}
}

View File

@@ -1,92 +0,0 @@
package org.springframework.data.graph.neo4j.support;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.NodeTypeRepresentationStrategy;
import org.springframework.data.graph.core.RelationshipBacked;
import org.springframework.data.graph.core.RelationshipTypeRepresentationStrategy;
public class NoopTypeRepresentationStrategy {
public static class NoopNodeStrategy implements NodeTypeRepresentationStrategy {
@Override
public void postEntityCreation(Node state, Class<? extends NodeBacked> type) {
}
@Override
public <U extends NodeBacked> Iterable<U> findAll(Class<U> clazz) {
throw new UnsupportedOperationException("findAll not supported by NoopTypeRepresentationStrategy.");
}
@Override
public long count(Class<? extends NodeBacked> entityClass) {
throw new UnsupportedOperationException("count not supported by NoopTypeRepresentationStrategy.");
}
@Override
public Class<? extends NodeBacked> getJavaType(Node state) {
throw new UnsupportedOperationException("getJavaType not supported by NoopTypeRepresentationStrategy.");
}
@Override
public void preEntityRemoval(NodeBacked entity) {
}
@Override
public <U extends NodeBacked> U createEntity(Node state) {
throw new UnsupportedOperationException("Creation with stored type not supported by NoopTypeRepresentationStrategy.");
}
@Override
public <U extends NodeBacked> U createEntity(Node state, Class<U> type) {
return projectEntity(state, type);
}
@Override
public <U extends NodeBacked> U projectEntity(Node state, Class<U> type) {
return null;
}
}
public static class NoopRelationshipStrategy implements RelationshipTypeRepresentationStrategy {
@Override
public void postEntityCreation(Relationship state, Class<? extends RelationshipBacked> type) {
}
@Override
public <U extends RelationshipBacked> Iterable<U> findAll(Class<U> clazz) {
throw new UnsupportedOperationException("findAll not supported by NoopTypeRepresentationStrategy.");
}
@Override
public long count(Class<? extends RelationshipBacked> entityClass) {
throw new UnsupportedOperationException("count not supported by NoopTypeRepresentationStrategy.");
}
@Override
public Class<? extends RelationshipBacked> getJavaType(Relationship state) {
throw new UnsupportedOperationException("getJavaType not supported by NoopTypeRepresentationStrategy.");
}
@Override
public void preEntityRemoval(RelationshipBacked entity) {
}
@Override
public <U extends RelationshipBacked> U createEntity(Relationship state) {
throw new UnsupportedOperationException("Creation with stored type not supported by NoopTypeRepresentationStrategy.");
}
@Override
public <U extends RelationshipBacked> U createEntity(Relationship state, Class<U> type) {
return projectEntity(state, type);
}
@Override
public <U extends RelationshipBacked> U projectEntity(Relationship state, Class<U> type) {
return null;
}
}
}

View File

@@ -100,34 +100,6 @@ public class SubReferenceNodeTypeRepresentationStrategy implements NodeTypeRepre
updateSuperClassSubrefs(type, subReference);
}
/**
* removes instanceof relationship and decrements instance counters for type nodes
* @param entity
*/
@Override
public void preEntityRemoval(NodeBacked entity) {
Class<? extends NodeBacked> clazz = entity.getClass();
final Node subReference = obtainSubreferenceNode(clazz);
Node subRefNode = entity.getPersistentState();
Relationship instanceOf = subRefNode.getSingleRelationship(INSTANCE_OF_RELATIONSHIP_TYPE, Direction.OUTGOING);
instanceOf.delete();
if (log.isDebugEnabled()) log.debug("Removed link to subref node: " + subReference + " with type: " + clazz.getName());
TraversalDescription traversal = Traversal.description().depthFirst().relationships(SUBCLASS_OF_RELATIONSHIP_TYPE, Direction.OUTGOING);
for (Node node : traversal.traverse(subReference).nodes()) {
Integer count = (Integer) node.getProperty(SUBREFERENCE_NODE_COUNTER_KEY);
Integer newCount = decrementAndGetCounter(node, SUBREFERENCE_NODE_COUNTER_KEY, 0);
if (log.isDebugEnabled()) log.debug("count on ref " + node + " was " + count + " new " + newCount);
}
}
// @Override
// public <T extends NodeBacked> Class<T> confirmType(Node node, Class<T> type) {
// Class<T> nodeType = this.<T>getJavaType(node);
// if (type.isAssignableFrom(nodeType)) return nodeType;
// throw new IllegalArgumentException(String.format("%s does not correspond to the node type %s of node %s",type,nodeType,node));
// }
private void updateSuperClassSubrefs(Class<?> clazz, Node subReference) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
@@ -165,7 +137,24 @@ public class SubReferenceNodeTypeRepresentationStrategy implements NodeTypeRepre
}
}
@Override
@Override
public void preEntityRemoval(Node state) {
Class<? extends NodeBacked> clazz = getJavaType(state);
if (clazz == null) return;
final Node subReference = obtainSubreferenceNode(clazz);
Relationship instanceOf = state.getSingleRelationship(INSTANCE_OF_RELATIONSHIP_TYPE, Direction.OUTGOING);
instanceOf.delete();
if (log.isDebugEnabled())
log.debug("Removed link to subref node: " + subReference + " with type: " + clazz.getName());
TraversalDescription traversal = Traversal.description().depthFirst().relationships(SUBCLASS_OF_RELATIONSHIP_TYPE, Direction.OUTGOING);
for (Node node : traversal.traverse(subReference).nodes()) {
Integer count = (Integer) node.getProperty(SUBREFERENCE_NODE_COUNTER_KEY);
Integer newCount = decrementAndGetCounter(node, SUBREFERENCE_NODE_COUNTER_KEY, 0);
if (log.isDebugEnabled()) log.debug("count on ref " + node + " was " + count + " new " + newCount);
}
}
@Override
public <T extends NodeBacked> Iterable<T> findAll(final Class<T> clazz) {
final Node subrefNode = findSubreferenceNode(clazz);
if (log.isDebugEnabled()) log.debug("Subref: " + subrefNode);

View File

@@ -57,7 +57,7 @@ public class TypeRepresentationStrategyFactory {
@Override
public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabaseService graphDatabaseService, EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator) {
return new NoopTypeRepresentationStrategy.NoopRelationshipStrategy();
return new NoopRelationshipTypeRepresentationStrategy();
}
},
Indexed {
@@ -74,12 +74,12 @@ public class TypeRepresentationStrategyFactory {
Noop {
@Override
public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
return new NoopTypeRepresentationStrategy.NoopNodeStrategy();
return new NoopNodeTypeRepresentationStrategy();
}
@Override
public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabaseService graphDatabaseService, EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator) {
return new NoopTypeRepresentationStrategy.NoopRelationshipStrategy();
return new NoopRelationshipTypeRepresentationStrategy();
}
};

View File

@@ -2,6 +2,7 @@ package org.springframework.data.graph.neo4j.template;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.springframework.data.graph.core.Property;
import java.util.Map;
@@ -40,11 +41,12 @@ public interface Neo4jOperations {
* Two shortcut means of providing the properties (very short with static imports)
* <code>template.createNode(PropertyMap._("name","value"));</code>
* <code>template.createNode(PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop");</code>
*
*
* @param props properties to be set at node creation might be null
* @param indexFields fields that are automatically indexed from the given properties for the newly created ndoe
* @return the newly created node
*/
Node createNode(Map<String, Object> props, String... indexFields);
Node createNode(Property... props);
/**
* Delegates to the GraphDatabaseService
@@ -59,14 +61,15 @@ public interface Neo4jOperations {
* Two shortcut means of providing the properties (very short with static imports)
* <code>template.createRelationship(from,to,TYPE, PropertyMap._("name","value"));</code>
* <code>template.createRelationship(from,to,TYPE, PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop");</code>
*
*
* @param startNode start-node of relationship
* @param endNode end-node of relationship
* @param type relationship type, might by an enum implementing RelationshipType or a DynamicRelationshipType.withName("name")
* @param props optional initial properties
* @param indexFields optional indexed fields
* @return the newly created relationship
*/
Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> props, String... indexFields);
Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Property... props);
/**
* Queries the supplied index with a lucene query string or query object (if the neo4j-index provider is lucene)
@@ -144,14 +147,4 @@ public interface Neo4jOperations {
* @return the provided element for convenience
*/
<T extends PropertyContainer> T index(String indexName, T element, String field, Object value);
/**
* Auto-indexes all indexFields for the given element's properties if they exist
* @param indexName Name of the index, will be checked against existing indexes according to the given element
* assumes a "node" node index or "relationship" relationship index for a null value
* @param element node or relationship to auto-index
* @param indexProperties property names to index
* @param <T> the provided element type
* @return the provided element for convenience
*/
<T extends PropertyContainer> T autoIndex(String indexName, T element, String... indexProperties);
}

View File

@@ -25,8 +25,7 @@ import org.neo4j.helpers.collection.IterableWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.graph.UncategorizedGraphStoreException;
import java.util.Map;
import org.springframework.data.graph.core.Property;
public class Neo4jTemplate implements Neo4jOperations {
@@ -117,13 +116,13 @@ public class Neo4jTemplate implements Neo4jOperations {
}
@Override
public Node createNode(final Map<String, Object> properties, final String... indexFields) {
public Node createNode(final Property... properties) {
return exec(new GraphCallback<Node>() {
@Override
public Node doWithGraph(GraphDatabaseService graph) throws Exception {
Node node = graphDatabaseService.createNode();
if (properties == null) return node;
return autoIndex(null, setProperties(node, properties), indexFields);
return setProperties(node, properties);
}
});
}
@@ -148,15 +147,6 @@ public class Neo4jTemplate implements Neo4jOperations {
}
}
@Override
public <T extends PropertyContainer> T autoIndex(String indexName, T element, String... indexFields) {
for (String indexField : indexFields) {
if (!element.hasProperty(indexField)) continue;
index(indexName,element, indexField,element.getProperty(indexField));
}
return element;
}
@Override
public <T extends PropertyContainer> T index(final String indexName, final T element, final String field, final Object value) {
notNull(element, "element", field, "field", value, "value");
@@ -290,26 +280,26 @@ public class Neo4jTemplate implements Neo4jOperations {
}
@Override
public Relationship createRelationship(final Node startNode, final Node endNode, final RelationshipType relationshipType, final Map<String, Object> properties, final String... indexFields) {
public Relationship createRelationship(final Node startNode, final Node endNode, final RelationshipType relationshipType, final Property... properties) {
notNull(startNode, "startNode", endNode, "endNode", relationshipType, "relationshipType", properties, "properties");
return exec(new GraphCallback<Relationship>() {
@Override
public Relationship doWithGraph(GraphDatabaseService graph) throws Exception {
Relationship relationship = startNode.createRelationshipTo(endNode, relationshipType);
if (properties == null) return relationship;
return autoIndex("relationship", setProperties(relationship, properties), indexFields);
return setProperties(relationship, properties);
}
});
}
private <T extends PropertyContainer> T setProperties(T primitive, Map<String, Object> properties) {
private <T extends PropertyContainer> T setProperties(T primitive, Property... properties) {
assert primitive != null;
if (properties==null) return primitive;
for (Map.Entry<String, Object> prop : properties.entrySet()) {
if (prop.getValue()==null) {
primitive.removeProperty(prop.getKey());
for (Property prop : properties) {
if (prop.value==null) {
primitive.removeProperty(prop.name);
} else {
primitive.setProperty(prop.getKey(), prop.getValue());
primitive.setProperty(prop.name, prop.value);
}
}
return primitive;

View File

@@ -0,0 +1,43 @@
package org.springframework.data.graph.core;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.graph.neo4j.support.DelegatingGraphDatabase;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
/**
* @author mh
* @since 29.03.11
*/
public class GraphDatabaseFactoryTest {
@Test
public void shouldCreateLocalDatabaseFromContext() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("GraphDatabaseFactory-context.xml");
try {
GraphDatabase graphDatabase = ctx.getBean("graphDatabase", GraphDatabase.class);
assertThat(graphDatabase, is(not(nullValue())));
assertThat(graphDatabase, is(instanceOf(DelegatingGraphDatabase.class)));
} finally {
ctx.close();
}
}
@Test
public void shouldCreateLocalDatabase() throws Exception {
GraphDatabaseFactory factory = new GraphDatabaseFactory();
try {
factory.setStoreLocation("target/test-db");
GraphDatabase graphDatabase = factory.getObject();
assertThat(graphDatabase, is(not(nullValue())));
assertThat(graphDatabase,is(instanceOf(DelegatingGraphDatabase.class)));
} finally {
factory.shutdown();
}
}
}

View File

@@ -0,0 +1,6 @@
package org.springframework.data.graph.neo4j;
import org.springframework.data.graph.neo4j.repository.RelationshipGraphRepository;
public interface FriendshipRepository extends RelationshipGraphRepository<Friendship> {
}

View File

@@ -2,26 +2,20 @@ package org.springframework.data.graph.neo4j.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.Matcher;
import org.junit.Assert;
import org.junit.Test;
import org.junit.internal.matchers.IsCollectionContaining;
import org.junit.runner.RunWith;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.graph.neo4j.Group;
import org.springframework.data.graph.neo4j.GroupRepository;
import org.springframework.data.graph.neo4j.Person;
import org.springframework.data.graph.neo4j.PersonRepository;
import org.springframework.data.graph.neo4j.*;
import org.springframework.data.graph.neo4j.support.node.Neo4jHelper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import static java.util.Arrays.asList;
@@ -34,8 +28,7 @@ import static org.springframework.data.graph.neo4j.Person.persistedPerson;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
public class FinderTest {
public class GraphRepositoryTest {
protected final Log log = LogFactory.getLog(getClass());
@@ -46,6 +39,8 @@ public class FinderTest {
private PersonRepository personRepository;
@Autowired
private GroupRepository groupRepository;
@Autowired
private FriendshipRepository friendshipRepository;
@BeforeTransaction
public void cleanDb() {
@@ -78,7 +73,7 @@ public class FinderTest {
Person p1 = new Person("Michael", 35);
personRepository.save(p1);
assertEquals("persisted person",true,p1.hasPersistentState());
assertThat(personRepository.findOne(p1.getId()),is(p1));
assertThat(personRepository.findOne(p1.getId()), is(p1));
}
@Test
public void testDeletePerson() {
@@ -94,6 +89,17 @@ public class FinderTest {
assertEquals("people deleted", false, personRepository.findAll().iterator().hasNext());
}
@Test
@Transactional
public void testFindRelationshipEntity() {
Person p1 = persistedPerson("Michael", 35);
Person p2 = persistedPerson("David", 27);
Friendship friendship = p1.knows(p2);
assertEquals("Wrong friendship count.", 1L, (long) friendshipRepository.count());
assertEquals("Did not find friendship.", Collections.singleton(friendship), new HashSet<Friendship>(IteratorUtil.asCollection(friendshipRepository.findAll())));
assertEquals(friendship, friendshipRepository.findOne(friendship.getRelationshipId()));
}
@Test
@Transactional
public void testFinderFindById() {

View File

@@ -78,7 +78,7 @@ public class IndexingNodeTypeRepresentationStrategyTest {
Transaction tx = graphDatabaseService.beginTx();
try
{
nodeTypeRepresentationStrategy.preEntityRemoval(thing);
nodeTypeRepresentationStrategy.preEntityRemoval(node(thing));
tx.success();
}
finally
@@ -92,9 +92,8 @@ public class IndexingNodeTypeRepresentationStrategyTest {
assertEquals(node(subThing), subThingHits.getSingle());
tx = graphDatabaseService.beginTx();
try
{
nodeTypeRepresentationStrategy.preEntityRemoval(subThing);
try {
nodeTypeRepresentationStrategy.preEntityRemoval(node(subThing));
tx.success();
}
finally

View File

@@ -74,7 +74,7 @@ public class IndexingRelationshipTypeRepresentationStrategyTest {
Transaction tx = graphDatabaseService.beginTx();
try
{
relationshipTypeRepresentationStrategy.preEntityRemoval(link);
relationshipTypeRepresentationStrategy.preEntityRemoval(rel(link));
tx.success();
}
finally

View File

@@ -1,7 +1,15 @@
package org.springframework.data.graph.neo4j.support;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.graph.annotation.NodeEntity;
import org.springframework.data.graph.annotation.RelationshipEntity;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -11,77 +19,109 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml",
"classpath:org/springframework/data/graph/neo4j/support/NoopNodeTypeStrategyOverride-context.xml"})
"classpath:org/springframework/data/graph/neo4j/support/NoopTypeRepresentationStrategyOverride-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class NoopTypeRepresentationStrategyTest {
//
// @Autowired
// private GraphDatabaseContext graphDatabaseContext;
// @Autowired
// private NoopTypeRepresentationStrategy nodeTypeStrategy;
//
// private Thing thing;
//
// @Before
// public void setUp() throws Exception {
// thing = createThing();
// }
@Autowired
private GraphDatabaseContext graphDatabaseContext;
@Autowired
private NoopNodeTypeRepresentationStrategy noopNodeStrategy;
@Autowired
private NoopRelationshipTypeRepresentationStrategy noopRelationshipStrategy;
private Thing thing;
private Link link;
@Before
public void setUp() throws Exception {
createThing();
}
@Test
public void testPostEntityCreation() throws Exception {
}
//
// @Test(expected = UnsupportedOperationException.class)
// public void testFindAll() throws Exception {
// nodeTypeStrategy.findAll(Thing.class);
// }
//
// @Test(expected = UnsupportedOperationException.class)
// public void testCount() throws Exception {
// nodeTypeStrategy.count(Thing.class);
// }
//
// @Test(expected = UnsupportedOperationException.class)
// public void testGetJavaType() throws Exception {
// nodeTypeStrategy.getJavaType(node(thing));
// }
//
// @Test
// public void testPreEntityRemoval() throws Exception {
// nodeTypeStrategy.preEntityRemoval(thing);
// }
//
// @Test
// public void testConfirmType() throws Exception {
// assertEquals(Thing.class, nodeTypeStrategy.confirmType(node(thing), Thing.class));
// }
//
// private static Node node(Thing thing) {
// return thing.getPersistentState();
// }
//
// private Thing createThing() {
// Transaction tx = graphDatabaseContext.beginTx();
// try {
// Node node = graphDatabaseContext.createNode();
// Thing thing = new Thing(node);
// nodeTypeStrategy.postEntityCreation(thing);
// tx.success();
// return thing;
// } finally {
// tx.finish();
// }
// }
//
// @NodeEntity
// public static class Thing {
// String name;
//
// public Thing() {
// }
//
// public Thing(Node n) {
// setPersistentState(n);
// }
// }
@Test(expected = UnsupportedOperationException.class)
public void testFindAllForNodeStrategy() throws Exception {
noopNodeStrategy.findAll(Thing.class);
}
@Test(expected = UnsupportedOperationException.class)
public void testFindAllForRelationshipStrategy() throws Exception {
noopRelationshipStrategy.findAll(Link.class);
}
@Test(expected = UnsupportedOperationException.class)
public void testCountForNodeStrategy() throws Exception {
noopNodeStrategy.count(Thing.class);
}
@Test(expected = UnsupportedOperationException.class)
public void testCountForRelationshipStrategy() throws Exception {
noopRelationshipStrategy.count(Link.class);
}
@Test(expected = UnsupportedOperationException.class)
public void testGetJavaTypeOnNodeStrategy() throws Exception {
noopNodeStrategy.getJavaType(null);
}
@Test(expected = UnsupportedOperationException.class)
public void testGetJavaTypeOnRelationshipStrategy() throws Exception {
noopRelationshipStrategy.getJavaType(null);
}
@Test
public void testPreEntityRemoval() throws Exception {
noopNodeStrategy.preEntityRemoval(node(thing));
noopRelationshipStrategy.preEntityRemoval(rel(link));
}
private static Node node(Thing thing) {
return thing.getPersistentState();
}
private static Relationship rel(Link link) {
return link.getPersistentState();
}
private Thing createThing() {
Transaction tx = graphDatabaseContext.beginTx();
try {
Node node = graphDatabaseContext.createNode();
thing = new Thing(node);
noopNodeStrategy.postEntityCreation(node, Thing.class);
Relationship rel = node.createRelationshipTo(graphDatabaseContext.createNode(), DynamicRelationshipType.withName("link"));
link = new Link(rel);
noopRelationshipStrategy.postEntityCreation(rel, Link.class);
tx.success();
return thing;
} finally {
tx.finish();
}
}
@NodeEntity
public static class Thing {
String name;
public Thing() {
}
public Thing(Node n) {
setPersistentState(n);
}
}
@RelationshipEntity
public static class Link {
public Link() {
}
public Link(Relationship rel) {
setPersistentState(rel);
}
}
}

View File

@@ -8,7 +8,10 @@ import org.neo4j.graphdb.*;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.graph.neo4j.Friendship;
import org.springframework.data.graph.neo4j.FriendshipRepository;
import org.springframework.data.graph.neo4j.Person;
import static org.junit.Assert.assertFalse;
import static org.springframework.data.graph.neo4j.Person.persistedPerson;
import org.springframework.data.graph.neo4j.repository.DirectGraphRepositoryFactory;
@@ -30,6 +33,10 @@ public class RelationshipEntityTest {
@Autowired
private GraphDatabaseContext graphDatabaseContext;
@Autowired
private GraphDatabaseService graphDatabaseService;
@Autowired
private FriendshipRepository friendshipRepository;
@Autowired
private DirectGraphRepositoryFactory graphRepositoryFactory;
@@ -97,6 +104,64 @@ public class RelationshipEntityTest {
Person p = persistedPerson("Michael", 35);
Person p2 = persistedPerson("David", 25);
Friendship f = p.knows(p2);
assertEquals(f,p.getRelationshipTo(p2,Friendship.class, "knows"));
assertEquals(f,p.getRelationshipTo(p2, Friendship.class, "knows"));
}
@Test
public void testRemoveRelationshipEntity() {
cleanDb();
Friendship f;
Transaction tx = graphDatabaseService.beginTx();
try
{
Person p = persistedPerson("Michael", 35);
Person p2 = persistedPerson("David", 25);
f = p.knows(p2);
tx.success();
}
finally
{
tx.finish();
}
Transaction tx2 = graphDatabaseService.beginTx();
try
{
f.remove();
tx2.success();
}
finally
{
tx2.finish();
}
assertFalse("Unexpected relationship entity found.", friendshipRepository.findAll().iterator().hasNext());
}
@Test
public void testRemoveRelationshipEntityIfNodeEntityIsRemoved() {
cleanDb();
Person p;
Transaction tx = graphDatabaseService.beginTx();
try
{
p = persistedPerson("Michael", 35);
Person p2 = persistedPerson("David", 25);
p.knows(p2);
tx.success();
}
finally
{
tx.finish();
}
Transaction tx2 = graphDatabaseService.beginTx();
try
{
p.remove();
tx2.success();
}
finally
{
tx2.finish();
}
assertFalse("Unexpected relationship entity found.", friendshipRepository.findAll().iterator().hasNext());
}
}

View File

@@ -113,11 +113,11 @@ public class SubReferenceNodeTypeRepresentationStrategyTest {
@Transactional
public void testPreEntityRemoval() throws Exception {
Node typeNode = getInstanceofRelationship(thingNode).getOtherNode(thingNode);
nodeTypeRepresentationStrategy.preEntityRemoval(thing);
nodeTypeRepresentationStrategy.preEntityRemoval(node(thing));
assertNull("instanceof relationship was removed", getInstanceofRelationship(thingNode));
assertNotNull("instanceof relationship was removed", getInstanceofRelationship(subThingNode));
assertEquals("no things left after removal", 1, typeNode.getProperty(SubReferenceNodeTypeRepresentationStrategy.SUBREFERENCE_NODE_COUNTER_KEY));
nodeTypeRepresentationStrategy.preEntityRemoval(subThing);
nodeTypeRepresentationStrategy.preEntityRemoval(node(subThing));
assertNull("instanceof relationship was removed", getInstanceofRelationship(subThingNode));
assertEquals("no things left after removal", 0, typeNode.getProperty(SubReferenceNodeTypeRepresentationStrategy.SUBREFERENCE_NODE_COUNTER_KEY));

View File

@@ -19,8 +19,7 @@ import java.util.Iterator;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.*;
import static org.springframework.data.graph.neo4j.template.PropertyMap._;
import static org.springframework.data.graph.neo4j.template.PropertyMap.props;
import static org.springframework.data.graph.core.Property._;
/**
* @author mh
@@ -189,7 +188,7 @@ public class Neo4jTemplateApiTest {
@Test
public void testCreateNodeWithProperties() throws Exception {
Node node=template.createNode(props().set("test", "testCreateNodeWithProperties").toMap());
Node node=template.createNode(_("test", "testCreateNodeWithProperties"));
assertTestPropertySet(node, "testCreateNodeWithProperties");
}

View File

@@ -13,8 +13,8 @@ import java.util.Set;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.neo4j.kernel.Traversal.returnAllButStartNode;
import static org.springframework.data.graph.core.Property._;
import static org.springframework.data.graph.neo4j.template.NeoTraversalTest.Type.HAS;
import static org.springframework.data.graph.neo4j.template.PropertyMap._;
public class NeoTraversalTest extends NeoApiTest {

View File

@@ -14,5 +14,11 @@
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--<bean id="typeRepresentationStrategy" class="org.springframework.data.graph.neo4j.support.NoopTypeRepresentationStrategy" />-->
<context:annotation-config />
<bean name="graphDatabase" class="org.springframework.data.graph.core.GraphDatabaseFactory">
<property name="storeLocation" value="target/test-db"/>
</bean>
</beans>

View File

@@ -152,6 +152,10 @@
<property name="repositoryInterface" value="org.springframework.data.graph.neo4j.GroupRepository" />
<property name="graphDatabaseContext" ref="graphDatabaseContext"/>
</bean>
<bean id="friendshipRepository" class="org.springframework.data.graph.neo4j.repository.GraphRepositoryFactoryBean">
<property name="repositoryInterface" value="org.springframework.data.graph.neo4j.FriendshipRepository" />
<property name="graphDatabaseContext" ref="graphDatabaseContext"/>
</bean>
<!-- Adds transaparent exception translation to the DAOs -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="noopNodeStrategy" class="org.springframework.data.graph.neo4j.support.NoopNodeTypeRepresentationStrategy" />
<bean id="noopRelationshipStrategy" class="org.springframework.data.graph.neo4j.support.NoopRelationshipTypeRepresentationStrategy" />
</beans>

View File

@@ -23,10 +23,11 @@
backing graph store afterwards.
</para>
<para>
The connection between the two entities is kept via a FOREIGN_ID field in the node that contains the JPA id (currently only single
value ids are supported). The entity class can be resolved via the NodeTypeStrategy that preserves the Java type hierarchy within the graph.
With the id and class, you can then retrieve the appropriate JPA entity for a given node.
</para>
The connection between the two entities is kept via a FOREIGN_ID field in the node that contains the JPA id
(currently only single value ids are supported). The entity class can be resolved via the
TypeRepresentationStrategy that manages the Java type hierarchy within the graph. With the id and class,
you can then retrieve the appropriate JPA entity for a given node.
</para>
<para>
The other direction is handled by indexing the Node with the FOREIGN_ID index which contains a concatenation of the fully qualified class
name of the JPA entity and the id. So it is possible on instantiation of a JPA id via the entity manager (or some other means like creating

View File

@@ -2,6 +2,7 @@
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section>
<title>Finding nodes with finders</title>
<note>TODO: rewrite to repositories</note>
<para>Spring Data Graph also comes with a typed, repository-like Finder implementation that provides methods for
locating nodes and relationships. Those methods return instances of the node and relationship entities,
not the graph primitives from Neo4j. Finders delegate to the configured <code>NodeTypeStrategy</code> for type
@@ -68,15 +69,8 @@ Iterable<Person> davesFriends = graphRepository.findAllByTraversal(dave,
Internally the mapping from java types to the graph is handled by a <code>NodeTypeStrategy</code> instance
that is configured with the <code>GraphDatabaseContext</code>. The strategy is called on entity creation and
removal and provides methods for retrieving entities based on type. It also comes with methods confirming or
retrieving java types from the actual graph node. (see also <xref linkend="nodetypestrategy"/>)
</para>
<para>
The default strategy (<code>IndexingNodeTypeStrategy</code>)
uses indexing (index "__types__") and node properties ("__type__") to store the type information in the graph.
The second strategy uses an in graph structure to represent the inheritance hierarchy links the actual node
entity nodes to their concrete class nodes. Instance counts are updated on each of the class nodes in the hierarchy.
The last provided strategy is a No-Op strategy that doesn't care about type information.
retrieving java types from the actual graph node. (see also
<xref linkend="reference:programming-model:typerepresentationstrategy"/>)
</para>
</section>
</section>

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section id="nodetypestrategy">
<title>Storing Type Information in the Graph</title>
<para>
There are several ways to represent the Java type hierarchy of the data model in the graph. In general for all
node and relationship entities type information is needed to perform certain repository operations. Some of
this type information is saved in the graph database.
</para>
<para>
Implementations of <code>NodeTypeStrategy</code> take care of persisting this information on entity instance
creation. They also provide the repository methods that use this type information to perform their operations
like findAll, count, etc.
</para>
<para>
There are three available implementations to choose from.
<itemizedlist>
<listitem>
<para><code>IndexingNodeTypeStrategy</code></para>
<para>
Stores entity types in the integrated index. Each entity node gets indexed with its type and
any supertypes that are also <code>@NodeEntity</code>-annotated. The special index used for this
is called <code>__types__</code>. Additionally, in order to get the type of an entity node, each
node has a property <code>__type__</code> with the type of that entity.
</para>
</listitem>
<listitem>
<para><code>SubReferenceNodeTypeStrategy</code></para>
<para>
Stores entity types in a tree in the graph representing the type hierarchy. Each entity
has a INSTANCE_OF relationship to a type node representing that entity's type. The type may or
may not have a SUBCLASS_OF relationship to another type node.
</para>
</listitem>
<listitem>
<para><code>NoopNodeTypeStrategy</code></para>
<para>
Does not store any type information, and does hence not support finding by type, counting by type,
or retrieving the type of any entity.
</para>
</listitem>
</itemizedlist>
</para>
<para>
The default implementation is <code>IndexingNodeTypeStrategy</code> for new graphs. If using an existing
graph, Spring Data Graph will default to the strategy first used when the graph was created.
</para>
</section>

View File

@@ -14,7 +14,7 @@
<xi:include href="finders.xml"/>
<xi:include href="transactions.xml"/>
<xi:include href="attachdetach.xml"/>
<xi:include href="nodetypestrategy.xml"/>
<xi:include href="typerepresentationstrategy.xml"/>
<xi:include href="introducedmethods.xml"/>
<xi:include href="projection.xml"/>
<xi:include href="beanvalidation.xml"/>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section id="reference:programming-model:typerepresentationstrategy">
<title>Storing type information in the graph</title>
<para>
There are several ways to represent the Java type hierarchy of the data model in the graph. In general, for all
node and relationship entities, type information is needed to perform certain repository operations. Some of
this type information is saved in the graph database.
</para>
<para>
Implementations of
<code>TypeRepresentationStrategy</code>
take care of persisting this information on entity instance
creation. They also provide the repository methods that use this type information to perform their operations,
like findAll and count.
</para>
<para>
There are three available implementations for node entities to choose from.
<itemizedlist>
<listitem>
<para>
<code>IndexingNodeTypeRepresentationStrategy</code>
</para>
<para>
Stores entity types in the integrated index. Each entity node gets indexed with its type and
any supertypes that are also<code>@NodeEntity</code>-annotated. The special index used for this
is called<code>__types__</code>. Additionally, in order to get the type of an entity node, each
node has a property
<code>__type__</code>
with the type of that entity.
</para>
</listitem>
<listitem>
<para>
<code>SubReferenceNodeTypeRepresentationStrategy</code>
</para>
<para>
Stores entity types in a tree in the graph representing the type hierarchy. Each entity
has a INSTANCE_OF relationship to a type node representing that entity's type. The type may or
may not have a SUBCLASS_OF relationship to another type node.
</para>
</listitem>
<listitem>
<para>
<code>NoopNodeTypeRepresentationStrategy</code>
</para>
<para>
Does not store any type information, and does hence not support finding by type, counting by type,
or retrieving the type of any entity.
</para>
</listitem>
</itemizedlist>
</para>
<para>
There are two implementations for relationship entities available, same behavior as the corresponding ones
above:
<itemizedlist>
<listitem>
<para>
<code>IndexingRelationshipTypeRepresentationStrategy</code>
</para>
</listitem>
<listitem>
<para>
<code>NoopRelationshipTypeRepresentationStrategy</code>
</para>
</listitem>
</itemizedlist>
</para>
<para>
Spring Data Graph will by default autodetect which are the most suitable strategies for node and relationship
entities. For new data stores, it will always opt for the indexing strategies. If a data store was created
with the older<code>SubReferenceNodeTypeRepresentationStrategy</code>, then it will continue to use that
strategy for node entities. It will however in that case use the no-op strategy for relationship entities,
which means that the old data stores have no support for searching for relationship entities. The indexing
strategies are recommended for all new users.
</para>
</section>

View File

@@ -98,6 +98,7 @@
</section>
<section>
<title>Setting Up Spring Data Graph - Spring Configuration</title>
<note>Out of date - should we even have this section?</note>
<para>The concrete configuration for Spring Data Graph is quite verbose as there is no autowiring involved. It sets up the following parts.
<itemizedlist>
<listitem>
@@ -120,7 +121,7 @@
<para>Finder factory</para>
</listitem>
<listitem>
<para>an appropriate NodeTypeStrategy</para>
<para>TypeRepresentationStrategies</para>
</listitem>
</itemizedlist>