added support for arrays, iterables, collections, sets and lists of primitive and convertible values as node properties

This commit is contained in:
Michael Hunger
2011-11-05 07:00:14 +01:00
parent c355db7278
commit 94684c6aba
8 changed files with 282 additions and 28 deletions

View File

@@ -29,6 +29,7 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.index.IndexType;
import java.util.Collection;
import java.util.Set;
@NodeEntity
public class Group {
@@ -79,6 +80,7 @@ public class Group {
@Indexed(level=Indexed.Level.INSTANCE)
private String indexLevelName;
private String[] roleNames;
public String getFullTextName() {
return fullTextName;
@@ -124,6 +126,33 @@ public class Group {
this.otherName = otherName;
}
public void setRoleNames(String...roleNames) {
this.roleNames=roleNames;
}
public String[] getRoleNames() {
return roleNames;
}
public enum Role { ADMIN, USER }
Role[] roles;
public Role[] getRoles() {
return roles;
}
public void setRoles(Role...roles) {
this.roles = roles;
}
Collection<String> roleNamesColl;
Collection<Role> rolesColl;
Set<String> roleNamesSet;
Set<Role> rolesSet;
Iterable<String> roleNamesIterable;
Iterable<Role> rolesIterable;
private static class PeopleTraversalBuilder implements FieldTraversalDescriptionBuilder {
@Override
public TraversalDescription build(Object start, Neo4jPersistentProperty property, String...params) {
@@ -177,4 +206,52 @@ public class Group {
public Iterable<Relationship> getPeopleRelationships() {
return peopleRelationships;
}
public Collection<String> getRoleNamesColl() {
return roleNamesColl;
}
public void setRoleNamesColl(Collection<String> roleNamesColl) {
this.roleNamesColl = roleNamesColl;
}
public Collection<Role> getRolesColl() {
return rolesColl;
}
public void setRolesColl(Collection<Role> rolesColl) {
this.rolesColl = rolesColl;
}
public Set<String> getRoleNamesSet() {
return roleNamesSet;
}
public void setRoleNamesSet(Set<String> roleNamesSet) {
this.roleNamesSet = roleNamesSet;
}
public Set<Role> getRolesSet() {
return rolesSet;
}
public void setRolesSet(Set<Role> rolesSet) {
this.rolesSet = rolesSet;
}
public Iterable<String> getRoleNamesIterable() {
return roleNamesIterable;
}
public void setRoleNamesIterable(Iterable<String> roleNamesIterable) {
this.roleNamesIterable = roleNamesIterable;
}
public Iterable<Role> getRolesIterable() {
return rolesIterable;
}
public void setRolesIterable(Iterable<Role> rolesIterable) {
this.rolesIterable = rolesIterable;
}
}

View File

@@ -28,6 +28,12 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.springframework.data.neo4j.aspects.Person.persistedPerson;
@@ -49,7 +55,7 @@ public class NodeEntityTest extends EntityTestBase {
@Test
@Transactional
public void testSetProperties() {
public void testSetSimpleProperties() {
String name = "Michael";
int age = 35;
short height = 182;
@@ -63,6 +69,80 @@ public class NodeEntityTest extends EntityTestBase {
assertEquals((Short)height, p.getHeight());
}
@Test
@Transactional
public void testArrayProperties() {
Group g = new Group().persist();
final String[] roleNames = {"a", "b", "c"};
g.setRoleNames(roleNames);
assertArrayEquals(roleNames, (String[])getNodeState(g).getProperty("roleNames"));
assertArrayEquals(roleNames, g.getRoleNames());
}
@Test
@Transactional
public void testConvertedArrayProperties() {
Group g = new Group().persist();
g.setRoles(Group.Role.values());
assertArrayEquals(new String[] {"ADMIN","USER"}, (String[])getNodeState(g).getProperty("roles"));
assertArrayEquals(Group.Role.values(), g.getRoles());
}
@Test
@Transactional
public void testCollectionProperties() {
Group g = new Group().persist();
final List<String> roleNames = asList("a", "b", "c");
g.setRoleNamesColl(roleNames);
assertArrayEquals(roleNames.toArray(), (String[])getNodeState(g).getProperty("roleNamesColl"));
assertEquals(roleNames, g.getRoleNamesColl());
}
@Test
@Transactional
public void testConvertedCollectionProperties() {
Group g = new Group().persist();
g.setRolesColl(asList(Group.Role.values()));
assertArrayEquals(new String[] {"ADMIN","USER"}, (String[])getNodeState(g).getProperty("rolesColl"));
assertEquals(asList(Group.Role.values()), g.getRolesColl());
}
@Test
@Transactional
public void testIterableProperties() {
Group g = new Group().persist();
final List<String> roleNames = asList("a", "b", "c");
g.setRoleNamesIterable(roleNames);
assertArrayEquals(roleNames.toArray(), (String[])getNodeState(g).getProperty("roleNamesIterable"));
assertEquals(roleNames, g.getRoleNamesIterable());
}
@Test
@Transactional
public void testConvertedIterableProperties() {
Group g = new Group().persist();
g.setRolesIterable(asList(Group.Role.values()));
assertArrayEquals(new String[] {"ADMIN","USER"}, (String[])getNodeState(g).getProperty("rolesIterable"));
assertEquals(asList(Group.Role.values()), g.getRolesIterable());
}
@Test
@Transactional
public void testSetProperties() {
Group g = new Group().persist();
final Set<String> roleNames = new LinkedHashSet<String>(asList("a", "b", "c"));
g.setRoleNamesSet(roleNames);
assertArrayEquals(roleNames.toArray(), (String[])getNodeState(g).getProperty("roleNamesSet"));
assertEquals(roleNames, g.getRoleNamesSet());
}
@Test
@Transactional
public void testConvertedSetProperties() {
Group g = new Group().persist();
final LinkedHashSet<Group.Role> roles = new LinkedHashSet<Group.Role>(asList(Group.Role.values()));
g.setRolesSet(roles);
assertArrayEquals(new String[] {"ADMIN","USER"}, (String[])getNodeState(g).getProperty("rolesSet"));
assertEquals(roles, g.getRolesSet());
}
@Test
@Transactional
public void testSetShortProperty() {

View File

@@ -76,7 +76,7 @@
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-rest-graphdb</artifactId>
<version>1.5.M02.U1</version>
<version>1.5-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.neo4j</groupId>

View File

@@ -18,7 +18,6 @@ package org.springframework.data.neo4j.fieldaccess;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.Neo4jTemplate;
@@ -44,7 +43,7 @@ public class ConvertingNodePropertyFieldAccessorFactory implements FieldAccessor
@Override
public boolean accept(final Neo4jPersistentProperty property) {
final ConversionService conversionService = getConversionService();
return property.isSerializableField(conversionService) && property.isDeserializableField(conversionService);
return property.isSerializablePropertyField(conversionService);
}
@Override
@@ -54,28 +53,31 @@ public class ConvertingNodePropertyFieldAccessorFactory implements FieldAccessor
public static class ConvertingNodePropertyFieldAccessor extends PropertyFieldAccessorFactory.PropertyFieldAccessor {
private final Class<String> targetType = String.class;
private final PropertyConverter propertyConverter;
public ConvertingNodePropertyFieldAccessor(Neo4jPersistentProperty property, Neo4jTemplate template) {
super(template, property);
propertyConverter = new PropertyConverter(template.getConversionService(),property);
}
@Override
public Object setValue(final Object entity, final Object newVal) {
super.setValue(entity, serializePropertyValue(newVal));
super.setValue(entity, propertyConverter.serializePropertyValue(newVal,targetType));
return newVal;
}
@Override
public Object doGetValue(final Object entity) {
return deserializePropertyValue(super.doGetValue(entity));
return propertyConverter.deserializePropertyValue(super.doGetValue(entity));
}
private Object serializePropertyValue(final Object newVal) {
return template.getConversionService().convert(newVal, String.class);
@Override
protected Object convertSimplePropertyValue(Object value) {
return value;
}
private Object deserializePropertyValue(final Object value) {
return template.getConversionService().convert(value, fieldType);
}
}
}

View File

@@ -0,0 +1,95 @@
/**
* 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.fieldaccess;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.util.TypeInformation;
import java.lang.reflect.Array;
import java.util.*;
import static java.util.Arrays.asList;
/**
* @author mh
* @since 05.11.11
*/
public class PropertyConverter {
private final ConversionService conversionService;
private final Neo4jPersistentProperty property;
private final TypeInformation<?> typeInformation;
public PropertyConverter(ConversionService conversionService, Neo4jPersistentProperty property) {
this.conversionService = conversionService;
this.property = property;
this.typeInformation = property.getTypeInformation();
}
public Object serializePropertyValue(final Object newVal, Class<?> targetType) {
if (newVal == null) return null;
final TypeInformation<?> typeInformation = property.getTypeInformation();
if (typeInformation.isCollectionLike()) {
return serializeCollection(newVal, conversionService, typeInformation, targetType);
}
return conversionService.convert(newVal, targetType);
}
public Object deserializePropertyValue(final Object newVal) {
if (newVal == null) return null;
if (typeInformation.isCollectionLike() && isCollectionLike(newVal)) {
return deserializeCollection(newVal, conversionService, typeInformation);
}
return conversionService.convert(newVal, typeInformation.getType());
}
private boolean isCollectionLike(Object val) {
return val != null && (val.getClass().isArray() || Collection.class.isAssignableFrom(val.getClass()));
}
private Object serializeCollection(Object newVal, ConversionService conversionService, TypeInformation<?> typeInformation, final Class<?> targetType) {
final List<Object> values = convertCollection(conversionService, targetType, toCollection(newVal));
return values.toArray((Object[]) Array.newInstance(targetType, values.size()));
}
@SuppressWarnings("unchecked")
private Object deserializeCollection(Object newVal, ConversionService conversionService, TypeInformation<?> typeInformation) {
final Class<?> actualType = typeInformation.getActualType().getType();
final List<Object> result = convertCollection(conversionService, actualType, toCollection(newVal));
final Class<?> fieldType = typeInformation.getType();
if (fieldType.isArray()) {
return result.toArray((Object[]) Array.newInstance(actualType, result.size()));
}
if (Set.class.isAssignableFrom(fieldType)) return new LinkedHashSet<Object>(result);
return result;
}
private List<Object> convertCollection(ConversionService conversionService, Class<?> targetType, Iterable<?> values) {
final List<Object> result = new ArrayList<Object>();
for (Object value : values) {
result.add(conversionService.convert(value, targetType));
}
return result;
}
private Iterable<?> toCollection(Object newVal) {
if (newVal.getClass().isArray()) {
return asList((Object[]) newVal);
} else {
return (Iterable<?>) newVal;
}
}
}

View File

@@ -84,14 +84,18 @@ public class PropertyFieldAccessorFactory implements FieldAccessorFactory {
if (element.hasProperty(propertyName)) {
Object value = element.getProperty(propertyName);
if (value == null || fieldType.isInstance(value)) return value;
if (template.getConversionService() !=null) {
return template.getConversionService().convert(value, fieldType);
}
return value;
return convertSimplePropertyValue(value);
}
return getDefaultValue(fieldType);
}
protected Object convertSimplePropertyValue(Object value) {
if (template.getConversionService() !=null) {
return template.getConversionService().convert(value, fieldType);
}
return value;
}
private Object getDefaultValue(final Class<?> type) {
if (type.isPrimitive()) {
if (type.equals(boolean.class)) return false;

View File

@@ -52,11 +52,7 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
String getNeo4jPropertyName();
boolean isSimpleValueField();
boolean isSerializableField(final ConversionService conversionService);
boolean isDeserializableField(final ConversionService conversionService);
boolean isSerializablePropertyField(final ConversionService conversionService);
boolean isNeo4jPropertyType();

View File

@@ -147,16 +147,16 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
}
public boolean isSimpleValueField() {
return !(getTypeInformation().isCollectionLike() || isRelationship());
public boolean isSerializablePropertyField(final ConversionService conversionService) {
if (isRelationship()) return false;
final Class<?> type = getType();
final Class<String> targetType = String.class;
if (getTypeInformation().isCollectionLike()) return isConvertibleBetween(conversionService, getComponentType(), targetType);
return isConvertibleBetween(conversionService, type, targetType);
}
public boolean isSerializableField(final ConversionService conversionService) {
return isSimpleValueField() && conversionService.canConvert(getType(), String.class);
}
public boolean isDeserializableField(final ConversionService conversionService) {
return isSimpleValueField() && conversionService.canConvert(String.class, getType());
private boolean isConvertibleBetween(ConversionService conversionService, Class<?> type, Class<String> targetType) {
return conversionService.canConvert(type, targetType) && conversionService.canConvert(targetType, type);
}
@Override