added support for load-store pattern, cascading copying of properties

This commit is contained in:
Michael Hunger
2011-10-09 20:12:27 +02:00
parent 6f7d141c99
commit b7a9bbb0ef
42 changed files with 993 additions and 356 deletions

View File

@@ -1,4 +1,6 @@
* eclipse / STS setup with m2e, AspectJConfigurator and Weaving enabled etc. https://jira.springsource.org/browse/DATAGRAPH-104
* fetch-strategies / behaviour
* cascading persist/save
* cascading persist/save
* @Indexed(level)
*

View File

@@ -36,14 +36,14 @@
</issueManagement>
<mailingLists>
<mailingList>
<name>Spring Batch Forum</name>
<name>Spring Data Forum</name>
<post>http://forum.springsource.org/forumdisplay.php?f=80</post>
<archive>http://forum.springsource.org/forumdisplay.php?f=80</archive>
</mailingList>
</mailingLists>
<ciManagement>
<system>Bamboo</system>
<url>https://build.springsource.org/browse/SPRINGDATA</url>
<url>https://build.springsource.org/browse/SPRINGDATA-DATAGRAPH</url>
</ciManagement>
<developers>

View File

@@ -16,22 +16,26 @@
package org.springframework.data.neo4j.aspects.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.aspects.support.node.Neo4jNodeBacking;
import org.springframework.data.neo4j.aspects.support.relationship.Neo4jRelationshipBacking;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.support.node.NodeEntityStateFactory;
import javax.annotation.PostConstruct;
/**
* @author mh
* @since 30.09.11
*/
@Configuration
public class Neo4jAspectConfiguration extends Neo4jConfiguration
{
@Bean
public Neo4jRelationshipBacking neo4jRelationshipBacking() throws Exception {
Neo4jRelationshipBacking aspect = Neo4jRelationshipBacking.aspectOf();
aspect.setGraphDatabaseContext(graphDatabaseContext());
aspect.setRelationshipEntityStateFactory(relationshipEntityStateFactory());
aspect.setRelationshipEntityStateFactory(relationshipEntityStateFactory());
return aspect;
}
@@ -43,4 +47,10 @@ aspect.setRelationshipEntityStateFactory(relationshipEntityStateFactory());
aspect.setNodeEntityStateFactory(entityStateFactory);
return aspect;
}
@Override
@PostConstruct
public void wireEntityStateFactories() throws Exception {
super.wireEntityStateFactories();
}
}

View File

@@ -125,7 +125,7 @@ public privileged aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMix
log.error("entityStateFactory not set, not creating accessors for " + entity.getClass());
} else {
if (entity.entityState != null) return;
entity.entityState = entityStateFactory.getEntityState(entity);
entity.entityState = entityStateFactory.getEntityState(entity, true);
}
}
@@ -140,7 +140,7 @@ public privileged aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMix
public void NodeBacked.setPersistentState(Node n) {
if (this.entityState == null) {
this.entityState = Neo4jNodeBacking.aspectOf().entityStateFactory.getEntityState(this);
this.entityState = Neo4jNodeBacking.aspectOf().entityStateFactory.getEntityState(this, false);
}
this.entityState.setPersistentState(n);
}

View File

