Skip to content

Commit e92c235

Browse files
Thomas Darimontodrotbohm
authored andcommitted
DATACMNS-650 - Introduced CloseableIterator abstraction as a foundation for streaming of results.
A CloseableIterator abstracts the underlying result with support for releasing the associated resources via close() which can be transparently be used with try-with-resources in Java 7 since Closeable implements AutoCloseable from Java 7 on upwards. Added detector methods to QueryMethod to check whether the current method returns a Stream. Introduced ReflectionUtils.isJava8StreamType to safely detect whether the given type is assignable to a Java 8 Stream. Introduced CloseableIteratorDisposingRunnable that can be registered as a cleanup action on a Stream. Original pull request: #116.
1 parent 64db95f commit e92c235

File tree

7 files changed

+244
-4
lines changed

7 files changed

+244
-4
lines changed

src/main/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadata.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2014 the original author or authors.
2+
* Copyright 2011-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import org.springframework.data.repository.core.RepositoryMetadata;
2525
import org.springframework.data.repository.util.QueryExecutionConverters;
2626
import org.springframework.data.util.ClassTypeInformation;
27+
import org.springframework.data.util.ReflectionUtils;
2728
import org.springframework.data.util.TypeInformation;
2829
import org.springframework.util.Assert;
2930

@@ -125,7 +126,7 @@ private static Class<?> unwrapWrapperTypes(TypeInformation<?> type) {
125126
Class<?> rawType = type.getType();
126127

127128
boolean needToUnwrap = Iterable.class.isAssignableFrom(rawType) || rawType.isArray()
128-
|| QueryExecutionConverters.supports(rawType);
129+
|| QueryExecutionConverters.supports(rawType) || ReflectionUtils.isJava8StreamType(rawType);
129130

130131
return needToUnwrap ? unwrapWrapperTypes(type.getComponentType()) : rawType;
131132
}

src/main/java/org/springframework/data/repository/query/QueryMethod.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2014 the original author or authors.
2+
* Copyright 2008-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,13 +26,15 @@
2626
import org.springframework.data.domain.Sort;
2727
import org.springframework.data.repository.core.EntityMetadata;
2828
import org.springframework.data.repository.core.RepositoryMetadata;
29+
import org.springframework.data.util.ReflectionUtils;
2930
import org.springframework.util.Assert;
3031

