From 186cb256051b81343ec917cf7bac30c063aa3d0b Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 3 Mar 2015 16:18:25 +0100 Subject: [PATCH] DATACMNS-650 - Polishing. Added stream to the list of supported query method prefixes. Allow Stream to be used as return type for paginating queries, too. Renamed Java8StreamUtils to StreamUtils. Some additional JavaDoc. Original pull request: #116. --- .../data/repository/query/QueryMethod.java | 32 ++++++----- .../repository/query/parser/PartTree.java | 4 +- .../data/util/CloseableIterator.java | 10 ++-- .../CloseableIteratorDisposingRunnable.java | 57 ------------------- .../data/util/ReflectionUtils.java | 4 +- ...Java8StreamUtils.java => StreamUtils.java} | 50 +++++++++++++++- .../query/QueryMethodUnitTests.java | 31 +++++++++- .../query/parser/PartTreeUnitTests.java | 15 ++--- ...mUtilsTests.java => StreamUtilsTests.java} | 7 ++- 9 files changed, 117 insertions(+), 93 deletions(-) delete mode 100644 src/main/java/org/springframework/data/util/CloseableIteratorDisposingRunnable.java rename src/main/java/org/springframework/data/util/{Java8StreamUtils.java => StreamUtils.java} (58%) rename src/test/java/org/springframework/data/util/{Java8StreamUtilsTests.java => StreamUtilsTests.java} (87%) 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)));