@@ -75,7 +75,7 @@ public aspect Neo4jRelationshipBacking {
public void RelationshipBacked.setPersistentState(Relationship r) {
if (this.entityState == null) {
this.entityState = Neo4jRelationshipBacking.aspectOf().entityStateFactory.getEntityState(this);
this.entityState = Neo4jRelationshipBacking.aspectOf().entityStateFactory.getEntityState(this, true);
}
this.entityState.setPersistentState(r);
}

View File

@@ -93,6 +93,7 @@
<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
</property>
<property name="mappingContext" ref="mappingContext"/>
<property name="nodeEntityConverter" ref="nodeEntityConverter"/>
</bean>
@@ -131,6 +132,39 @@
<property name="nodeEntityStateFactory" ref="nodeEntityStateFactory"/>
</bean-->
<bean id="nodeEntityConverter" class="org.springframework.data.neo4j.mapping.Neo4jEntityConverterImpl">
<constructor-arg index="0" ref="mappingContext"/>
<constructor-arg index="1" ref="conversionService"/>
<constructor-arg index="2" ref="graphEntityInstantiator"/>
<constructor-arg index="3" ref="entityStateHandler"/>
<constructor-arg index="4" ref="nodeTypeMapper"/>
<constructor-arg index="5" ref="nodeSourceStateTransmitter"/>
<constructor-arg index="6" ref="entityFetchHandler"/>
</bean>
<bean id="entityFetchHandler" class="org.springframework.data.neo4j.mapping.Neo4jEntityFetchHandler">
<constructor-arg index="0" ref="entityStateHandler"/>
<constructor-arg index="1" ref="conversionService"/>
<constructor-arg index="2" ref="nodeSourceStateTransmitter"/>
<constructor-arg index="3" ref="relationshipSourceStateTransmitter"/>
</bean>
<bean id="nodeSourceStateTransmitter" class="org.springframework.data.neo4j.mapping.SourceStateTransmitter">
<constructor-arg index="0" ref="nodeEntityStateFactory"/>
</bean>
<bean id="relationshipSourceStateTransmitter" class="org.springframework.data.neo4j.mapping.SourceStateTransmitter">
<constructor-arg index="0" ref="relationshipEntityStateFactory"/>
</bean>
<bean id="nodeTypeMapper" class="org.springframework.data.convert.DefaultTypeMapper">
<constructor-arg index="0">
<bean class="org.springframework.data.neo4j.mapping.TRSTypeAliasAccessor">
<constructor-arg index="0" ref="nodeTypeRepresentationStrategy"/>
</bean>
</constructor-arg>
</bean>
<bean id="mappingContext" class="org.springframework.data.neo4j.mapping.Neo4jMappingContext"/>
<bean id="relationshipEntityStateFactory" class="org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory">

View File

@@ -78,13 +78,13 @@ public class CrossStoreNeo4jConfiguration extends Neo4jAspectConfiguration {
}
@Bean
public NodeEntityStateFactory nodeEntityStateFactory() throws Exception {
public CrossStoreNodeEntityStateFactory nodeEntityStateFactory() throws Exception {
return new CrossStoreNodeEntityStateFactory();
}
CrossStoreNodeEntityStateFactory entityStateFactory = new CrossStoreNodeEntityStateFactory();
entityStateFactory.setGraphDatabaseContext(graphDatabaseContext());
entityStateFactory.setEntityManagerFactory(entityManagerFactory);
entityStateFactory.setMappingContext(mappingContext());
entityStateFactory.setNodeDelegatingFieldAccessorFactory(nodeDelegatingFieldAccessorFactory());
return entityStateFactory;
@Override
public void wireEntityStateFactories() throws Exception {
super.wireEntityStateFactories();
nodeEntityStateFactory().setEntityManagerFactory(entityManagerFactory);
}
}

View File

@@ -36,12 +36,15 @@ public class CrossStoreNodeEntityStateFactory extends NodeEntityStateFactory {
private CrossStoreNodeEntityState.CrossStoreNodeDelegatingFieldAccessorFactory delegatingFieldAccessorFactory;
private EntityManagerFactory entityManagerFactory;
public EntityState<Node> getEntityState(final Object entity) {
public EntityState<Node> getEntityState(final Object entity, boolean detachable) {
final Class<?> entityType = entity.getClass();
final NodeEntity graphEntityAnnotation = entityType.getAnnotation(NodeEntity.class); // todo cache ??
final Neo4jPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);
if (graphEntityAnnotation.partial()) {
final CrossStoreNodeEntityState<NodeBacked> partialNodeEntityState = new CrossStoreNodeEntityState<NodeBacked>(null, (NodeBacked)entity, (Class<? extends NodeBacked>) entityType, graphDatabaseContext, getPersistenceUnitUtils(), delegatingFieldAccessorFactory, persistentEntity);
if (isPartial(entityType)) {
final Neo4jPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);
@SuppressWarnings("unchecked") final CrossStoreNodeEntityState<NodeBacked> partialNodeEntityState =
new CrossStoreNodeEntityState<NodeBacked>(null, (NodeBacked)entity, (Class<? extends NodeBacked>) entityType,
graphDatabaseContext, getPersistenceUnitUtils(), delegatingFieldAccessorFactory,
persistentEntity);
if (!detachable) return partialNodeEntityState;
return new DetachedEntityState<Node>(partialNodeEntityState, graphDatabaseContext) {
@Override
protected boolean isDetached() {
@@ -49,12 +52,15 @@ public class CrossStoreNodeEntityStateFactory extends NodeEntityStateFactory {
}
};
} else {
NodeEntityState nodeEntityState = new NodeEntityState(null, entity, entityType, graphDatabaseContext, nodeDelegatingFieldAccessorFactory, (Neo4jPersistentEntity) persistentEntity);
// alternative was return new NestedTransactionEntityState<NodeBacked, Node>(nodeEntityState,graphDatabaseContext);
return new DetachedEntityState<Node>(nodeEntityState, graphDatabaseContext);
return super.getEntityState(entity,detachable);
}
}
private boolean isPartial(Class<?> entityType) {
final NodeEntity graphEntityAnnotation = entityType.getAnnotation(NodeEntity.class); // todo cache ??
return graphEntityAnnotation.partial();
}
private PersistenceUnitUtil getPersistenceUnitUtils() {
if (entityManagerFactory == null|| !entityManagerFactory.isOpen()) return null;
return entityManagerFactory.getPersistenceUnitUtil();

View File

@@ -22,6 +22,7 @@ import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.neo4j.rest.graphdb.RequestResult;
import org.springframework.data.neo4j.aspects.Person;
@@ -38,6 +39,7 @@ import static org.springframework.data.neo4j.aspects.Person.persistedPerson;
* @author mh
* @since 14.04.11
*/
@Ignore("TODO")
public class ServerPluginTest extends RestTestBase {
private Person person;

View File

@@ -10,6 +10,6 @@
<neo4j:config graphDatabaseService="graphDatabaseService"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.aspects"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.rest"/>
</beans>

View File

@@ -53,6 +53,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -103,6 +104,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
@@ -132,7 +134,7 @@
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>server-api</artifactId>
<optional>true</optional>
<optional>true</optional>
</dependency>
<dependency>

View File

@@ -16,6 +16,8 @@
package org.springframework.data.neo4j.annotation;
import org.springframework.data.annotation.Reference;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -30,5 +32,6 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Reference
public @interface EndNode {
}

View File

@@ -16,6 +16,8 @@
package org.springframework.data.neo4j.annotation;
import org.springframework.data.annotation.Reference;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -30,5 +32,6 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Reference
public @interface StartNode {
}

View File

@@ -28,13 +28,15 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.convert.DefaultTypeMapper;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
import org.springframework.data.neo4j.fieldaccess.DelegatingFieldAccessorFactory;
import org.springframework.data.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean;
import org.springframework.data.neo4j.fieldaccess.NodeDelegatingFieldAccessorFactory;
import org.springframework.data.neo4j.fieldaccess.RelationshipDelegatingFieldAccessorFactory;
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.mapping.Neo4jNodeConverterImpl;
import org.springframework.data.neo4j.mapping.*;
import org.springframework.data.neo4j.repository.DirectGraphRepositoryFactory;
import org.springframework.data.neo4j.support.DelegatingGraphDatabase;
import org.springframework.data.neo4j.support.EntityInstantiator;
@@ -53,6 +55,8 @@ import org.springframework.transaction.jta.UserTransactionAdapter;
import javax.annotation.PostConstruct;
import javax.validation.Validator;
import static java.util.Arrays.asList;
/**
* Abstract base class for code based configuration of Spring managed Neo4j infrastructure.
* <p>Subclasses are required to provide an implementation of graphDbService ....
@@ -78,20 +82,15 @@ public abstract class Neo4jConfiguration {
@Bean
public GraphDatabaseContext graphDatabaseContext() throws Exception {
EntityInstantiator<Relationship> relationshipEntityInstantiator = graphRelationshipInstantiator();
EntityInstantiator<Node> graphEntityInstantiator = graphEntityInstantiator();
TypeRepresentationStrategyFactory typeRepresentationStrategyFactory =
new TypeRepresentationStrategyFactory(graphDatabaseService, graphEntityInstantiator, relationshipEntityInstantiator);
GraphDatabaseContext gdc = new GraphDatabaseContext();
gdc.setGraphDatabaseService(getGraphDatabaseService());
gdc.setConversionService(conversionService());
gdc.setMappingContext(mappingContext());
gdc.setConverter(neo4jConverter());
gdc.setNodeEntityConverter(nodeEntityConverter());
gdc.setEntityStateHandler(entityStateHandler());
gdc.setNodeTypeRepresentationStrategy(typeRepresentationStrategyFactory.getNodeTypeRepresentationStrategy());
gdc.setRelationshipTypeRepresentationStrategy(typeRepresentationStrategyFactory.getRelationshipTypeRepresentationStrategy());
gdc.setNodeTypeRepresentationStrategy(nodeTypeRepresentationStrategy());
gdc.setRelationshipTypeRepresentationStrategy(relationshipTypeRepresentationStrategy());
if (validator!=null) {
gdc.setValidator(validator);
}
@@ -99,13 +98,45 @@ public abstract class Neo4jConfiguration {
}
@Bean
public EntityStateHandler entityStateHandler() {
return new EntityStateHandler(mappingContext(),graphDatabaseService);
public TypeRepresentationStrategy<Relationship> relationshipTypeRepresentationStrategy() throws Exception {
return typeRepresentationStrategyFactory().getRelationshipTypeRepresentationStrategy();
}
@Bean
public Neo4jNodeConverterImpl neo4jConverter() throws Exception {
return new Neo4jNodeConverterImpl();
public TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy() throws Exception {
return typeRepresentationStrategyFactory().getNodeTypeRepresentationStrategy();
}
@Bean
public TypeRepresentationStrategyFactory typeRepresentationStrategyFactory() throws Exception {
return new TypeRepresentationStrategyFactory(graphDatabaseService, graphEntityInstantiator(), graphRelationshipInstantiator());
}
@Bean
public EntityStateHandler entityStateHandler() {
return new EntityStateHandler(mappingContext(),graphDatabaseService);
}
@Bean
public Neo4jEntityConverter<Object,Node> nodeEntityConverter() throws Exception {
return new Neo4jEntityConverterImpl<Object, Node>(mappingContext(), conversionService(), graphEntityInstantiator() ,entityStateHandler(),typeMapper(), nodeStateTransmitter(), entityFetchHandler());
}
private TypeMapper<Node> typeMapper() throws Exception {
return new DefaultTypeMapper<Node>(new TRSTypeAliasAccessor<Node>(nodeTypeRepresentationStrategy()),asList(new ClassValueTypeInformationMapper()));
}
@Bean
public Neo4jEntityFetchHandler entityFetchHandler() throws Exception {
final SourceStateTransmitter<Node> nodeSourceStateTransmitter = nodeStateTransmitter();
final SourceStateTransmitter<Relationship> relationshipSourceStateTransmitter = new SourceStateTransmitter<Relationship>(relationshipEntityStateFactory());
return new Neo4jEntityFetchHandler(entityStateHandler(), conversionService(), relationshipSourceStateTransmitter, nodeSourceStateTransmitter);
}
@Bean
public SourceStateTransmitter<Node> nodeStateTransmitter() throws Exception {
return new SourceStateTransmitter<Node>(nodeEntityStateFactory());
}
@Bean
@@ -128,31 +159,34 @@ public abstract class Neo4jConfiguration {
return new DirectGraphRepositoryFactory(graphDatabaseContext());
}
@Bean
public RelationshipEntityStateFactory relationshipEntityStateFactory() throws Exception {
RelationshipEntityStateFactory entityStateFactory = new RelationshipEntityStateFactory();
entityStateFactory.setGraphDatabaseContext(graphDatabaseContext());
entityStateFactory.setMappingContext(mappingContext());
entityStateFactory.setRelationshipDelegatingFieldAccessorFactory(relationshipDelegatingFieldAccessorFactory());
return entityStateFactory;
}
@Bean
public Neo4jMappingContext mappingContext() {
return new Neo4jMappingContext();
}
@PostConstruct
public void setupContext() throws Exception {
neo4jConverter().setNodeEntityStateFactory(nodeEntityStateFactory());
@Bean
public RelationshipEntityStateFactory relationshipEntityStateFactory() throws Exception {
return new RelationshipEntityStateFactory();
}
@Bean
public NodeEntityStateFactory nodeEntityStateFactory() throws Exception {
NodeEntityStateFactory entityStateFactory = new NodeEntityStateFactory();
entityStateFactory.setGraphDatabaseContext(graphDatabaseContext());
entityStateFactory.setMappingContext(mappingContext());
entityStateFactory.setNodeDelegatingFieldAccessorFactory(nodeDelegatingFieldAccessorFactory());
return entityStateFactory;
return new NodeEntityStateFactory();
}
@PostConstruct
public void wireEntityStateFactories() throws Exception {
final NodeEntityStateFactory nodeEntityStateFactory = nodeEntityStateFactory();
nodeEntityStateFactory.setGraphDatabaseContext(graphDatabaseContext());
nodeEntityStateFactory.setMappingContext(mappingContext());
nodeEntityStateFactory.setNodeDelegatingFieldAccessorFactory(nodeDelegatingFieldAccessorFactory());
final RelationshipEntityStateFactory relationshipEntityStateFactory = relationshipEntityStateFactory();
relationshipEntityStateFactory.setGraphDatabaseContext(graphDatabaseContext());
relationshipEntityStateFactory.setMappingContext(mappingContext());
relationshipEntityStateFactory.setRelationshipDelegatingFieldAccessorFactory(relationshipDelegatingFieldAccessorFactory());
}
@Bean

View File

@@ -31,16 +31,19 @@ public class ConvertingNodePropertyFieldAccessorFactory implements FieldAccessor
private final GraphDatabaseContext graphDatabaseContext;
private ConversionService conversionService;
public ConvertingNodePropertyFieldAccessorFactory(GraphDatabaseContext graphDatabaseContext) {
this.graphDatabaseContext = graphDatabaseContext;
this.conversionService = graphDatabaseContext.getConversionService();
}
private ConversionService getConversionService() {
return graphDatabaseContext.getConversionService();
}
@Override
public boolean accept(final Neo4jPersistentProperty property) {
final ConversionService conversionService = getConversionService();
return property.isSerializableField(conversionService) && property.isDeserializableField(conversionService);
}

View File

@@ -41,6 +41,7 @@ public class RelationshipDelegatingFieldAccessorFactory extends DelegatingFieldA
protected Collection<? extends FieldAccessorFactory> createAccessorFactories() {
return Arrays.<FieldAccessorFactory>asList(
new TransientFieldAccessorFactory(),
new IdFieldAccessorFactory(graphDatabaseContext),
new RelationshipNodeFieldAccessorFactory(graphDatabaseContext),
new PropertyFieldAccessorFactory(graphDatabaseContext),
new ConvertingNodePropertyFieldAccessorFactory(graphDatabaseContext),

View File

@@ -0,0 +1,40 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
import org.springframework.data.convert.TypeInformationMapper;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
/**
* @author mh
* @since 09.10.11
*/
public class ClassValueTypeInformationMapper implements TypeInformationMapper {
@Override
public TypeInformation<?> resolveTypeFrom(Object alias) {
if (!(alias instanceof Class)) {
return null;
}
return ClassTypeInformation.from((Class<?>) alias);
}
@Override
public Object createAliasFor(TypeInformation<?> type) {
return type.getType();
}
}

View File

@@ -221,4 +221,15 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
public Neo4jPersistentEntity<?> getOwner() {
return (Neo4jPersistentEntity<?>)super.getOwner();
}
@Override
public boolean isEntity() {
return super.isEntity() && (isRelationshipEntity() || isNodeEntity());
}
private boolean isRelationshipEntity() {
return getType().isAnnotationPresent(RelationshipEntity.class);
}
private boolean isNodeEntity() {
return getType().isAnnotationPresent(NodeEntity.class);
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.convert.EntityWriter;
/**
* @author mh
* @since 27.09.11
*/
public interface Neo4jEntityConverter<T, S extends PropertyContainer> extends EntityConverter<Neo4jPersistentEntity<?>, Neo4jPersistentProperty, T, S>, EntityWriter<T,S>,
EntityReader<T, S> {
}

View File

@@ -0,0 +1,157 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.PropertyContainer;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.support.EntityInstantiator;
import org.springframework.data.neo4j.support.EntityStateHandler;
import org.springframework.data.neo4j.support.ManagedEntity;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import java.lang.reflect.InvocationTargetException;
/**
* @author mh
* @since 07.10.11
*/
public class Neo4jEntityConverterImpl<T,S extends PropertyContainer> implements Neo4jEntityConverter<T,S> {
private final Neo4jMappingContext mappingContext;
private final ConversionService conversionService;
private final EntityInstantiator<S> entityInstantiator;
private final EntityStateHandler entityStateHandler;
private final TypeMapper<S> typeMapper;
private final SourceStateTransmitter<S> sourceStateTransmitter;
private final Neo4jEntityFetchHandler entityFetchHandler;
public Neo4jEntityConverterImpl(Neo4jMappingContext mappingContext, ConversionService conversionService, EntityInstantiator<S> entityInstantiator,
EntityStateHandler entityStateHandler, TypeMapper<S> typeMapper,
SourceStateTransmitter<S> sourceStateTransmitter, Neo4jEntityFetchHandler entityFetchHandler) {
this.mappingContext = mappingContext;
this.conversionService = conversionService;
this.entityInstantiator = entityInstantiator;
this.entityStateHandler = entityStateHandler;
this.typeMapper = typeMapper;
this.sourceStateTransmitter = sourceStateTransmitter;
this.entityFetchHandler = entityFetchHandler;
}
@Override
public MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> getMappingContext() {
return mappingContext;
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
@Override
public <R extends T> R read(Class<R> requestedType, S source) {
// 1) source -> type alias
// 2) type alias -> type
// 3) check for subtype matching / enforcement
final TypeInformation<R> requestedTypeInformation = ClassTypeInformation.from(requestedType);
final TypeInformation<? extends R> targetType = typeMapper.readType(source, requestedTypeInformation);
// retrieve meta-information about the type
@SuppressWarnings("unchecked") final Neo4jPersistentEntityImpl<R> persistentEntity = (Neo4jPersistentEntityImpl<R>) mappingContext.getPersistentEntity(targetType);
// 4) create object instance
final R createdEntity = entityInstantiator.createEntityFromState(source, targetType.getType());
// 5) connect state
entityStateHandler.setPersistentState(createdEntity,source);
if (!persistentEntity.isManaged()) {
// 5a) depending on mode -> copy data
final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper = BeanWrapper.create(createdEntity, conversionService);
sourceStateTransmitter.copyPropertiesFrom(wrapper, source, persistentEntity);
// 6) handle cascading fetches
cascadeFetch(persistentEntity, wrapper);
}
return createdEntity;
}
private <R extends T> void cascadeFetch(Neo4jPersistentEntityImpl<R> persistentEntity, final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper) {
persistentEntity.doWithAssociations(new AssociationHandler<Neo4jPersistentProperty>() {
@Override
public void doWithAssociation(Association<Neo4jPersistentProperty> association) {
final Neo4jPersistentProperty property = association.getInverse();
if (property.isRelationship()) {
final Object value = getProperty(wrapper, property);
@SuppressWarnings("unchecked") final Neo4jPersistentEntityImpl<Object> persistentEntity =
(Neo4jPersistentEntityImpl<Object>) mappingContext.getPersistentEntity(property.getTypeInformation().getActualType());
final Object fetchedValue = entityFetchHandler.fetch(value, persistentEntity, property);
// replace fetched one-time iterables and similiar managed values
sourceStateTransmitter.setProperty(wrapper, property, fetchedValue);
}
}
});
}
private <R> Object getProperty(BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper, Neo4jPersistentProperty property) {
try {
return wrapper.getProperty(property);
} catch (IllegalAccessException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e);
} catch (InvocationTargetException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e.getTargetException());
}
}
@Override
public void write(T source, S sink) {
final Class<?> sourceType = source.getClass();
@SuppressWarnings("unchecked") final Neo4jPersistentEntityImpl<T> persistentEntity = (Neo4jPersistentEntityImpl<T>) mappingContext.getPersistentEntity(sourceType);
if (persistentEntity.isManaged()) { // todo check if typerepreentationstragegy is called ??
((ManagedEntity)source).persist();
return;
}
final BeanWrapper<Neo4jPersistentEntity<T>, T> wrapper = BeanWrapper.create(source, conversionService);
if (sink == null) {
sink = entityStateHandler.useOrCreateState(source,sink); // todo handling of changed state
entityStateHandler.setPersistentState(source, sink);
typeMapper.writeType(sourceType, sink);
}
sourceStateTransmitter.copyPropertiesTo(wrapper, sink, persistentEntity);
}
/*
private Node useGetOrCreateNode(S node, Neo4jPersistentEntity<?> persistentEntity, BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper) {
if (node != null) return node;
final Neo4jPersistentProperty idProperty = persistentEntity.getIdProperty();
final Long id = getProperty(wrapper, idProperty, Long.class, true);
if (id == null) {
final Node newNode = getGraphDatabaseContext().createNode();
setProperty(wrapper, idProperty, newNode.getId());
return newNode;
}
try {
return getGraphDatabaseContext().getNodeById(id);
} catch (NotFoundException nfe) {
throw new MappingException("Could not find node with id " + id);
}
}
*/
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.neo4j.annotation.Fetch;
import org.springframework.data.neo4j.support.EntityStateHandler;
import java.util.ArrayList;
import java.util.List;
/**
* @author mh
* @since 08.10.11
*/
public class Neo4jEntityFetchHandler {
private final SourceStateTransmitter<Node> nodeStateTransmitter;
private final SourceStateTransmitter<Relationship> relationshipStateTransmitter;
private final EntityStateHandler entityStateHandler;
private final ConversionService conversionService;
public Neo4jEntityFetchHandler(EntityStateHandler entityStateHandler, ConversionService conversionService, SourceStateTransmitter<Relationship> relationshipStateTransmitter, SourceStateTransmitter<Node> nodeStateTransmitter) {
this.conversionService = conversionService;
this.entityStateHandler = entityStateHandler;
this.relationshipStateTransmitter = relationshipStateTransmitter;
this.nodeStateTransmitter = nodeStateTransmitter;
}
// todo actually cascade !!
public Object fetch(final Object value, Neo4jPersistentEntity<Object> persistentEntity, Neo4jPersistentProperty property) {
if (value == null || !property.isAnnotationPresent(Fetch.class)) return value;
if (property.getTypeInformation().isCollectionLike()) {
List<Object> replacement = new ArrayList<Object>();
for (Object inner : ((Iterable) value)) {
final BeanWrapper<Neo4jPersistentEntity<Object>, Object> innerWrapper = BeanWrapper.create(inner, conversionService);
final PropertyContainer state = entityStateHandler.getPersistentState(inner);
fetchValue(innerWrapper, state, persistentEntity);
replacement.add(inner);
//sourceStateTransmitter.copyPropertiesFrom(innerWrapper, entityStateHandler.<S>getPersistentState(inner), persistentEntity);
}
return replacement;
} else {
final BeanWrapper<Neo4jPersistentEntity<Object>, Object> innerWrapper = BeanWrapper.create(value, conversionService);
final PropertyContainer state = entityStateHandler.getPersistentState(value);
fetchValue(innerWrapper, state, persistentEntity);
// sourceStateTransmitter.copyPropertiesFrom(innerWrapper, entityStateHandler.<S>getPersistentState(value), persistentEntity);
}
return value;
}
public void fetchValue(final BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper, PropertyContainer source, Neo4jPersistentEntity<Object> persistentEntity) {
if (persistentEntity.isNodeEntity()) {
nodeStateTransmitter.copyPropertiesFrom(wrapper, (Node) source,persistentEntity);
}
if (persistentEntity.isRelationshipEntity()) {
relationshipStateTransmitter.copyPropertiesFrom(wrapper, (Relationship) source, persistentEntity);
}
}
}

View File

@@ -19,8 +19,12 @@ package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.PropertyContainer;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.util.TypeInformation;
import scala.annotation.target.field;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
@@ -34,7 +38,14 @@ import java.lang.reflect.Field;
public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentEntityImpl<?>, Neo4jPersistentProperty> {
protected <T> Neo4jPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
return new Neo4jPersistentEntityImpl<T>(typeInformation);
final Class<T> type = typeInformation.getType();
if (type.isAnnotationPresent(NodeEntity.class)) {
return new Neo4jPersistentEntityImpl<T>(typeInformation);
}
if (type.isAnnotationPresent(RelationshipEntity.class)) {
return new Neo4jPersistentEntityImpl<T>(typeInformation);
}
throw new MappingException("Type "+type+" is neither a @NodeEntity nor a @RelationshipEntity");
}
@Override
@@ -55,5 +66,4 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
final Neo4jPersistentEntityImpl<?> persistentEntity = getPersistentEntity(entity.getClass());
persistentEntity.setPersistentState(entity, pc);
}
}

View File

@@ -24,7 +24,6 @@ import org.springframework.data.convert.EntityWriter;
* @author mh
* @since 27.09.11
*/
public interface Neo4jNodeConverter extends EntityConverter<Neo4jPersistentEntity<?>, Neo4jPersistentProperty, Object, Node>, EntityWriter<Object,Node>,
EntityReader<Object, Node> {
public interface Neo4jNodeConverter<T> extends Neo4jEntityConverter<T,Node> {
}

View File

@@ -1,186 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.Transaction;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.core.EntityState;
import org.springframework.data.neo4j.support.DoReturn;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.node.NodeEntityStateFactory;
import org.springframework.util.Assert;
import java.lang.reflect.InvocationTargetException;
/**
* @author mh
* @since 27.09.11
*/
public class Neo4jNodeConverterImpl implements Neo4jNodeConverter {
private NodeEntityStateFactory nodeEntityStateFactory;
public Neo4jNodeConverterImpl() {
}
public void setNodeEntityStateFactory(NodeEntityStateFactory nodeEntityStateFactory) {
this.nodeEntityStateFactory = nodeEntityStateFactory;
nodeEntityStateFactory.setCreateDetachableEntities(false);
}
@Override
public MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> getMappingContext() {
return nodeEntityStateFactory.getMappingContext();
}
@Override
public ConversionService getConversionService() {
return getGraphDatabaseContext().getConversionService();
}
@Override
public <R> R read(Class<R> targetType, Node node) {
Assert.notNull(targetType);
Assert.notNull(node);
@SuppressWarnings("unchecked") final Neo4jPersistentEntity<R> persistentEntity = (Neo4jPersistentEntity<R>) getMappingContext().getPersistentEntity(targetType);
final GraphDatabaseContext graphDatabaseContext = nodeEntityStateFactory.getGraphDatabaseContext();
final R entity = graphDatabaseContext.createEntityFromState(node, targetType);
final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper = BeanWrapper.create(entity, getConversionService());
final Transaction tx = getGraphDatabaseContext().beginTx();
try {
//final Node targetNode = useGetOrCreateNode(node, persistentEntity, wrapper);
final EntityState<Node> nodeState = nodeEntityStateFactory.getEntityState(entity);
nodeState.setPersistentState(node);
nodeState.persist();
persistentEntity.doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
@Override
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
getEntityStateValue(property, nodeState, wrapper);
}
});
persistentEntity.doWithAssociations(new AssociationHandler<Neo4jPersistentProperty>() {
@Override
public void doWithAssociation(Association<Neo4jPersistentProperty> association) {
final Neo4jPersistentProperty property = association.getInverse();
getEntityStateValue(property, nodeState, wrapper);
}
});
tx.success();
return entity;
} finally {
tx.finish();
}
}
private <R> void getEntityStateValue(Neo4jPersistentProperty property, EntityState<Node> nodeState, BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper) {
final Object value = nodeState.getValue(property.getField());
setProperty(wrapper, property, value);
}
@Override
public void write(Object source, final Node node) {
Assert.notNull(source);
final Neo4jPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(source.getClass());
final BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper = BeanWrapper.create(source, getConversionService());
final Transaction tx = getGraphDatabaseContext().beginTx();
try {
//final Node targetNode = useGetOrCreateNode(node, persistentEntity, wrapper);
final EntityState<Node> nodeState = nodeEntityStateFactory.getEntityState(source);
nodeState.setPersistentState(node);
nodeState.persist();
persistentEntity.doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
@Override
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
setEntityStateValue(property, nodeState, wrapper);
}
});
persistentEntity.doWithAssociations(new AssociationHandler<Neo4jPersistentProperty>() {
@Override
public void doWithAssociation(Association<Neo4jPersistentProperty> association) {
final Neo4jPersistentProperty property = association.getInverse();
setEntityStateValue(property, nodeState, wrapper);
}
});
tx.success();
} finally {
tx.finish();
}
}
private void setEntityStateValue(Neo4jPersistentProperty property, EntityState<Node> nodeState, BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper) {
if (!nodeState.isWritable(property.getField())) return;
final Object value = getProperty(wrapper, property);
nodeState.setValue(property, value);
}
private Node useGetOrCreateNode(Node node, Neo4jPersistentEntity<?> persistentEntity, BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper) {
if (node != null) return node;
final Neo4jPersistentProperty idProperty = persistentEntity.getIdProperty();
final Long id = getProperty(wrapper, idProperty, Long.class, true);
if (id == null) {
final Node newNode = getGraphDatabaseContext().createNode();
setProperty(wrapper, idProperty, newNode.getId());
return newNode;
}
try {
return getGraphDatabaseContext().getNodeById(id);
} catch (NotFoundException nfe) {
throw new MappingException("Could not find node with id " + id);
}
}
private GraphDatabaseContext getGraphDatabaseContext() {
return nodeEntityStateFactory.getGraphDatabaseContext();
}
private <T> T getProperty(BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper, Neo4jPersistentProperty property, Class<T> type, boolean fieldAccessOnly) {
try {
return wrapper.getProperty(property, type, fieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e);
} catch (InvocationTargetException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e.getTargetException());
}
}
private Object getProperty(BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper, Neo4jPersistentProperty property) {
try {
return wrapper.getProperty(property);
} catch (IllegalAccessException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e);
} catch (InvocationTargetException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e.getTargetException());
}
}
private <R> void setProperty(BeanWrapper<Neo4jPersistentEntity<R>, ?> wrapper, Neo4jPersistentProperty property, Object value) {
try {
wrapper.setProperty(property, DoReturn.unwrap(value));
} catch (IllegalAccessException e) {
throw new MappingException("Setting property " + property.getName() + " to " + value + " on " + wrapper.getBean(), e);
} catch (InvocationTargetException e) {
throw new MappingException("Setting property " + property.getName() + " to " + value + " on " + wrapper.getBean(), e.getTargetException());
}
}
}

