diff --git a/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/src/main/java/org/springframework/data/repository/query/QueryMethod.java index e92b08eb8..c00fbf48d 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryMethod.java +++ b/src/main/java/org/springframework/data/repository/query/QueryMethod.java @@ -63,18 +63,22 @@ public class QueryMethod { } } + this.method = method; + this.parameters = createParameters(method); + this.metadata = metadata; + if (hasParameterOfType(method, Pageable.class)) { - assertReturnTypeAssignable(method, Slice.class, Page.class, List.class); + + if (!isStreamQuery()) { + assertReturnTypeAssignable(method, Slice.class, Page.class, List.class); + } + if (hasParameterOfType(method, Sort.class)) { throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameter. " + "Use sorting capabilities on Pageble instead! Offending method: %s", method.toString())); } } - this.method = method; - this.parameters = createParameters(method); - this.metadata = metadata; - Assert.notNull(this.parameters); if (isPageQuery()) { @@ -205,6 +209,16 @@ public class QueryMethod { return getDomainClass().isAssignableFrom(getReturnedObjectType()); } + /** + * Returns whether the method returns a Stream. + * + * @return + * @since 1.10 + */ + public boolean isStreamQuery() { + return ReflectionUtils.isJava8StreamType(method.getReturnType()); + } + /** * Returns the {@link Parameters} wrapper to gain additional information about {@link Method} parameters. * @@ -222,12 +236,4 @@ public class QueryMethod { public String toString() { return method.toString(); } - - /** - * @return - * @since 1.10 - */ - public boolean isStreamQuery() { - return ReflectionUtils.isJava8StreamType(method.getReturnType()); - } } diff --git a/src/main/java/org/springframework/data/repository/query/parser/PartTree.java b/src/main/java/org/springframework/data/repository/query/parser/PartTree.java index 0dddbf757..ce0bfb88d 100644 --- a/src/main/java/org/springframework/data/repository/query/parser/PartTree.java +++ b/src/main/java/org/springframework/data/repository/query/parser/PartTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 the original author or authors. + * Copyright 2008-2015 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. @@ -50,7 +50,7 @@ public class PartTree implements Iterable { * @see http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html#ubc */ private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\P{InBASIC_LATIN}))"; - private static final String QUERY_PATTERN = "find|read|get|query"; + private static final String QUERY_PATTERN = "find|read|get|query|stream"; private static final String COUNT_PATTERN = "count"; private static final String DELETE_PATTERN = "delete|remove"; private static final Pattern PREFIX_TEMPLATE = Pattern.compile( // diff --git a/src/main/java/org/springframework/data/util/CloseableIterator.java b/src/main/java/org/springframework/data/util/CloseableIterator.java index 84d6798c5..57c50d792 100644 --- a/src/main/java/org/springframework/data/util/CloseableIterator.java +++ b/src/main/java/org/springframework/data/util/CloseableIterator.java @@ -20,8 +20,8 @@ import java.util.Iterator; /** * A {@link CloseableIterator} serves as a bridging data structure for the underlying data store specific results that - * can be wrapped in a Java 8 {@link java.util.stream.Stream}. - *

+ * can be wrapped in a Java 8 {@link java.util.stream.Stream}. This allows implementations to clean up any resources + * they need to keep open to iterate over elements. * * @author Thomas Darimont * @param @@ -29,10 +29,10 @@ import java.util.Iterator; */ public interface CloseableIterator extends Iterator, Closeable { - /** - * Closes this iterator, freeing any resources created. + /* + * (non-Javadoc) + * @see java.io.Closeable#close() */ @Override void close(); - } diff --git a/src/main/java/org/springframework/data/util/CloseableIteratorDisposingRunnable.java b/src/main/java/org/springframework/data/util/CloseableIteratorDisposingRunnable.java deleted file mode 100644 index 546d934ce..000000000 --- a/src/main/java/org/springframework/data/util/CloseableIteratorDisposingRunnable.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015 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.util; - -/** - * A {@link Runnable} that closes the given {@link CloseableIterator} in its {@link #run()} method. If the given - * {@code closeable} is {@literal null} the {@link #run()} method effectively becomes a noop. - *

- * Can be used in conjunction with streams as close action via: - * - *

- * CloseableIterator result = ...;
- * Spliterator spliterator = ...;
- * 
- * return StreamSupport.stream(spliterator, false).onClose(new CloseableIteratorDisposingRunnable(result));
- * 
- * - * @author Thomas Darimont - */ -public class CloseableIteratorDisposingRunnable implements Runnable { - - private CloseableIterator closeable; - - /** - * Creates a new {@link CloseableIteratorDisposingRunnable}. - * - * @param closeable may be {@literal null} - */ - public CloseableIteratorDisposingRunnable(CloseableIterator closeable) { - this.closeable = closeable; - } - - @Override - public void run() { - - CloseableIterator ci = closeable; - if (ci == null) { - return; - } - - closeable = null; - ci.close(); - } -} diff --git a/src/main/java/org/springframework/data/util/ReflectionUtils.java b/src/main/java/org/springframework/data/util/ReflectionUtils.java index c511be100..5790d5107 100644 --- a/src/main/java/org/springframework/data/util/ReflectionUtils.java +++ b/src/main/java/org/springframework/data/util/ReflectionUtils.java @@ -225,9 +225,9 @@ public abstract class ReflectionUtils { } /** - * Tests whether the given {@code type} is assignable from a Java 8 {@link Stream}. + * Tests whether the given type is assignable to a Java 8 {@link Stream}. * - * @param type may be {@literal null} + * @param type can be {@literal null}. * @return */ public static boolean isJava8StreamType(Class type) { diff --git a/src/main/java/org/springframework/data/util/Java8StreamUtils.java b/src/main/java/org/springframework/data/util/StreamUtils.java similarity index 58% rename from src/main/java/org/springframework/data/util/Java8StreamUtils.java rename to src/main/java/org/springframework/data/util/StreamUtils.java index 4072c10be..bf2deffb7 100644 --- a/src/main/java/org/springframework/data/util/Java8StreamUtils.java +++ b/src/main/java/org/springframework/data/util/StreamUtils.java @@ -27,11 +27,12 @@ import org.springframework.util.Assert; * Spring Data specific Java {@link Stream} utility methods and classes. * * @author Thomas Darimont - * @since 1.8 + * @author Oliver Gierke + * @since 1.10 */ -public class Java8StreamUtils { +public class StreamUtils { - private Java8StreamUtils() {} + private StreamUtils() {} /** * Returns a {@link Stream} backed by the given {@link Iterator}. @@ -53,4 +54,47 @@ public class Java8StreamUtils { return iterator instanceof CloseableIterator ? stream.onClose(new CloseableIteratorDisposingRunnable( (CloseableIterator) iterator)) : stream; } + + /** + * A {@link Runnable} that closes the given {@link CloseableIterator} in its {@link #run()} method. If the given + * {@code closeable} is {@literal null} the {@link #run()} method effectively becomes a noop. + *

+ * Can be used in conjunction with streams as close action via: + * + *

+	 * CloseableIterator result = ...;
+	 * Spliterator spliterator = ...;
+	 * 
+	 * return StreamSupport.stream(spliterator, false).onClose(new CloseableIteratorDisposingRunnable(result));
+	 * 
+ * + * @author Thomas Darimont + * @author Oliver Gierke + * @since 1.10 + */ + private static class CloseableIteratorDisposingRunnable implements Runnable { + + private CloseableIterator closeable; + + /** + * Creates a new {@link CloseableIteratorDisposingRunnable} for the given {@link CloseableIterator}. + * + * @param closeable can be {@literal null}. + */ + public CloseableIteratorDisposingRunnable(CloseableIterator closeable) { + this.closeable = closeable; + } + + /* + * (non-Javadoc) + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + + if (closeable != null) { + closeable.close(); + } + } + } } diff --git a/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java b/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java index f3a4d6985..a5d27071b 100644 --- a/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 the original author or authors. + * Copyright 2008-2015 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. @@ -20,6 +20,7 @@ import static org.junit.Assert.*; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.stream.Stream; import org.junit.Test; import org.springframework.data.domain.Page; @@ -138,6 +139,30 @@ public class QueryMethodUnitTests { assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(true)); } + /** + * @see DATACMNS-650 + */ + @Test + public void considersMethodReturningAStreamStreaming() throws Exception { + + RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class); + Method method = SampleRepository.class.getMethod("streaming"); + + assertThat(new QueryMethod(method, repositoryMetadata).isStreamQuery(), is(true)); + } + + /** + * @see DATACMNS-650 + */ + @Test + public void doesNotRejectStreamingForPagination() throws Exception { + + RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class); + Method method = SampleRepository.class.getMethod("streaming", Pageable.class); + + assertThat(new QueryMethod(method, repositoryMetadata).isStreamQuery(), is(true)); + } + interface SampleRepository extends Repository { String pagingMethodWithInvalidReturnType(Pageable pageable); @@ -157,6 +182,10 @@ public class QueryMethodUnitTests { Slice sliceOfUsers(); User[] arrayOfUsers(); + + Stream streaming(); + + Stream streaming(Pageable pageable); } class User { diff --git a/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java b/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java index 001e892ea..e08522763 100644 --- a/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 the original author or authors. + * Copyright 2008-2015 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 @@ -46,7 +46,7 @@ import org.springframework.data.repository.query.parser.PartTree.OrPart; */ public class PartTreeUnitTests { - private String[] PREFIXES = { "find", "read", "get", "query", "count", "delete", "remove" }; + private String[] PREFIXES = { "find", "read", "get", "query", "stream", "count", "delete", "remove" }; @Test(expected = IllegalArgumentException.class) public void rejectsNullSource() throws Exception { @@ -650,25 +650,26 @@ public class PartTreeUnitTests { assertLimiting("countFirst10DistinctUsersByLastname", User.class, false, null, true); assertLimiting("countTop10DistinctUsersByLastname", User.class, false, null, true); } - + /** * @see DATACMNS-581 */ @Test public void parsesIsNotContainingCorrectly() throws Exception { - assertType(asList("firstnameIsNotContaining", "firstnameNotContaining", "firstnameNotContains"), NOT_CONTAINING, "firstname"); + assertType(asList("firstnameIsNotContaining", "firstnameNotContaining", "firstnameNotContains"), NOT_CONTAINING, + "firstname"); } - + /** * @see DATACMNS-581 */ @Test public void buildsPartTreeForNotContainingCorrectly() throws Exception { - + PartTree tree = new PartTree("findAllByLegalNameNotContaining", Organization.class); assertPart(tree, new Part[] { new Part("legalNameNotContaining", Organization.class) }); } - + private static void assertLimiting(String methodName, Class entityType, boolean limiting, Integer maxResults) { assertLimiting(methodName, entityType, limiting, maxResults, false); } diff --git a/src/test/java/org/springframework/data/util/Java8StreamUtilsTests.java b/src/test/java/org/springframework/data/util/StreamUtilsTests.java similarity index 87% rename from src/test/java/org/springframework/data/util/Java8StreamUtilsTests.java rename to src/test/java/org/springframework/data/util/StreamUtilsTests.java index f536514d6..51631b99c 100644 --- a/src/test/java/org/springframework/data/util/Java8StreamUtilsTests.java +++ b/src/test/java/org/springframework/data/util/StreamUtilsTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.util; +import static org.springframework.data.util.StreamUtils.*; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @@ -29,9 +30,9 @@ import org.junit.Test; * Spring Data specific Java {@link Stream} utility methods and classes. * * @author Thomas Darimont - * @since 1.8 + * @since 1.10 */ -public class Java8StreamUtilsTests { +public class StreamUtilsTests { /** * @see DATACMNS-650 @@ -40,7 +41,7 @@ public class Java8StreamUtilsTests { public void shouldConvertAnIteratorToAStream() { List input = Arrays.asList("a", "b", "c"); - Stream stream = Java8StreamUtils.createStreamFromIterator(input.iterator()); + Stream stream = createStreamFromIterator(input.iterator()); List output = stream.collect(Collectors. toList()); assertThat(input, is(equalTo(output)));