DATAGRAPH-285 adding application events for save and delete

This commit is contained in:
Lasse Westh-Nielsen
2012-08-31 11:15:43 +01:00
parent 3ba54fd400
commit 635fe4e17f
9 changed files with 798 additions and 46 deletions

View File

@@ -0,0 +1,38 @@
/**
* 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.lifecycle;
import org.springframework.context.ApplicationEvent;
public class AfterSaveEvent<T> extends ApplicationEvent {
private final T entity;
/**
* Create a new ApplicationEvent.
*
* @param source the component that published the event (never <code>null</code>)
* @param entity
*/
public AfterSaveEvent(Object source, T entity) {
super(source);
this.entity = entity;
}
public T getEntity() {
return entity;
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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.lifecycle;
import org.springframework.context.ApplicationEvent;
public class BeforeSaveEvent<T> extends ApplicationEvent {
private final T entity;
/**
* @param source the component that published the event (never <code>null</code>)
* @param entity the entity that is about to be saved
*/
public BeforeSaveEvent(Object source, T entity) {
super(source);
this.entity = entity;
}
public T getEntity() {
return entity;
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.lifecycle;
import org.springframework.context.ApplicationEvent;
public class DeleteEvent<T> extends ApplicationEvent {
private final T entity;
public DeleteEvent(Object source, T entity) {
super(source);
this.entity = entity;
}
public T getEntity() {
return entity;
}
}

View File

@@ -23,6 +23,9 @@ import org.neo4j.helpers.collection.ClosableIterable;
import org.neo4j.index.lucene.ValueContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -36,6 +39,9 @@ import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
import org.springframework.data.neo4j.core.UncategorizedGraphStoreException;
import org.springframework.data.neo4j.fieldaccess.GraphBackedEntityIterableWrapper;
import org.springframework.data.neo4j.lifecycle.AfterSaveEvent;
import org.springframework.data.neo4j.lifecycle.BeforeSaveEvent;
import org.springframework.data.neo4j.lifecycle.DeleteEvent;
import org.springframework.data.neo4j.mapping.IndexInfo;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
@@ -45,11 +51,7 @@ import org.springframework.data.neo4j.repository.NodeGraphRepositoryImpl;
import org.springframework.data.neo4j.repository.RelationshipGraphRepository;
import org.springframework.data.neo4j.support.index.IndexProvider;
import org.springframework.data.neo4j.support.index.IndexType;
import org.springframework.data.neo4j.support.mapping.EntityStateHandler;
import org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister;
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.support.mapping.Neo4jPersistentEntityImpl;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
import org.springframework.data.neo4j.support.mapping.*;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.data.neo4j.template.GraphCallback;
import org.springframework.data.neo4j.template.Neo4jOperations;
@@ -78,10 +80,11 @@ import static org.springframework.data.neo4j.support.ParameterCheck.notNull;
/*
TODO This is a merge of GraphDatabaseContext and the previous Neo4jTemplate, so it still contains inconsistencies, if you spot them, please mark them with a TODO
*/
public class Neo4jTemplate implements Neo4jOperations {
public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(Neo4jTemplate.class);
private final Infrastructure infrastructure;
private ApplicationContext applicationContext;
/**
* @param graphDatabase the neo4j graph database
@@ -89,17 +92,17 @@ public class Neo4jTemplate implements Neo4jOperations {
*/
public Neo4jTemplate(final GraphDatabase graphDatabase, PlatformTransactionManager transactionManager) {
notNull(graphDatabase, "graphDatabase");
this.infrastructure = MappingInfrastructureFactoryBean.createDirect(graphDatabase,transactionManager);
this.infrastructure = MappingInfrastructureFactoryBean.createDirect(graphDatabase, transactionManager);
}
public Neo4jTemplate(final GraphDatabase graphDatabase) {
notNull(graphDatabase, "graphDatabase");
this.infrastructure = MappingInfrastructureFactoryBean.createDirect(graphDatabase,null);
this.infrastructure = MappingInfrastructureFactoryBean.createDirect(graphDatabase, null);
}
public Neo4jTemplate(final GraphDatabaseService graphDatabaseService) {
notNull(graphDatabaseService, "graphDatabaseService");
this.infrastructure = MappingInfrastructureFactoryBean.createDirect(graphDatabaseService,null);
this.infrastructure = MappingInfrastructureFactoryBean.createDirect(graphDatabaseService, null);
}
public Neo4jTemplate(Infrastructure infrastructure) {
@@ -109,7 +112,7 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
public <T> GraphRepository<T> repositoryFor(Class<T> clazz) {
notNull(clazz,"entity type");
notNull(clazz, "entity type");
if (isNodeEntity(clazz)) return new NodeGraphRepositoryImpl<T>(clazz, this);
if (isRelationshipEntity(clazz)) return new RelationshipGraphRepository<T>(clazz, this);
throw new IllegalArgumentException("Can't create graph repository for non-graph entity of type " + clazz);
@@ -143,12 +146,12 @@ public class Neo4jTemplate implements Neo4jOperations {
final Neo4jPersistentEntityImpl<?> persistentEntity = getPersistentEntity(entityClass);
if (persistentEntity.isNodeEntity()) {
final Node node = getNode(id);
if (node==null) return null;
if (node == null) return null;
return infrastructure.getEntityPersister().createEntityFromState(node, entityClass, persistentEntity.getMappingPolicy(), this);
}
if (persistentEntity.isRelationshipEntity()) {
final Relationship relationship = getRelationship(id);
if (relationship==null) return null;
if (relationship == null) return null;
return infrastructure.getEntityPersister().createEntityFromState(relationship, entityClass, persistentEntity.getMappingPolicy(), this);
}
throw new IllegalArgumentException("provided entity type is neither annotated with @NodeEntiy nor @RelationshipEntity");
@@ -156,39 +159,41 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
public <T> EndResult<T> findAll(final Class<T> entityClass) {
notNull(entityClass,"entity type");
notNull(entityClass, "entity type");
final ClosableIterable<PropertyContainer> all = infrastructure.getTypeRepresentationStrategies().findAll(getEntityType(entityClass));
return new QueryResultBuilder<PropertyContainer>(all, getDefaultConverter()).to(entityClass);
}
@Override
public <T> long count(final Class<T> entityClass) {
notNull(entityClass,"entity type");
notNull(entityClass, "entity type");
return infrastructure.getTypeRepresentationStrategies().count(getEntityType(entityClass));
}
public <S extends PropertyContainer, T> T createEntityFromStoredType(S state, MappingPolicy mappingPolicy) {
notNull(state,"node or relationship");
notNull(state, "node or relationship");
return infrastructure.getEntityPersister().createEntityFromStoredType(state, mappingPolicy, this);
}
public <S extends PropertyContainer, T> T createEntityFromState(S state, Class<T> type, MappingPolicy mappingPolicy) {
notNull(state,"node or relationship");
notNull(state, "node or relationship");
return infrastructure.getEntityPersister().createEntityFromState(state, type, mappingPolicy, this);
}
@Override
public <S extends PropertyContainer, T> T load(S state, Class<T> type) {
notNull(state,"node or relationship",type,"entity class");
notNull(state, "node or relationship", type, "entity class");
return infrastructure.getEntityPersister().createEntityFromState(state, type, getMappingPolicy(type), this);
}
@Override
public <T> T projectTo(Object entity, Class<T> targetType) {
notNull(entity,"entity",targetType,"new entity class");
notNull(entity, "entity", targetType, "new entity class");
return infrastructure.getEntityPersister().projectTo(entity, targetType, this);
}
public <T> T projectTo(Object entity, Class<T> targetType, MappingPolicy mappingPolicy) {
notNull(entity,"entity",targetType,"new entity class");
notNull(entity, "entity", targetType, "new entity class");
return infrastructure.getEntityPersister().projectTo(entity, targetType, mappingPolicy, this);
}
@@ -214,8 +219,9 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
public void delete(final Object entity) {
notNull(entity, "entity");
infrastructure.getEntityRemover().remove(entity);
if (applicationContext != null) applicationContext.publishEvent(new DeleteEvent<Object>(this, entity));
}
/**
@@ -278,12 +284,15 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
@SuppressWarnings("unchecked")
public <T> T save(T entity) {
return save( entity, null );
return save(entity, null);
}
@SuppressWarnings("unchecked")
public <T> T save( T entity, RelationshipType annotationProvidedRelationshipType ) {
return (T) infrastructure.getEntityPersister().persist(entity, getMappingPolicy(entity), this, annotationProvidedRelationshipType );
public <T> T save(T entity, final RelationshipType annotationProvidedRelationshipType) {
if (applicationContext != null) applicationContext.publishEvent(new BeforeSaveEvent<T>(this, entity));
T t = (T) infrastructure.getEntityPersister().persist(entity, getMappingPolicy(entity), this, annotationProvidedRelationshipType);
if (applicationContext != null) applicationContext.publishEvent(new AfterSaveEvent<T>(this, entity));
return t;
}
public boolean isManaged(Object entity) {
@@ -311,10 +320,10 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
@SuppressWarnings("unchecked")
public <R> R getRelationshipBetween(Object start, Object end, Class<R> relationshipEntityClass, String relationshipType) {
notNull(start,"start",end,"end",relationshipEntityClass,"relationshipEntityClass",relationshipType,"relationshipType");
notNull(start, "start", end, "end", relationshipEntityClass, "relationshipEntityClass", relationshipType, "relationshipType");
final Relationship relationship = infrastructure.getEntityStateHandler().getRelationshipBetween(start, end, relationshipType);
if (relationship == null) return null;
if (Relationship.class.isAssignableFrom(relationshipEntityClass)) return (R)relationship;
if (Relationship.class.isAssignableFrom(relationshipEntityClass)) return (R) relationship;
final Neo4jPersistentEntityImpl<?> persistentEntity = getPersistentEntity(relationshipEntityClass);
return infrastructure.getEntityPersister().createEntityFromState(relationship, relationshipEntityClass, persistentEntity.getMappingPolicy(), this);
}
@@ -322,27 +331,28 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
@SuppressWarnings("unchecked")
public <R> Iterable<R> getRelationshipsBetween(Object start, Object end, Class<R> relationshipEntityClass, String relationshipType) {
notNull(start,"start",end,"end",relationshipEntityClass,"relationshipEntityClass",relationshipType,"relationshipType");
notNull(start, "start", end, "end", relationshipEntityClass, "relationshipEntityClass", relationshipType, "relationshipType");
final Iterable<Relationship> relationships = infrastructure.getEntityStateHandler().getRelationshipsBetween(start, end, relationshipType);
if (relationships == null) return null;
if (Relationship.class.isAssignableFrom(relationshipEntityClass)) return (Iterable<R>)relationships;
if (Relationship.class.isAssignableFrom(relationshipEntityClass)) return (Iterable<R>) relationships;
return GraphBackedEntityIterableWrapper.create(relationships, relationshipEntityClass, this);
}
@Override
public Relationship getRelationshipBetween(Object start, Object end, String relationshipType) {
notNull(start,"start",end,"end",relationshipType,"relationshipType");
notNull(start, "start", end, "end", relationshipType, "relationshipType");
return infrastructure.getEntityStateHandler().getRelationshipBetween(start, end, relationshipType);
}
@Override
public void deleteRelationshipBetween(Object start, Object end, String type) {
notNull(start,"start",end,"end",type,"relationshipType");
notNull(start, "start", end, "end", type, "relationshipType");
infrastructure.getEntityRemover().removeRelationshipBetween(start, end, type);
}
@Override
public <R> R createRelationshipBetween(Object start, Object end, Class<R> relationshipEntityClass, String relationshipType, boolean allowDuplicates) {
notNull(start,"start",end,"end",relationshipEntityClass,"relationshipEntityClass",relationshipType,"relationshipType");
notNull(start, "start", end, "end", relationshipEntityClass, "relationshipEntityClass", relationshipType, "relationshipType");
final RelationshipResult result = infrastructure.getEntityStateHandler().createRelationshipBetween(start, end, relationshipType, allowDuplicates);
if (result.type == RelationshipResult.Type.NEW) {
// TODO
@@ -364,7 +374,7 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
public Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map<String, Object> properties) {
return getGraphDatabase().getOrCreateRelationship(indexName,key,value,startNode,endNode,type,properties);
return getGraphDatabase().getOrCreateRelationship(indexName, key, value, startNode, endNode, type, properties);
}
private final Neo4jExceptionTranslator exceptionTranslator = new Neo4jExceptionTranslator();
@@ -493,7 +503,7 @@ public class Neo4jTemplate implements Neo4jOperations {
public ResultConverter getDefaultConverter() {
final ResultConverter resultConverter = infrastructure.getResultConverter();
if (resultConverter instanceof Neo4jTemplateAware) {
return ((Neo4jTemplateAware<ResultConverter>)resultConverter).with(this);
return ((Neo4jTemplateAware<ResultConverter>) resultConverter).with(this);
}
return resultConverter;
}
@@ -507,7 +517,7 @@ public class Neo4jTemplate implements Neo4jOperations {
@SuppressWarnings("unchecked")
public Result<Map<String, Object>> query(String statement, Map<String, Object> params) {
notNull(statement, "statement");
final QueryEngine<Map<String,Object>> queryEngine = queryEngineFor(QueryType.Cypher);
final QueryEngine<Map<String, Object>> queryEngine = queryEngineFor(QueryType.Cypher);
return queryEngine.query(statement, params);
}
@@ -526,7 +536,7 @@ public class Neo4jTemplate implements Neo4jOperations {
@SuppressWarnings("unchecked")
public <T> Iterable<T> traverse(Object entity, Class<?> targetType, TraversalDescription traversalDescription) {
notNull(entity,"entity",targetType,"target type",traversalDescription,"traversal description");
notNull(entity, "entity", targetType, "target type", traversalDescription, "traversal description");
return traverse(entity, traversalDescription).to((Class<T>) targetType);
}
@@ -553,7 +563,7 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
public <T extends PropertyContainer> Result<T> lookup(final Class<?> indexedType, String propertyName, final Object value) {
notNull(propertyName, "property name", indexedType, "indexedType",value,"query value");
notNull(propertyName, "property name", indexedType, "indexedType", value, "query value");
try {
final Index<T> index = getIndex(indexedType, propertyName);
@@ -571,15 +581,15 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
public <T extends PropertyContainer> Index<T> getIndex(Class<?> indexedType, String propertyName) {
final Neo4jPersistentProperty property = getPersistentProperty(indexedType, propertyName);
if (property==null) return getIndexProvider().getIndex(indexedType,null);
if (property == null) return getIndexProvider().getIndex(indexedType, null);
return getIndexProvider().getIndex(property, indexedType);
}
public Neo4jPersistentProperty getPersistentProperty(Class<?> type, String propertyName) {
if (type==null || propertyName==null) return null;
if (type == null || propertyName == null) return null;
final Neo4jPersistentEntityImpl<?> persistentEntity = getPersistentEntity(type);
final int dotIndex = propertyName.lastIndexOf(".");
if (dotIndex >-1) propertyName = propertyName.substring(dotIndex, propertyName.length());
if (dotIndex > -1) propertyName = propertyName.substring(dotIndex, propertyName.length());
return persistentEntity.getPersistentProperty(propertyName);
}
@@ -635,27 +645,28 @@ public class Neo4jTemplate implements Neo4jOperations {
public String getIndexKey(Neo4jPersistentProperty property) {
return property.getIndexKey();
}
public <S extends PropertyContainer> Index<S> getIndex(Neo4jPersistentProperty property, final Class<?> instanceType) {
return getIndexProvider().getIndex(property, instanceType);
}
public MappingPolicy getMappingPolicy(Object entity) {
ParameterCheck.notNull(entity,"entity");
return getMappingPolicy( entity.getClass() );
ParameterCheck.notNull(entity, "entity");
return getMappingPolicy(entity.getClass());
}
public StoredEntityType getStoredEntityType(Object entity) {
final PropertyContainer container = entity instanceof PropertyContainer ? (PropertyContainer)entity: getPersistentState(entity);
if (container==null) return null;
final PropertyContainer container = entity instanceof PropertyContainer ? (PropertyContainer) entity : getPersistentState(entity);
if (container == null) return null;
final Object alias = getInfrastructure().getTypeRepresentationStrategies().readAliasFrom(container);
return getMappingContext().getPersistentEntity(alias).getEntityType();
}
@Override
public Class getStoredJavaType(Object entity) {
final PropertyContainer container = entity instanceof PropertyContainer ? (PropertyContainer)entity: getPersistentState(entity);
if (container==null) return null;
final PropertyContainer container = entity instanceof PropertyContainer ? (PropertyContainer) entity : getPersistentState(entity);
if (container == null) return null;
final Object alias = getInfrastructure().getTypeRepresentationStrategies().readAliasFrom(container);
return getMappingContext().getPersistentEntity(alias).getType();
}
@@ -668,9 +679,14 @@ public class Neo4jTemplate implements Neo4jOperations {
final Neo4jPersistentEntityImpl<?> persistentEntity = getPersistentEntity(entity.getClass());
final Neo4jPersistentProperty uniqueProperty = persistentEntity.getUniqueProperty();
Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY);
if (value==null) return createNode();
if (value == null) return createNode();
final IndexInfo indexInfo = uniqueProperty.getIndexInfo();
if (value instanceof Number && indexInfo.isNumeric()) value=ValueContext.numeric((Number)value);
if (value instanceof Number && indexInfo.isNumeric()) value = ValueContext.numeric((Number) value);
return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String, Object>emptyMap());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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.lifecycle;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.test.ImpermanentGraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.neo4j.helpers.collection.IteratorUtil.single;
@NodeEntity
class Bar {
@GraphId
Long id;
String generatedId = "no event";
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
public class AfterSaveEventTests {
@Configuration
@EnableNeo4jRepositories
static class TestConfig extends Neo4jConfiguration {
@Bean
GraphDatabaseService graphDatabaseService() {
return new ImpermanentGraphDatabase();
}
@Bean
ApplicationListener<AfterSaveEvent> beforeSaveEventApplicationListener() {
return new ApplicationListener<AfterSaveEvent>() {
@Override
public void onApplicationEvent(AfterSaveEvent event) {
Bar bar = (Bar) event.getEntity();
bar.generatedId = "after event";
}
};
}
}
@Autowired
private Neo4jTemplate template;
@Test
public void shouldFireAfterEntityIsSaved() throws Exception {
Bar entity = new Bar();
assertThat(template.save(entity).generatedId, is("no event"));
assertThat(entity.generatedId, is("after event"));
assertThat(single(template.findAll(Bar.class)).generatedId, is("no event"));
}
}

View File

@@ -0,0 +1,79 @@
/**
* 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.lifecycle;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.test.ImpermanentGraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.neo4j.helpers.collection.IteratorUtil.single;
@NodeEntity
class Foo {
@GraphId
Long id;
String generatedId = "no event";
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
public class BeforeSaveEventTests {
@Configuration
@EnableNeo4jRepositories
static class TestConfig extends Neo4jConfiguration {
@Bean
GraphDatabaseService graphDatabaseService() {
return new ImpermanentGraphDatabase();
}
@Bean
ApplicationListener<BeforeSaveEvent> beforeSaveEventApplicationListener() {
return new ApplicationListener<BeforeSaveEvent>() {
@Override
public void onApplicationEvent(BeforeSaveEvent event) {
Foo foo = (Foo) event.getEntity();
foo.generatedId = "before event";
}
};
}
}
@Autowired
private Neo4jTemplate template;
@Test
public void shouldFireBeforeEntityIsSaved() throws Exception {
assertThat(template.save(new Foo()).generatedId, is("before event"));
assertThat(single(template.findAll(Foo.class)).generatedId, is("before event"));
}
}

View File

@@ -0,0 +1,107 @@
/**
* 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.lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.test.ImpermanentGraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;
import java.util.LinkedList;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.hasItem;
@NodeEntity
class Program {
@GraphId
Long id;
String name;
Program() {
}
public Program(String name) {
this.name = name;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
public class DeleteEventTests {
@Configuration
@EnableNeo4jRepositories
static class TestConfig extends Neo4jConfiguration {
@Bean
GraphDatabaseService graphDatabaseService() {
return new ImpermanentGraphDatabase();
}
@Bean
ApplicationListener<DeleteEvent<Program>> deleteEventApplicationListener() {
return new ApplicationListener<DeleteEvent<Program>>() {
@Override
public void onApplicationEvent(DeleteEvent<Program> event) {
deletions.add(event.getEntity());
}
};
}
}
@Autowired
Neo4jTemplate template;
@Autowired
GraphDatabaseService graphDatabaseService;
static final LinkedList<Program> deletions = new LinkedList<Program>();
@BeforeTransaction
public void beforeTransaction() {
Neo4jHelper.cleanDb(template);
}
@Before
public void before() {
deletions.clear();
}
@Test
public void shouldFireEventOnNodeDeletion() throws Exception {
Program sark = template.save(new Program("Sark"));
template.delete(sark);
assertThat(deletions, hasItem(sark));
}
}

View File

@@ -0,0 +1,332 @@
/**
* 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.lifecycle;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.test.ImpermanentGraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.annotation.*;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.matchers.JUnitMatchers.either;
import static org.springframework.data.neo4j.SetHelper.asSet;
@RelationshipEntity(type = "slew")
class Slaying {
@GraphId
Long id;
@StartNode
FictionalCharacter slayor;
@EndNode
FictionalCharacter slayee;
String battle;
Slaying() {
}
Slaying(FictionalCharacter slayor, FictionalCharacter slayee, String battle) {
this.slayor = slayor;
this.slayee = slayee;
this.battle = battle;
}
}
@NodeEntity
class FictionalCharacter {
@GraphId
Long id;
String name;
@RelatedToVia
Slaying slew;
@RelatedToVia(type = "slayered")
Set<Slaying> slayings;
FictionalCharacter() {
}
FictionalCharacter(String name) {
this.name = name;
}
void slew(FictionalCharacter fictionalCharacter, String battle) {
slew = new Slaying(this, fictionalCharacter, battle);
}
void slew(FictionalCharacter... victims) {
slayings = new HashSet<Slaying>();
for (FictionalCharacter victim : victims) {
slayings.add(new Slaying(this, victim, null));
}
}
}
@NodeEntity
class Parent {
@GraphId
Long id;
String name;
@Fetch
Child eldest;
@Fetch
Set<Child> youngsters;
Parent() {
}
Parent(Child eldest) {
this.eldest = eldest;
}
public Parent(String name) {
this.name = name;
}
public Parent(Set<Child> youngsters) {
this.youngsters = youngsters;
}
}
@NodeEntity
class Child {
@GraphId
Long id;
String name;
Child() {
}
Child(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Child child = (Child) o;
if (id != null ? !id.equals(child.id) : child.id != null) return false;
if (name != null ? !name.equals(child.name) : child.name != null) return false;
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
public class SaveEventTests {
@Configuration
@EnableNeo4jRepositories
static class TestConfig extends Neo4jConfiguration {
@Bean
GraphDatabaseService graphDatabaseService() {
return new ImpermanentGraphDatabase();
}
@Bean
ApplicationListener<BeforeSaveEvent> beforeSaveEventApplicationListener() {
return new ApplicationListener<BeforeSaveEvent>() {
@Override
public void onApplicationEvent(BeforeSaveEvent event) {
entities.add(event.getEntity());
}
};
}
}
@Autowired
Neo4jTemplate template;
static final LinkedList<Object> entities = new LinkedList<Object>();
@BeforeTransaction
public void beforeTransaction() {
Neo4jHelper.cleanDb(template);
}
@Before
public void before() {
entities.clear();
}
@Test
public void shouldFireEventForNodeEntity() throws Exception {
Child child = new Child();
template.save(child);
assertThat(entities.size(), is(1));
assertThat((Child) entities.get(0), is(child));
}
@Test
public void shouldFireEventForRelationshipEntity() throws Exception {
FictionalCharacter fingolfin = template.save(new FictionalCharacter("Fingolfin"));
FictionalCharacter morgoth = template.save(new FictionalCharacter("Morgoth"));
entities.clear();
Slaying slaying = new Slaying(morgoth, fingolfin, "Dagor Bragollach");
template.save(slaying);
assertThat(entities.size(), is(1));
assertThat(((Slaying) entities.get(0)).battle, is(equalTo("Dagor Bragollach")));
}
@Test
public void shouldFireEventForNodeEntities() throws Exception {
Child child1 = new Child("Huey");
Child child2 = new Child("Louie");
Child child3 = new Child("Dewey");
Parent parent = new Parent(asSet(child1, child2, child3));
template.save(parent);
assertThat(entities.size(), is(4));
assertThat((Parent) entities.get(0), is(parent));
assertThat((Child) entities.get(1), is(child1));
assertThat((Child) entities.get(2), is(child2));
assertThat((Child) entities.get(3), is(child3));
}
@Test
public void shouldFireEventForRelationshipEntities() throws Exception {
FictionalCharacter beleg = template.save(new FictionalCharacter("Beleg"));
FictionalCharacter brandir = template.save(new FictionalCharacter("Brandir"));
entities.clear();
FictionalCharacter turin = new FictionalCharacter("Túrin");
turin.slew(beleg, brandir);
template.save(turin);
assertThat(entities.size(), is(3));
assertThat(((FictionalCharacter) entities.get(0)).id, is(turin.id));
assertThat(((Slaying) entities.get(1)).slayee.id, is(either(equalTo(beleg.id)).or(equalTo(brandir.id))));
assertThat(((Slaying) entities.get(2)).slayee.id, is(either(equalTo(beleg.id)).or(equalTo(brandir.id))));
}
@Test
public void shouldFireEventForNodeEntityWhenItIsSavedIndirectly() throws Exception {
Child child = new Child();
Parent parent = new Parent(child);
template.save(parent);
assertThat(entities.size(), is(2));
assertThat((Parent) entities.get(0), is(parent));
assertThat((Child) entities.get(1), is(child));
}
@Test
public void shouldFireEventForRelationshipEntityWhenItIsSavedIndirectly() throws Exception {
FictionalCharacter fingolfin = template.save(new FictionalCharacter("Fingolfin"));
entities.clear();
FictionalCharacter morgoth = new FictionalCharacter("Morgoth");
morgoth.slew(fingolfin, "Dagor Bragollach");
template.save(morgoth);
assertThat(entities.size(), is(2));
assertThat(((FictionalCharacter) entities.get(0)).id, is(morgoth.id));
assertThat(((Slaying) entities.get(1)).battle, is(equalTo("Dagor Bragollach")));
}
@Test
public void shouldFireEventForUpdatedEntities() throws Exception {
Parent parent = template.save(new Parent("Donald Duck"));
entities.clear();
parent = template.findOne(parent.id, Parent.class);
parent.name = "Mickey Mouse";
template.save(parent);
assertThat(entities.size(), is(1));
assertThat((Parent) entities.get(0), is(parent));
}
@Test
public void shouldFireEventEvenIfEntityHasNotBeenUpdated() throws Exception {
Parent parent = template.save(new Parent("Uncle Scrooge"));
entities.clear();
parent = template.findOne(parent.id, Parent.class);
template.save(parent);
assertThat(entities.size(), is(1));
assertThat((Parent) entities.get(0), is(parent));
}
/**
* because we do not save recursively, the child entity isn't saved and thus no event is fired
* <p/>
* so this is basically saying, "we do not save recursively"
* <p/>
* is that different in the advanced mapping mode case?
*/
@Test
public void shouldNotFireEventForUpdatedRelatedEntities() throws Exception {
Parent parent = template.save(new Parent(new Child("Daisy Duck")));
entities.clear();
parent = template.findOne(parent.id, Parent.class);
parent.eldest.name = "Minnie Mouse";
template.save(parent);
assertThat(entities.size(), is(1));
assertThat((Parent) entities.get(0), is(parent));
}
}

View File

@@ -107,4 +107,35 @@
via the graph database.
</para>
</section>
<section>
<title>Lifecycle Events</title>
<para>Neo4j Template offers basic lifecycle events via Spring's event mechanism using ApplicationListener and ApplicationEvent. The following hooks are available in the form of types of application event:</para>
<itemizedlist>
<listitem><para>BeforeSaveEvent</para></listitem>
<listitem><para>AfterSaveEvent</para></listitem>
<listitem><para>DeleteEvent - after the event has been deleted</para></listitem>
</itemizedlist>
<para>The following example demostrates how to register a listener and perform behaviour across types of entities</para>
<example>
<title>Timestamping Entities</title>
<programlisting language="java">
<![CDATA[@Configuration
@EnableNeo4jRepositories
public class ApplicationConfig extends Neo4jConfiguration {
...
@Bean
ApplicationListener<BeforeSaveEvent> beforeSaveEventApplicationListener() {
return new ApplicationListener<BeforeSaveEvent>() {
@Override
public void onApplicationEvent(BeforeSaveEvent event) {
Auditable entity = (Auditable) event.getEntity();
entity.setLastUpdated(new Date());
}
};
}
...]]>
</programlisting>
</example>
<para>Changes made to entities in the before-save event handler are reflected in the stored entity - after-save ones are not.</para>
</section>
</section>