View File

@@ -36,4 +36,6 @@ public interface Neo4jPersistentEntity<T> extends PersistentEntity<T, Neo4jPersi
void setPersistentState(Object entity, PropertyContainer pc);
Object getPersistentId(Object entity);
RelationshipProperties getRelationshipProperties();
}

View File

@@ -19,10 +19,11 @@ package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.neo4j.annotation.*;
import org.springframework.data.neo4j.support.ManagedEntity;
import org.springframework.data.util.TypeInformation;
import java.lang.annotation.Annotation;
@@ -34,9 +35,13 @@ import java.util.Map;
*
* @author Oliver Gierke
*/
public class Neo4jPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4jPersistentProperty> implements Neo4jPersistentEntity<T> {
public class Neo4jPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4jPersistentProperty> implements Neo4jPersistentEntity<T>, RelationshipProperties {
private Map<Class<? extends Annotation>,Annotation> annotations=new IdentityHashMap<Class<? extends Annotation>,Annotation>();
private final boolean managed;
private Neo4jPersistentProperty startNodeProperty;
private Neo4jPersistentProperty endNodeProperty;
private Neo4jPersistentProperty relationshipType;
/**
* Creates a new {@link Neo4jPersistentEntityImpl} instance.
@@ -48,6 +53,7 @@ public class Neo4jPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4j
for (Annotation annotation : information.getType().getAnnotations()) {
annotations.put(annotation.annotationType(),annotation);
}
managed = ManagedEntity.class.isAssignableFrom(information.getType());
}
public boolean useShortNames() {
@@ -103,7 +109,51 @@ public class Neo4jPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4j
return idProperty.getValue(entity);
}
@Override
public RelationshipProperties getRelationshipProperties() {
return isRelationshipEntity() ? this : null;
}
public String getEntityName() {
return getType().getName();
}
public boolean isManaged() {
return managed;
}
@Override
public void addPersistentProperty(Neo4jPersistentProperty property) {
super.addPersistentProperty(property);
if (property.isAnnotationPresent(RelationshipType.class)) {
this.relationshipType = property;
}
}
@Override
public void addAssociation(Association<Neo4jPersistentProperty> neo4jPersistentPropertyAssociation) {
super.addAssociation(neo4jPersistentPropertyAssociation);
final Neo4jPersistentProperty property = neo4jPersistentPropertyAssociation.getInverse();
if (property.isAnnotationPresent(StartNode.class)) {
this.startNodeProperty = property;
}
if (property.isAnnotationPresent(EndNode.class)) {
this.endNodeProperty = property;
}
}
@Override
public Neo4jPersistentProperty getStartNodeProperty() {
return startNodeProperty;
}
@Override
public Neo4jPersistentProperty getEndeNodeProperty() {
return endNodeProperty;
}
@Override
public Neo4jPersistentProperty getTypeProperty() {
return relationshipType;
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
/**
* @author mh
* @since 07.10.11
*/
public interface RelationshipProperties {
Neo4jPersistentProperty getStartNodeProperty();
Neo4jPersistentProperty getEndeNodeProperty();
Neo4jPersistentProperty getTypeProperty();
}

View File

@@ -0,0 +1,159 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Transaction;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.core.EntityState;
import org.springframework.data.neo4j.support.DoReturn;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.node.EntityStateFactory;
import java.lang.reflect.InvocationTargetException;
/**
* @author mh
* @since 07.10.11
*/
public class SourceStateTransmitter<S extends PropertyContainer> {
private final EntityStateFactory<S> entityStateFactory;
public SourceStateTransmitter(EntityStateFactory<S> entityStateFactory) {
this.entityStateFactory = entityStateFactory;
}
public <R> R copyPropertiesFrom(final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper, S source, Neo4jPersistentEntity<R> persistentEntity) {
final R entity = wrapper.getBean();
final Transaction tx = getGraphDatabaseContext().beginTx();
try {
final EntityState<S> entityState = entityStateFactory.getEntityState(entity, false);
entityState.setPersistentState(source);
entityState.persist();
persistentEntity.doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
@Override
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
copyEntityStatePropertyValue(property, entityState, wrapper);
}
});
persistentEntity.doWithAssociations(new AssociationHandler<Neo4jPersistentProperty>() {
@Override
public void doWithAssociation(Association<Neo4jPersistentProperty> association) {
final Neo4jPersistentProperty property = association.getInverse();
copyEntityStatePropertyValue(property, entityState, wrapper);
}
});
tx.success();
return entity;
} finally {
tx.finish();
}
}
private <R> void setEntityStateValue(Neo4jPersistentProperty property, EntityState<S> entityState, BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper) {
if (!entityState.isWritable(property.getField())) return;
final Object value = getProperty(wrapper, property);
entityState.setValue(property, value);
}
private GraphDatabaseContext getGraphDatabaseContext() {
return entityStateFactory.getGraphDatabaseContext();
}
private <T> T getProperty(BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper, Neo4jPersistentProperty property, Class<T> type, boolean fieldAccessOnly) {
try {
return wrapper.getProperty(property, type, fieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e);
} catch (InvocationTargetException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e.getTargetException());
}
}
private <R> Object getProperty(BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper, Neo4jPersistentProperty property) {
try {
return wrapper.getProperty(property);
} catch (IllegalAccessException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e);
} catch (InvocationTargetException e) {
throw new MappingException("Error retrieving property " + property.getName() + " from " + wrapper.getBean(), e.getTargetException());
}
}
public <R> void setProperty(BeanWrapper<Neo4jPersistentEntity<R>, ?> wrapper, Neo4jPersistentProperty property, Object value) {
try {
wrapper.setProperty(property,value);
} catch (IllegalAccessException e) {
throw new MappingException("Setting property " + property.getName() + " to " + value + " on " + wrapper.getBean(), e);
} catch (InvocationTargetException e) {
throw new MappingException("Setting property " + property.getName() + " to " + value + " on " + wrapper.getBean(), e.getTargetException());
}
}
private <R> Object copyEntityStatePropertyValue(Neo4jPersistentProperty property, EntityState<S> nodeState, BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper) {
final Object value = DoReturn.unwrap(nodeState.getValue(property.getField()));
setProperty(wrapper, property, value);
return value;
}
public <R> void copyPropertiesTo(final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper, S target, Neo4jPersistentEntity<R> persistentEntity) {
final Transaction tx = getGraphDatabaseContext().beginTx();
try {
//final Node targetNode = useGetOrCreateNode(node, persistentEntity, wrapper);
final EntityState<S> entityState = entityStateFactory.getEntityState(wrapper.getBean(), false);
entityState.setPersistentState(target);
entityState.persist();
persistentEntity.doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
@Override
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
setEntityStateValue(property, entityState, wrapper);
}
});
persistentEntity.doWithAssociations(new AssociationHandler<Neo4jPersistentProperty>() {
@Override
public void doWithAssociation(Association<Neo4jPersistentProperty> association) {
final Neo4jPersistentProperty property = association.getInverse();
setEntityStateValue(property, entityState, wrapper);
}
});
tx.success();
} finally {
tx.finish();
}
}
private Node useGetOrCreateNode(Node node, Neo4jPersistentEntity<?> persistentEntity, BeanWrapper<Neo4jPersistentEntity<Object>, Object> wrapper) {
if (node != null) return node;
final Neo4jPersistentProperty idProperty = persistentEntity.getIdProperty();
final Long id = getProperty(wrapper, idProperty, Long.class, true);
if (id == null) {
final Node newNode = getGraphDatabaseContext().createNode();
setProperty(wrapper, idProperty, newNode.getId());
return newNode;
}
try {
return getGraphDatabaseContext().getNodeById(id);
} catch (NotFoundException nfe) {
throw new MappingException("Could not find node with id " + id);
}
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.PropertyContainer;
import org.springframework.data.convert.TypeAliasAccessor;
import org.springframework.data.convert.TypeInformationMapper;
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
/**
* @author mh
* @since 08.10.11
*/
public class TRSTypeAliasAccessor<S extends PropertyContainer> implements TypeAliasAccessor<S> {
private final TypeRepresentationStrategy<S> typeRepresentationStrategy;
public TRSTypeAliasAccessor(TypeRepresentationStrategy<S> typeRepresentationStrategy) {
this.typeRepresentationStrategy = typeRepresentationStrategy;
}
@Override
public Object readAliasFrom(S source) {
try {
return typeRepresentationStrategy.getJavaType(source);
} catch (UnsupportedOperationException uoe) {
return null;
}
}
@Override
public void writeTypeTo(S sink, Object alias) {
typeRepresentationStrategy.postEntityCreation(sink, (Class) alias);
}
}

View File

@@ -42,4 +42,9 @@ public class ProvidedClassPathXmlApplicationContext extends ClassPathXmlApplicat
beanFactory.registerResolvableDependency(GraphDatabaseService.class, database);
beanFactory.registerSingleton("graphDatabaseService", database);
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
}
}

