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

This commit is contained in:
Michael Hunger
2011-03-24 21:50:23 +01:00
49 changed files with 753 additions and 230 deletions

View File

@@ -16,7 +16,7 @@
<org.slf4j.version>1.5.10</org.slf4j.version>
<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
<data.commons.version>1.0.0.BUILD-SNAPSHOT</data.commons.version>
<neo4j.version>1.3.M04</neo4j.version>
<neo4j.version>1.3-SNAPSHOT</neo4j.version>
<aspectj.version>1.6.11.M2</aspectj.version>
</properties>
<profiles>
@@ -229,6 +229,17 @@
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>server-api</artifactId>
<version>${neo4j.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.ow2.jotm</groupId>

View File

@@ -88,25 +88,27 @@
<constructor-arg index="0" value="target/data/emtest" />
</bean>
<bean id="graphDatabaseContext" class="org.springframework.data.graph.neo4j.support.GraphDatabaseContext">
<property name="graphDatabaseService" ref="graphDatabaseService"/>
<property name="relationshipEntityInstantiator">
<bean class="org.springframework.data.graph.neo4j.support.relationship.ConstructorBypassingGraphRelationshipInstantiator" />
</property>
<property name="graphEntityInstantiator">
<bean class="org.springframework.data.graph.neo4j.support.node.Neo4jConstructorGraphEntityInstantiator" />
</property>
<property name="conversionService">
<bean class="org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
</property>
<property name="nodeTypeStrategy">
<bean class="org.springframework.data.graph.neo4j.support.SubReferenceNodeTypeStrategy">
<constructor-arg index="0" ref="graphDatabaseContext"/>
</bean>
</property>
</bean>
<bean id="nodeEntityStateFactory" class="org.springframework.data.graph.neo4j.fieldaccess.NodeEntityStateFactory">
<bean id="graphDatabaseContext" class="org.springframework.data.graph.neo4j.support.GraphDatabaseContext">
<property name="graphDatabaseService" ref="graphDatabaseService"/>
<property name="relationshipEntityInstantiator">
<bean class="org.springframework.data.graph.neo4j.support.relationship.ConstructorBypassingGraphRelationshipInstantiator"/>
</property>
<property name="graphEntityInstantiator" ref="graphEntityInstantiator"/>
<property name="conversionService">
<bean class="org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
</property>
<property name="nodeTypeStrategy" ref="nodeTypeStrategy"/>
</bean>
<bean id="nodeTypeStrategy" class="org.springframework.data.graph.neo4j.support.NodeTypeStrategyFactoryBean">
<constructor-arg ref="graphDatabaseService"/>
<constructor-arg ref="graphEntityInstantiator"/>
</bean>
<bean id="graphEntityInstantiator"
class="org.springframework.data.graph.neo4j.support.node.Neo4jConstructorGraphEntityInstantiator"/>
<bean id="nodeEntityStateFactory" class="org.springframework.data.graph.neo4j.fieldaccess.NodeEntityStateFactory">
<property name="nodeDelegatingFieldAccessorFactory">
<bean class="org.springframework.data.graph.neo4j.fieldaccess.NodeDelegatingFieldAccessorFactory">
<constructor-arg ref="graphDatabaseContext"/>

View File

@@ -132,6 +132,17 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>server-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
@@ -168,6 +179,14 @@
</dependencies>
<repositories>
<repository>
<id>neo4j-public-repository</id>
<name>Neo4J Public Repository</name>
<url>http://m2.neo4j.org</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jboss-repository</id>
<name>JBoss Public Repository</name>

View File

@@ -16,14 +16,14 @@
package org.springframework.data.graph.annotation;
import org.springframework.data.graph.core.Direction;
import org.springframework.data.graph.core.NodeBacked;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.graph.core.Direction;
import org.springframework.data.graph.core.NodeBacked;
/**
* Annotation for {@link org.springframework.data.graph.annotation.NodeEntity} fields that relate to other entities via
* relationships. Works for one-to-one and one-to-many relationships. It is optionally possible to define the relationship type,
@@ -31,10 +31,11 @@ import org.springframework.data.graph.core.NodeBacked;
*
* Collection based one-to-many relationships return managed collections that reflect addition and removal to the underlying relationships.
*
* Examples:
* <pre>
* &#64;RelatedTo([type=&quot;friends&quot;], elementClass=Person.class)
* &#64;RelatedTo(elementClass=Person.class)
* Collection&lt;Person&gt; friends;
* &#64;RelatedTo([type=&quot;spouse&quot;], [elementClass=Person.class])
* &#64;RelatedTo(type=&quot;partner&quot;)
* Person spouse;
* </pre>
@@ -47,7 +48,7 @@ public @interface RelatedTo {
/**
* @return name of the relationship type, optional, can be inferred from the field name
*/
String type();
String type() default "";
/**
* @return direction for the relationship, by default outgoing

View File

@@ -52,5 +52,5 @@ public @interface RelatedToVia {
/**
* @return target relationship entity class
*/
Class<? extends RelationshipBacked> elementClass() default RelationshipBacked.class;
Class<? extends RelationshipBacked> elementClass();
}

View File

@@ -0,0 +1,67 @@
package org.springframework.data.graph.neo4j.config;
import org.neo4j.graphdb.Transaction;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import javax.annotation.PostConstruct;
/**
* Validates correct configuration of Neo4j and Spring, especially transaction-managers
*/
public class ConfigurationCheck {
GraphDatabaseContext graphDatabaseContext;
PlatformTransactionManager transactionManager;
public ConfigurationCheck(GraphDatabaseContext graphDatabaseContext, PlatformTransactionManager transactionManager) {
this.graphDatabaseContext = graphDatabaseContext;
this.transactionManager = transactionManager;
}
@PostConstruct
private void checkConfiguration() {
checkInjection();
checkSpringTransactionManager();
checkNeo4jTransactionManager();
}
private void checkInjection() {
assert graphDatabaseContext.getGraphDatabaseService()!=null : "graphDatabaseService not correctly configured, please refer to the manual, setup section";
}
private void checkSpringTransactionManager() {
try {
TransactionStatus transaction = transactionManager.getTransaction(null);
updateStartTime();
transactionManager.commit(transaction);
} catch(Exception e) {
AssertionError error = new AssertionError("transactionManager not correctly configured, please refer to the manual, setup section");
error.initCause(e);
throw error;
}
}
private void checkNeo4jTransactionManager() {
Transaction tx = null;
try {
tx = graphDatabaseContext.beginTx();
updateStartTime();
tx.success();
} catch (Exception e) {
AssertionError error = new AssertionError("transactionManager not correctly configured, please refer to the manual, setup section");
error.initCause(e);
throw error;
} finally {
try {
if (tx != null) tx.finish();
} catch(Exception e) {
// ignore
}
}
}
private void updateStartTime() {
graphDatabaseContext.getReferenceNode().setProperty("startTime", System.currentTimeMillis());
}
}

View File