3132
/**
3233
* Abstraction of a method that is designated to execute a finder query. Enriches the standard {@link Method} interface
3334
* with specific information that is necessary to construct {@link RepositoryQuery}s for the method.
3435
*
3536
* @author Oliver Gierke
37+
* @author Thomas Darimont
3638
*/
3739
public class QueryMethod {
3840

@@ -220,4 +222,12 @@ public boolean isQueryForEntity() {
220222
public String toString() {
221223
return method.toString();
222224
}
225+
226+
/**
227+
* @return
228+
* @since 1.10
229+
*/
230+
public boolean isStreamQuery() {
231+
return ReflectionUtils.isJava8StreamType(method.getReturnType());
232+
}
223233
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.util;
17+
18+
import java.io.Closeable;
19+
import java.util.Iterator;
20+
21+
/**
22+
* A {@link CloseableIterator} serves as a bridging data structure for the underlying data store specific results that
23+
* can be wrapped in a Java 8 {@link java.util.stream.Stream}.
24+
* <p>
25+
*
26+
* @author Thomas Darimont
27+
* @param <T>
28+
* @since 1.10
29+
*/
30+
public interface CloseableIterator<T> extends Iterator<T>, Closeable {
31+
32+
/**
33+
* Closes this iterator, freeing any resources created.
34+
*/
35+
@Override
36+
void close();
37+
38+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.util;
17+
18+
/**
19+
* A {@link Runnable} that closes the given {@link CloseableIterator} in its {@link #run()} method. If the given
20+
* {@code closeable} is {@literal null} the {@link #run()} method effectively becomes a noop.
21+
* <p>
22+
* Can be used in conjunction with streams as close action via:
23+
*
24+
* <pre>
25+
* CloseableIterator<T> result = ...;
26+
* Spliterator<T> spliterator = ...;
27+
*
28+
* return StreamSupport.stream(spliterator, false).onClose(new CloseableIteratorDisposingRunnable(result));
29+
* </pre>
30+
*
31+
* @author Thomas Darimont
32+
*/
33+
public class CloseableIteratorDisposingRunnable implements Runnable {
34+
35+
private CloseableIterator<?> closeable;
36+
37+
/**
38+
* Creates a new {@link CloseableIteratorDisposingRunnable}.
39+
*
40+
* @param closeable may be {@literal null}
41+
*/
42+
public CloseableIteratorDisposingRunnable(CloseableIterator<?> closeable) {
43+
this.closeable = closeable;
44+
}
45+
46+
@Override
47+
public void run() {
48+
49+
CloseableIterator<?> ci = closeable;
50+
if (ci == null) {
51+
return;
52+
}
53+
54+
closeable = null;
55+
ci.close();
56+
}
57+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.util;
17+
18+
import java.util.Iterator;
19+
import java.util.Spliterator;
20+
import java.util.Spliterators;
21+
import java.util.stream.Stream;
22+
import java.util.stream.StreamSupport;
23+
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* Spring Data specific Java {@link Stream} utility methods and classes.
28+
*
29+
* @author Thomas Darimont
30+
* @since 1.8
31+
*/
32+
public class Java8StreamUtils {
33+
34+
private Java8StreamUtils() {}
35+
36+
/**
37+
* Returns a {@link Stream} backed by the given {@link Iterator}.
38+
* <p>
39+
* If the given iterator is an {@link CloseableIterator} add a {@link CloseableIteratorDisposingRunnable} wrapping the
40+
* given iterator to propagate {@link Stream#close()} accordingly.
41+
*
42+
* @param iterator must not be {@literal null}
43+
* @return
44+
* @since 1.8
45+
*/
46+
public static <T> Stream<T> createStreamFromIterator(Iterator<T> iterator) {
47+
48+
Assert.notNull(iterator, "Iterator must not be null!");
49+
50+
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL);
51+
Stream<T> stream = StreamSupport.stream(spliterator, false);
52+
53+
return iterator instanceof CloseableIterator ? stream.onClose(new CloseableIteratorDisposingRunnable(
54+
(CloseableIterator<T>) iterator)) : stream;
55+
}
56+
}

src/main/java/org/springframework/data/util/ReflectionUtils.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2013 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Field;
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Modifier;
22+
import java.util.stream.Stream;
2223

2324
import org.springframework.beans.BeanUtils;
2425
import org.springframework.core.annotation.AnnotationUtils;
@@ -30,10 +31,24 @@
3031
* Spring Data specific reflection utility methods and classes.
3132
*
3233
* @author Oliver Gierke
34+
* @author Thomas Darimont
3335
* @since 1.5
3436
*/
3537
public abstract class ReflectionUtils {
3638

39+
private static final Class<?> JAVA8_STREAM_TYPE;
40+
41+
static {
42+
43+
Class<?> cls = null;
44+
45+
try {
46+
cls = Class.forName("java.util.stream.Stream");
47+
} catch (ClassNotFoundException ignore) {}
48+
49+
JAVA8_STREAM_TYPE = cls;
50+
}
51+
3752
private ReflectionUtils() {}
3853

3954
/**
@@ -208,4 +223,19 @@ public static void setField(Field field, Object target, Object value) {
208223
org.springframework.util.ReflectionUtils.makeAccessible(field);
209224
org.springframework.util.ReflectionUtils.setField(field, target, value);
210225
}
226+
227+
/**
228+
* Tests whether the given {@code type} is assignable from a Java 8 {@link Stream}.
229+
*
230+
* @param type may be {@literal null}
231+
* @return
232+
*/
233+
public static boolean isJava8StreamType(Class<?> type) {
234+
235+
if (type == null || JAVA8_STREAM_TYPE == null) {
236+
return false;
237+
}
238+
239+
return JAVA8_STREAM_TYPE.isAssignableFrom(type);
240+
}
211241
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.util;
17+
18+
import static org.hamcrest.CoreMatchers.*;
19+
import static org.junit.Assert.*;
20+
21+
import java.util.Arrays;
22+
import java.util.List;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.Stream;
25+
26+
import org.junit.Test;
27+
28+
/**
29+
* Spring Data specific Java {@link Stream} utility methods and classes.
30+
*
31+
* @author Thomas Darimont
32+
* @since 1.8
33+
*/
34+
public class Java8StreamUtilsTests {
35+
36+
/**
37+
* @see DATACMNS-650
38+
*/
39+
@Test
40+
public void shouldConvertAnIteratorToAStream() {
41+
42+
List<String> input = Arrays.asList("a", "b", "c");
43+
Stream<String> stream = Java8StreamUtils.createStreamFromIterator(input.iterator());
44+
List<String> output = stream.collect(Collectors.<String> toList());
45+
46+
assertThat(input, is(equalTo(output)));
47+
}
48+
}

0 commit comments

Comments
 (0)