View File

@@ -15,10 +15,10 @@
*/
package org.springframework.data.neo4j.support;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.*;
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntityImpl;
import org.springframework.data.neo4j.mapping.RelationshipProperties;
/**
* @author mh
@@ -40,7 +40,7 @@ public class EntityStateHandler {
return;
}
if (isManaged(entity)) {
((ManagedEntity<S,Object>) entity).setPersistentState(state);
((ManagedEntity<S, Object>) entity).setPersistentState(state);
return;
}
final Class<?> type = entity.getClass();
@@ -51,6 +51,7 @@ public class EntityStateHandler {
public boolean isManaged(Object entity) {
return entity instanceof ManagedEntity;
}
public boolean isManaged(Class type) {
return ManagedEntity.class.isAssignableFrom(type);
}
@@ -61,7 +62,7 @@ public class EntityStateHandler {
return (S) entity;
}
if (isManaged(entity)) {
return ((ManagedEntity<S,Object>) entity).getPersistentState();
return ((ManagedEntity<S, Object>) entity).getPersistentState();
}
final Class<?> type = entity.getClass();
final Neo4jPersistentEntityImpl<?> persistentEntity = mappingContext.getPersistentEntity(type);
@@ -86,4 +87,32 @@ public class EntityStateHandler {
public boolean isRelationshipEntity(Class targetType) {
return mappingContext.isRelationshipEntity(targetType);
}
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> S useOrCreateState(Object entity, S state) {
if (state != null) return state;
final S containedState = getPersistentState(entity);
if (containedState == null) return containedState;
final Class<?> type = entity.getClass();
final Neo4jPersistentEntityImpl<?> persistentEntity = mappingContext.getPersistentEntity(type);
if (persistentEntity.isNodeEntity()) {
return (S) service.createNode();
}
if (persistentEntity.isRelationshipEntity()) {
return createRelationship(entity, persistentEntity);
}
throw new IllegalArgumentException("The entity " + persistentEntity.getEntityName() + " has to be either annotated with @NodeEntity or @RelationshipEntity");
}
@SuppressWarnings("unchecked")
private <S extends PropertyContainer> S createRelationship(Object entity, Neo4jPersistentEntityImpl<?> persistentEntity) {
final RelationshipProperties relationshipProperties = persistentEntity.getRelationshipProperties();
Node startNode = (Node) relationshipProperties.getStartNodeProperty().getValue(entity);
Node endNode = (Node) relationshipProperties.getStartNodeProperty().getValue(entity);
Object relType = relationshipProperties.getTypeProperty().getValue(entity);
if (relType instanceof RelationshipType) {
return (S) startNode.createRelationshipTo(endNode, (RelationshipType) relType);
}
return (S) startNode.createRelationshipTo(endNode, DynamicRelationshipType.withName(relType.toString()));
}
}

View File

@@ -28,8 +28,12 @@ import org.neo4j.helpers.collection.IterableWrapper;
import org.neo4j.index.impl.lucene.LuceneIndexImplementation;
import org.neo4j.kernel.AbstractGraphDatabase;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.neo4j.core.*;
import org.springframework.data.neo4j.mapping.Neo4jEntityConverter;
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.mapping.Neo4jNodeConverter;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
@@ -60,11 +64,11 @@ public class GraphDatabaseContext {
private GraphDatabaseService graphDatabaseService;
private ConversionService conversionService;
private Neo4jNodeConverter converter;
private Neo4jEntityConverter<Object,Node> converter;
private Validator validator;
private NodeTypeRepresentationStrategy nodeTypeRepresentationStrategy;
private TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy;
private RelationshipTypeRepresentationStrategy relationshipTypeRepresentationStrategy;
private TypeRepresentationStrategy<Relationship> relationshipTypeRepresentationStrategy;
private Neo4jMappingContext mappingContext;
private CypherQueryExecutor cypherQueryExecutor;
@@ -313,19 +317,19 @@ public class GraphDatabaseContext {
this.cypherQueryExecutor = new CypherQueryExecutor(this);
}
public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() {
public TypeRepresentationStrategy<Node> getNodeTypeRepresentationStrategy() {
return nodeTypeRepresentationStrategy;
}
public void setNodeTypeRepresentationStrategy(NodeTypeRepresentationStrategy nodeTypeRepresentationStrategy) {
public void setNodeTypeRepresentationStrategy(TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy) {
this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy;
}
public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy() {
public TypeRepresentationStrategy<Relationship> getRelationshipTypeRepresentationStrategy() {
return relationshipTypeRepresentationStrategy;
}
public void setRelationshipTypeRepresentationStrategy(RelationshipTypeRepresentationStrategy relationshipTypeRepresentationStrategy) {
public void setRelationshipTypeRepresentationStrategy(TypeRepresentationStrategy<Relationship> relationshipTypeRepresentationStrategy) {
this.relationshipTypeRepresentationStrategy = relationshipTypeRepresentationStrategy;
}
@@ -350,11 +354,13 @@ public class GraphDatabaseContext {
}
public boolean isNodeEntity(Class<?> targetType) {
return mappingContext.isNodeEntity(targetType);
return targetType.isAnnotationPresent(NodeEntity.class);
//return mappingContext.isNodeEntity(targetType);
}
public boolean isRelationshipEntity(Class targetType) {
return mappingContext.isRelationshipEntity(targetType);
return targetType.isAnnotationPresent(RelationshipEntity.class);
// return mappingContext.isRelationshipEntity(targetType);
}
public Object save(Object entity) {
@@ -371,7 +377,7 @@ public class GraphDatabaseContext {
return entityStateHandler.isManaged(entity);
}
public void setConverter(Neo4jNodeConverter converter) {
public void setNodeEntityConverter(Neo4jEntityConverter<Object, Node> converter) {
this.converter = converter;
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.node;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.springframework.data.neo4j.core.EntityState;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
/**
* @author mh
* @since 07.10.11
*/
public interface EntityStateFactory<S extends PropertyContainer> {
EntityState<S> getEntityState(final Object entity, boolean detachable);
GraphDatabaseContext getGraphDatabaseContext();
}