@@ -1,10 +1,14 @@
package org.springframework.data.graph.neo4j.config;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.w3c.dom.Element;
import static org.springframework.util.StringUtils.hasText;
@@ -15,12 +19,29 @@ public class DataGraphBeanDefinitionParser extends AbstractBeanDefinitionParser
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext context) {
BeanDefinitionBuilder configBuilder = BeanDefinitionBuilder.rootBeanDefinition(Neo4jConfiguration.class);
BeanDefinitionBuilder configBuilder = createConfigurationBeanDefinition();
setupGraphDatabase(element, context, configBuilder);
setupEntityManagerFactory(element, configBuilder);
setupConfigurationClassPostProcessor(context);
return getSourcedBeanDefinition(configBuilder, element, context);
}
private BeanDefinitionBuilder createConfigurationBeanDefinition() {
BeanDefinitionBuilder configBuilder = BeanDefinitionBuilder.rootBeanDefinition(Neo4jConfiguration.class);
configBuilder.setAutowireMode(Autowire.BY_TYPE.value());
return configBuilder;
}
private void setupConfigurationClassPostProcessor(final ParserContext parserContext) {
BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
BeanDefinitionBuilder configurationClassPostProcessor = BeanDefinitionBuilder.rootBeanDefinition(ConfigurationClassPostProcessor.class);
BeanNameGenerator beanNameGenerator = parserContext.getReaderContext().getReader().getBeanNameGenerator();
AbstractBeanDefinition configurationClassPostProcessorBeanDefinition = configurationClassPostProcessor.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(configurationClassPostProcessorBeanDefinition, beanDefinitionRegistry);
beanDefinitionRegistry.registerBeanDefinition(beanName, configurationClassPostProcessorBeanDefinition);
}
@Override
protected boolean shouldGenerateId() {
return true;

View File

@@ -17,32 +17,32 @@
package org.springframework.data.graph.neo4j.config;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.kernel.impl.transaction.SpringTransactionManager;
import org.neo4j.kernel.impl.transaction.UserTransactionImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean;
import org.springframework.data.graph.neo4j.fieldaccess.NodeDelegatingFieldAccessorFactory;
import org.springframework.data.graph.neo4j.fieldaccess.NodeEntityStateFactory;
import org.springframework.data.graph.neo4j.fieldaccess.RelationshipEntityStateFactory;
import org.springframework.data.graph.neo4j.finder.FinderFactory;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.data.graph.neo4j.support.SubReferenceNodeTypeStrategy;
import org.springframework.data.graph.neo4j.support.NodeTypeStrategyFactoryBean;
import org.springframework.data.graph.neo4j.support.node.Neo4jConstructorGraphEntityInstantiator;
import org.springframework.data.graph.neo4j.support.node.Neo4jNodeBacking;
import org.springframework.data.graph.neo4j.support.node.PartialNeo4jEntityInstantiator;
import org.springframework.data.graph.neo4j.support.relationship.ConstructorBypassingGraphRelationshipInstantiator;
import org.springframework.data.graph.neo4j.support.relationship.Neo4jRelationshipBacking;
import org.springframework.data.graph.neo4j.transaction.ChainedTransactionManager;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.persistence.transaction.NaiveDoubleTransactionManager;
import org.springframework.data.persistence.EntityInstantiator;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.validation.Validator;
@@ -82,7 +82,7 @@ public class Neo4jConfiguration {
}
public boolean isUsingCrossStorePersistence() {
return entityManagerFactory!=null;
return entityManagerFactory != null;
}
@Bean
@@ -90,20 +90,25 @@ public class Neo4jConfiguration {
GraphDatabaseContext gdc = new GraphDatabaseContext();
gdc.setGraphDatabaseService(getGraphDatabaseService());
gdc.setRelationshipEntityInstantiator(new ConstructorBypassingGraphRelationshipInstantiator());
if (isUsingCrossStorePersistence()) {
gdc.setGraphEntityInstantiator(new PartialNeo4jEntityInstantiator(new Neo4jConstructorGraphEntityInstantiator(), entityManagerFactory));
}
else {
gdc.setGraphEntityInstantiator(new Neo4jConstructorGraphEntityInstantiator());
}
EntityInstantiator<NodeBacked, Node> graphEntityInstantiator = getGraphEntityInstantiator();
gdc.setGraphEntityInstantiator(graphEntityInstantiator);
gdc.setConversionService(new Neo4jConversionServiceFactoryBean().getObject());
gdc.setNodeTypeStrategy(new SubReferenceNodeTypeStrategy(gdc));
NodeTypeStrategyFactoryBean nodeTypeStrategyFactoryBean = new NodeTypeStrategyFactoryBean(graphDatabaseService, graphEntityInstantiator);
gdc.setNodeTypeStrategy(nodeTypeStrategyFactoryBean.getObject());
if (validator!=null) {
gdc.setValidator(validator);
}
return gdc;
}
private EntityInstantiator<NodeBacked, Node> getGraphEntityInstantiator() {
if (isUsingCrossStorePersistence()) {
return new PartialNeo4jEntityInstantiator(new Neo4jConstructorGraphEntityInstantiator(), entityManagerFactory);
} else {
return new Neo4jConstructorGraphEntityInstantiator();
}
}
@Bean
public FinderFactory finderFactory(GraphDatabaseContext graphDatabaseContext) throws Exception {
return new FinderFactory(graphDatabaseContext);
@@ -149,4 +154,9 @@ public class Neo4jConfiguration {
return transactionManager;
}
}
@Bean
public ConfigurationCheck configurationCheck() throws Exception {
return new ConfigurationCheck(graphDatabaseContext(),transactionManager());
}
}

View File

@@ -43,8 +43,8 @@ public abstract class DefaultEntityState<ENTITY extends GraphBacked<STATE>, STAT
this.entity = entity;
this.type = type;
fieldAccessorFactoryProviders = delegatingFieldAccessorFactory.accessorFactoriesFor(type);
this.fieldAccessors= fieldAccessorFactoryProviders.getFieldAccessors();
this.fieldAccessorListeners= fieldAccessorFactoryProviders.getFieldAccessListeners();
this.fieldAccessors = fieldAccessorFactoryProviders.getFieldAccessors();
this.fieldAccessorListeners = fieldAccessorFactoryProviders.getFieldAccessListeners();
}
@Override

View File

@@ -119,11 +119,11 @@ public abstract class DelegatingFieldAccessorFactory<T> implements FieldAccessor
private final Map<Class<?>, FieldAccessorFactoryProviders> acessorFactoryProviderCache = new HashMap<Class<?>, FieldAccessorFactoryProviders>();
private final Map<Class<?>, FieldAccessorFactoryProviders> accessorFactoryProviderCache = new HashMap<Class<?>, FieldAccessorFactoryProviders>();
public <T> FieldAccessorFactoryProviders<T> accessorFactoriesFor(final Class<T> type) {
synchronized (this) {
final FieldAccessorFactoryProviders<T> fieldAccessorFactoryProviders = acessorFactoryProviderCache.get(type);
final FieldAccessorFactoryProviders<T> fieldAccessorFactoryProviders = accessorFactoryProviderCache.get(type);
if (fieldAccessorFactoryProviders != null) return fieldAccessorFactoryProviders;
final FieldAccessorFactoryProviders<T> newFieldAccessorFactories = new FieldAccessorFactoryProviders<T>(type);
ReflectionUtils.doWithFields(type, new ReflectionUtils.FieldCallback() {
@@ -133,7 +133,7 @@ public abstract class DelegatingFieldAccessorFactory<T> implements FieldAccessor
newFieldAccessorFactories.add(field, factory, listenerFactories);
}
});
acessorFactoryProviderCache.put(type, newFieldAccessorFactories);
accessorFactoryProviderCache.put(type, newFieldAccessorFactories);
return newFieldAccessorFactories;
}
}

View File

@@ -20,7 +20,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.index.Index;
import org.neo4j.index.impl.lucene.ValueContext;
import org.neo4j.index.lucene.ValueContext;
import org.springframework.data.annotation.Indexed;
import org.springframework.data.graph.annotation.NodeEntity;
import org.springframework.data.graph.core.GraphBacked;

View File

