diff --git a/spring-data-neo4j-rest/pom.xml b/spring-data-neo4j-rest/pom.xml index a16d22487..82ef9d30a 100644 --- a/spring-data-neo4j-rest/pom.xml +++ b/spring-data-neo4j-rest/pom.xml @@ -42,7 +42,6 @@ org.springframework.data spring-data-neo4j 1.0.0.BUILD-SNAPSHOT - test org.hibernate diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/kernel/RestConfig.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/kernel/RestConfig.java index 87a15ac0b..e8008799a 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/kernel/RestConfig.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/kernel/RestConfig.java @@ -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; } } -} +} \ No newline at end of file diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/GraphDatabaseFactory.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/GraphDatabaseFactory.java deleted file mode 100644 index 497c61737..000000000 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/GraphDatabaseFactory.java +++ /dev/null @@ -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); - } - } -} diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestEntity.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestEntity.java index 2b9c201a9..aa06cb93a 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestEntity.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestEntity.java @@ -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; } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestGraphDatabase.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestGraphDatabase.java index dc11bcbfa..eaca8e9f2 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestGraphDatabase.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestGraphDatabase.java @@ -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 TransactionEventHandler registerTransactionEventHandler( TransactionEventHandler tTransactionEventHandler ) { - throw new UnsupportedOperationException(); - } - - public TransactionEventHandler unregisterTransactionEventHandler( TransactionEventHandler 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 config ) { - throw new UnsupportedOperationException(); - } - - public Iterable 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 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 Index getIndex(String indexName) { + return (Index) index().forNodes(indexName); // todo + } + + @Override + public Index createIndex(Class type, String indexName, boolean fullText) { + return (Index) 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 getAllNodes() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterable getRelationshipTypes() { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + + } + + @Override + public boolean enableRemoteShell() { + return false; + } + + @Override + public boolean enableRemoteShell(Map initialProperties) { + return false; + } + + @Override + public Transaction beginTx() { + return new Transaction() { + @Override + public void failure() { + } + + @Override + public void success() { + } + + @Override + public void finish() { + } + }; + } + + @Override + public TransactionEventHandler registerTransactionEventHandler(TransactionEventHandler handler) { + return handler; + } + + @Override + public TransactionEventHandler unregisterTransactionEventHandler(TransactionEventHandler 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 diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestNode.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestNode.java index 27bdb2a60..b0870e38e 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestNode.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestNode.java @@ -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 getRelationships() { @@ -48,7 +48,7 @@ public class RestNode extends RestEntity implements Node { (Collection) restRequest.toEntity( response ) ) { @Override protected Relationship underlyingObjectToObject( Object data ) { - return new RestRelationship( (Map) data, getGraphDatabase() ); + return new RestRelationship( (Map) data, getRestGraphDatabase() ); } }; } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestRelationship.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestRelationship.java index 1ec933624..e81600200 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestRelationship.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestRelationship.java @@ -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() { diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestTraversal.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestTraversal.java index ba2a734d1..d7598115a 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestTraversal.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestTraversal.java @@ -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() diff --git a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestTestBase.java b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestTestBase.java index 03851bc5f..d737eb92f 100644 --- a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestTestBase.java +++ b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestTestBase.java @@ -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 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() { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/GraphDatabase.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/GraphDatabase.java new file mode 100644 index 000000000..d73b2ec94 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/GraphDatabase.java @@ -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) + * graphDatabase.createNode(PropertyMap._("name","value")); + * graphDatabase.createNode(PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop"); + * + * @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) + * graphDatabase.createRelationship(from,to,TYPE, PropertyMap._("name","value")); + * graphDatabase.createRelationship(from,to,TYPE, PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop"); + * + * + * @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 + */ + Index 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} + */ + Index createIndex(Class type, String indexName, boolean fullText); + + + /** + * @return a TraversalDescription as starting point for defining a traversal + */ + TraversalDescription createTraversalDescription(); + +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/GraphDatabaseFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/GraphDatabaseFactory.java new file mode 100644 index 000000000..f2d3a6740 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/GraphDatabaseFactory.java @@ -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 { + + 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; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/Property.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/Property.java new file mode 100644 index 000000000..06f0de6af --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/core/Property.java @@ -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 properties) { + Property[] result = new Property[properties.size()]; + int i = 0; + for (Map.Entry entry : properties.entrySet()) { + result[i++] = new Property(entry.getKey(), entry.getValue()); + } + return result; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/DelegatingGraphDatabase.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/DelegatingGraphDatabase.java new file mode 100644 index 000000000..9ac73e436 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/DelegatingGraphDatabase.java @@ -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 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 Index getIndex(String indexName) { + IndexManager indexManager = delegate.index(); + if (indexManager.existsForNodes(indexName)) return (Index) indexManager.forNodes(indexName); + if (indexManager.existsForRelationships(indexName)) return (Index) indexManager.forRelationships(indexName); + throw new IllegalArgumentException("Index "+indexName+" does not exist."); + } + + // TODO handle existing indexes + @Override + public Index createIndex(Class type, String indexName, boolean fullText) { + IndexManager indexManager = delegate.index(); + if (isNode(type)) { + if (indexManager.existsForNodes(indexName)) + return (Index) checkAndGetExistingIndex(indexName, fullText, indexManager.forNodes(indexName)); + return (Index) indexManager.forNodes(indexName, indexConfigFor(fullText)); + } else { + if (indexManager.existsForRelationships(indexName)) + return (Index) checkAndGetExistingIndex(indexName, fullText, indexManager.forRelationships(indexName)); + return (Index) indexManager.forRelationships(indexName, indexConfigFor(fullText)); + } + } + + public boolean isNode(Class 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 Index checkAndGetExistingIndex(final String indexName, boolean fullText, final Index index) { + Map existingConfig = delegate.index().getConfiguration(index); + Map config = indexConfigFor(fullText); + if (config.equals(existingConfig)) return index; + throw new IllegalArgumentException("Setup for index "+indexName+" does not match "+(fullText ? "fulltext":"exact")); + } + + private Map indexConfigFor(boolean fullText) { + return fullText ? LuceneIndexImplementation.FULLTEXT_CONFIG : LuceneIndexImplementation.EXACT_CONFIG; + } + + @Override + public TraversalDescription createTraversalDescription() { + return Traversal.description(); + } + + public void shutdown() { + delegate.shutdown(); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jOperations.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jOperations.java index 6636c9b7a..38b26ac58 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jOperations.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jOperations.java @@ -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) * template.createNode(PropertyMap._("name","value")); * template.createNode(PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop"); + * + * * @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 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) * template.createRelationship(from,to,TYPE, PropertyMap._("name","value")); * template.createRelationship(from,to,TYPE, PropertyMap.props().set("name","value").set("prop","anotherValue").toMap(), "name", "prop"); + * + * * @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 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 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 the provided element type - * @return the provided element for convenience - */ - T autoIndex(String indexName, T element, String... indexProperties); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jTemplate.java index f6e90caa7..0281d5e98 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/template/Neo4jTemplate.java @@ -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 properties, final String... indexFields) { + public Node createNode(final Property... properties) { return exec(new GraphCallback() { @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 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 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 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() { @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 setProperties(T primitive, Map properties) { + private T setProperties(T primitive, Property... properties) { assert primitive != null; if (properties==null) return primitive; - for (Map.Entry 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; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/graph/core/GraphDatabaseFactoryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/graph/core/GraphDatabaseFactoryTest.java new file mode 100644 index 000000000..7c7a4065b --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/graph/core/GraphDatabaseFactoryTest.java @@ -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(); + } + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/Neo4jTemplateApiTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/Neo4jTemplateApiTest.java index 0374db728..de6c7f1b0 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/Neo4jTemplateApiTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/Neo4jTemplateApiTest.java @@ -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"); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/NeoTraversalTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/NeoTraversalTest.java index 80d4cfb9a..3fc670fb4 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/NeoTraversalTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/template/NeoTraversalTest.java @@ -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 { diff --git a/spring-data-neo4j/src/test/resources/GraphDatabaseFactory-context.xml b/spring-data-neo4j/src/test/resources/GraphDatabaseFactory-context.xml new file mode 100644 index 000000000..e1863ed64 --- /dev/null +++ b/spring-data-neo4j/src/test/resources/GraphDatabaseFactory-context.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + \ No newline at end of file