From 3e0e0e70e6e9bb92e4c126b87f91098cdbadfb5d Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Sun, 30 Mar 2014 14:37:13 +0200 Subject: [PATCH] DATAGRAPH-440 Add support for Slice API --- .../data/neo4j/rest/SpringEndResult.java | 14 +++++++ .../data/neo4j/rest/SpringRestResult.java | 14 +++++++ .../neo4j/conversion/ContainerConverter.java | 16 +++++++- .../data/neo4j/conversion/EndResult.java | 5 +++ .../neo4j/conversion/QueryResultBuilder.java | 23 +++++++++++ .../repository/AbstractGraphRepository.java | 12 ++++-- .../query/CypherGraphRepositoryQuery.java | 2 +- .../query/GraphRepositoryQuery.java | 30 ++++++++------ .../conversion/ContainerConverterTest.java | 39 +++++++++++++++++++ .../conversion/QueryResultBuilderTests.java | 20 +++++++++- .../neo4j/repositories/PersonRepository.java | 4 ++ .../repository/GraphRepositoryTests.java | 25 ++++++++++++ .../programming-model/repositories.xml | 11 +++--- src/docbkx/tutorial/webapp.xml | 21 +--------- src/main/resources/changelog.txt | 8 ++++ 15 files changed, 198 insertions(+), 46 deletions(-) create mode 100644 spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/ContainerConverterTest.java diff --git a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java index 05bea7aae..4a0760064 100644 --- a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java +++ b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java @@ -17,6 +17,10 @@ package org.springframework.data.neo4j.rest; import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.rest.graphdb.util.ConvertedResult; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.neo4j.conversion.ContainerConverter; import org.springframework.data.neo4j.conversion.EndResult; @@ -54,6 +58,16 @@ class SpringEndResult implements EndResult { return ContainerConverter.toContainer(container,this); } + @Override + public Slice slice(int page, int pageSize) { + return slice(new PageRequest(page,pageSize)); + } + + @Override + public Slice slice(Pageable page) { + return ContainerConverter.slice(this, page); + } + @Override public void finish() { diff --git a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java index bb1117681..def190dfe 100644 --- a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java +++ b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java @@ -17,6 +17,10 @@ package org.springframework.data.neo4j.rest; import org.neo4j.rest.graphdb.util.ConvertedResult; import org.neo4j.rest.graphdb.util.ResultConverter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.neo4j.conversion.ContainerConverter; import org.springframework.data.neo4j.conversion.EndResult; import org.springframework.data.neo4j.conversion.Result; import org.springframework.data.neo4j.mapping.MappingPolicy; @@ -53,6 +57,16 @@ class SpringRestResult implements Result { queryResult.handle(new SpringHandler(handler)); } + @Override + public Slice slice(int page, int pageSize) { + return slice(new PageRequest(page,pageSize)); + } + + @Override + public Slice slice(Pageable page) { + return ContainerConverter.slice( queryResult, page ); + } + @Override public Iterator iterator() { return queryResult.iterator(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java index 25c7aee4f..5522e2f1d 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/ContainerConverter.java @@ -16,8 +16,7 @@ package org.springframework.data.neo4j.conversion; import org.neo4j.helpers.collection.IteratorUtil; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.*; import java.util.*; @@ -35,6 +34,19 @@ public class ContainerConverter { if (Page.class.isAssignableFrom(container)) { return (C) new PageImpl(list); } + if (Slice.class.isAssignableFrom(container)) { + return (C) new SliceImpl(list); + } return (C) list; } + + public static Slice slice(Iterable data, Pageable page) { + int offset = page.getOffset(); + Iterator it = data.iterator(); + while (it.hasNext() && offset > 0) { it.next(); offset--; } + int pageSize = page.getPageSize(); + List result = new ArrayList<>(pageSize); + while (it.hasNext() && pageSize > 0) { result.add(it.next()); pageSize--; } + return new SliceImpl(result,page,it.hasNext()); + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java index c3edc5e43..c9f62619c 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java @@ -16,6 +16,9 @@ package org.springframework.data.neo4j.conversion; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + /** * @author mh * @since 28.06.11 @@ -25,5 +28,7 @@ public interface EndResult extends Iterable { R singleOrNull(); void handle(Handler handler); > C as(Class container); + Slice slice(int page, int pageSize); + Slice slice(Pageable page); void finish(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java index 23754a75e..96f31462a 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java @@ -20,6 +20,9 @@ import org.neo4j.graphdb.index.IndexHits; import org.neo4j.helpers.collection.ClosableIterable; import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.helpers.collection.IteratorWrapper; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.neo4j.mapping.MappingPolicy; import java.util.Arrays; @@ -128,6 +131,16 @@ public class QueryResultBuilder implements Result { return ContainerConverter.toContainer(container, this); } + @Override + public Slice slice(Pageable page) { + return ContainerConverter.slice(this,page); + } + + @Override + public Slice slice(int page, int size) { + return ContainerConverter.slice(this,new PageRequest(page,size)); + } + @Override public void finish() { @@ -142,6 +155,16 @@ public class QueryResultBuilder implements Result { return ContainerConverter.toContainer(container, this); } + @Override + public Slice slice(Pageable page) { + return ContainerConverter.slice(this,page); + } + + @Override + public Slice slice(int page, int size) { + return slice(new PageRequest(page,size)); + } + @Override public void handle(Handler handler) { try { 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 8a7fed146..fc67d1d2d 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 @@ -23,10 +23,7 @@ import org.neo4j.graphdb.index.IndexHits; import org.neo4j.graphdb.index.ReadableIndex; import org.neo4j.helpers.collection.ClosableIterable; import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.*; import org.springframework.data.neo4j.conversion.EndResult; import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; @@ -375,6 +372,13 @@ public abstract class AbstractGraphRepository im return new PageImpl(result, pageable, total); } + private SliceImpl extractSlice(Pageable pageable, int count, int offset, Iterator iterator) { + final List result = new ArrayList(count); + int total=subList(offset, count, iterator, result); + boolean hasNext = iterator.hasNext(); + return new SliceImpl<>(result, pageable, hasNext); + } + private int subList(int skip, int limit, Iterator source, final List list) { int count=0; while (source.hasNext()) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherGraphRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherGraphRepositoryQuery.java index 4e179c5d8..ef97dc0f2 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherGraphRepositoryQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherGraphRepositoryQuery.java @@ -45,7 +45,7 @@ class CypherGraphRepositoryQuery extends GraphRepositoryQuery { if (pageable==null) { return baseQuery; } - return baseQuery + " skip "+pageable.getOffset() + " limit " + pageable.getPageSize(); + return baseQuery + " skip "+pageable.getOffset() + " limit " + pageable.getPageSize() + 1 ; } private String addSorting(String baseQuery, Sort sort) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java index 92994de48..c56adaaaa 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java @@ -20,6 +20,7 @@ import org.neo4j.graphdb.Relationship; import org.neo4j.helpers.collection.IteratorUtil; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.SliceImpl; import org.springframework.data.neo4j.conversion.EndResult; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.support.Neo4jTemplate; @@ -102,10 +103,10 @@ abstract class GraphRepositoryQuery implements RepositoryQuery, ParameterResolve GraphQueryMethod queryMethod = getQueryMethod(); final QueryEngine queryEngine = getQueryEngine(); final Class compoundType = queryMethod.getCompoundType(); - if (queryMethod.isPageQuery()) { + if (queryMethod.isPageQuery() || queryMethod.isSliceQuery()) { @SuppressWarnings("unchecked") final Iterable result = queryEngine.query(queryString, params).to(compoundType); Long count = computeCount(params); - return createPage(result, accessor.getPageable(),count); + return createPage(result, accessor.getPageable(),count, queryMethod.isPageQuery()); } if (queryMethod.isIterableResult()) { final EndResult result = queryEngine.query(queryString, params).to(compoundType); @@ -129,21 +130,28 @@ abstract class GraphRepositoryQuery implements RepositoryQuery, ParameterResolve @SuppressWarnings({"unchecked", "rawtypes"}) - protected Object createPage(Iterable result, Pageable pageable, Long count) { + protected Object createPage(Iterable result, Pageable pageable, Long count, boolean isPageQuery) { final List resultList = IteratorUtil.addToCollection(result, new ArrayList()); if (pageable==null) { - return new PageImpl(resultList); + return isPageQuery ? new PageImpl(resultList) : new SliceImpl(resultList); } - long currentTotal; + int pageSize = pageable.getPageSize(); + int requestedCountStart = pageable.getOffset(); + int resultSize = resultList.size(); + int currentTotal; if (count!=null) { - currentTotal = count; + currentTotal = count.intValue(); } else { - int pageSize = pageable.getPageSize(); - long requestedCountStart = pageable.getOffset() * pageSize; - long resultSize = resultList.size(); - currentTotal = resultSize == pageSize ? requestedCountStart + pageSize : requestedCountStart+resultSize; + if (resultSize == pageSize) currentTotal = requestedCountStart + pageSize; + else currentTotal = requestedCountStart + resultSize; } - return new PageImpl(resultList, pageable, currentTotal); + int resultWindowSize = Math.min(resultSize, pageSize); + boolean hasNext = resultWindowSize < resultSize; + List resultListPage = resultList.subList(0, resultWindowSize); + + return isPageQuery ? + new PageImpl(resultListPage, pageable, currentTotal) : + new SliceImpl(resultListPage,pageable, hasNext); } protected abstract CypherQueryEngine getQueryEngine(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/ContainerConverterTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/ContainerConverterTest.java new file mode 100644 index 000000000..74a20b6b1 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/ContainerConverterTest.java @@ -0,0 +1,39 @@ +package org.springframework.data.neo4j.conversion; + +import org.junit.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; + +import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author mh + * @since 30.03.14 + */ +public class ContainerConverterTest { + @Test + public void testSliceFirst() throws Exception { + Slice slice = ContainerConverter.slice(asList(1, 2, 3), new PageRequest(0, 1)); + assertThat(slice.getContent(), hasItem(1)); + assertThat(slice.getSize(), is(1)); + assertThat(slice.hasNext(), is(true)); + } + @Test + public void testSliceLast() throws Exception { + Slice slice = ContainerConverter.slice(asList(1,2,3), new PageRequest(2, 1)); + assertThat(slice.getContent(), hasItem(3)); + assertThat(slice.getSize(), is(1)); + assertThat(slice.hasNext(), is(false)); + } + @Test + public void testSliceAll() throws Exception { + Slice slice = ContainerConverter.slice(asList(1, 2, 3), new PageRequest(0, 3)); + assertThat(slice.getContent(), hasItems(1, 2, 3)); + assertThat(slice.getSize(), is(3)); + assertThat(slice.hasNext(), is(false)); + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/QueryResultBuilderTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/QueryResultBuilderTests.java index 5861ba226..50cc5f983 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/QueryResultBuilderTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/conversion/QueryResultBuilderTests.java @@ -17,16 +17,20 @@ package org.springframework.data.neo4j.conversion; import org.junit.Before; import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; import org.springframework.data.neo4j.annotation.MapResult; import org.springframework.data.neo4j.support.conversion.EntityResultConverter; -import java.util.Collections; -import java.util.Map; +import java.util.*; import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.neo4j.helpers.collection.MapUtil.map; import static org.springframework.data.neo4j.conversion.QueryResultBuilder.from; /** @@ -69,7 +73,19 @@ public class QueryResultBuilderTests { @Test public void testAs() throws Exception { + Map value1 = map("key", 1); + Map value2 = map("key", 2); + QueryResultBuilder> builder = new QueryResultBuilder>(Arrays.asList(value1, value2)); + List> list = builder.as(List.class); + assertEquals(2,list.size()); + assertThat(list,hasItems(value1,value2)); + Page> page = builder.as(Page.class); + assertEquals(2,page.getNumberOfElements()); + assertThat(page,hasItems(value1,value2)); + Slice> slice = builder.as(Slice.class); + assertEquals(2,slice.getNumberOfElements()); + assertThat(page,hasItems(value1,value2)); } @Test diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/PersonRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/PersonRepository.java index ceaafbbc9..786ec6569 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/PersonRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repositories/PersonRepository.java @@ -18,6 +18,7 @@ package org.springframework.data.neo4j.repositories; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.neo4j.annotation.Query; import org.springframework.data.neo4j.annotation.QueryResult; @@ -78,6 +79,9 @@ public interface PersonRepository extends GraphRepository, NamedIndexRep @Query("match (team)-[:persons]->(member) where id(team) = {p_team} return member") Page findAllTeamMembersPaged(@Param("p_team") Group team, Pageable page); + @Query("match (team)-[:persons]->(member) where id(team) = {p_team} return member") + Slice findAllTeamMembersSliced(@Param("p_team") Group team, Pageable page); + @Query("match (team)-[:persons]->(member) where id(team) = {p_team} return member") Iterable findAllTeamMembersSorted(@Param("p_team") Group team, Sort sort); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTests.java index ea6ef32bd..e55d7201d 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTests.java @@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.neo4j.model.*; import org.springframework.data.neo4j.repositories.*; @@ -295,6 +296,30 @@ public class GraphRepositoryTests { assertThat(teamMemberPage1, hasItem(testTeam.david)); } + @Test @Transactional + public void testFindSliced() { + final PageRequest page = new PageRequest(0, 1, Sort.Direction.ASC, "member.name","member.age"); + Slice teamMemberSlice = personRepository.findAllTeamMembersSliced(testTeam.sdg, page); + assertThat(teamMemberSlice, hasItem(testTeam.david)); + assertThat(teamMemberSlice.hasNext(),is(true)); + } + + @Test @Transactional + public void testFindSlicedLastPage() { + final PageRequest page = new PageRequest(2, 1, Sort.Direction.ASC, "member.name","member.age"); + Slice teamMemberSlice = personRepository.findAllTeamMembersSliced(testTeam.sdg, page); + assertThat(teamMemberSlice, hasItem(testTeam.michael)); + assertThat(teamMemberSlice.hasNext(),is(false)); + } + + @Test @Transactional + public void testFindSlicedLastAll() { + final PageRequest page = new PageRequest(0, 3, Sort.Direction.ASC, "member.name","member.age"); + Slice teamMemberSlice = personRepository.findAllTeamMembersSliced(testTeam.sdg, page); + assertThat(teamMemberSlice, hasItems(testTeam.david, testTeam.emil, testTeam.michael)); + assertThat(teamMemberSlice.hasNext(), is(false)); + } + @Test @Transactional public void testFindPagedDescending() { final PageRequest page = new PageRequest(0, 2, Sort.Direction.DESC, "member.name"); diff --git a/src/docbkx/reference/programming-model/repositories.xml b/src/docbkx/reference/programming-model/repositories.xml index 74c2cf333..e4bcf7a4f 100644 --- a/src/docbkx/reference/programming-model/repositories.xml +++ b/src/docbkx/reference/programming-model/repositories.xml @@ -49,9 +49,8 @@ Iterate over all nodes of a node entity type EndResult<T> findAll() - (supported in future versions: EndResult<T> findAll(Sort) and - Page<T> findAll(Pageable)) + Page<T> findAll(Pageable) Count the instances of the repository entity type @@ -125,7 +124,7 @@ will use the supplied query string. The named or indexed parameter {node} will be substituted by the actual method parameter. Node and Relationship-Entities are handled directly, Iterables thereof as well. All other parameters are replaced directly (i.e. Strings, Longs, etc). There is special support for the Sort and Pageable - parameters from Spring Data Commons, which are supported to add programmatic paging and sorting (alternatively + parameters from Spring Data Commons, which are supported to add programmatic paging, slicing and sorting (alternatively static paging and sorting can be supplied in the query string itself). For using the named parameters you have to either annotate the parameters of the method with the @@ -133,7 +132,7 @@ If it is required that paged results return the correct total count, the @Query annotation can be supplied with a count query in the countQuery - attribute. This query is executed separately after the result query and its result is used to populate the totalCount property of the returned Page. + attribute. This query is executed separately after the result query and its result is used to populate the totalCount property of the returned Page. @@ -148,7 +147,7 @@
Query results - Typical results for queries are Iterable<Type>, Iterable<Map<String,Object>>, Type and Page<Type>. + Typical results for queries are Iterable<Type>, Iterable<Map<String,Object>>, Type, Slice<Type> and Page<Type>. Nodes and Relationships are converted to their respective Entities (if they exist). Other values are converted using the registered Spring conversion services (e.g. enums). @@ -532,4 +531,4 @@ Iterable friends = personRepository.findFriends(dave);
- \ No newline at end of file + diff --git a/src/docbkx/tutorial/webapp.xml b/src/docbkx/tutorial/webapp.xml index f152bb945..fccfdf46d 100644 --- a/src/docbkx/tutorial/webapp.xml +++ b/src/docbkx/tutorial/webapp.xml @@ -85,28 +85,9 @@ public String singleMovieView(final Model model, @PathVariable String movieId) { findByTitleLike(String title, Pageable page); + Slice findAll(Pageable page); } ]]> - diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 47f260c83..9e812af3e 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,14 @@ Spring Data Neo4j Changelog =========================== +Changes in version 3.1.0.M1 (2014-03-31) +---------------------------------------- +* DATAGRAPH-440 Add support for Slice API +* DATAGRAPH-451 RelationshipEntities will be validated when manipulated +* DATAGRAPH-450 Updated example code +* DATAGRAPH-449 Updated docs for 3.x +* DATAGRAPH-452 - Adapted to changes in BeanWrapper generics. + Changes in version 3.0.1.RELEASE (2014-03-13) --------------------------------------------- * DATAGRAPH-433 - Unique Creation of label based entities