DATAGRAPH-440 Add support for Slice API
This commit is contained in:
@@ -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<R> implements EndResult<R> {
|
||||
return ContainerConverter.toContainer(container,this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<R> slice(int page, int pageSize) {
|
||||
return slice(new PageRequest(page,pageSize));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<R> slice(Pageable page) {
|
||||
return ContainerConverter.slice(this, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish()
|
||||
{
|
||||
|
||||
@@ -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<T> implements Result<T> {
|
||||
queryResult.handle(new SpringHandler<T>(handler));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<T> slice(int page, int pageSize) {
|
||||
return slice(new PageRequest(page,pageSize));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<T> slice(Pageable page) {
|
||||
return ContainerConverter.slice( queryResult, page );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return queryResult.iterator();
|
||||
|
||||
@@ -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<T>(list);
|
||||
}
|
||||
if (Slice.class.isAssignableFrom(container)) {
|
||||
return (C) new SliceImpl<T>(list);
|
||||
}
|
||||
return (C) list;
|
||||
}
|
||||
|
||||
public static <T> Slice<T> slice(Iterable<T> data, Pageable page) {
|
||||
int offset = page.getOffset();
|
||||
Iterator<T> it = data.iterator();
|
||||
while (it.hasNext() && offset > 0) { it.next(); offset--; }
|
||||
int pageSize = page.getPageSize();
|
||||
List<T> result = new ArrayList<>(pageSize);
|
||||
while (it.hasNext() && pageSize > 0) { result.add(it.next()); pageSize--; }
|
||||
return new SliceImpl<T>(result,page,it.hasNext());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<R> extends Iterable<R> {
|
||||
R singleOrNull();
|
||||
void handle(Handler<R> handler);
|
||||
<C extends Iterable<R>> C as(Class<C> container);
|
||||
Slice<R> slice(int page, int pageSize);
|
||||
Slice<R> slice(Pageable page);
|
||||
void finish();
|
||||
}
|
||||
|
||||
@@ -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<T> implements Result<T> {
|
||||
return ContainerConverter.toContainer(container, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<R> slice(Pageable page) {
|
||||
return ContainerConverter.slice(this,page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<R> slice(int page, int size) {
|
||||
return ContainerConverter.slice(this,new PageRequest(page,size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish()
|
||||
{
|
||||
@@ -142,6 +155,16 @@ public class QueryResultBuilder<T> implements Result<T> {
|
||||
return ContainerConverter.toContainer(container, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<T> slice(Pageable page) {
|
||||
return ContainerConverter.slice(this,page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<T> slice(int page, int size) {
|
||||
return slice(new PageRequest(page,size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Handler<T> handler) {
|
||||
try {
|
||||
|
||||
@@ -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<S extends PropertyContainer, T> im
|
||||
return new PageImpl<T>(result, pageable, total);
|
||||
}
|
||||
|
||||
private SliceImpl<T> extractSlice(Pageable pageable, int count, int offset, Iterator<T> iterator) {
|
||||
final List<T> result = new ArrayList<T>(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<T> source, final List<T> list) {
|
||||
int count=0;
|
||||
while (source.hasNext()) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Integer> 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<Integer> 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<Integer> 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));
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> value1 = map("key", 1);
|
||||
Map<String, Object> value2 = map("key", 2);
|
||||
QueryResultBuilder<Map<String,Object>> builder = new QueryResultBuilder<Map<String,Object>>(Arrays.asList(value1, value2));
|
||||
List<Map<String,Object>> list = builder.as(List.class);
|
||||
assertEquals(2,list.size());
|
||||
assertThat(list,hasItems(value1,value2));
|
||||
Page<Map<String,Object>> page = builder.as(Page.class);
|
||||
assertEquals(2,page.getNumberOfElements());
|
||||
assertThat(page,hasItems(value1,value2));
|
||||
|
||||
Slice<Map<String,Object>> slice = builder.as(Slice.class);
|
||||
assertEquals(2,slice.getNumberOfElements());
|
||||
assertThat(page,hasItems(value1,value2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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<Person>, NamedIndexRep
|
||||
@Query("match (team)-[:persons]->(member) where id(team) = {p_team} return member")
|
||||
Page<Person> findAllTeamMembersPaged(@Param("p_team") Group team, Pageable page);
|
||||
|
||||
@Query("match (team)-[:persons]->(member) where id(team) = {p_team} return member")
|
||||
Slice<Person> findAllTeamMembersSliced(@Param("p_team") Group team, Pageable page);
|
||||
|
||||
@Query("match (team)-[:persons]->(member) where id(team) = {p_team} return member")
|
||||
Iterable<Person> findAllTeamMembersSorted(@Param("p_team") Group team, Sort sort);
|
||||
|
||||
|
||||
@@ -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<Person> 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<Person> 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<Person> 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");
|
||||
|
||||
@@ -49,9 +49,8 @@
|
||||
<varlistentry>
|
||||
<term>Iterate over all nodes of a node entity type</term>
|
||||
<listitem><para><code>EndResult<T> findAll()</code>
|
||||
(supported in future versions:
|
||||
<code>EndResult<T> findAll(Sort)</code> and
|
||||
<code>Page<T> findAll(Pageable)</code>)</para></listitem>
|
||||
<code>Page<T> findAll(Pageable)</code></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Count the instances of the repository entity type</term>
|
||||
@@ -125,7 +124,7 @@
|
||||
will use the supplied query string. The named or indexed parameter <code>{node}</code> 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 <code>Sort</code> and <code>Pageable</code>
|
||||
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 @@
|
||||
</para>
|
||||
<para>
|
||||
If it is required that paged results return the correct total count, the <code>@Query</code> annotation can be supplied with a count query in the <code>countQuery</code>
|
||||
attribute. This query is executed separately after the result query and its result is used to populate the <code>totalCount</code> property of the returned Page.
|
||||
attribute. This query is executed separately after the result query and its result is used to populate the <code>totalCount</code> property of the returned <code>Page</code>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -148,7 +147,7 @@
|
||||
</section>
|
||||
<section>
|
||||
<title>Query results</title>
|
||||
<para>Typical results for queries are <code>Iterable<Type>, Iterable<Map<String,Object>>, Type and Page<Type></code>.
|
||||
<para>Typical results for queries are <code>Iterable<Type>, Iterable<Map<String,Object>>, Type, Slice<Type> and Page<Type></code>.
|
||||
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).
|
||||
</para>
|
||||
@@ -532,4 +531,4 @@ Iterable<Person> friends = personRepository.findFriends(dave);
|
||||
</para>
|
||||
</note>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -85,28 +85,9 @@ public String singleMovieView(final Model model, @PathVariable String movieId) {
|
||||
<programlisting language="java"><![CDATA[public interface MovieRepository ... {
|
||||
Movie findById(String id);
|
||||
Page<Movie> findByTitleLike(String title, Pageable page);
|
||||
Slice<Movie> findAll(Pageable page);
|
||||
}
|
||||
]]></programlisting>
|
||||
<!-- MOVE TO REFERENCE programlisting language="java"><![CDATA[public class CineastRepository {
|
||||
Page<Movie> findByTitle(String title, PageRequest page);
|
||||
....
|
||||
public void List<Movie> findMovies(String query, int count) {
|
||||
List<Movie> movies=new ArrayList<Movie>(count);
|
||||
ClosableIterable<Movie> searchResults = movieRepository.findAllByQuery("title", query);
|
||||
for (Movie movie : searchResults) {
|
||||
movies.add(movie);
|
||||
if (count- - == 0) break;
|
||||
}
|
||||
searchResults.close();
|
||||
return movies;
|
||||
}
|
||||
}
|
||||
|
||||
The corresponding
|
||||
repository method is called <code>findAllByQuery</code>. To restrict the size of the returned set we simply
|
||||
added a limit that truncates the result.
|
||||
|
||||
]]></programlisting-->
|
||||
</example>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user