View File

@@ -17,7 +17,6 @@
package org.springframework.data.neo4j.support.node;
import org.neo4j.graphdb.Node;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.core.EntityState;
import org.springframework.data.neo4j.fieldaccess.DelegatingFieldAccessorFactory;
@@ -26,7 +25,7 @@ import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
public class NodeEntityStateFactory {
public class NodeEntityStateFactory implements EntityStateFactory<Node> {
protected GraphDatabaseContext graphDatabaseContext;
@@ -34,16 +33,16 @@ public class NodeEntityStateFactory {
protected Neo4jMappingContext mappingContext;
private boolean createDetachableEntities = true;
public EntityState<Node> getEntityState(final Object entity) {
public EntityState<Node> getEntityState(final Object entity, boolean detachable) {
final Class<?> entityType = entity.getClass();
final NodeEntity graphEntityAnnotation = entityType.getAnnotation(NodeEntity.class); // todo cache ??
final Neo4jPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);
NodeEntityState nodeEntityState = new NodeEntityState(null, entity, entityType, graphDatabaseContext, nodeDelegatingFieldAccessorFactory, (Neo4jPersistentEntity) persistentEntity);
// alternative was return new NestedTransactionEntityState<NodeBacked, Node>(nodeEntityState,graphDatabaseContext);
if (createDetachableEntities) return new DetachedEntityState<Node>(nodeEntityState, graphDatabaseContext);
return nodeEntityState;
@SuppressWarnings("unchecked") final Neo4jPersistentEntity<Object> persistentEntity =
(Neo4jPersistentEntity<Object>) mappingContext.getPersistentEntity(entityType);
NodeEntityState nodeEntityState = new NodeEntityState(null, entity, entityType, graphDatabaseContext,
nodeDelegatingFieldAccessorFactory, persistentEntity);
if (!detachable) {
return nodeEntityState;
}
return new DetachedEntityState<Node>(nodeEntityState, graphDatabaseContext);
}
public void setNodeDelegatingFieldAccessorFactory(
@@ -67,7 +66,4 @@ public class NodeEntityStateFactory {
return graphDatabaseContext;
}
public void setCreateDetachableEntities(boolean createDetachableEntities) {
this.createDetachableEntities = createDetachableEntities;
}
}

View File

@@ -1,63 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.query;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
/**
* @author mh
* @since 28.06.11
*/
@SuppressWarnings("unchecked")
public class EntityResultConverter2<R> implements ResultConverter<Object,R> {
private final TypeRepresentationStrategy nodeTypeRepresentationStrategy;
private final TypeRepresentationStrategy relationshipTypeRepresentationStrategy;
private final ConversionService conversionService;
public EntityResultConverter2(GraphDatabaseContext ctx) {
this.conversionService = ctx.getConversionService();
this.nodeTypeRepresentationStrategy = ctx.getNodeTypeRepresentationStrategy();
relationshipTypeRepresentationStrategy = ctx.getRelationshipTypeRepresentationStrategy();
}
public R convert(Object value, Class<R> type) {
if (type == null) return (R) convertValue(value);
if (type.isInstance(value)) return type.cast(value);
if (value instanceof Node) {
return (R) nodeTypeRepresentationStrategy.createEntity((Node) value, type);
}
if (value instanceof Relationship) {
return (R) relationshipTypeRepresentationStrategy.createEntity((Relationship) value, type);
}
return conversionService.convert(value, type);
}
private Object convertValue(Object value) {
if (value instanceof Node) {
return nodeTypeRepresentationStrategy.createEntity((Node) value);
}
if (value instanceof Relationship) {
return relationshipTypeRepresentationStrategy.createEntity((Relationship) value);
}
return value;
}
}

View File

@@ -23,8 +23,9 @@ import org.springframework.data.neo4j.fieldaccess.DelegatingFieldAccessorFactory
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.node.EntityStateFactory;
public class RelationshipEntityStateFactory {
public class RelationshipEntityStateFactory implements EntityStateFactory<Relationship> {
private GraphDatabaseContext graphDatabaseContext;
@@ -32,7 +33,7 @@ public class RelationshipEntityStateFactory {
private Neo4jMappingContext mappingContext;
@SuppressWarnings("unchecked")
public EntityState<Relationship> getEntityState(final Object entity) {
public EntityState<Relationship> getEntityState(final Object entity, boolean detachable) {
final Class<?> entityType = entity.getClass();
return new RelationshipEntityState(null,entity, entityType, graphDatabaseContext, relationshipDelegatingFieldAccessorFactory, (Neo4jPersistentEntity) mappingContext.getPersistentEntity(entityType));
}
@@ -49,4 +50,9 @@ public class RelationshipEntityStateFactory {
public void setMappingContext(Neo4jMappingContext mappingContext) {
this.mappingContext = mappingContext;
}
@Override
public GraphDatabaseContext getGraphDatabaseContext() {
return graphDatabaseContext;
}
}

View File

@@ -22,8 +22,14 @@ import org.neo4j.graphdb.*;
import org.neo4j.graphdb.index.Index;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.test.ImpermanentGraphDatabase;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.convert.DefaultTypeMapper;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
import org.springframework.data.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean;
import org.springframework.data.neo4j.fieldaccess.NodeDelegatingFieldAccessorFactory;
import org.springframework.data.neo4j.fieldaccess.RelationshipDelegatingFieldAccessorFactory;
import org.springframework.data.neo4j.model.Friendship;
import org.springframework.data.neo4j.model.Group;
import org.springframework.data.neo4j.model.Person;
import org.springframework.data.neo4j.model.Personality;
@@ -31,7 +37,10 @@ import org.springframework.data.neo4j.support.EntityStateHandler;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.node.NodeEntityInstantiator;
import org.springframework.data.neo4j.support.node.NodeEntityStateFactory;
import org.springframework.data.neo4j.support.relationship.RelationshipEntityInstantiator;
import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory;
import org.springframework.data.neo4j.support.typerepresentation.NoopNodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.typerepresentation.NoopRelationshipTypeRepresentationStrategy;
import java.util.*;
@@ -43,10 +52,11 @@ import static org.junit.Assert.assertEquals;
* @author mh
* @since 19.09.11
*/
public class Neo4jNodeConverterTest {
public class Neo4jEntityConverterTest {
public static final DynamicRelationshipType PERSONS = DynamicRelationshipType.withName("persons");
private Neo4jNodeConverterImpl converter;
private static final RelationshipType KNOWS = DynamicRelationshipType.withName("knows");
private Neo4jEntityConverterImpl<Object,Node> converter;
private Transaction tx;
private GraphDatabaseContext gdc;
private Group group;
@@ -54,24 +64,75 @@ public class Neo4jNodeConverterTest {
private Person emil;
private Person andres;
/* OUCH
[MC]
[GDC]->[GDB]
[GDC]->[MC]
[GDC]->[ESH]
[ESH]->[MC]
[ESH]->[GDB]
[TRS]->[EI]
[EI]->[ESH]
[GDC]->[CS]
[GDC]->[TRS]
[GDC]->[ESH]
[ESF]->[MC]
[ESF]->[GDC]
[ESF]->[FAF]
[FAF]->[GDC]
[GDC]->[EC]
[EC]->[ESF]
[EC]->[CS]
[EC]->[EI]
[EC]->[ESH]
[EC]->[SST]
[EC]->[TRS]
[SST]->[ESF]
*/
@Before
public void setUp() throws Exception {
final Neo4jMappingContext mappingContext = new Neo4jMappingContext();
gdc = createContext(mappingContext);
tx = gdc.beginTx();
final NodeEntityStateFactory nodeEntityStateFactory = new NodeEntityStateFactory();
nodeEntityStateFactory.setMappingContext(mappingContext);
nodeEntityStateFactory.setGraphDatabaseContext(gdc);
nodeEntityStateFactory.setNodeDelegatingFieldAccessorFactory(new NodeDelegatingFieldAccessorFactory(gdc));
converter = new Neo4jNodeConverterImpl();
converter.setNodeEntityStateFactory(nodeEntityStateFactory);
gdc.setConverter(converter);
final NodeEntityStateFactory nodeEntityStateFactory = createNodeEntityStateFactory(mappingContext);
final RelationshipEntityStateFactory relationshipEntityStateFactory = createRelationshipEntityStateFactory(mappingContext);
final EntityStateHandler entityStateHandler = new EntityStateHandler(mappingContext, gdc.getGraphDatabaseService());
final NodeEntityInstantiator entityInstantiator = new NodeEntityInstantiator(entityStateHandler);
final TypeRepresentationStrategy<Node> typeRepresentationStrategy = gdc.getNodeTypeRepresentationStrategy();
TypeMapper<Node> typeMapper = new DefaultTypeMapper<Node>(new TRSTypeAliasAccessor<Node>(typeRepresentationStrategy),asList(new ClassValueTypeInformationMapper()));
SourceStateTransmitter<Node> nodeStateTransmitter = new SourceStateTransmitter<Node>(nodeEntityStateFactory);
SourceStateTransmitter<Relationship> relationshipStateTransmitter = new SourceStateTransmitter<Relationship>(relationshipEntityStateFactory);
final ConversionService conversionService = gdc.getConversionService();
Neo4jEntityFetchHandler fetchHandler=new Neo4jEntityFetchHandler(entityStateHandler, conversionService, relationshipStateTransmitter , nodeStateTransmitter);
converter = new Neo4jEntityConverterImpl<Object,Node>(mappingContext, conversionService, entityInstantiator, entityStateHandler, typeMapper, nodeStateTransmitter, fetchHandler);
gdc.setNodeEntityConverter(converter);
group = new Group();
michael = new Person("Michael", 37);
emil = new Person("Emil", 30);
andres = new Person("Andrés", 36);
}
private NodeEntityStateFactory createNodeEntityStateFactory(Neo4jMappingContext mappingContext) {
final NodeEntityStateFactory nodeEntityStateFactory = new NodeEntityStateFactory();
nodeEntityStateFactory.setMappingContext(mappingContext);
nodeEntityStateFactory.setGraphDatabaseContext(gdc);
nodeEntityStateFactory.setNodeDelegatingFieldAccessorFactory(new NodeDelegatingFieldAccessorFactory(gdc));
return nodeEntityStateFactory;
}
private RelationshipEntityStateFactory createRelationshipEntityStateFactory(Neo4jMappingContext mappingContext) {
final RelationshipEntityStateFactory relationshipEntityStateFactory = new RelationshipEntityStateFactory();
relationshipEntityStateFactory.setMappingContext(mappingContext);
relationshipEntityStateFactory.setGraphDatabaseContext(gdc);
relationshipEntityStateFactory.setRelationshipDelegatingFieldAccessorFactory(new RelationshipDelegatingFieldAccessorFactory(gdc));
return relationshipEntityStateFactory;
}
private GraphDatabaseContext createContext(Neo4jMappingContext mappingContext) throws Exception {
GraphDatabaseContext gdc = new GraphDatabaseContext();
final ImpermanentGraphDatabase gdb = new ImpermanentGraphDatabase();
@@ -79,6 +140,7 @@ public class Neo4jNodeConverterTest {
gdc.setMappingContext(mappingContext);
final EntityStateHandler entityStateHandler = new EntityStateHandler(mappingContext, gdb);
gdc.setNodeTypeRepresentationStrategy(new NoopNodeTypeRepresentationStrategy(new NodeEntityInstantiator(entityStateHandler)));
gdc.setRelationshipTypeRepresentationStrategy(new NoopRelationshipTypeRepresentationStrategy(new RelationshipEntityInstantiator(entityStateHandler)));
gdc.setConversionService(new Neo4jConversionServiceFactoryBean().getObject());
gdc.setEntityStateHandler(entityStateHandler);
gdc.createCypherExecutor();
@@ -155,7 +217,7 @@ public class Neo4jNodeConverterTest {
}
@Test
public void testReadConvertedPropertiesToExistingNode() {
public void testReadConvertedPropertiesFromExistingNode() {
final Node existingNode = createNewNode();
existingNode.setProperty("name", "Michael");
existingNode.setProperty("age", 36);
@@ -197,16 +259,17 @@ public class Neo4jNodeConverterTest {
private Group storeInGraph(Group g) {
final Long id = g.getId();
if (id!=null) {
if (id != null) {
converter.write(g, gdc.getNodeById(id));
} else {
converter.write(g, null);
}
return g;
}
private Person storeInGraph(Person p) {
final Long id = p.getId();
if (id!=null) {
if (id != null) {
converter.write(p, gdc.getNodeById(id));
} else {
converter.write(p, null);
@@ -380,4 +443,34 @@ public class Neo4jNodeConverterTest {
assertEquals("added member to group", michael.getId(), (Long) michaelNode.getId());
}
@Test
public void testCascadingReadWithProperties() {
Node groupNode = createNewNode();
Node julianNode = createNewNode();
julianNode.setProperty("name", "Julian");
groupNode.createRelationshipTo(julianNode, PERSONS);
Group g = converter.read(Group.class, groupNode);
Person julian = IteratorUtil.first(g.getPersons());
assertEquals("Julian", julian.getName());
}
@Test
public void testLoadFriendShipsFromPersons() throws Exception {
storeInGraph(michael);
storeInGraph(andres);
Relationship friendshipRelationship = michaelNode().createRelationshipTo(andresNode(), KNOWS);
friendshipRelationship.setProperty("Friendship.years", 19);
Person m = converter.read(Person.class, michaelNode());
Friendship friendship = IteratorUtil.first(m.getFriendships());
assertEquals((Long) friendshipRelationship.getId(), friendship.getId());
assertEquals(19, friendship.getYears());
assertEquals(friendship.getPerson1(), michael);
assertEquals(friendship.getPerson2(), andres);
}
}

View File

@@ -24,6 +24,8 @@ import java.util.Date;
@RelationshipEntity(useShortNames = false)
public class Friendship {
@GraphId
private Long id;
public Friendship() {
}
@@ -110,4 +112,8 @@ public class Friendship {
public DynamicProperties getPersonalProperties() {
return personalProperties;
}
public Long getId() {
return id;
}
}

View File

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

View File

@@ -67,6 +67,7 @@ public class Person {
@RelatedTo(type = "boss", direction = Direction.INCOMING)
private Person boss;
@Fetch
@RelatedToVia(type = "knows", elementClass = Friendship.class)
private Iterable<Friendship> friendships;

View File

@@ -89,7 +89,7 @@ public abstract class DocumentingTestBase {
StringBuilder snippetText = new StringBuilder();
boolean inSnippet = false;
while ((line = reader.readLine()) != null) {
if (line.matches(".*//.+SNIPPET\\s+"+snippet+".*")) {
if (line.matches(".*//.+SNIPPET\\s+.*\\b"+snippet+"\\b.*")) {
inSnippet = !inSnippet;
continue;
}

View File

@@ -33,4 +33,5 @@
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<!--context:load-time-weaver aspectj-weaving="autodetect"/-->
</beans>