Merge branch 'master' of github.com:SpringSource/spring-data-graph
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
* @RelatedTo([type="friends"], elementClass=Person.class)
|
||||
* @RelatedTo(elementClass=Person.class)
|
||||
* Collection<Person> friends;
|
||||
* @RelatedTo([type="spouse"], [elementClass=Person.class])
|
||||
* @RelatedTo(type="partner")
|
||||
* 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
|
||||
|
||||
@@ -52,5 +52,5 @@ public @interface RelatedToVia {
|
||||
/**
|
||||
* @return target relationship entity class
|
||||
*/
|
||||
Class<? extends RelationshipBacked> elementClass() default RelationshipBacked.class;
|
||||
Class<? extends RelationshipBacked> elementClass();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" ...
|
||||
|
||||
Reference in New Issue
Block a user