@@ -21,7 +21,7 @@ import org.neo4j.graphdb.NotInTransactionException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.persistence.support.StateProvider;
import org.springframework.data.persistence.StateProvider;
/**
* @author Michael Hunger

View File

@@ -18,6 +18,7 @@ package org.springframework.data.graph.neo4j.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.graph.annotation.RelatedTo;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
@@ -58,12 +59,21 @@ abstract class NodeRelationshipFieldAccessorFactory implements FieldAccessorFact
return DynamicRelationshipType.withName(relAnnotation.type());
}
protected DynamicRelationshipType typeFrom(Field field, RelatedTo relAnnotation) {
return "".equals(relAnnotation.type()) ? typeFrom(field) : typeFrom(relAnnotation);
}
protected RelatedTo getRelationshipAnnotation(Field field) {
return field.getAnnotation(RelatedTo.class);
}
protected boolean hasValidRelationshipAnnotation(Field field) {
final RelatedTo relAnnotation = getRelationshipAnnotation(field);
return relAnnotation != null && !relAnnotation.elementClass().equals(NodeBacked.class);
if (relAnnotation == null) return false;
boolean hasElementClass = !relAnnotation.elementClass().equals(NodeBacked.class);
if (!hasElementClass) throw new InvalidDataAccessApiUsageException(String.format(
"Missing mandatory attribute @RelatedTo.elementClass for one-to-N relationship field %s in class: %s",
field.getName(), field.getDeclaringClass().getName()));
return true;
}
}

View File

@@ -50,9 +50,15 @@ public class OneToNRelationshipEntityFieldAccessorFactory implements FieldAccess
return new OneToNRelationshipEntityFieldAccessor(typeFrom(relEntityAnnotation), dirFrom(relEntityAnnotation), targetFrom(relEntityAnnotation), graphDatabaseContext);
}
private boolean hasValidRelationshipAnnotation(final Field f) {
final RelatedToVia relEntityAnnotation = getRelationshipAnnotation(f);
return relEntityAnnotation != null && !RelationshipBacked.class.equals(relEntityAnnotation.elementClass());
private boolean hasValidRelationshipAnnotation(final Field field) {
final RelatedToVia relEntityAnnotation = getRelationshipAnnotation(field);
if (relEntityAnnotation == null) return false;
Class<? extends RelationshipBacked> elementClass = relEntityAnnotation.elementClass();
boolean hasElementClass = elementClass != null && !RelationshipBacked.class.equals(elementClass);
if (!hasElementClass) throw new InvalidDataAccessApiUsageException(String.format(
"Missing mandatory attribute @RelatedTo.elementClass for one-to-N relationship field %s in class: %s",
field.getName(), field.getDeclaringClass().getName()));
return hasElementClass;
}
private RelatedToVia getRelationshipAnnotation(final Field field) {

View File

@@ -44,7 +44,7 @@ public class OneToNRelationshipFieldAccessorFactory extends NodeRelationshipFiel
@Override
public FieldAccessor<NodeBacked> forField(final Field field) {
final RelatedTo relAnnotation = getRelationshipAnnotation(field);
return new OneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphDatabaseContext);
return new OneToNRelationshipFieldAccessor(typeFrom(field, relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphDatabaseContext);
}
public static class OneToNRelationshipFieldAccessor extends NodeToNodesRelationshipFieldAccessor<NodeBacked> {

View File

@@ -26,7 +26,7 @@ import org.springframework.data.graph.annotation.RelatedTo;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.finder.FinderFactory;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.persistence.support.StateProvider;
import org.springframework.data.persistence.StateProvider;
import javax.persistence.Id;
import java.lang.reflect.Field;

View File

@@ -39,7 +39,7 @@ public class ReadOnlyOneToNRelationshipFieldAccessorFactory extends NodeRelation
@Override
public FieldAccessor<NodeBacked> forField(final Field field) {
final RelatedTo relAnnotation = getRelationshipAnnotation(field);
return new ReadOnlyOneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphDatabaseContext);
return new ReadOnlyOneToNRelationshipFieldAccessor(typeFrom(field, relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphDatabaseContext);
}
public static class ReadOnlyOneToNRelationshipFieldAccessor extends OneToNRelationshipFieldAccessorFactory.OneToNRelationshipFieldAccessor {

View File

@@ -24,7 +24,6 @@ import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
@@ -46,7 +45,7 @@ public class SingleRelationshipFieldAccessorFactory extends NodeRelationshipFiel
final RelatedTo relAnnotation = getRelationshipAnnotation(field);
if (relAnnotation == null)
return new SingleRelationshipFieldAccessor(typeFrom(field), Direction.OUTGOING, targetFrom(field), graphDatabaseContext);
return new SingleRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(field), graphDatabaseContext);
return new SingleRelationshipFieldAccessor(typeFrom(field, relAnnotation), dirFrom(relAnnotation), targetFrom(field), graphDatabaseContext);
}
public static class SingleRelationshipFieldAccessor extends NodeToNodesRelationshipFieldAccessor<NodeBacked> {

View File

@@ -6,7 +6,6 @@ import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.helpers.collection.IterableWrapper;
import org.neo4j.index.impl.lucene.ValueContext;
import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;

View File

@@ -0,0 +1,29 @@
package org.springframework.data.graph.neo4j.server;
import org.neo4j.graphdb.GraphDatabaseService;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Context that merges the provided graph database service with the given context locations,
* so that spring beans that consume a graph database are populated properly.
*/
public class ProvidedClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {
private final GraphDatabaseService database;
public ProvidedClassPathXmlApplicationContext(GraphDatabaseService database, final String[] locations)
throws org.springframework.beans.BeansException {
super();
setConfigLocations(locations);
this.database = database;
refresh();
}
@Override
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.prepareBeanFactory(beanFactory);
beanFactory.registerResolvableDependency(GraphDatabaseService.class, database);
beanFactory.registerSingleton("graphDatabaseService", database);
}
}

View File

@@ -0,0 +1,84 @@
package org.springframework.data.graph.neo4j.server;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.beanutils.BeanFactory;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.server.plugins.PluginLifecycle;
import org.neo4j.server.plugins.Injectable;
import org.springframework.context.ApplicationContext;
import java.util.ArrayList;
import java.util.Collection;
/**
* Initializer to run Spring Data Graph based Server Plugins in a Neo4j REST-server. It takes the list of
* config locations and a number of spring beans from those contexts that should be exposed
* as injectable dependencies with a Jersey @Context.<br/>
* For example:</br>
* <pre>
* class MyInitializer extends SpringPluginInitializer {
* public MyInitializer() {
* super(new String[]{"myContext.xml"},"finderFactory","myRepository");
* }
* }
* </pre>
*/
public abstract class SpringPluginInitializer implements PluginLifecycle {
private String[] contextLocations;
private String[] exposedBeans;
protected ProvidedClassPathXmlApplicationContext ctx;
public SpringPluginInitializer(String[] contextLocations, String... exposedBeans) {
this.contextLocations = contextLocations;
this.exposedBeans = exposedBeans;
}
/**
* Binds the provided graph database to the spring contexts so that spring beans that consume a
* graph database can be populated.<br/>
* @param graphDatabaseService of the Neo4j server
* @param config of the Neo4j Server
* @return Exposes the requested Spring beans as @{see Injectable}s
*/
@Override
public Collection<Injectable<?>> start(GraphDatabaseService graphDatabaseService, Configuration config) {
ctx = new ProvidedClassPathXmlApplicationContext(graphDatabaseService, contextLocations);
Collection<Injectable<?>> result = new ArrayList<Injectable<?>>(exposedBeans.length);
for (final String exposedBean : exposedBeans) {
result.add(new SpringBeanInjectable(SpringPluginInitializer.this.ctx, exposedBean));
}
return result;
}
/**
* closes the spring context
*/
public void stop() {
if (ctx!=null) {
ctx.close();
}
}
/**
* provides access to the Spring bean, proxying the @{see Injectable}
* @param <T> optional type of the bean
*/
private static class SpringBeanInjectable<T extends Object> implements Injectable<T> {
private final String exposedBean;
protected ApplicationContext ctx;
public SpringBeanInjectable(final ApplicationContext ctx, String exposedBean) {
this.exposedBean = exposedBean;
this.ctx = ctx;
}
public T getValue() {
return (T) ctx.getBean(exposedBean);
}
public Class<T> getType() {
return (Class<T>) ctx.getType(exposedBean);
}
}
}

