All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ratpack.rx.RxRatpack Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 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 ratpack.rx;

import org.reactivestreams.Publisher;
import ratpack.exec.*;
import ratpack.func.Action;
import ratpack.registry.RegistrySpec;
import ratpack.rx.internal.DefaultSchedulers;
import ratpack.rx.internal.ExecControllerBackedScheduler;
import ratpack.stream.Streams;
import ratpack.stream.TransformablePublisher;
import ratpack.util.Exceptions;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Scheduler;
import rx.Subscriber;
import rx.exceptions.OnCompletedFailedException;
import rx.exceptions.OnErrorNotImplementedException;
import rx.plugins.RxJavaErrorHandler;
import rx.plugins.RxJavaObservableExecutionHook;
import rx.plugins.RxJavaPlugins;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Provides integration with RxJava.
 * 

* IMPORTANT: the {@link #initialize()} method must be called to fully enable integration. *

* The methods of this class provide bi-directional conversion between Ratpack's {@link Promise} and RxJava's {@link Observable}. * This allows Ratpack promise based API to be integrated into an RxJava based app and vice versa. *

* Conveniently, the {@link #initialize()} method installs an RxJava extension that provides a default error handling strategy for observables that integrates with Ratpack's execution model. *

* To test observable based services that use Ratpack's execution semantics, use the {@code ExecHarness} and convert the observable back to a promise with {@link #promise(Observable)}. *

* The methods in this class are also provided as Groovy Extensions. * When using Groovy, each static method in this class is able to act as an instance-level method against the {@link Observable} type. * * @deprecated since 1.7.0. Use {@code ratpack-rx2} instead. */ @Deprecated public abstract class RxRatpack { private RxRatpack() { } /** * Registers an {@link RxJavaObservableExecutionHook} with RxJava that provides a default error handling strategy of forwarding exceptions to the execution error handler. *

* This method is idempotent. * It only needs to be called once per JVM, regardless of how many Ratpack applications are running within the JVM. *

* For a Java application, a convenient place to call this is in the handler factory implementation. *

{@code
   * import ratpack.error.ServerErrorHandler;
   * import ratpack.test.embed.EmbeddedApp;
   * import rx.Observable;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String... args) throws Exception {
   *     ratpack.rx.RxRatpack.initialize(); // must be called once for the life of the JVM
   *
   *     EmbeddedApp.fromHandlers(chain -> chain
   *       .register(s -> s
   *         .add(ServerErrorHandler.class, (ctx, throwable) ->
   *           ctx.render("caught by error handler: " + throwable.getMessage())
   *         )
   *       )
   *       .get(ctx -> Observable.error(new Exception("!")).subscribe(ctx::render))
   *     ).test(httpClient ->
   *       assertEquals("caught by error handler: !", httpClient.getText())
   *     );
   *   }
   * }
   * }
*/ @SuppressWarnings("deprecation") public static void initialize() { RxJavaPlugins plugins = RxJavaPlugins.getInstance(); ExecutionHook ourHook = new ExecutionHook(); try { plugins.registerObservableExecutionHook(ourHook); } catch (IllegalStateException e) { RxJavaObservableExecutionHook existingHook = plugins.getObservableExecutionHook(); if (!(existingHook instanceof ExecutionHook)) { throw new IllegalStateException("Cannot install RxJava integration because another execution hook (" + existingHook.getClass() + ") is already installed"); } } ErrorHandler ourErrorHandler = new ErrorHandler(); try { plugins.registerErrorHandler(ourErrorHandler); } catch (IllegalStateException e) { RxJavaErrorHandler existingErrorHandler = plugins.getErrorHandler(); if (!(existingErrorHandler instanceof ErrorHandler)) { throw new IllegalStateException("Cannot install RxJava integration because another error handler (" + existingErrorHandler.getClass() + ") is already installed"); } } try { plugins.registerSchedulersHook(new DefaultSchedulers()); } catch (IllegalStateException e) { rx.plugins.RxJavaSchedulersHook existingSchedulers = plugins.getSchedulersHook(); if (!(existingSchedulers instanceof DefaultSchedulers)) { throw new IllegalStateException("Cannot install RxJava integration because another set of default schedulers (" + existingSchedulers.getClass() + ") is already installed"); } } } /** * Converts a {@link Promise} into an {@link Observable}. *

* The returned observable emits the promise's single value if it succeeds, and emits the error (i.e. via {@code onError()}) if it fails. *

* This method works well as a method reference to the {@link Promise#to(ratpack.func.Function)} method. *

{@code
   * import ratpack.exec.Promise;
   * import ratpack.test.exec.ExecHarness;
   *
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static String value;
   *   public static void main(String... args) throws Exception {
   *     ExecHarness.runSingle(e ->
   *       Promise.value("hello world")
   *         .to(ratpack.rx.RxRatpack::observe)
   *         .map(String::toUpperCase)
   *         .subscribe(s -> value = s)
   *     );
   *
   *     assertEquals("HELLO WORLD", value);
   *   }
   * }
   * }
* * @param promise the promise * @param the type of value promised * @return an observable for the promised value */ public static Observable observe(Promise promise) { return Observable.create(subscriber -> promise.onError(subscriber::onError).then(value -> { subscriber.onNext(value); subscriber.onCompleted(); }) ); } /** * Converts a {@link Operation} into an {@link Observable}. *

* The returned observable emits completes upon completion of the operation without emitting a value, and emits the error (i.e. via {@code onError()}) if it fails. *

{@code
   * import ratpack.exec.Operation;
   * import ratpack.test.exec.ExecHarness;
   *
   * import static org.junit.Assert.assertTrue;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static boolean executed;
   *   public static void main(String... args) throws Exception {
   *     ExecHarness.runSingle(e ->
   *       Operation.of(() -> executed = true)
   *         .to(ratpack.rx.RxRatpack::observe)
   *         .subscribe()
   *     );
   *
   *     assertTrue(executed);
   *   }
   * }
   * }
* * @param operation the operation * @return an observable for the operation */ public static Observable observe(Operation operation) { return Observable.create(subscriber -> operation.onError(subscriber::onError).then(subscriber::onCompleted)); } /** * Converts a {@link Promise} for an iterable into an {@link Observable}. *

* The promised iterable will be emitted to the observer one element at a time, like {@link Observable#from(Iterable)}. * *

{@code
   * import ratpack.exec.Promise;
   * import ratpack.test.exec.ExecHarness;
   *
   * import java.util.Arrays;
   * import java.util.LinkedList;
   * import java.util.List;
   *
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String... args) throws Exception {
   *   final List items = new LinkedList<>();
   *     ExecHarness.runSingle(e ->
   *         Promise.value(Arrays.asList("foo", "bar"))
   *           .to(ratpack.rx.RxRatpack::observeEach)
   *           .subscribe(items::add)
   *     );
   *
   *     assertEquals(Arrays.asList("foo", "bar"), items);
   *   }
   * }
   * }
* * @param promise the promise * @param the element type of the promised iterable * @param the type of iterable * @return an observable for each element of the promised iterable * @see #observe(ratpack.exec.Promise) */ public static > Observable observeEach(Promise promise) { return Observable.merge(observe(promise).map(Observable::from)); } /** * Converts an {@link Observable} into a {@link Promise}, for all of the observable's items. *

* This method can be used to simply adapt an observable to a promise, but can also be used to bind an observable to the current execution. * It is sometimes more convenient to use {@link #promise(Observable.OnSubscribe)} over this method. * *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import java.util.List;
   * import java.util.Arrays;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   static class AsyncService {
   *     public  Observable observe(final T value) {
   *       return Observable.create(subscriber ->
   *         new Thread(() -> {
   *           subscriber.onNext(value);
   *           subscriber.onCompleted();
   *         }).start()
   *       );
   *     }
   *   }
   *
   *   public static void main(String[] args) throws Throwable {
   *     List results = ExecHarness.yieldSingle(execution ->
   *       ratpack.rx.RxRatpack.promise(new AsyncService().observe("foo"))
   *     ).getValue();
   *
   *     assertEquals(Arrays.asList("foo"), results);
   *   }
   * }
   * }
* *

* This method uses {@link Observable#toList()} to collect the observable's contents into a list. * It therefore should not be used with observables with many or infinite items. *

* If it is expected that the observable only emits one element, it is typically more convenient to use {@link #promiseSingle(rx.Observable)}. *

* If the observable emits an error, the returned promise will fail with that error. *

* This method must be called during an execution. * * @param observable the observable * @param the type of the value observed * @return a promise that returns all values from the observable * @see #promiseSingle(Observable) * @throws UnmanagedThreadException if called outside of an execution */ public static Promise> promise(Observable observable) throws UnmanagedThreadException { return Promise.async(f -> observable.toList().subscribe(f::success, f::error)); } /** * Converts an {@link Observable} into a {@link Promise}, for all of the observable's items. *

* This method can be used to simply adapt an observable to a promise, but can also be used to bind an observable to the current execution. * It is intended to be used in conjunction with the {@link Observable#extend} method as a method reference. * *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import java.util.List;
   * import java.util.Arrays;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   static class AsyncService {
   *     public  Observable observe(final T value) {
   *       return Observable.create(subscriber ->
   *         new Thread(() -> {
   *           subscriber.onNext(value);
   *           subscriber.onCompleted();
   *         }).start()
   *       );
   *     }
   *   }
   *
   *   public static void main(String[] args) throws Throwable {
   *     List results = ExecHarness.yieldSingle(execution ->
   *       new AsyncService().observe("foo").extend(ratpack.rx.RxRatpack::promise)
   *     ).getValue();
   *
   *     assertEquals(Arrays.asList("foo"), results);
   *   }
   * }
   * }
* *

* This method uses {@link Observable#toList()} to collect the observable's contents into a list. * It therefore should not be used with observables with many or infinite items. *

* If it is expected that the observable only emits one element, it is typically more convenient to use {@link #promiseSingle(rx.Observable)}. *

* If the observable emits an error, the returned promise will fail with that error. *

* This method must be called during an execution. * * @param onSubscribe the on subscribe function * @param the type of the value observed * @return a promise that returns all values from the observable * @see #promiseSingle(Observable) * @see #promise(Observable) * @throws UnmanagedThreadException if called outside of an execution */ public static Promise> promise(Observable.OnSubscribe onSubscribe) throws UnmanagedThreadException { return promise(Observable.create(onSubscribe)); } /** * Converts an {@link Observable} into a {@link Promise}, for the observable's single item. *

* This method can be used to simply adapt an observable to a promise, but can also be used to bind an observable to the current execution. * It is sometimes more convenient to use {@link #promiseSingle(Observable.OnSubscribe)} over this method. * *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   static class AsyncService {
   *     public  Observable observe(final T value) {
   *       return Observable.create(subscriber ->
   *         new Thread(() -> {
   *           subscriber.onNext(value);
   *           subscriber.onCompleted();
   *         }).start()
   *       );
   *     }
   *   }
   *
   *   public static void main(String[] args) throws Throwable {
   *     String result = ExecHarness.yieldSingle(execution ->
   *       ratpack.rx.RxRatpack.promiseSingle(new AsyncService().observe("foo"))
   *     ).getValue();
   *
   *     assertEquals("foo", result);
   *   }
   * }
   * }
* *

* This method uses {@link Observable#single()} to enforce that the observable only emits one item. * If the observable may be empty, then use {@link Observable#singleOrDefault(Object)} to provide a default value. * *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String[] args) throws Throwable {
   *     String result = ExecHarness.yieldSingle(execution ->
   *       ratpack.rx.RxRatpack.promiseSingle(Observable.empty().singleOrDefault("foo"))
   *     ).getValue();
   *     assertEquals("foo", result);
   *   }
   * }
   * }
* *

* If it is expected that the observable may emit more than one element, use {@link #promise(rx.Observable)}. *

* If the observable emits an error, the returned promise will fail with that error. * If the observable emits no items, the returned promise will fail with a {@link java.util.NoSuchElementException}. * If the observable emits more than one item, the returned promise will fail with an {@link IllegalStateException}. *

* This method must be called during an execution. * * @param observable the observable * @param the type of the value observed * @return a promise that returns the sole value from the observable * @see #promise(Observable) * @see #promiseSingle(Observable.OnSubscribe) */ public static Promise promiseSingle(Observable observable) throws UnmanagedThreadException { return Promise.async(f -> observable.single().subscribe(f::success, f::error)); } /** * Converts an {@link Observable} into a {@link Promise}, for the observable's single item. *

* This method can be used to simply adapt an observable to a promise, but can also be used to bind an observable to the current execution. * It is intended to be used in conjunction with the {@link Observable#extend} method as a method reference. * *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   static class AsyncService {
   *     public  Observable observe(final T value) {
   *       return Observable.create(subscriber ->
   *         new Thread(() -> {
   *           subscriber.onNext(value);
   *           subscriber.onCompleted();
   *         }).start()
   *       );
   *     }
   *   }
   *
   *   public static void main(String[] args) throws Throwable {
   *     String result = ExecHarness.yieldSingle(execution ->
   *       new AsyncService().observe("foo").extend(ratpack.rx.RxRatpack::promiseSingle)
   *     ).getValue();
   *
   *     assertEquals("foo", result);
   *   }
   * }
   * }
*

* This method uses {@link Observable#single()} to enforce that the observable only emits one item. * If the observable may be empty, then use {@link Observable#singleOrDefault(Object)} to provide a default value. *

{@code
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String[] args) throws Throwable {
   *     String result = ExecHarness.yieldSingle(execution ->
   *       Observable.empty().singleOrDefault("foo").extend(ratpack.rx.RxRatpack::promiseSingle)
   *     ).getValue();
   *     assertEquals("foo", result);
   *   }
   * }
   * }
* If it is expected that the observable may emit more than one element, use {@link #promise(rx.Observable.OnSubscribe)}. *

* If the observable emits an error, the returned promise will fail with that error. * If the observable emits no items, the returned promise will fail with a {@link java.util.NoSuchElementException}. * If the observable emits more than one item, the returned promise will fail with an {@link IllegalStateException}. *

* This method must be called during an execution. * * @param onSubscribe the on subscribe function * @param the type of the value observed * @return a promise that returns the sole value from the observable * @see #promise(Observable.OnSubscribe) * @see #promiseSingle(Observable) */ public static Promise promiseSingle(Observable.OnSubscribe onSubscribe) throws UnmanagedThreadException { return promiseSingle(Observable.create(onSubscribe)); } /** * Converts an {@link Observable} into a {@link Publisher}, for all of the observable's items. *

* This method can be used to simply adapt an observable to a ReactiveStreams publisher. * It is sometimes more convenient to use {@link #publisher(Observable.OnSubscribe)} over this method. * *

{@code
   * import ratpack.stream.Streams;
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import java.util.List;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   static class AsyncService {
   *     public  Observable observe(final T value) {
   *       return Observable.create(subscriber ->
   *         new Thread(() -> {
   *           subscriber.onNext(value);
   *           subscriber.onCompleted();
   *         }).start()
   *       );
   *     }
   *   }
   *
   *   public static void main(String[] args) throws Throwable {
   *     List result = ExecHarness.yieldSingle(execution ->
   *       ratpack.rx.RxRatpack.publisher(new AsyncService().observe("foo")).toList()
   *     ).getValue();
   *     assertEquals("foo", result.get(0));
   *   }
   * }
   * }
* @param observable the observable * @param the type of the value observed * @return a ReactiveStreams publisher containing each value of the observable */ public static TransformablePublisher publisher(Observable observable) { return Streams.transformable(RxReactiveStreams.toPublisher(observable)); } /** * Converts an {@link Observable} into a {@link Publisher}, for all of the observable's items. *

* This method can be used to simply adapt an observable to a ReactiveStreams publisher. * It is intended to be used in conjunction with the {@link Observable#extend} method as a method reference. * *

{@code
   * import ratpack.stream.Streams;
   * import ratpack.test.exec.ExecHarness;
   * import rx.Observable;
   * import java.util.List;
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   static class AsyncService {
   *     public  Observable observe(final T value) {
   *       return Observable.create(subscriber ->
   *         new Thread(() -> {
   *           subscriber.onNext(value);
   *           subscriber.onCompleted();
   *         }).start()
   *       );
   *     }
   *   }
   *
   *   public static void main(String[] args) throws Throwable {
   *     List result = ExecHarness.yieldSingle(execution ->
   *       new AsyncService().observe("foo").extend(ratpack.rx.RxRatpack::publisher).toList()
   *     ).getValue();
   *     assertEquals("foo", result.get(0));
   *   }
   * }
   * }
* @param onSubscribe the on subscribe function * @param the type of the value observed * @return a ReactiveStreams publisher containing each value of the observable */ public static TransformablePublisher publisher(Observable.OnSubscribe onSubscribe) { return publisher(Observable.create(onSubscribe)); } /** * Binds the given observable to the current execution, allowing integration of third-party asynchronous observables with Ratpack's execution model. *

* This method is useful when you want to consume an asynchronous observable within a Ratpack execution, as an observable. * It is just a combination of {@link #promise(Observable)} and {@link #observeEach(Promise)}. * *

{@code
   * import rx.Observable;
   * import ratpack.test.exec.ExecHarness;
   * import java.util.Arrays;
   * import java.util.List;
   * import static org.junit.Assert.*;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String... args) throws Exception {
   *     Observable asyncObservable = Observable.create(subscriber ->
   *       new Thread(() -> {
   *         subscriber.onNext("foo");
   *         subscriber.onNext("bar");
   *         subscriber.onCompleted();
   *       }).start()
   *     );
   *
   *     List strings = ExecHarness.yieldSingle(e ->
   *       ratpack.rx.RxRatpack.promise(asyncObservable.compose(ratpack.rx.RxRatpack::bindExec))
   *     ).getValue();
   *
   *     assertEquals(Arrays.asList("foo", "bar"), strings);
   *   }
   * }
   * }
*

* * @param source the observable source * @param the type of item observed * @return an observable stream equivalent to the given source * @see #observeEach(Promise) * @see #promise(Observable) */ public static Observable bindExec(Observable source) { return Exceptions.uncheck(() -> promise(source).to(RxRatpack::observeEach)); } /** * Parallelize an observable by forking it's execution onto a different Ratpack compute thread and automatically binding * the result back to the original execution. *

* This method can be used for simple parallel processing. It's behavior is similar to the * subscribeOn but allows the use of * Ratpack compute threads. Using fork modifies the execution of the upstream observable. *

* This is different than forkEach which modifies where the downstream is executed. * *

{@code
   * import ratpack.func.Pair;
   * import ratpack.test.exec.ExecHarness;
   *
   * import rx.Observable;
   *
   * import static org.junit.Assert.assertEquals;
   * import static org.junit.Assert.assertNotEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String[] args) throws Exception {
   *     ratpack.rx.RxRatpack.initialize();
   *
   *     try (ExecHarness execHarness = ExecHarness.harness(6)) {
   *       Integer sum = execHarness.yield(execution -> {
   *         final String originalComputeThread = Thread.currentThread().getName();
   *
   *         Observable unforkedObservable = Observable.just(1);
   *
   *         // `map` is executed upstream from the fork; that puts it on another parallel compute thread
   *         Observable> forkedObservable = Observable.just(2)
   *             .map((val) -> Pair.of(val, Thread.currentThread().getName()))
   *             .compose(ratpack.rx.RxRatpack::fork);
   *
   *         return ratpack.rx.RxRatpack.promiseSingle(
   *             Observable.zip(unforkedObservable, forkedObservable, (Integer intVal, Pair pair) -> {
   *               String forkedComputeThread = pair.right;
   *               assertNotEquals(originalComputeThread, forkedComputeThread);
   *               return intVal + pair.left;
   *             })
   *         );
   *       }).getValueOrThrow();
   *
   *       assertEquals(sum.intValue(), 3);
   *     }
   *   }
   * }
   * }
* * @param observable the observable sequence to execute on a different compute thread * @param the element type * @return an observable on the compute thread that fork was called from * @since 1.4 * @see #forkEach(Observable) */ public static Observable fork(Observable observable) { return observeEach(promise(observable).fork()); } /** * * A variant of {@link #fork} that allows access to the registry of the forked execution inside an {@link Action}. *

* This allows the insertion of objects via {@link RegistrySpec#add} that will be available to the forked observable. *

* You do not have access to the original execution inside the {@link Action}. * *

{@code
   * import ratpack.exec.Execution;
   * import ratpack.registry.RegistrySpec;
   * import ratpack.test.exec.ExecHarness;
   *
   * import rx.Observable;
   *
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String[] args) throws Exception {
   *     ratpack.rx.RxRatpack.initialize();
   *
   *     try (ExecHarness execHarness = ExecHarness.harness(6)) {
   *       String concatenatedResult = execHarness.yield(execution -> {
   *
   *         Observable notYetForked = Observable.just("foo")
   *             .map((value) -> value + Execution.current().get(String.class));
   *
   *         Observable forkedObservable = ratpack.rx.RxRatpack.fork(
   *             notYetForked,
   *             (RegistrySpec registrySpec) -> registrySpec.add("bar")
   *         );
   *
   *         return ratpack.rx.RxRatpack.promiseSingle(forkedObservable);
   *       }).getValueOrThrow();
   *
   *       assertEquals(concatenatedResult, "foobar");
   *     }
   *   }
   * }
   * }
* * @param observable the observable sequence to execute on a different compute thread * @param doWithRegistrySpec an Action where objects can be inserted into the registry of the forked execution * @param the element type * @return an observable on the compute thread that fork was called from * @throws Exception * @since 1.4 * @see #fork(Observable) */ public static Observable fork(Observable observable, Action doWithRegistrySpec) throws Exception { return observeEach(promise(observable).fork(execSpec -> execSpec.register(doWithRegistrySpec))); } /** * Parallelize an observable by creating a new Ratpack execution for each element. * *
{@code
   * import ratpack.util.Exceptions;
   * import ratpack.test.exec.ExecHarness;
   *
   * import rx.Observable;
   *
   * import java.util.List;
   * import java.util.Arrays;
   * import java.util.LinkedList;
   * import java.util.Collection;
   * import java.util.Collections;
   * import java.util.concurrent.CyclicBarrier;
   *
   * import static org.junit.Assert.assertEquals;
   *
   * {@literal @}SuppressWarnings("deprecation")
   * public class Example {
   *   public static void main(String[] args) throws Exception {
   *     ratpack.rx.RxRatpack.initialize();
   *
   *     CyclicBarrier barrier = new CyclicBarrier(5);
   *
   *     try (ExecHarness execHarness = ExecHarness.harness(6)) {
   *       List values = execHarness.yield(execution ->
   *         ratpack.rx.RxRatpack.promise(
   *           Observable.just(1, 2, 3, 4, 5)
   *             .compose(ratpack.rx.RxRatpack::forkEach) // parallelize
   *             .doOnNext(value -> Exceptions.uncheck(() -> barrier.await())) // wait for all values
   *             .map(integer -> integer.intValue() * 2)
   *             .serialize()
   *         )
   *       ).getValue();
   *
   *       List sortedValues = new LinkedList<>(values);
   *       Collections.sort(sortedValues);
   *       assertEquals(Arrays.asList(2, 4, 6, 8, 10), sortedValues);
   *     }
   *   }
   * }
   * }
* * @param observable the observable sequence to process each element of in a forked execution * @param the element type * @return an observable */ public static Observable forkEach(Observable observable) { return RxRatpack.forkEach(observable, Action.noop()); } /** * A variant of {@link #forkEach} that allows access to the registry of each forked execution inside an {@link Action}. *

* This allows the insertion of objects via {@link RegistrySpec#add} that will be available to every forked observable. *

* You do not have access to the original execution inside the {@link Action}. * * @param observable the observable sequence to process each element of in a forked execution * @param doWithRegistrySpec an Action where objects can be inserted into the registry of the forked execution * @param the element type * @return an observable * @since 1.4 * @see #forkEach(Observable) * @see #fork(Observable, Action) */ public static Observable forkEach(Observable observable, Action doWithRegistrySpec) { return observable.lift(downstream -> new Subscriber(downstream) { private final AtomicInteger wip = new AtomicInteger(1); private final AtomicBoolean closed = new AtomicBoolean(); @Override public void onCompleted() { maybeDone(); } @Override public void onError(final Throwable e) { terminate(() -> downstream.onError(e)); } private void maybeDone() { if (wip.decrementAndGet() == 0) { terminate(downstream::onCompleted); } } private void terminate(Runnable runnable) { if (closed.compareAndSet(false, true)) { runnable.run(); } } @Override public void onNext(final T t) { // Avoid the overhead of creating executions if downstream is no longer interested if (isUnsubscribed() || closed.get()) { return; } wip.incrementAndGet(); Execution.fork() .register(doWithRegistrySpec) .onComplete(e -> this.maybeDone()) .onError(this::onError) .start(e -> { if (!closed.get()) { downstream.onNext(t); } }); } }); } /** * A scheduler that uses the application event loop and initialises each job as an {@link ratpack.exec.Execution} (via {@link ExecController#fork()}). * * @param execController the execution controller to back the scheduler * @return a scheduler */ public static Scheduler scheduler(ExecController execController) { return new ExecControllerBackedScheduler(execController); } /** * A scheduler that uses the application event loop and initialises each job as an {@link ratpack.exec.Execution} (via {@link ExecController#fork()}). *

* That same as {@link #scheduler(ExecController)}, but obtains the exec controller via {@link ExecController#require()}. * * @return a scheduler */ public static Scheduler scheduler() { return scheduler(ExecController.require()); } private static class ErrorHandler extends RxJavaErrorHandler { @Override public void handleError(Throwable e) { } } private static class ExecutionHook extends RxJavaObservableExecutionHook { @Override public Throwable onSubscribeError(Throwable e) { if (e instanceof OnCompletedFailedException) { Promise.error(e).then(Action.noop()); } return e; } @Override public Observable.OnSubscribe onSubscribeStart(Observable observableInstance, Observable.OnSubscribe onSubscribe) { return ExecController.current() .map(e -> executionBackedOnSubscribe(onSubscribe)) .orElse(onSubscribe); } private Observable.OnSubscribe executionBackedOnSubscribe(final Observable.OnSubscribe onSubscribe) { return (subscriber) -> onSubscribe.call(new ExecutionBackedSubscriber<>(subscriber)); } } private static class ExecutionBackedSubscriber extends Subscriber { private final Subscriber subscriber; public ExecutionBackedSubscriber(Subscriber subscriber) { super(subscriber); this.subscriber = subscriber; } @Override public void onCompleted() { try { subscriber.onCompleted(); } catch (final OnErrorNotImplementedException e) { Promise.error(e.getCause()).then(Action.noop()); } } @Override public void onError(Throwable e) { try { subscriber.onError(e); } catch (OnErrorNotImplementedException e2) { Promise.error(e2.getCause()).then(Action.noop()); } } public void onNext(T t) { try { subscriber.onNext(t); } catch (OnErrorNotImplementedException e) { Promise.error(e.getCause()).then(Action.noop()); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy