diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/ResultColumn.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/ResultColumn.java new file mode 100644 index 000000000..2e74b5ec1 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/ResultColumn.java @@ -0,0 +1,27 @@ +/** + * 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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface ResultColumn { + String value() default ""; +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryMapResulConverter.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryMapResulConverter.java new file mode 100644 index 000000000..c7e5a5b9f --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryMapResulConverter.java @@ -0,0 +1,59 @@ +/** + * 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.conversion; + +import org.springframework.data.neo4j.annotation.ResultColumn; +import org.springframework.data.neo4j.template.Neo4jOperations; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; + +public class QueryMapResulConverter implements ResultConverter, T> { + private final Neo4jOperations template; + + public QueryMapResulConverter(Neo4jOperations template) { + this.template = template; + } + + @Override + public T convert(Map value, Class type) { + final Map valueCopy = value; + + + T resultProxy = (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() { + @Override + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + ResultColumn column = method.getAnnotation(ResultColumn.class); + TypeInformation returnType = ClassTypeInformation.fromReturnTypeOf(method); + Object columnValue = valueCopy.get(column.value()); + + Object result; + if (returnType.isCollectionLike()) + result = template.convert((Iterable) columnValue).to(returnType.getActualType().getType()); + else + result = template.convert(columnValue, returnType.getType()); + + + return result; + } + }); + return resultProxy; + } +} \ No newline at end of file diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/QueryMapResultConverterTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/QueryMapResultConverterTest.java new file mode 100644 index 000000000..7c6d0a894 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/QueryMapResultConverterTest.java @@ -0,0 +1,103 @@ +/** + * 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.conversion; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.neo4j.annotation.ResultColumn; +import org.springframework.data.neo4j.mapping.Neo4jPersistentTestBase; +import org.springframework.data.neo4j.model.Friendship; +import org.springframework.data.neo4j.model.Person; + +import java.util.List; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.matchers.JUnitMatchers.hasItems; +import static org.neo4j.helpers.collection.MapUtil.map; + + +public class QueryMapResultConverterTest extends Neo4jPersistentTestBase { + + private Map simpleMap; + private List friends; + private Map advancedMap; + + public interface PersonAndFriendsData { + @ResultColumn("person") + Person getPerson(); + + @ResultColumn("collect(r)") + Iterable getFriends(); + } + + public interface SimplestQuery { + @ResultColumn("name") + String getName(); + + @ResultColumn("age") + Integer getAge(); + + @ResultColumn("friends") + Iterable getFriendNames(); + } + + @Before + public void init() throws Exception { + storeInGraph(michael); + storeInGraph(andres); + storeInGraph(emil); + + friends = asList("Michael", "Emil", "Anders"); + simpleMap = map("name", "Andres", "age", 36L, "friends", friends); + advancedMap = map("person", michaelNode(), "collect(r)", michaelNode().getRelationships(KNOWS)); + + makeFriends(michaelNode(), andresNode(), 19); + makeFriends(michaelNode(), emilNode(), 6); + } + + @Test + public void shouldBeAbleToGetAStringFromAResultMap() throws Exception { + QueryMapResulConverter converter = getConverter(); + SimplestQuery query = converter.convert(simpleMap, SimplestQuery.class); + + assertThat(query.getName(), equalTo("Andres")); + assertThat(query.getAge(), equalTo(36)); + } + + @Test + public void shouldBeAbleToHandleAnIterableOfString() throws Exception { + QueryMapResulConverter converter = getConverter(); + SimplestQuery query = converter.convert(simpleMap, SimplestQuery.class); + + assertThat(query.getFriendNames(), hasItems("Michael", "Emil", "Anders")); + } + + @Test + public void shouldHandleANodeBackedEntity() throws Exception { + QueryMapResulConverter converter = new QueryMapResulConverter(template); + PersonAndFriendsData result = converter.convert(advancedMap, PersonAndFriendsData.class); + + assertThat(result.getPerson(), equalTo(michael)); + assertThat(result.getFriends(), equalTo(michael.getFriendships())); + } + + private QueryMapResulConverter getConverter() { + return new QueryMapResulConverter(template); + } +} \ No newline at end of file diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java index 3e8f40b88..3d8ef7911 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jEntityConverterTest.java @@ -298,8 +298,7 @@ public class Neo4jEntityConverterTest extends Neo4jPersistentTestBase { storeInGraph(michael); storeInGraph(andres); - Relationship friendshipRelationship = michaelNode().createRelationshipTo(andresNode(), KNOWS); - friendshipRelationship.setProperty("Friendship.years", 19); + Relationship friendshipRelationship = makeFriends(michaelNode(), andresNode(), 19); Person m = readPerson(michaelNode()); Friendship friendship = IteratorUtil.first(m.getFriendships()); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java index 0c445f651..86a1f9f21 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java @@ -30,7 +30,9 @@ import org.springframework.data.neo4j.fieldaccess.NodeDelegatingFieldAccessorFac import org.springframework.data.neo4j.fieldaccess.RelationshipDelegatingFieldAccessorFactory; import org.springframework.data.neo4j.model.Group; import org.springframework.data.neo4j.model.Person; -import org.springframework.data.neo4j.support.*; +import org.springframework.data.neo4j.support.DelegatingGraphDatabase; +import org.springframework.data.neo4j.support.MappingInfrastructure; +import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.data.neo4j.support.mapping.*; import org.springframework.data.neo4j.support.node.NodeEntityInstantiator; import org.springframework.data.neo4j.support.node.NodeEntityStateFactory; @@ -100,7 +102,7 @@ public class Neo4jPersistentTestBase { nodeEntityInstantiator = new NodeEntityInstantiator(entityStateHandler); relationshipEntityInstantiator = new RelationshipEntityInstantiator(entityStateHandler); - nodeTypeMapper = new DefaultTypeMapper(new TRSTypeAliasAccessor(infrastructure.getNodeTypeRepresentationStrategy()),asList(new ClassValueTypeInformationMapper())); + nodeTypeMapper = new DefaultTypeMapper(new TRSTypeAliasAccessor(infrastructure.getNodeTypeRepresentationStrategy()), asList(new ClassValueTypeInformationMapper())); nodeStateTransmitter = new SourceStateTransmitter(nodeEntityStateFactory); relationshipStateTransmitter = new SourceStateTransmitter(relationshipEntityStateFactory); conversionService = template.getConversionService(); @@ -135,7 +137,7 @@ public class Neo4jPersistentTestBase { } private MappingInfrastructure createInfrastructure(Neo4jMappingContext mappingContext) throws Exception { - MappingInfrastructure infrastructure=new MappingInfrastructure(); + MappingInfrastructure infrastructure = new MappingInfrastructure(); final ImpermanentGraphDatabase gdb = new ImpermanentGraphDatabase(); infrastructure.setGraphDatabaseService(gdb); final DelegatingGraphDatabase graphDatabase = new DelegatingGraphDatabase(gdb); @@ -187,7 +189,7 @@ public class Neo4jPersistentTestBase { if (id != null) { write(p, template.getNode(id)); } else { - write(p,null); + write(p, null); } return p; } @@ -199,7 +201,7 @@ public class Neo4jPersistentTestBase { @SuppressWarnings("unchecked") private T storeInGraph(T obj) { - return (T) write(obj,null); + return (T) write(obj, null); } protected Node groupNode() { @@ -225,6 +227,13 @@ public class Neo4jPersistentTestBase { public Person readPerson(Node node) { return entityPersister.read(Person.class, node); } + + protected Relationship makeFriends(Node from, Node to, int years) { + Relationship friendship = from.createRelationshipTo(to, KNOWS); + friendship.setProperty("Friendship.years", years); + return friendship; + } + public Group readGroup(Node node) { return entityPersister.read(Group.class, node); }