View File

@@ -29,7 +29,7 @@ import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.NodeTypeStrategy;
import org.springframework.data.graph.core.RelationshipBacked;
import org.springframework.persistence.support.EntityInstantiator;
import org.springframework.data.persistence.EntityInstantiator;
import javax.transaction.Status;
import javax.transaction.SystemException;
@@ -269,28 +269,6 @@ public class GraphDatabaseContext {
}
/**
* @param relType
* @return
*/
public Node getOrCreateSubReferenceNode(final RelationshipType relType) {
return getOrCreateSingleOtherNode(graphDatabaseService.getReferenceNode(), relType, Direction.OUTGOING);
}
private Node getOrCreateSingleOtherNode(Node fromNode, RelationshipType type,
Direction direction) {
Relationship singleRelationship = fromNode.getSingleRelationship(type, direction);
if (singleRelationship != null) {
return singleRelationship.getOtherNode(fromNode);
}
Node otherNode = graphDatabaseService.createNode();
fromNode.createRelationshipTo(otherNode, type);
return otherNode;
}
/**
* @return Neo4j Transaction manager
*/

View File

@@ -11,11 +11,14 @@ import org.neo4j.helpers.collection.IterableWrapper;
import org.springframework.data.graph.annotation.NodeEntity;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.NodeTypeStrategy;
import org.springframework.persistence.support.EntityInstantiator;
import org.springframework.data.persistence.EntityInstantiator;
public class IndexingNodeTypeStrategy implements NodeTypeStrategy {
private EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
public static final String NODE_INDEX_NAME = "__types__";
public static final String TYPE_PROPERTY_NAME = "__type__";
public static final String INDEX_KEY = "className";
private EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
private GraphDatabaseService graphDb;
public IndexingNodeTypeStrategy(GraphDatabaseService graphDb, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
@@ -24,7 +27,7 @@ public class IndexingNodeTypeStrategy implements NodeTypeStrategy {
}
private Index<Node> getTypesIndex() {
return graphDb.index().forNodes("__types__");
return graphDb.index().forNodes(NODE_INDEX_NAME);
}
@Override
@@ -32,20 +35,20 @@ public class IndexingNodeTypeStrategy implements NodeTypeStrategy {
Node node = entity.getPersistentState();
Class<? extends NodeBacked> entityClass = entity.getClass();
addToTypesIndex(node, entityClass);
node.setProperty("__type__", entityClass.getName());
node.setProperty(TYPE_PROPERTY_NAME, entityClass.getName());
}
private void addToTypesIndex(Node node, Class<? extends NodeBacked> entityClass) {
Class<?> klass = entityClass;
while (klass.getAnnotation(NodeEntity.class) != null) {
getTypesIndex().add(node, "className", klass.getName());
getTypesIndex().add(node, INDEX_KEY, klass.getName());
klass = klass.getSuperclass();
}
}
@Override
public <ENTITY extends NodeBacked> Iterable<ENTITY> findAll(Class<ENTITY> clazz) {
final IndexHits<Node> allEntitiesOfType = getTypesIndex().get("className", clazz.getName());
final IndexHits<Node> allEntitiesOfType = getTypesIndex().get(INDEX_KEY, clazz.getName());
return new FilteringIterable<ENTITY>(new IterableWrapper<ENTITY, Node>(allEntitiesOfType) {
@Override
@SuppressWarnings("unchecked")
@@ -64,7 +67,11 @@ public class IndexingNodeTypeStrategy implements NodeTypeStrategy {
@Override
public long count(Class<? extends NodeBacked> entityClass) {
return getTypesIndex().get("className", entityClass.getName()).size();
long count = 0;
for (Node node : getTypesIndex().get(INDEX_KEY, entityClass.getName())) {
count += 1;
}
return count;
}
@Override
@@ -72,7 +79,7 @@ public class IndexingNodeTypeStrategy implements NodeTypeStrategy {
public <ENTITY extends NodeBacked> Class<ENTITY> getJavaType(Node node) {
if (node == null) throw new IllegalArgumentException("Node is null");
try {
return (Class<ENTITY>) Class.forName((String) node.getProperty("__type__"));
return (Class<ENTITY>) Class.forName((String) node.getProperty(TYPE_PROPERTY_NAME));
} catch (NotFoundException e) {
return null;
} catch (ClassNotFoundException e) {
@@ -82,7 +89,7 @@ public class IndexingNodeTypeStrategy implements NodeTypeStrategy {
@Override
public void preEntityRemoval(NodeBacked entity) {
getTypesIndex().remove(entity.getPersistentState(), "className", entity.getClass().getName());
getTypesIndex().remove(entity.getPersistentState());
}
@Override

View File

@@ -0,0 +1,93 @@
package org.springframework.data.graph.neo4j.support;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.NodeTypeStrategy;
import org.springframework.data.persistence.EntityInstantiator;
public class NodeTypeStrategyFactoryBean implements FactoryBean<NodeTypeStrategy> {
private GraphDatabaseService graphDatabaseService;
private EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
private Strategy strategy;
public NodeTypeStrategyFactoryBean(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
this.graphDatabaseService = graphDatabaseService;
this.graphEntityInstantiator = graphEntityInstantiator;
strategy = chooseStrategy();
}
private Strategy chooseStrategy() {
if (isAlreadyIndexed()) return Strategy.Indexed;
if (isAlreadySubRef()) return Strategy.SubRef;
return Strategy.Indexed;
}
private boolean isAlreadyIndexed() {
return graphDatabaseService.index().existsForNodes(IndexingNodeTypeStrategy.NODE_INDEX_NAME);
}
private boolean isAlreadySubRef() {
for (Relationship rel : graphDatabaseService.getReferenceNode().getRelationships()) {
if (rel.getType().name().startsWith(SubReferenceNodeTypeStrategy.SUBREF_PREFIX)) {
return true;
}
}
return false;
}
@Override
public NodeTypeStrategy getObject() throws Exception {
return strategy.getObject(graphDatabaseService, graphEntityInstantiator);
}
@Override
public Class<?> getObjectType() {
return strategy.getObjectType();
}
@Override
public boolean isSingleton() {
return false;
}
private enum Strategy {
SubRef {
@Override
NodeTypeStrategy getObject(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
return new SubReferenceNodeTypeStrategy(graphDatabaseService, graphEntityInstantiator);
}
@Override
Class<? extends NodeTypeStrategy> getObjectType() {
return SubReferenceNodeTypeStrategy.class;
}
},
Indexed {
@Override
NodeTypeStrategy getObject(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
return new IndexingNodeTypeStrategy(graphDatabaseService, graphEntityInstantiator);
}
@Override
Class<? extends NodeTypeStrategy> getObjectType() {
return IndexingNodeTypeStrategy.class;
}
},
Noop {
@Override
NodeTypeStrategy getObject(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
return new NoopNodeTypeStrategy();
}
@Override
Class<? extends NodeTypeStrategy> getObjectType() {
return NoopNodeTypeStrategy.class;
}
};
abstract NodeTypeStrategy getObject(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator);
abstract Class<? extends NodeTypeStrategy> getObjectType();
}
}

View File

@@ -25,6 +25,7 @@ import org.neo4j.helpers.collection.IterableWrapper;
import org.neo4j.kernel.impl.traversal.TraversalDescriptionImpl;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.NodeTypeStrategy;
import org.springframework.data.persistence.EntityInstantiator;
import java.util.*;
@@ -47,11 +48,13 @@ public class SubReferenceNodeTypeStrategy implements NodeTypeStrategy {
public static final String SUBREF_PREFIX = "SUBREF_";
public static final String SUBREF_CLASS_KEY = "class";
private final GraphDatabaseContext graphDatabaseContext;
private GraphDatabaseService graphDatabaseService;
private EntityInstantiator<NodeBacked, Node> entityInstantiator;
public SubReferenceNodeTypeStrategy(final GraphDatabaseContext graphDatabaseContext) {
this.graphDatabaseContext = graphDatabaseContext;
}
public SubReferenceNodeTypeStrategy(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> entityInstantiator) {
this.graphDatabaseService = graphDatabaseService;
this.entityInstantiator = entityInstantiator;
}
public static Node getSingleOtherNode(Node node, RelationshipType type,
Direction direction) {
@@ -184,7 +187,7 @@ public class SubReferenceNodeTypeStrategy implements NodeTypeStrategy {
@Override
protected T underlyingObjectToObject(final Relationship rel) {
final Node node = rel.getStartNode();
T entity = (T) graphDatabaseContext.createEntityFromState(node, getJavaType(node));
T entity = (T) entityInstantiator.createEntityFromState(node, getJavaType(node));
if (log.isDebugEnabled()) log.debug("Converting node: " + node + " to entity: " + entity);
return entity;
}
@@ -195,15 +198,33 @@ public class SubReferenceNodeTypeStrategy implements NodeTypeStrategy {
public Node obtainSubreferenceNode(final Class<?> entityClass) {
return graphDatabaseContext.getOrCreateSubReferenceNode(subRefRelationshipType(entityClass));
return getOrCreateSubReferenceNode(subRefRelationshipType(entityClass));
}
public Node findSubreferenceNode(final Class<? extends NodeBacked> entityClass) {
final Relationship subrefRelationship = graphDatabaseContext.getReferenceNode().getSingleRelationship(subRefRelationshipType(entityClass), Direction.OUTGOING);
final Relationship subrefRelationship = graphDatabaseService.getReferenceNode().getSingleRelationship(subRefRelationshipType(entityClass), Direction.OUTGOING);
return subrefRelationship != null ? subrefRelationship.getEndNode() : null;
}
private DynamicRelationshipType subRefRelationshipType(Class<?> clazz) {
return DynamicRelationshipType.withName(SUBREF_PREFIX + clazz.getName());
}
public Node getOrCreateSubReferenceNode(final RelationshipType relType) {
return getOrCreateSingleOtherNode(graphDatabaseService.getReferenceNode(), relType, Direction.OUTGOING);
}
private Node getOrCreateSingleOtherNode(Node fromNode, RelationshipType type,
Direction direction) {
Relationship singleRelationship = fromNode.getSingleRelationship(type, direction);
if (singleRelationship != null) {
return singleRelationship.getOtherNode(fromNode);
}
Node otherNode = graphDatabaseService.createNode();
fromNode.createRelationshipTo(otherNode, type);
return otherNode;
}
}

View File

@@ -18,7 +18,7 @@ package org.springframework.data.graph.neo4j.support.node;
import org.neo4j.graphdb.Node;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.persistence.support.AbstractConstructorEntityInstantiator;
import org.springframework.data.persistence.AbstractConstructorEntityInstantiator;
/**
* Implementation of an entity instantiator for neo4j graphdb nodes, binding the entity type to a NodeBacked and the

View File

@@ -20,7 +20,7 @@ import org.neo4j.graphdb.Node;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.fieldaccess.PartialNodeEntityState;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.persistence.support.EntityInstantiator;
import org.springframework.data.persistence.EntityInstantiator;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

View File

@@ -20,7 +20,7 @@ import java.lang.reflect.Constructor;
import org.neo4j.graphdb.Relationship;
import org.springframework.data.graph.core.RelationshipBacked;
import org.springframework.persistence.support.EntityInstantiator;
import org.springframework.data.persistence.EntityInstantiator;
import sun.reflect.ReflectionFactory;

View File

@@ -26,7 +26,7 @@ public class Group {
public final static String OTHER_NAME_INDEX="other_name";
public static final String SEARCH_GROUPS_INDEX = "search-groups";
@RelatedTo(type = "persons", direction = Direction.OUTGOING, elementClass = Person.class)
@RelatedTo(direction = Direction.OUTGOING, elementClass = Person.class)
private Collection<Person> persons;
@RelatedTo(type = "persons", elementClass = Person.class)

View File

@@ -0,0 +1,12 @@
package org.springframework.data.graph.neo4j;
import org.springframework.data.graph.annotation.NodeEntity;
import org.springframework.data.graph.annotation.RelatedTo;
import java.util.Collection;
@NodeEntity
public class InvalidOneToNEntity {
@RelatedTo
private Collection<InvalidOneToNEntity> others;
}

View File

@@ -0,0 +1,10 @@
package org.springframework.data.graph.neo4j;
import org.springframework.data.graph.annotation.NodeEntity;
import org.springframework.data.graph.annotation.RelatedTo;
@NodeEntity
public class InvalidReadOnlyOneToNEntity {
@RelatedTo
private Iterable<InvalidReadOnlyOneToNEntity> others;
}

View File

@@ -44,7 +44,7 @@ public class Person {
private Car car;
@RelatedTo(type = "mother", direction = Direction.OUTGOING)
@RelatedTo
private Person mother;
@RelatedTo(type = "boss", direction = Direction.INCOMING)

View File

@@ -0,0 +1,16 @@
package org.springframework.data.graph.neo4j.config;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author mh
* @since 23.03.11
*/
public class ConfigurationConfirmationTest {
@Test(expected = BeanCreationException.class)
public void testInvalidTransactionManagerFails() {
ClassPathXmlApplicationContext appCtx = new ClassPathXmlApplicationContext("classpath:org/springframework/data/graph/neo4j/config/ConfigurationCofirmationTest-context.xml");
}
}

View File

@@ -24,11 +24,11 @@ import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml",
"classpath:org/springframework/data/graph/neo4j/support/IndexingNodeTypeStrategyOverride-context.xml"})
@Ignore
public class IndexingNodeTypeStrategyTest {
@Autowired
@@ -57,45 +57,76 @@ public class IndexingNodeTypeStrategyTest {
Index<Node> typesIndex = graphDatabaseService.index().forNodes("__types__");
IndexHits<Node> thingHits = typesIndex.get("className", thing.getClass().getName());
assertEquals(set(node(thing), node(subThing)), IteratorUtil.addToCollection((Iterable<Node>)thingHits, new HashSet<Node>()));
IndexHits<Node> subThingHits = typesIndex.get("className", subThing.getClass().getName());
assertEquals(node(subThing), subThingHits.getSingle());
assertEquals(thing.getClass().getName(), node(thing).getProperty("__type__"));
assertEquals(subThing.getClass().getName(), node(subThing).getProperty("__type__"));
}
@Test
public void testFindAll() throws Exception {
assertEquals("Did not find all things.",
Arrays.asList(thing, subThing), IteratorUtil.addToCollection(nodeTypeStrategy.findAll(Thing.class), new ArrayList<Thing>()));
public void testPreEntityRemoval() throws Exception {
manualCleanDb();
createThings();
Index<Node> typesIndex = graphDatabaseService.index().forNodes("__types__");
IndexHits<Node> thingHits;
IndexHits<Node> subThingHits;
Transaction tx = graphDatabaseService.beginTx();
try
{
nodeTypeStrategy.preEntityRemoval(thing);
tx.success();
}
finally
{
tx.finish();
}
thingHits = typesIndex.get("className", thing.getClass().getName());
assertEquals(node(subThing), thingHits.getSingle());
subThingHits = typesIndex.get("className", subThing.getClass().getName());
assertEquals(node(subThing), subThingHits.getSingle());
tx = graphDatabaseService.beginTx();
try
{
nodeTypeStrategy.preEntityRemoval(subThing);
tx.success();
}
finally
{
tx.finish();
}
thingHits = typesIndex.get("className", thing.getClass().getName());
assertNull(thingHits.getSingle());
subThingHits = typesIndex.get("className", subThing.getClass().getName());
assertNull(subThingHits.getSingle());
}
@Test
@Transactional
public void testFindAll() throws Exception {
assertEquals("Did not find all things.",
Arrays.asList(thing, subThing),
IteratorUtil.addToCollection(nodeTypeStrategy.findAll(Thing.class), new ArrayList<Thing>()));
}
@Test
@Transactional
public void testCount() throws Exception {
assertEquals(2, nodeTypeStrategy.count(Thing.class));
}
@Test
@Transactional
public void testGetJavaType() throws Exception {
assertEquals(Thing.class, nodeTypeStrategy.getJavaType(node(thing)));
assertEquals(SubThing.class, nodeTypeStrategy.getJavaType(node(subThing)));
}
@Test
public void testPreEntityRemoval() throws Exception {
manualCleanDb();
Transaction tx;
tx = graphDatabaseService.beginTx();
try {
nodeTypeStrategy.preEntityRemoval(thing);
nodeTypeStrategy.preEntityRemoval(subThing);
tx.success();
} finally {
tx.finish();
}
Index<Node> typesIndex = graphDatabaseService.index().forNodes("__types__");
IndexHits<Node> thingHits = typesIndex.get("className", thing.getClass().getName());
assertEquals(0, thingHits.size());
}
@Test
@Transactional
public void testConfirmType() throws Exception {
assertEquals(Thing.class, nodeTypeStrategy.confirmType(node(thing), Thing.class));
assertEquals(SubThing.class, nodeTypeStrategy.confirmType(node(subThing), Thing.class));

View File

@@ -61,7 +61,7 @@ public class NodeEntityRelationshipTest {
Person p = persistedPerson("Michael", 35);
Person mother = persistedPerson("Gabi", 60);
p.setMother(mother);
Node motherNode = p.getPersistentState().getSingleRelationship(DynamicRelationshipType.withName("mother"), Direction.OUTGOING).getEndNode();
Node motherNode = p.getPersistentState().getSingleRelationship(DynamicRelationshipType.withName("Person.mother"), Direction.OUTGOING).getEndNode();
assertEquals(mother.getPersistentState(), motherNode);
assertEquals(mother, p.getMother());
}

View File

@@ -2,19 +2,14 @@ package org.springframework.data.graph.neo4j.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.graph.neo4j.Friendship;
import org.springframework.data.graph.neo4j.Person;
import static org.springframework.data.graph.neo4j.Person.persistedPerson;
import org.springframework.data.graph.neo4j.Personality;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.graph.neo4j.*;
import org.springframework.data.graph.neo4j.finder.FinderFactory;
import org.springframework.data.graph.neo4j.support.node.Neo4jHelper;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -24,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import static org.junit.Assert.assertEquals;
import static org.springframework.data.graph.neo4j.Person.persistedPerson;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
@@ -131,4 +127,16 @@ public class PropertyTest {
Friendship f = p.knows(p2);
assertEquals("Wrong ID.", (Long)f.getPersistentState().getId(), f.getRelationshipId());
}
@Test(expected = InvalidDataAccessApiUsageException.class)
@Transactional
public void testFailFastOnMisconfiguredOneToNProperty() {
new InvalidOneToNEntity();
}
@Test(expected = InvalidDataAccessApiUsageException.class)
@Transactional
public void testFailFastOnMisconfiguredReadOnlyOneToNProperty() {
new InvalidReadOnlyOneToNEntity();
}
}

View File

@@ -39,7 +39,8 @@ import static org.junit.Assert.assertEquals;
* @since 20.01.11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml",
"classpath:org/springframework/data/graph/neo4j/support/SubReferenceNodeTypeStrategyOverride-context.xml"})
public class SubReferenceNodeTypeStrategyTest {
protected final Log log = LogFactory.getLog(getClass());
@@ -48,8 +49,8 @@ public class SubReferenceNodeTypeStrategyTest {
GraphDatabaseContext graphDatabaseContext;
@Autowired
private FinderFactory finderFactory;
private NodeTypeStrategy nodeTypeStrategy;
@Autowired
private SubReferenceNodeTypeStrategy nodeTypeStrategy;
private Node thingNode;
private Thing thing;
@@ -61,7 +62,6 @@ public class SubReferenceNodeTypeStrategyTest {
@Before
public void setUp() {
nodeTypeStrategy = graphDatabaseContext.getNodeTypeStrategy();
thingNode = createThing();
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:datagraph="http://www.springframework.org/schema/data/graph"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/graph http://www.springframework.org/schema/data/graph/datagraph-1.0.xsd
">
<context:annotation-config/>
<datagraph:config storeDirectory="target/config-test"/>
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:test"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
</beans>

View File

@@ -86,28 +86,30 @@
<constructor-arg index="0" value="target/data/recommendation" />
</bean>
<bean id="graphDatabaseContext" class="org.springframework.data.graph.neo4j.support.GraphDatabaseContext">
<property name="graphDatabaseService" ref="graphDatabaseService"/>
<property name="relationshipEntityInstantiator">
<bean class="org.springframework.data.graph.neo4j.support.relationship.ConstructorBypassingGraphRelationshipInstantiator" />
</property>
<property name="graphEntityInstantiator">
<bean class="org.springframework.data.graph.neo4j.support.node.PartialNeo4jEntityInstantiator">
<constructor-arg>
<bean class="org.springframework.data.graph.neo4j.support.node.Neo4jConstructorGraphEntityInstantiator"/>
</constructor-arg>
<constructor-arg ref="entityManagerFactory"/>
</bean>
</property>
<property name="conversionService">
<bean class="org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
</property>
<property name="nodeTypeStrategy">
<bean class="org.springframework.data.graph.neo4j.support.SubReferenceNodeTypeStrategy">
<constructor-arg index="0" ref="graphDatabaseContext"/>
</bean>
</property>
</bean>
<bean id="graphDatabaseContext" class="org.springframework.data.graph.neo4j.support.GraphDatabaseContext">
<property name="graphDatabaseService" ref="graphDatabaseService"/>
<property name="relationshipEntityInstantiator">
<bean class="org.springframework.data.graph.neo4j.support.relationship.ConstructorBypassingGraphRelationshipInstantiator"/>
</property>
<property name="graphEntityInstantiator">
<bean class="org.springframework.data.graph.neo4j.support.node.PartialNeo4jEntityInstantiator">
<constructor-arg ref="graphEntityInstantiator"/>
<constructor-arg ref="entityManagerFactory"/>
</bean>
</property>
<property name="conversionService">
<bean class="org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
</property>
<property name="nodeTypeStrategy" ref="nodeTypeStrategy"/>
</bean>
<bean id="nodeTypeStrategy" class="org.springframework.data.graph.neo4j.support.NodeTypeStrategyFactoryBean">
<constructor-arg ref="graphDatabaseService"/>
<constructor-arg ref="graphEntityInstantiator"/>
</bean>
<bean id="graphEntityInstantiator"
class="org.springframework.data.graph.neo4j.support.node.Neo4jConstructorGraphEntityInstantiator"/>
<bean id="nodeEntityStateFactory" class="org.springframework.data.graph.neo4j.fieldaccess.NodeEntityStateFactory">
<property name="nodeDelegatingFieldAccessorFactory">

View File

@@ -100,8 +100,9 @@
<bean id="graphEntityInstantiator"
class="org.springframework.data.graph.neo4j.support.node.Neo4jConstructorGraphEntityInstantiator"/>
<bean id="nodeTypeStrategy" class="org.springframework.data.graph.neo4j.support.SubReferenceNodeTypeStrategy">
<constructor-arg index="0" ref="graphDatabaseContext"/>
<bean id="nodeTypeStrategy" class="org.springframework.data.graph.neo4j.support.NodeTypeStrategyFactoryBean">
<constructor-arg ref="graphDatabaseService" />
<constructor-arg ref="graphEntityInstantiator" />
</bean>
<bean id="nodeEntityStateFactory" class="org.springframework.data.graph.neo4j.fieldaccess.NodeEntityStateFactory">

View File

@@ -0,0 +1,22 @@
<?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="nodeTypeStrategy" class="org.springframework.data.graph.neo4j.support.SubReferenceNodeTypeStrategy">
<constructor-arg ref="graphDatabaseService"/>
<constructor-arg ref="graphEntityInstantiator" />
</bean>
</beans>

View File

@@ -7,8 +7,9 @@
annotation.
</para>
<section>
<title>Entities with @NodeEntity</title>
<para>The <code>@NodeEntity</code> annotation is used to declare a POJO entity to be backed by a node in the
<title>@NodeEntity: The basic building block</title>
<para>
The <code>@NodeEntity</code> annotation is used to declare a POJO entity to be backed by a node in the
graph store. Simple fields on the entity are mapped by default to properties of the node. Object
references to other NodeEntities (whether single or Collection) are mapped via relationships. If
the annotation parameter <code>useShortNames</code> is set to false, the properties and relationship
@@ -26,47 +27,20 @@ public class Movie {
}
]]></programlisting>
</section>
<section>
<title>RelationshipEntities with @RelationshipEntity</title>
<para>To access the rich data model of graph relationships, POJOs can also be annotated with
@RelationshipEntity. Relationship entities can't be instantiated directly but are rather accessed via
node entities, either by @RelatedToVia fields or by the <code>relateTo</code> or
<code>getRelationshipTo</code> methods.
Relationship entities may contain fields that are mapped to properties and two special fields that are
annotated with @StartNode and @EndNode which point to the start and end node entities respectively. These
fields are treated as read only fields.
</para>
<programlisting language="java"><![CDATA[
@RelationshipEntity
public class Role {
@StartNode
private Actor actor;
@EndNode
private Movie movie;
}
]]></programlisting>
</section>
<section>
<title>Fields with @GraphProperty</title>
<para>It is not necessary to annotate fields as they are persisted by default; all fields that contain primitive
values are persisted directly to the graph. All fields
convertible to String using the Spring conversion services will be stored as a string. Transient fields are
not persisted.
This annotation is mainly used for cross-store persistence.
</para>
</section>
<section>
<title>Fields with @RelatedTo pointing to other NodeEntities</title>
<title>@RelatedTo: Connecting NodeEntities</title>
<para>
Relationships to other NodeEntities are mapped to graph relationships. Those can either be single
relationships (1:1) or multiple relationships (1:n). In most cases single relationships to other
relationships (1:1) or multiple relationships (1:N). In most cases single relationships to other
node entities don't have to be annotated as Spring Data Graph can extract all necessary information
from the field using reflection. In the case of multiple relationships, the <code>elementClass</code>
parameter of @RelatedTo must be specified because of type erasure. The <code>direction</code>
(default OUTGOING) and <code>type</code> (inferred from field name) parameters of the annotation are
optional.
</para>
<para>Relationships to single node entities are created when setting the field and deleted when setting it to
<para>
Relationships to single node entities are created when setting the field and deleted when setting it to
null. For multi-relationships the field provides a managed collection (Set) that handles addition and
removal of node entities and reflects those in the graph relationships.
</para>
@@ -87,9 +61,33 @@ public class Actor {
}
]]></programlisting>
</section>
<section>
<title>Fields with @RelatedToVia pointing to RelationshipEntities</title>
<para>To provide easy programmatic access to the richer relationship entities of the data model a different
<title>@RelationshipEntity: Rich relationships</title>
<para>
To access the full data model of graph relationships, POJOs can also be annotated with
@RelationshipEntity. Relationship entities can't be instantiated directly but are rather accessed via
node entities, either by @RelatedToVia fields or by the <code>relateTo</code> or
<code>getRelationshipTo</code> methods.
Relationship entities may contain fields that are mapped to properties and two special fields that are
annotated with @StartNode and @EndNode which point to the start and end node entities respectively. These
fields are treated as read only fields.
</para>
<programlisting language="java"><![CDATA[
@RelationshipEntity
public class Role {
@StartNode
private Actor actor;
@EndNode
private Movie movie;
}
]]></programlisting>
</section>
<section>
<title>@RelatedToVia: Connecting NodeEntitites via RelationshipEntities</title>
<para>
To provide easy programmatic access to the richer relationship entities of the data model a different
annotation @RelatedToVia can be declared on fields of Iterables of the relationship entity type. These
Iterables then provide read only access to instances of the entity that backs the relationship of this
relationship type. Those instances are initialized with the properties of the relationship and the start
@@ -104,15 +102,16 @@ public class Actor {
]]></programlisting>
</section>
<section>
<title>@StartNode</title>
<title>@StartNode: Starting NodeEntity of RelationshipEntity</title>
<para>Annotation for the start node of a relationship entity, read only.</para>
</section>
<section>
<title>@EndNode</title>
<title>@EndNode: Ending NodeEntity of RelationshipEntity</title>
<para>Annotation for the end node of a relationship entity, read only.</para>
</section>
<section>
<title>@Indexed</title>
<title>@Indexed: Making entities searchable by field value</title>
<para>The @Indexed annotation can be declared on fields that are intended to be indexed by the Neo4j
IndexManager, triggered by value modification.
The resulting index can be used to later retrieve nodes or relationships that contain a certain property
@@ -128,6 +127,7 @@ public class Actor {
it defaults to the one configured with Neo4j ("node" and "relationship").
</para>
</section>
<section>
<title>@GraphTraversal</title>
<para>The @GraphTraversal annotation leverages the delegation infrastructure used by the Spring Data Graph
@@ -138,4 +138,15 @@ public class Actor {
<code>elementClass</code> attribute.
</para>
</section>
<section>
<title>@GraphProperty: Cross-store persisted fields</title>
<para>It is not necessary to annotate fields as they are persisted by default; all fields that contain primitive
values are persisted directly to the graph. All fields
convertible to String using the Spring conversion services will be stored as a string. Transient fields are
not persisted.
This annotation is mainly used for cross-store persistence.
</para>
</section>
</section>

View File

@@ -4,20 +4,45 @@
<title>Reified types for entities</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. That's
why the hierarchy up to <code>java.lang.Object</code> of all these classes will be persisted in the graph.
Implementations of NodeTypeStrategy take care of persisting this information on entity instance
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.
like findAll, count, etc.
</para>
<para>
The current implementation uses nodes to represent the Java type hierarchy which are connected via SUBCLASS_OF
relationships to their superclass nodes and via INSTANCE_OF relationships to the concrete node entity
instance node.
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>
An alternative approach could use indexing operations to perform the same functionality. Or one could skip the
NodeTypeStrategy altogether if no strict checks on type conformity are needed, which would allow for a much
more flexible data model.
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

@@ -9,7 +9,7 @@
</para><para>
It's time to put this to a test. How can we be assured that a field is persisted to the graph store? There seemed to be two possibilities. First was to get a
GraphDatabaseContext injected and use its getById() method. The other one was a Finder approach. But let's try to keep things simple.
How can we persist an entity and how to get its id? No idea, so time to hit the documentation again, revealing that there are a bunch of methods introduced to the
How can we persist an entity and how to get its id? Looking at the documentation revealed that there are a bunch of methods introduced to the
entities by the aspects. That's not obvious, but we found the two that would help here - entity.persist() and entity.getNodeId().
</para><para>
So our test looked like this.

View File

@@ -3,7 +3,7 @@
<chapter id="tutorial_domain">
<title>Setting the Stage - Movies Domain</title>
<para>
The domain model was the next thing we planned to work on. We wanted to sketch it out first before diving into library details. We also looked at the datamodel of core themoviedb data to
The domain model was the next thing we planned to work on. We wanted to sketch it out first before diving into library details. We also looked at the datamodel of core themoviedb data to
confirm that it matched our expectations.
<!--
@@ -35,11 +35,13 @@ class Actor {
Set<Movie> filmography;
Role playedIn(Movie movie, String role);
}
class Role {
Movie movie;
Actor actor;
String role;
}
class User {
String login;
String name;
@@ -49,6 +51,7 @@ class User {
Rating rate(Movie movie, int stars, String comment);
void befriend(User user);
}
class Rating {
User user;
Movie movie;

View File

@@ -3,14 +3,14 @@
<chapter id="tutorial_neo4j">
<title>Graphs ahead - Learning Neo4j</title>
<para>
Then came the unknown - how to put these domain objects into the graph. First we read up about graph databases, especially <ulink url="http://neo4j.org">Neo4j</ulink>.
The Neo4j datamodel consists of nodes and relationships all of which can have properties. Relationships are first class citizens in Neo4j, meaning we can link together nodes into semantically rich networks - we really liked that.
Then we found we could <ulink url="http://wiki.neo4j.org/content/Index_Framework">index nodes and relationships</ulink> by {name, value} pairs to quickly get hold of them as starting points for further processing. We also found we could imperatively traverse of relationships using the core API, and in a declarative way using a query-like <ulink url="http://wiki.neo4j.org/content/Traversal_Framework">Traversal Description</ulink>.
Now came the unknown - how to put these domain objects into the graph. First we read up about graph databases, especially <ulink url="http://neo4j.org">Neo4j</ulink>.
The Neo4j datamodel consists of nodes and relationships, both of which can have properties. Relationships are first class citizens in Neo4j, meaning we can link together nodes into semantically rich networks - we really liked that.
Then we found we could <ulink url="http://docs.neo4j.org/chunked/snapshot/indexing.html">index nodes and relationships</ulink> by {name, value} pairs to quickly get hold of them as starting points for further processing. We also found we could imperatively traverse of relationships using the core API, and in a declarative way using a query-like <ulink url="http://wiki.neo4j.org/content/Traversal_Framework">Traversal Description</ulink>.
</para><para>
We also learned that Neo4j was fully transactional and completely upholds ACID guarantees for out data. This is unusual for NoSQL databases, but easier for us to get
my head around than non-transactional eventual consistency. It also makes us feel safe, though it also means that we had to manage transactions. Keep that in mind.
our head around than non-transactional eventual consistency. It also makes us feel safe, though it also means that we had to manage transactions. Keep that in mind.
</para><para>
Initially we used the core Neo4j API to get a feeling for that. And also to see, how (probably) the domain might look when it's saved in the graph store. After adding the maven
Initially we used the core Neo4j API to get a feeling for that. And also to see, how (probably) the domain might look when it's saved in the graph store. After adding the Maven
dependency, it was ready to go.
</para><para>
<programlisting language="xnk" ><![CDATA[

View File

@@ -7,7 +7,7 @@
account had to be secured as well.
</para><para>
We used Spring Security for that, writing a simple UserDetailsService that used a repository for looking up the users and validating their credentials. The config is located
in a separate applicationContext-security.xml. But first, as always, maven and web.xml setup.
in a separate applicationContext-security.xml. But first, as always, Maven and web.xml setup.
</para>
<para>
<example>

View File

@@ -7,19 +7,19 @@
that should be enough.
</para><para>
What database would fit both the complex network of cineasts, movies, actors, roles, ratings and friends? And also be able to support the
recommendation algorithms that I thought of? I had no idea.
recommendation algorithms that we had in mind? We had no idea.
</para><para>
But, wait, there is the new Spring Data project, started in 2010, which brings
the convenience of the Spring programming model to NoSQL databases. That should fit our experience and help us to get started. We looked
at the list of projects supporting the different NoSQL databases. Only one mentioned the kind of social network we were thinking of -
Spring Data Graph for Neo4j, a graph database. Neo4j's pitch of "value in relationships" and the accompanying docs looked like what we needed.
So we decided to give it a try.
We decided to give it a try.
</para>
<section>
<title>Preparations - Required Setup</title>
<para>
To setup the project we created a public github account and began setting up the infrastructure for a spring web project using maven as build
system. So we added the dependencies for the springframework libraries, put the web.xml for the DispatcherServlet and the applicationContext.xml
To setup the project we created a public github account and began setting up the infrastructure for a spring web project using Maven as build
system. So we added the dependencies for the Spring Framework libraries, put the web.xml for the DispatcherServlet and the applicationContext.xml
in the webapp directory.
</para><para>
@@ -54,7 +54,6 @@
<webAppConfig>
<contextPath>/</contextPath>
</webAppConfig>
<!--scanIntervalSeconds>1</scanIntervalSeconds-->
</configuration>
</plugin>
</plugins></build>
@@ -84,8 +83,8 @@
</para><para>
With this setup we were ready for the first spike: creating a simple MovieController showing a static view. Check. Next was the setup for Spring Data Graph.
We looked at the README at github and then checked it with the manual. Quite a lot of maven setup for aspectj but otherwise not so much to add.
Time to add a few lines to our spring configuration.
We looked at the README at github and then checked it with the manual. Quite a lot of Maven setup for AspectJ but otherwise not so much to add.
Time to add a few lines to our Spring configuration.
</para><para>
@@ -129,7 +128,7 @@
</example>
</para><para>
We spun up jetty to see if there were any obvious issues with the config. Check.
We spun up Jetty to see if there were any obvious issues with the config. It all seemed to work just fine. Check.
</para>
</section>
</chapter>

View File

@@ -1,16 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
x<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_about-spring-data">
<title>Conjuring Magic - Spring Data Graph</title>
<para>
But that was the pure graph database. Using this in our domain would pollute my classes with lots of graph
That was the pure graph database. Using this in our domain would pollute our classes with lots of graph
database details. We don't want that. Spring Data Graph
promised to do the heavy lifting for us. So we checked that next. Obviously it heavily depended on aspectj magic.
So there would be certain behavior that was
just observable without being visible in our code, but we were going to give it a try.
promised to do the heavy lifting for us. So we checked that next.
Spring Data Graph depends heavily on AspectJ magic. Some parts of our classes would behave differently,
but it would not be visible in our code. We were going to give it a try.
</para>
<para>
First step was lots of maven configuration.
First step was lots of Maven configuration.
<programlisting language="xml"><![CDATA[
<properties>
@@ -71,7 +72,7 @@
</plugin> </plugins> </build>
]]></programlisting>
</para><para>
The spring configuration was much easier, thanks to a provided namespace.
The Spring configuration was much easier, thanks to a provided namespace.
</para><para>
<programlisting language="xml"><![CDATA[
<beans xmlns="http://www.springframework.org/schema/beans" ...