From 35b3d4da86b65917e7e6e4929c03aeb0cfed03e1 Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Sun, 25 Mar 2012 04:15:00 +0200 Subject: [PATCH] DATAGRAPH-214 numeric fields can be indexed non-numerically DATAGRAPH-181 fix for creating unique entities with numeric unique fields, still needs a fix in Neo4j --- .../data/neo4j/aspects/Group.java | 11 +++++ .../data/neo4j/aspects/support/IndexTest.java | 17 +++++++ .../data/neo4j/annotation/Indexed.java | 2 + ...gPropertyFieldAccessorListenerFactory.java | 2 +- .../data/neo4j/mapping/IndexInfo.java | 6 +++ .../repository/AbstractGraphRepository.java | 10 ++-- .../data/neo4j/support/Neo4jTemplate.java | 15 ++++-- .../data/neo4j/unique/UniqueEntityTest.java | 16 ++++++ .../unique/domain/UniqueNumericIdClub.java | 49 +++++++++++++++++++ .../UniqueNumericIdClubRepository.java | 26 ++++++++++ 10 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/domain/UniqueNumericIdClub.java create mode 100644 spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/repository/UniqueNumericIdClubRepository.java diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/Group.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/Group.java index 8f06f5f03..81c8a4a48 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/Group.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/Group.java @@ -89,6 +89,9 @@ public class Group { private String indexLevelName; private String[] roleNames; + @Indexed(numeric = false) + private Byte secret; + public Date getCreationDate() { return creationDate; } @@ -285,4 +288,12 @@ public class Group { public void setMentorship(Mentorship mentorship) { this.mentorship = mentorship; } + + public Byte getSecret() { + return secret; + } + + public void setSecret(Byte secret) { + this.secret = secret; + } } diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java index c65141e20..2f40dc451 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java @@ -21,6 +21,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.neo4j.graphdb.DynamicRelationshipType; import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.PropertyContainer; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; @@ -265,6 +266,22 @@ public class IndexTest extends EntityTestBase { assertEquals(new HashSet(Arrays.asList(group, group2)), result); } + @Test + @Transactional + public void testFindAllGroupsByNonNumericIndexedNumber() { + final Group group = new Group(); + final byte value = (byte) 100; + group.setSecret(value); + groupRepository.save(group); + final PropertyContainer node = neo4jTemplate.getPersistentState(group); + final Iterable found = this.groupRepository.findAllByPropertyValue("secret", value); + assertEquals(1, IteratorUtil.count(found)); + final Node foundWithTemplate = neo4jTemplate.lookup("Group","secret", value).to(Node.class).singleOrNull(); + assertEquals(node, foundWithTemplate); + final Node foundGroup = neo4jTemplate.getGraphDatabaseService().index().forNodes("Group").get("secret", value).getSingle(); + assertEquals(node, foundGroup); + } + @Test @Transactional public void shouldFindGroupyByQueryString() { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Indexed.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Indexed.java index 129d4c7a3..6016a8cec 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Indexed.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Indexed.java @@ -41,6 +41,8 @@ public @interface Indexed { boolean unique() default false; + boolean numeric() default true; + // FQN is a fix for javac compiler bug http://bugs.sun.com/view_bug.do?bug_id=6512707 org.springframework.data.neo4j.annotation.Indexed.Level level() default org.springframework.data.neo4j.annotation.Indexed.Level.CLASS; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java index 8b7e9843a..8a69f131b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java @@ -75,7 +75,7 @@ public class IndexingPropertyFieldAccessorListenerFactory index = template.getIndex(property, entity.getClass()); - if (newVal instanceof Number) newVal = ValueContext.numeric((Number) newVal); + if (newVal instanceof Number && property.getIndexInfo().isNumeric()) newVal = ValueContext.numeric((Number) newVal); final T state = template.getPersistentState(entity); index.remove(state, indexKey); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java index 3a5c7d4ec..b4c6d50af 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java @@ -29,6 +29,7 @@ public class IndexInfo { private final Indexed.Level level; private String indexKey; private final boolean unique; + private boolean numeric; public IndexInfo(Indexed annotation, Neo4jPersistentProperty property) { this.indexName = determineIndexName(annotation, property); @@ -37,6 +38,7 @@ public class IndexInfo { this.indexKey = fieldName.isEmpty() ? property.getNeo4jPropertyName() : fieldName; unique = annotation.unique(); level = annotation.level(); + numeric = annotation.numeric(); } @@ -66,4 +68,8 @@ public class IndexInfo { public boolean isUnique() { return unique; } + + public boolean isNumeric() { + return numeric; + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java index a7aeccd57..dafbd8623 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java @@ -33,6 +33,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.neo4j.annotation.QueryType; import org.springframework.data.neo4j.conversion.EndResult; import org.springframework.data.neo4j.conversion.Result; +import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.data.neo4j.support.index.NoSuchIndexException; import org.springframework.data.neo4j.support.index.NullReadableIndex; @@ -177,12 +178,13 @@ public abstract class AbstractGraphRepository im } - private IndexHits getIndexHits(String indexName, String property, Object value) { - if (value instanceof Number) { + private IndexHits getIndexHits(String indexName, String propertyName, Object value) { + final Neo4jPersistentProperty property = template.getPersistentProperty(clazz, propertyName); + if (value instanceof Number && (property==null || property.getIndexInfo().isNumeric())) { Number number = (Number) value; - return getIndex(indexName, property).query(property, createInclusiveRangeQuery(property, number,number)); + return getIndex(indexName, propertyName).query(propertyName, createInclusiveRangeQuery(propertyName, number,number)); } - return getIndex(indexName, property).get(property, value); + return getIndex(indexName, propertyName).get(propertyName, value); } protected ReadableIndex getIndex(String indexName, String property) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java index 98e4dc031..7d05c58e6 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java @@ -20,6 +20,7 @@ import org.neo4j.graphdb.*; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.traversal.TraversalDescription; import org.neo4j.helpers.collection.ClosableIterable; +import org.neo4j.index.lucene.ValueContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.convert.ConversionService; @@ -583,12 +584,19 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister { @Override public Index getIndex(Class indexedType, String propertyName) { - final Neo4jPersistentEntityImpl persistentEntity = getPersistentEntity(indexedType); - final Neo4jPersistentProperty property = persistentEntity.getPersistentProperty(propertyName); + final Neo4jPersistentProperty property = getPersistentProperty(indexedType, propertyName); 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; + final Neo4jPersistentEntityImpl persistentEntity = getPersistentEntity(type); + final int dotIndex = propertyName.lastIndexOf("."); + if (dotIndex >-1) propertyName = propertyName.substring(dotIndex, propertyName.length()); + return persistentEntity.getPersistentProperty(propertyName); + } + private IndexProvider getIndexProvider() { return infrastructure.getIndexProvider(); } @@ -677,9 +685,10 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister { public Node createUniqueNode(Object entity) { final Neo4jPersistentEntityImpl persistentEntity = getPersistentEntity(entity.getClass()); final Neo4jPersistentProperty uniqueProperty = persistentEntity.getUniqueProperty(); - final Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY); + Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY); if (value==null) return createNode(); final IndexInfo indexInfo = uniqueProperty.getIndexInfo(); + if (value instanceof Number && indexInfo.isNumeric()) value=ValueContext.numeric((Number)value); return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap()); } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/UniqueEntityTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/UniqueEntityTest.java index 20ffc7f78..1d7821644 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/UniqueEntityTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/UniqueEntityTest.java @@ -25,9 +25,11 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.neo4j.unique.domain.Club; import org.springframework.data.neo4j.unique.domain.InvalidClub; import org.springframework.data.neo4j.unique.domain.UniqueClub; +import org.springframework.data.neo4j.unique.domain.UniqueNumericIdClub; import org.springframework.data.neo4j.unique.repository.ClubRepository; import org.springframework.data.neo4j.unique.repository.InvalidClubRepository; import org.springframework.data.neo4j.unique.repository.UniqueClubRepository; +import org.springframework.data.neo4j.unique.repository.UniqueNumericIdClubRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @@ -51,6 +53,9 @@ public class UniqueEntityTest { @Autowired protected GraphDatabaseService graphDatabaseService; + @Autowired + private UniqueNumericIdClubRepository uniqueNumericIdClubRepository; + @Before public void setup() { clubRepository.deleteAll(); @@ -69,6 +74,17 @@ public class UniqueEntityTest { assertEquals(1, uniqueClubRepository.count()); } + @Test + public void shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity() { + UniqueNumericIdClub club = new UniqueNumericIdClub(); + club.setClubId(100L); + uniqueNumericIdClubRepository.save(club); + + club = new UniqueNumericIdClub(100L); + uniqueNumericIdClubRepository.save(club); + + assertEquals(1, uniqueNumericIdClubRepository.count()); + } @Test public void shouldCreateMultipleInstancesForNonUniqueNodeEntity() { diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/domain/UniqueNumericIdClub.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/domain/UniqueNumericIdClub.java new file mode 100644 index 000000000..f415556c5 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/domain/UniqueNumericIdClub.java @@ -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.unique.domain; + +import org.springframework.data.neo4j.annotation.GraphId; +import org.springframework.data.neo4j.annotation.Indexed; +import org.springframework.data.neo4j.annotation.NodeEntity; + +@NodeEntity +public class UniqueNumericIdClub { + + @Indexed(unique = true, numeric = false) + private Long clubId; + + @GraphId + Long id; + + public UniqueNumericIdClub() { + } + + public UniqueNumericIdClub(Long clubId) { + this.clubId = clubId; + } + + public Long getClubId() { + return clubId; + } + + public void setClubId(Long clubId) { + this.clubId = clubId; + } + + public Long getId() { + return id; + } +} \ No newline at end of file diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/repository/UniqueNumericIdClubRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/repository/UniqueNumericIdClubRepository.java new file mode 100644 index 000000000..bedf0b698 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/repository/UniqueNumericIdClubRepository.java @@ -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.unique.repository; + +import org.springframework.data.neo4j.repository.GraphRepository; +import org.springframework.data.neo4j.repository.NamedIndexRepository; +import org.springframework.data.neo4j.unique.domain.Club; +import org.springframework.data.neo4j.unique.domain.UniqueClub; +import org.springframework.data.neo4j.unique.domain.UniqueNumericIdClub; + +public interface UniqueNumericIdClubRepository extends GraphRepository, NamedIndexRepository { + +}