DATAGRAPH-251 @RelatedToVia, annotation-provided relationship type and precedence

This commit is contained in:
Lasse Westh-Nielsen
2012-06-14 17:49:41 +01:00
committed by Michael Hunger
parent e36c771098
commit aaa72aaf8c
30 changed files with 818 additions and 73 deletions

View File

@@ -90,13 +90,13 @@ public class RelatedToViaCollectionFieldAccessorFactory implements FieldAccessor
final Map<Node, Object> endNodeToEntityMapping = loadEndNodeToRelationshipEntityMapping(newVal, startNode);
relationshipHelper.removeMissingRelationshipsInStoreAndKeepOnlyNewRelationShipsInSet(startNode, endNodeToEntityMapping.keySet());
persistEntities(endNodeToEntityMapping.values());
persistEntities(endNodeToEntityMapping.values(), relationshipHelper.getRelationshipType());
return createManagedSet(entity, (Set<?>) newVal, property.obtainMappingPolicy(mappingPolicy));
}
private void persistEntities(final Collection<Object> relationshipEntities) {
private void persistEntities( final Collection<Object> relationshipEntities, RelationshipType relationshipType ) {
for (Object entity : relationshipEntities) {
template.save(entity);
template.save(entity, relationshipType);
}
}

View File

@@ -78,7 +78,7 @@ public class RelatedToViaSingleFieldAccessorFactory implements FieldAccessorFact
final Node startNode = relationshipHelper.checkAndGetNode(entity);
final Map<Node,Object> endNodeToEntityMapping = relationshipEntities.loadEndNodeToRelationshipEntityMapping(startNode, toSet(newVal), relatedType);
relationshipHelper.removeMissingRelationshipsInStoreAndKeepOnlyNewRelationShipsInSet(startNode, endNodeToEntityMapping.keySet());
persistEntities(endNodeToEntityMapping.values());
persistEntities(endNodeToEntityMapping.values(), relationshipHelper.getRelationshipType() );
return newVal;
}
@@ -87,9 +87,9 @@ public class RelatedToViaSingleFieldAccessorFactory implements FieldAccessorFact
return Collections.singleton(newVal);
}
private void persistEntities(final Collection<Object> relationshipEntities) {
private void persistEntities( final Collection<Object> relationshipEntities, RelationshipType relationshipType ) {
for (Object entity : relationshipEntities) {
template.save(entity);
template.save(entity, relationshipType);
}
}

View File

@@ -148,4 +148,9 @@ public class RelationshipHelper {
public Relationship getSingleRelationship(Node node) {
return node.getSingleRelationship(type,direction);
}
public RelationshipType getRelationshipType()
{
return type;
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.neo4j.support.Neo4jTemplate;
@@ -27,7 +28,8 @@ import org.springframework.data.neo4j.support.mapping.Neo4jPersistentEntityImpl;
*/
public interface Neo4jEntityConverter<T, S extends PropertyContainer> {
<R extends T> R read(Class<R> type, S source, MappingPolicy mappingPolicy, final Neo4jTemplate template);
void write(T source, S sink, MappingPolicy mappingPolicy, final Neo4jTemplate template);
void write( T source, S sink, MappingPolicy mappingPolicy, final Neo4jTemplate template, RelationshipType
annotationProvidedRelationshipType );
MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> getMappingContext();
ConversionService getConversionService();
<R extends T> R loadEntity(R entity, S source, MappingPolicy mappingPolicy, Neo4jPersistentEntityImpl<R> persistentEntity, final Neo4jTemplate template);

View File

@@ -19,6 +19,7 @@ package org.springframework.data.neo4j.mapping;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.annotation.RelatedTo;
import org.springframework.data.neo4j.annotation.RelatedToVia;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
@@ -96,7 +97,8 @@ public class RelationshipInfo {
final TypeInformation<?> relationshipEntityType = elementClass(annotation, typeInformation);
final RelationshipEntity relationshipEntity = relationshipEntityType.getType().getAnnotation(RelationshipEntity.class);
if (!relationshipEntity.type().isEmpty()) return relationshipEntity.type();
return field.getName();
throw new MappingException( "Relationship entity must have a default type" );
}
private static TypeInformation<?> elementClass(RelatedToVia annotation, TypeInformation<?> typeInformation) {

View File

@@ -293,7 +293,12 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
@SuppressWarnings("unchecked")
public <T> T save(T entity) {
return (T) infrastructure.getEntityPersister().persist(entity, getMappingPolicy(entity), this);
return save( entity, null );
}
public <T> T save( T entity, RelationshipType annotationProvidedRelationshipType )
{
return (T) infrastructure.getEntityPersister().persist(entity, getMappingPolicy(entity), this, annotationProvidedRelationshipType );
}
public boolean isManaged(Object entity) {
@@ -650,7 +655,7 @@ public class Neo4jTemplate implements Neo4jOperations {
public MappingPolicy getMappingPolicy(Object entity) {
ParameterCheck.notNull(entity,"entity");
return getMappingPolicy(entity.getClass());
return getMappingPolicy( entity.getClass() );
}
public StoredEntityType getStoredEntityType(Object entity) {

View File

@@ -118,7 +118,8 @@ public class EntityStateHandler {
}
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> S useOrCreateState(Object entity, S state) {
public <S extends PropertyContainer> S useOrCreateState( Object entity, S state, RelationshipType
annotationProvidedRelationshipType ) {
if (state != null) return state;
final S containedState = getPersistentState(entity);
if (containedState != null) return containedState;
@@ -131,7 +132,7 @@ public class EntityStateHandler {
return (S) graphDatabase.createNode(null);
}
if (persistentEntity.isRelationshipEntity()) {
return getOrCreateRelationship(entity, persistentEntity);
return getOrCreateRelationship(entity, persistentEntity, annotationProvidedRelationshipType );
}
throw new IllegalArgumentException("The entity " + persistentEntity.getEntityName() + " has to be either annotated with @NodeEntity or @RelationshipEntity");
}
@@ -144,13 +145,13 @@ public class EntityStateHandler {
}
@SuppressWarnings("unchecked")
private <S extends PropertyContainer> S getOrCreateRelationship(Object entity, Neo4jPersistentEntity<?> persistentEntity) {
private <S extends PropertyContainer> S getOrCreateRelationship( Object entity, Neo4jPersistentEntity<?> persistentEntity, RelationshipType annotationProvidedRelationshipType ) {
final RelationshipProperties relationshipProperties = persistentEntity.getRelationshipProperties();
final Neo4jPersistentProperty startNodeProperty = relationshipProperties.getStartNodeProperty();
Node startNode = (Node) getPersistentState(startNodeProperty.getValue(entity, startNodeProperty.getMappingPolicy()));
final Neo4jPersistentProperty endNodeProperty = relationshipProperties.getEndNodeProperty();
Node endNode = (Node) getPersistentState(endNodeProperty.getValue(entity, endNodeProperty.getMappingPolicy()));
RelationshipType relationshipType = getRelationshipType(persistentEntity,entity);
RelationshipType relationshipType = getRelationshipType(persistentEntity,entity, annotationProvidedRelationshipType );
if (persistentEntity.isUnique()) {
final Neo4jPersistentProperty uniqueProperty = persistentEntity.getUniqueProperty();
final IndexInfo indexInfo = uniqueProperty.getIndexInfo();
@@ -163,20 +164,36 @@ public class EntityStateHandler {
return (S) graphDatabase.createRelationship(startNode, endNode, relationshipType, Collections.<String,Object>emptyMap());
}
private RelationshipType getRelationshipType(Neo4jPersistentEntity persistentEntity, Object entity) {
private RelationshipType getRelationshipType( Neo4jPersistentEntity persistentEntity, Object entity,
RelationshipType annotationProvidedRelationshipType )
{
final RelationshipProperties relationshipProperties = persistentEntity.getRelationshipProperties();
final Neo4jPersistentProperty typeProperty = relationshipProperties.getTypeProperty();
Object relType;
if (typeProperty!=null) {
relType = typeProperty.getValue(entity, typeProperty.getMappingPolicy());
} else {
relType = relationshipProperties.getRelationshipType();
if ( typeProperty != null )
{
Object value = typeProperty.getValue( entity, typeProperty.getMappingPolicy() );
if ( value != null )
{
return value instanceof RelationshipType ? (RelationshipType) value : DynamicRelationshipType
.withName( value.toString() );
}
}
if (relType instanceof RelationshipType) {
return (RelationshipType) relType;
if ( annotationProvidedRelationshipType != null )
{
return annotationProvidedRelationshipType;
}
if (relType==null) throw new MappingException("Could not determine relationship-type for "+persistentEntity.getName());
return DynamicRelationshipType.withName(relType.toString());
String relationshipTypeAsString = relationshipProperties.getRelationshipType();
if ( relationshipTypeAsString == null )
{
throw new MappingException( "Could not determine relationship-type for " + persistentEntity.getName() );
}
return DynamicRelationshipType.withName( relationshipTypeAsString );
}
public RelationshipResult relateTo(Object source, Object target, String type) {

View File

@@ -16,6 +16,7 @@
package org.springframework.data.neo4j.support.mapping;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.Association;
@@ -133,7 +134,8 @@ public class Neo4jEntityConverterImpl<T,S extends PropertyContainer> implements
}
@Override
public void write(T source, S sink, MappingPolicy mappingPolicy, final Neo4jTemplate template) {
public void write( T source, S sink, MappingPolicy mappingPolicy, final Neo4jTemplate template, RelationshipType
annotationProvidedRelationshipType ) {
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 ??
@@ -143,7 +145,7 @@ public class Neo4jEntityConverterImpl<T,S extends PropertyContainer> implements
final BeanWrapper<Neo4jPersistentEntity<T>, T> wrapper = BeanWrapper.<Neo4jPersistentEntity<T>, T>create(source, conversionService);
if (sink == null) {
sink = entityStateHandler.useOrCreateState(source,sink); // todo handling of changed state
sink = entityStateHandler.useOrCreateState(source,sink, annotationProvidedRelationshipType ); // todo handling of changed state
entityStateHandler.setPersistentState(source, sink);
typeMapper.writeType(sourceType, sink);
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.support.mapping;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.neo4j.mapping.*;
@@ -173,8 +174,9 @@ public class Neo4jEntityPersister implements EntityPersister, Neo4jEntityConvert
}
@Override
public void write(Object source, S sink, MappingPolicy mappingPolicy, final Neo4jTemplate template) {
delegate.write(source,sink,mappingPolicy, template);
public void write( Object source, S sink, MappingPolicy mappingPolicy, final Neo4jTemplate template,
RelationshipType annotationProvidedRelationshipType ) {
delegate.write(source,sink,mappingPolicy, template, annotationProvidedRelationshipType );
}
}
@@ -220,12 +222,13 @@ public class Neo4jEntityPersister implements EntityPersister, Neo4jEntityConvert
}
public Object persist(Object entity, final MappingPolicy mappingPolicy, final Neo4jTemplate template) {
public Object persist( Object entity, final MappingPolicy mappingPolicy, final Neo4jTemplate template,
RelationshipType annotationProvidedRelationshipType ) {
final Class<?> type = entity.getClass();
if (isManaged(entity)) {
return ((ManagedEntity)entity).persist();
} else {
return persist(entity, type, mappingPolicy, template);
return persist(entity, type, mappingPolicy, template, annotationProvidedRelationshipType );
}
}
@@ -233,16 +236,17 @@ public class Neo4jEntityPersister implements EntityPersister, Neo4jEntityConvert
return entityStateHandler.isManaged(entity);
}
private Object persist(Object entity, Class<?> type, MappingPolicy mappingPolicy, final Neo4jTemplate template) {
private Object persist( Object entity, Class<?> type, MappingPolicy mappingPolicy, final Neo4jTemplate template,
RelationshipType annotationProvidedRelationshipType ) {
if (isNodeEntity(type)) {
final Node node = this.<Node>getPersistentState(entity);
this.nodeConverter.write(entity, node,mappingPolicy, template);
this.nodeConverter.write(entity, node,mappingPolicy, template, null );
return createEntityFromState(getPersistentState(entity),type, getMappingPolicy(type), template);
//return entity; // TODO ?
}
if (isRelationshipEntity(type)) {
final Relationship relationship = this.<Relationship>getPersistentState(entity);
this.relationshipConverter.write(entity, relationship,mappingPolicy, template);
this.relationshipConverter.write(entity, relationship,mappingPolicy, template, annotationProvidedRelationshipType );
return createEntityFromState(getPersistentState(entity),type, getMappingPolicy(type), template);
// return entity; // TODO ?
}
@@ -291,8 +295,9 @@ public class Neo4jEntityPersister implements EntityPersister, Neo4jEntityConvert
}
@Override
public void write(Object source, Node sink, MappingPolicy mappingPolicy, final Neo4jTemplate template) {
nodeConverter.write(source,sink,mappingPolicy, template);
public void write( Object source, Node sink, MappingPolicy mappingPolicy, final Neo4jTemplate template,
RelationshipType annotationProvidedRelationshipType ) {
nodeConverter.write(source,sink,mappingPolicy, template, annotationProvidedRelationshipType );
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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.annotation;
import java.util.HashSet;
import java.util.Set;
@NodeEntity
public class City extends IdentifiableEntity {
private String name;
@RelatedToVia
private Set<TransportInfrastructure> existingMetroLines = new HashSet<TransportInfrastructure>();
@RelatedToVia(type = "planned")
private Set<TransportInfrastructure> plannedMetroLines = new HashSet<TransportInfrastructure>();
public City() {
}
public City( String name ) {
this.name = name;
}
public void has( MetroLine... metroLines ) {
addTo( metroLines, existingMetroLines );
}
public void plans( MetroLine... metroLines ) {
addTo( metroLines, plannedMetroLines );
}
private void addTo( MetroLine[] metroLines, Set<TransportInfrastructure> transportInfrastructures ) {
for ( MetroLine metroLine : metroLines ) {
transportInfrastructures.add( new TransportInfrastructure( this, metroLine ) );
}
}
public Set<MetroLine> getCurrentMetroLines() {
return getMetroLines( existingMetroLines );
}
public Set<MetroLine> getFutureMetroLines() {
return getMetroLines( plannedMetroLines );
}
private Set<MetroLine> getMetroLines( Set<TransportInfrastructure> transportInfrastructures ) {
HashSet<MetroLine> metroLines = new HashSet<MetroLine>();
for ( TransportInfrastructure transportInfrastructure : transportInfrastructures ) {
metroLines.add( transportInfrastructure.getMetroLine() );
}
return metroLines;
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.annotation;
import org.springframework.data.neo4j.repository.CRUDRepository;
public interface CityRepository extends CRUDRepository<City>
{
}

View File

@@ -0,0 +1,47 @@
/**
* 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.annotation;
@NodeEntity
public class Country extends IdentifiableEntity
{
private String name;
@RelatedToVia
private InterCountryRelationship acquaintance;
@RelatedToVia(type = "special")
private InterCountryRelationship friend;
public Country()
{
}
public Country( String name )
{
this.name = name;
}
public void hasCordialRelationsWith( Country country, String description )
{
acquaintance = new InterCountryRelationship(this, country, description );
}
public void hasSpecialRelationsWith( Country country )
{
friend = new InterCountryRelationship( this, country, "The Special Relationship" );
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.annotation;
import org.springframework.data.neo4j.repository.CRUDRepository;
public interface CountryRepository extends CRUDRepository<Country>
{
}

View File

@@ -0,0 +1,35 @@
/**
* 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.annotation;
public abstract class IdentifiableEntity {
@GraphId
private Long id;
public Long getId() {
return id;
}
@Override
public boolean equals( Object obj ) {
return obj instanceof IdentifiableEntity && id.equals( ((IdentifiableEntity) obj).getId() );
}
@Override
public int hashCode() {
return 0;
}
}

View File

@@ -0,0 +1,42 @@
/**
* 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.annotation;
@RelationshipEntity(type = "cordial")
public class InterCountryRelationship
{
@GraphId
private Long id;
@StartNode
private Country a;
@EndNode
private Country b;
private String description;
public InterCountryRelationship()
{
}
public InterCountryRelationship( Country a, Country b, String description )
{
this.a = a;
this.b = b;
this.description = description;
}
}

View File

@@ -0,0 +1,28 @@
/**
* 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.annotation;
@NodeEntity
public class MetroLine extends IdentifiableEntity {
private String name;
public MetroLine() {
}
public MetroLine( String name ) {
this.name = name;
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.annotation;
import org.springframework.data.neo4j.repository.CRUDRepository;
public interface MetroLineRepository extends CRUDRepository<MetroLine>
{
}

View File

@@ -0,0 +1,48 @@
/**
* 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.annotation;
@RelationshipEntity(type = "unrelated")
public class PerpetualRelations
{
@GraphId
private Long id;
@StartNode
private SuperState ss1;
@EndNode
private SuperState ss2;
@RelationshipType
private String status;
public PerpetualRelations()
{
}
public PerpetualRelations( SuperState ss1, SuperState ss2 )
{
this.ss1 = ss1;
this.ss2 = ss2;
}
public PerpetualRelations( SuperState ss1, SuperState ss2, String status )
{
this(ss1, ss2 );
this.status = status;
}
}

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.annotation;
@NodeEntity
public class Player extends IdentifiableEntity
{
private String name;
public Player()
{
}
public Player( String name )
{
this.name = name;
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.annotation;
import org.springframework.data.neo4j.repository.CRUDRepository;
public interface PlayerRepository extends CRUDRepository<Player>
{
}

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.annotation;
@RelationshipEntity(type = "notplaying")
public class PlayerStatus
{
@GraphId
private Long id;
@StartNode
private Team team;
@EndNode
private Player player;
@RelationshipType
private String status;
public PlayerStatus()
{
}
public PlayerStatus( Team team, Player player )
{
this.team = team;
this.player = player;
}
public PlayerStatus( Team team, Player player, String status )
{
this( team, player );
this.status = status;
}
}

View File

@@ -21,14 +21,18 @@ import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.matchers.JUnitMatchers.hasItem;
import static org.neo4j.helpers.collection.IteratorUtil.first;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.Relationship;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException;
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;
@@ -36,23 +40,47 @@ import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:related-to-via-test-context.xml"})
@Transactional
public class RelatedToViaTests
{
public class RelatedToViaTests {
public static final String DISTRICT_LINE = "District Line";
public static final String CENTRAL_LINE = "Central Line";
@Autowired
private UndergroundRepository tfl;
@Autowired
private CountryRepository countries;
@Autowired
private SuperStateRepository dystopia;
@Autowired
private MetroLineRepository metroLines;
@Autowired
private CityRepository cities;
@Autowired
private PlayerRepository players;
@Autowired
private TeamRepository teams;
@Autowired
private Neo4jTemplate template;
@Before
public void before()
{
public void before() {
tfl.deleteAll();
countries.deleteAll();
dystopia.deleteAll();
metroLines.deleteAll();
cities.deleteAll();
players.deleteAll();
teams.deleteAll();
}
@Test
public void shouldMapRelationshipAsFirstClassCitizen() throws Exception
{
public void shouldMapRelationshipAsFirstClassCitizen() throws Exception {
TubeStation westHam = tfl.save( new TubeStation( "West Ham" ) );
TubeStation stratford = tfl.save( new TubeStation( "Stratford" ) );
TubeStation mileEnd = new TubeStation( "Mile End" );
@@ -61,29 +89,108 @@ public class RelatedToViaTests
tfl.save( mileEnd );
assertThat( first( template.getNode( mileEnd.getId() ).getRelationships() ).getType().name(), is( "route" ) );
mileEnd = tfl.findOne( mileEnd.getId() );
Line line = mileEnd.getLines().iterator().next();
assertThat( mileEnd.getId(), is( equalTo( line.getOrigin().getId() ) ) );
Line line = first( mileEnd.getLines());
assertThat( mileEnd, is( equalTo( line.getOrigin() ) ) );
assertThat( asList( DISTRICT_LINE, CENTRAL_LINE ), hasItem( line.getName() ) );
assertThat( asList( westHam.getId(), stratford.getId() ), hasItem( line.getDestination().getId() ) );
assertThat( asList( westHam, stratford ), hasItem( line.getDestination() ) );
}
@Test
public void shouldValidateEndNode() throws Exception
{
public void shouldGivePrecedenceToAnnotationProvidedRelationshipTypeOverDefault() throws Exception {
Country france = countries.save( new Country( "République française" ) );
Country usa = countries.save( new Country( "United States of America" ) );
Country uk = new Country( "United Kingdom of Great Britain and Northern Ireland" );
uk.hasCordialRelationsWith( france, "Entente Cordiale" );
uk.hasSpecialRelationsWith( usa );
countries.save( uk );
assertThat( getRelationshipNames( uk ), is( equalTo( asSet( "cordial", "special" ) ) ) );
}
@Test
public void shouldGivePrecedenceToAnnotationProvidedRelationshipTypeOverDefaultForCollections() throws Exception {
MetroLine m1 = metroLines.save( new MetroLine( "M1 - Vanløse to Vestamager" ) );
MetroLine m2 = metroLines.save( new MetroLine( "M2 - Vanløse to Lufthavnen" ) );
MetroLine m3 = metroLines.save( new MetroLine( "M3 - Cityringen" ) );
MetroLine m4 = metroLines.save( new MetroLine( "M4 - Nørrebro to København H" ) );
City copenhagen = new City( "Copenhagen" );
copenhagen.has( m1, m2 );
copenhagen.plans( m3, m4 );
cities.save( copenhagen );
assertThat( getRelationshipNames( copenhagen ), is( equalTo( asSet( "existing", "planned" ) ) ) );
assertThat( copenhagen.getCurrentMetroLines(), is( equalTo( asSet( m1, m2 ) ) ) );
assertThat( copenhagen.getFutureMetroLines(), is( equalTo( asSet( m3, m4 ) ) ) );
}
@Test
public void shouldGivePrecedenceToDynamicRelationshipTypeOverAnnotationProvidedRelationshipType() throws Exception {
SuperState eastasia = dystopia.save( new SuperState( "Eastasia" ) );
SuperState eurasia = dystopia.save( new SuperState( "Eurasia" ) );
SuperState oceania = new SuperState( "Oceania" );
oceania.isAlliedWith( eastasia );
dystopia.save( oceania );
assertThat( getFirstRelationshipName( oceania ), is( equalTo( "alliedWith" ) ) );
oceania = dystopia.findOne( oceania.getId() );
oceania.isAtWarWith( eurasia );
dystopia.save( oceania );
assertThat( getFirstRelationshipName( oceania ), is( equalTo( "atWarWith" ) ) );
}
@Test
public void shouldGivePrecedenceToDynamicRelationshipTypeOverAnnotationProvidedRelationshipTypeForCollections()
throws Exception {
Player jordan = players.save( new Player( "Michael Jordan" ) );
Player pippen = players.save( new Player( "Scottie Pippen" ) );
Team bulls = new Team( "Chicago Bulls" );
bulls.add( new PlayerStatus( bulls, jordan ) );
bulls.add( new PlayerStatus( bulls, pippen, "substitute" ) );
teams.save( bulls );
assertThat( getRelationshipNames( bulls ), is( equalTo( asSet( "starter", "substitute" ) ) ) );
}
@Test
public void shouldValidateEndNode() throws Exception {
TubeStation mileEnd = new TubeStation( "East Ham" );
mileEnd.connectsTo( null, DISTRICT_LINE );
try
{
try {
tfl.save( mileEnd );
fail();
}
catch ( InvalidDataAccessApiUsageException e )
{
catch ( InvalidDataAccessApiUsageException e ) {
assertThat( e.getCause().getMessage(), is( equalTo( "End node must not be null (org.springframework.data" +
".neo4j.annotation.Line)" ) ) );
}
}
private String getFirstRelationshipName( IdentifiableEntity entity ) {
return first( template.getNode( entity.getId() ).getRelationships() ).getType().name();
}
private Set<String> getRelationshipNames( IdentifiableEntity entity ) {
HashSet<String> relationshipNames = new HashSet<String>();
for ( Relationship relationship : template.getNode( entity.getId() ).getRelationships() ) {
relationshipNames.add( relationship.getType().name() );
}
return relationshipNames;
}
private <T> Set<T> asSet( T... ts ) {
return new HashSet<T>( asList( ts ) );
}
}

View File

@@ -0,0 +1,39 @@
/**
* 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.annotation;
@NodeEntity
public class SuperState extends IdentifiableEntity {
private String name;
@RelatedToVia(type = "alliedWith")
private PerpetualRelations neighbour;
public SuperState() {
}
public SuperState( String name ) {
this.name = name;
}
public void isAlliedWith( SuperState superState ) {
neighbour = new PerpetualRelations( this, superState );
}
public void isAtWarWith( SuperState superState ) {
neighbour = new PerpetualRelations( this, superState, "atWarWith" );
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.annotation;
import org.springframework.data.neo4j.repository.CRUDRepository;
public interface SuperStateRepository extends CRUDRepository<SuperState>
{
}

View File

@@ -0,0 +1,43 @@
/**
* 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.annotation;
import java.util.HashSet;
import java.util.Set;
@NodeEntity
public class Team extends IdentifiableEntity
{
private String name;
@RelatedToVia(type = "starter")
private Set<PlayerStatus> starters = new HashSet<PlayerStatus>();
public Team()
{
}
public Team( String name )
{
this.name = name;
}
public void add( PlayerStatus playerStatus )
{
starters.add( playerStatus );
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.annotation;
import org.springframework.data.neo4j.repository.CRUDRepository;
public interface TeamRepository extends CRUDRepository<Team>
{
}

View File

@@ -0,0 +1,49 @@
/**
* 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.annotation;
@RelationshipEntity(type = "existing")
public class TransportInfrastructure
{
@GraphId
private Long id;
@StartNode
private City city;
@EndNode
private MetroLine metroLine;
public TransportInfrastructure()
{
}
public TransportInfrastructure( City city, MetroLine metroLine )
{
this.city = city;
this.metroLine = metroLine;
}
public Long getId()
{
return id;
}
public MetroLine getMetroLine()
{
return metroLine;
}
}

View File

@@ -19,43 +19,29 @@ import java.util.HashSet;
import java.util.Set;
@NodeEntity
public class TubeStation
{
@GraphId
private Long id;
public class TubeStation extends IdentifiableEntity {
private String name;
@RelatedToVia
private Set<Line> lines = new HashSet<Line>();
public TubeStation()
{
public TubeStation() {
}
TubeStation( String name )
{
TubeStation( String name ) {
this.name = name;
}
public Long getId()
{
return id;
}
public void connectsTo( TubeStation tubeStation, String line )
{
public void connectsTo( TubeStation tubeStation, String line ) {
lines.add( new Line( this, tubeStation, line ) );
}
public Set<Line> getLines()
{
public Set<Line> getLines() {
return lines;
}
public String getName()
{
public String getName() {
return name;
}
}

View File

@@ -62,7 +62,7 @@ public class Neo4jEntityPersisterTest extends Neo4jPersistentTestBase {
@Test
public void testPersist() throws Exception {
entityPersister.persist(michael, template.getMappingPolicy(michael), template);
entityPersister.persist(michael, template.getMappingPolicy(michael), template, null );
assertEquals((Long) michaelNode().getId(), michael.getId());
assertEquals(michaelNode(), entityPersister.getPersistentState(michael));
assertEquals(michaelNode().getProperty("name"), michael.getName());

View File

@@ -192,7 +192,7 @@ public class Neo4jPersistentTestBase {
}
protected Object write(Object entity, Node node) {
entityPersister.write(entity, node, template.getMappingPolicy(entity), template);
entityPersister.write(entity, node, template.getMappingPolicy(entity), template, null );
return entity;
}