3 changed files with 406 additions and 9 deletions
@ -0,0 +1,240 @@
@@ -0,0 +1,240 @@
|
||||
/* |
||||
* Copyright 2019 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.mongodb.core; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import reactor.core.CoreSubscriber; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.test.StepVerifier; |
||||
import reactor.util.context.Context; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.Test; |
||||
import org.reactivestreams.Subscriber; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
*/ |
||||
public class Fluxperiment { |
||||
|
||||
@Test |
||||
public void applySkipFromFlux() { |
||||
|
||||
hackedFlux().skip(3) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectAccessibleContext().assertThat(ctx -> { |
||||
|
||||
assertThat(ctx.getOrEmpty("skip")).contains(3L); |
||||
assertThat(ctx.getOrEmpty("take")).isEmpty(); |
||||
}).then() //
|
||||
.expectNext("4") //
|
||||
.expectNext("5") //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void applyTakeFromFlux() { |
||||
|
||||
hackedFlux().limitRequest(3) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectAccessibleContext().assertThat(ctx -> { |
||||
|
||||
assertThat(ctx.getOrEmpty("skip")).isEmpty(); |
||||
assertThat(ctx.getOrEmpty("take")).contains(3L); |
||||
}).then() //
|
||||
.expectNext("1") //
|
||||
.expectNext("2") //
|
||||
.expectNext("3") //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void applySkipAndLimitFromFlux/* in that order */() { |
||||
|
||||
hackedFlux().skip(1) /* in DB */.limitRequest(2) /* in DB */ //
|
||||
.as(StepVerifier::create) //
|
||||
.expectAccessibleContext().assertThat(ctx -> { |
||||
|
||||
assertThat(ctx.getOrEmpty("skip")).contains(1L); |
||||
assertThat(ctx.getOrEmpty("take")).contains(2L); |
||||
}).then() //
|
||||
.expectNext("2") //
|
||||
.expectNext("3") //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void applyTakeButNotSkipFromFlux/* cause order matters */() { |
||||
|
||||
hackedFlux().limitRequest(3)/* in DB */.skip(1) /* in memory */ //
|
||||
.as(StepVerifier::create) //
|
||||
.expectAccessibleContext().assertThat(ctx -> { |
||||
|
||||
assertThat(ctx.getOrEmpty("skip")).isEmpty(); |
||||
assertThat(ctx.getOrEmpty("take")).contains(3L); |
||||
}).then() //
|
||||
.expectNext("2") //
|
||||
.expectNext("3") //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void justApplySkipButNotTakeIfTheyDoNotFollowOneAnother() { |
||||
|
||||
hackedFlux().skip(1)/* in DB */.map(v -> v).limitRequest(2) /* in memory */ //
|
||||
.as(StepVerifier::create) //
|
||||
.expectAccessibleContext().assertThat(ctx -> { |
||||
|
||||
assertThat(ctx.getOrEmpty("skip")).contains(1L); |
||||
assertThat(ctx.getOrEmpty("take")).isEmpty(); |
||||
}).then() //
|
||||
.expectNext("2") //
|
||||
.expectNext("3") //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void applyNeitherSkipNorTakeIfPrecededWithOtherOperator() { |
||||
|
||||
hackedFlux().map(v -> v).skip(1).limitRequest(2) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectAccessibleContext().assertThat(ctx -> { |
||||
|
||||
assertThat(ctx.getOrEmpty("skip")).isEmpty(); |
||||
assertThat(ctx.getOrEmpty("take")).isEmpty(); |
||||
}).then() //
|
||||
.expectNext("2") //
|
||||
.expectNext("3") //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
public void applyOnlyFirstSkip/* toDatabase */() { |
||||
|
||||
hackedFlux().skip(3)/* in DB */.skip(1)/* in memory */ //
|
||||
.as(StepVerifier::create) //
|
||||
.expectAccessibleContext().assertThat(ctx -> { |
||||
|
||||
assertThat(ctx.getOrEmpty("skip")).contains(3L); |
||||
assertThat(ctx.getOrEmpty("take")).isEmpty(); |
||||
}).then() //
|
||||
.expectNext("5") //
|
||||
.verifyComplete(); |
||||
} |
||||
|
||||
Flux<String> hackedFlux() { |
||||
|
||||
return new Flux<String>() { |
||||
|
||||
@Override |
||||
public void subscribe(CoreSubscriber actual) { |
||||
|
||||
Long skip = extractSkip(actual); |
||||
Long take = extractLimit(actual); |
||||
|
||||
System.out.println(String.format("Using offset: %s and limit: %s", skip, take)); |
||||
|
||||
// and here we use the original Flux and evaluate skip / take in the template
|
||||
Stream<String> source = Stream.of("1", "2", "3", "4", "5"); |
||||
Context context = Context.empty(); |
||||
|
||||
// and here we use the original Flux and evaluate skip / take in the template
|
||||
if (skip != null && skip > 0L) { |
||||
context = context.put("skip", skip); |
||||
source = source.skip(skip); |
||||
} |
||||
if (take != null && take > 0L) { |
||||
|
||||
context = context.put("take", take); |
||||
source = source.limit(take); |
||||
} |
||||
|
||||
Flux.fromStream(source).subscriberContext(context).subscribe(actual); |
||||
|
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Nullable |
||||
static Long extractSkip(Subscriber subscriber) { |
||||
|
||||
if (subscriber == null || !ClassUtils.getShortName(subscriber.getClass()).endsWith("SkipSubscriber")) { |
||||
return null; |
||||
} |
||||
|
||||
java.lang.reflect.Field field = ReflectionUtils.findField(subscriber.getClass(), "remaining"); |
||||
if (field == null) { |
||||
return null; |
||||
} |
||||
|
||||
ReflectionUtils.makeAccessible(field); |
||||
Long skip = (Long) ReflectionUtils.getField(field, subscriber); |
||||
if (skip != null && skip > 0L) { |
||||
|
||||
// reset the field, otherwise we'd skip stuff in the code.
|
||||
ReflectionUtils.setField(field, subscriber, 0L); |
||||
} |
||||
|
||||
return skip; |
||||
} |
||||
|
||||
@Nullable |
||||
static Long extractLimit(Subscriber subscriber) { |
||||
|
||||
if (subscriber == null) { |
||||
return null; |
||||
} |
||||
|
||||
if (!ClassUtils.getShortName(subscriber.getClass()).endsWith("TakeSubscriber") |
||||
&& !ClassUtils.getShortName(subscriber.getClass()).endsWith("FluxLimitRequestSubscriber")) { |
||||
return extractLimit(extractPotentialTakeSubscriber(subscriber)); |
||||
} |
||||
|
||||
java.lang.reflect.Field field = ReflectionUtils.findField(subscriber.getClass(), "n"); // from TakeSubscriber
|
||||
if (field == null) { |
||||
|
||||
field = ReflectionUtils.findField(subscriber.getClass(), "toProduce"); // from FluxLimitRequestSubscriber
|
||||
if (field == null) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
ReflectionUtils.makeAccessible(field); |
||||
return (Long) ReflectionUtils.getField(field, subscriber); |
||||
} |
||||
|
||||
@Nullable |
||||
static Subscriber extractPotentialTakeSubscriber(Subscriber subscriber) { |
||||
|
||||
if (!ClassUtils.getShortName(subscriber.getClass()).endsWith("SkipSubscriber")) { |
||||
return null; |
||||
} |
||||
|
||||
Field field = ReflectionUtils.findField(subscriber.getClass(), "actual"); |
||||
if (field == null) { |
||||
return null; |
||||
} |
||||
|
||||
ReflectionUtils.makeAccessible(field); |
||||
return (Subscriber) ReflectionUtils.getField(field, subscriber); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue