Package ratpack.rx

Source Code of ratpack.rx.RxRatpack$PromiseSubscribe

/*
* 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 ratpack.exec.ExecControl;
import ratpack.exec.ExecController;
import ratpack.exec.Promise;
import ratpack.func.Action;
import ratpack.rx.internal.DefaultSchedulers;
import ratpack.rx.internal.ExecControllerBackedScheduler;
import ratpack.util.ExceptionUtils;
import rx.Observable;
import rx.Scheduler;
import rx.Subscriber;
import rx.exceptions.OnErrorNotImplementedException;
import rx.functions.Action2;
import rx.plugins.RxJavaObservableExecutionHook;
import rx.plugins.RxJavaPlugins;
import rx.plugins.RxJavaSchedulersHook;

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

import static ratpack.util.ExceptionUtils.toException;

/**
* Provides integration with <a href="https://github.com/Netflix/RxJava">RxJava</a>.
* <p>
* <b>IMPORTANT:</b> the {@link #initialize()} method must be called to fully enable integration.
* <p>
* 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.
* <p>
* When using observables for asynchronous actions, it is generally required to wrap promises created by an {@link ExecControl} in order to integrate with Ratpack's execution model.
* This typically means using {@link ExecControl#promise(Action)} or {@link ExecControl#blocking(java.util.concurrent.Callable)} to initiate the operation and then wrapping with {@link #observe(Promise)} or similar.
* <p>
* 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 #asPromise(Observable)}.
*/
public abstract class RxRatpack {

  /**
   * Registers an {@link RxJavaObservableExecutionHook} with RxJava that provides a default error handling strategy of forwarding exceptions to the execution error handler.
   * <p>
   * This method is idempotent.
   * It only needs to be called once per JVM, regardless of how many Ratpack applications are running within the JVM.
   * <p>
   * For a Java application, a convenient place to call this is in the handler factory implementation.
   * <pre class="java">{@code
   * import ratpack.error.ServerErrorHandler;
   * import ratpack.rx.RxRatpack;
   * import ratpack.test.UnitTest;
   * import ratpack.test.handling.HandlingResult;
   * import rx.Observable;
   *
   * public class Example {
   *   public static void main(String... args) throws Exception {
   *     RxRatpack.initialize(); // must be called once per JVM
   *
   *     HandlingResult result = UnitTest.requestFixture().handleChain(chain -> {
   *       chain.register(registry ->
   *         registry.add(ServerErrorHandler.class, (context, throwable) ->
   *           context.render("caught by error handler: " + throwable.getMessage())
   *         )
   *       );
   *
   *       chain.get(ctx -> Observable.<String>error(new Exception("!")).subscribe((s) -> {}));
   *     });
   *
   *     assert result.rendered(String.class).equals("caught by error handler: !");
   *   }
   * }
   * }</pre>
   */
  @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");
      }
    }

    System.setProperty("rxjava.plugin." + RxJavaSchedulersHook.class.getSimpleName() + ".implementation", DefaultSchedulers.class.getName());
    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");
    }
  }

  /**
   * A scheduler that uses the application event loop and initialises each job as an {@link ratpack.exec.Execution} (via {@link ratpack.exec.ExecControl#exec()}).
   *
   * @param execController the execution controller to back the scheduler
   * @return a scheduler
   */
  public static Scheduler scheduler(final ExecController execController) {
    return new ExecControllerBackedScheduler(execController);
  }

  /**
   * Forks the current execution in order to subscribe to the given source, then joining the original execution with the source values.
   * <p>
   * This method supports parallelism in the observable stream.
   * <p>
   * This method uses {@link rx.Observable#toList()} on the given source to collect all values before returning control to the original execution.
   * As such, {@code source} should not be an infinite or extremely large stream.
   *
   * @param execControl the execution control
   * @param source the observable source
   * @param <T> the type of item observed
   * @return an observable stream equivalent to the given source
   */
  public static <T> Observable<T> forkAndJoin(final ExecControl execControl, final Observable<T> source) {
    Promise<List<T>> promise = execControl.promise(fulfiller -> execControl.exec().start(execution -> source
      .toList()
      .subscribe(new Subscriber<List<T>>() {
        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {
          fulfiller.error(e);
        }

        @Override
        public void onNext(List<T> ts) {
          fulfiller.success(ts);
        }
      })));

    return observeEach(promise);
  }

  /**
   * Converts a Ratpack promise into an Rx observable.
   * <p>
   * For example, this can be used to observe blocking operations.
   * <pre class="java">{@code
   * import ratpack.exec.Promise;
   * import ratpack.test.handling.HandlingResult;
   *
   * import static ratpack.rx.RxRatpack.observe;
   * import static ratpack.test.UnitTest.requestFixture;
   *
   * public class Example {
   *   public static void main(String... args) throws Exception {
   *     HandlingResult result = requestFixture().handle(context -> {
   *       Promise<String> promise = context.blocking(() -> "hello world");
   *       observe(promise).map(String::toUpperCase).subscribe(context::render);
   *     });
   *
   *     assert result.rendered(String.class).equals("HELLO WORLD");
   *   }
   * }
   * }</pre>
   *
   * @param promise the promise
   * @param <T> the type of value promised
   * @return an observable for the promised value
   */
  public static <T> Observable<T> observe(Promise<T> promise) {
    return Observable.create(new PromiseSubscribe<>(promise, (thing, subscriber) -> subscriber.onNext(thing)));
  }

  /**
   * Converts a Rx {@link Observable} into a Ratpack {@link Promise}.
   * <p>
   * For example, this can be used unit test Rx observables.
   * <pre class="java">{@code
   * import ratpack.test.exec.ExecHarness;
   * import ratpack.test.exec.ExecResult;
   * import rx.Observable;
   *
   * import java.util.List;
   *
   * import static ratpack.rx.RxRatpack.asPromise;
   *
   * public class Example {
   *
   *   static class AsyncService {
   *     // Our method under test
   *     // Typically this would be returning an Observable of an asynchronously produced value (using RxRatpack.observe()),
   *     // but for this example we are just faking it with a simple literal observable
   *     public <T> Observable<T> observe(final T value) {
   *       return Observable.just(value);
   *     }
   *   }
   *
   *   public static void main(String[] args) throws Throwable {
   *
   *     // set up the code under test that returns observables
   *     final AsyncService service = new AsyncService();
   *
   *     // exercise the async code using the harness, blocking until the promised value is available
   *     ExecResult<List<String>> result = ExecHarness.yieldSingle(execution -> asPromise(service.observe("foo")));
   *
   *     List<String> results = result.getValue();
   *     assert results.size() == 1;
   *     assert results.get(0).equals("foo");
   *   }
   * }
   * }</pre>
   *
   * @param observable the observable
   * @param <T> the type of the value observed
   * @return a promise that returns all values from the observable
   * @see #asPromiseSingle(Observable)
   */
  public static <T> Promise<List<T>> asPromise(Observable<T> observable) {
    return ExecController.require().getControl().promise(f -> observable.toList().subscribe(f::success, f::error));
  }

  /**
   * Convenience for converting an {@link Observable} to a {@link Promise} when it is known that the observable sequence is of zero or one elements.
   * <p>
   * Has the same behavior as {@link #asPromise(Observable)}, except that the list representation of the sequence is “unpacked”.
   * <p>
   * If the observable sequence produces no elements, the promised value will be {@code null}.
   * If the observable sequence produces one element, the promised value will be that element.
   * If the observable sequence produces more than one element, the promised will fail with an {@link IllegalAccessException}.
   *
   * @param observable the observable
   * @param <T> the type of the value observed
   * @return a promise that returns the sole value from the observable
   * @see #asPromise(Observable)
   */
  public static <T> Promise<T> asPromiseSingle(Observable<T> observable) {
    return asPromise(observable).map(l -> {
      int size = l.size();
      if (size == 0) {
        return null;
      } else if (size == 1) {
        return l.get(0);
      } else {
        throw new IllegalArgumentException("Observable sequence returned more than one element");
      }
    });
  }

  /**
   * Converts a Ratpack promise of an iterable value into an Rx observable for each element of the promised iterable.
   * <p>
   * The promised iterable will be emitted to the observer one element at a time.
   * For example, this can be used to observe background operations that produce some kind of iterable&hellip;
   *
   * <pre class="java">{@code
   * import ratpack.exec.Promise;
   * import ratpack.test.handling.HandlingResult;
   *
   * import java.util.Arrays;
   * import java.util.List;
   *
   * import static ratpack.rx.RxRatpack.observeEach;
   * import static ratpack.test.UnitTest.requestFixture;
   *
   * public class Example {
   *   public static void main(String... args) throws Exception {
   *     HandlingResult result = requestFixture().handle(context -> {
   *       Promise<List<String>> promise = context.blocking(() -> Arrays.asList("hello", "world"));
   *       observeEach(promise)
   *         .map(String::toUpperCase)
   *         .toList()
   *         .subscribe(strings -> context.render(String.join(" ", strings)));
   *     });
   *
   *     assert result.rendered(String.class).equals("HELLO WORLD");
   *   }
   * }
   * }</pre>
   *
   * @param promise the promise
   * @param <T> the element type of the promised iterable
   * @param <I> the type of iterable
   * @return an observable for each element of the promised iterable
   * @see #observe(ratpack.exec.Promise)
   */
  public static <T, I extends Iterable<T>> Observable<T> observeEach(Promise<I> promise) {
    return Observable.create(new PromiseSubscribe<>(promise, (things, subscriber) -> {
      for (T thing : things) {
        subscriber.onNext(thing);
      }
    }));
  }

  /**
   * Alternative method for forking the execution to process each observable element.
   * <p>
   * This method is alternative to {@link #forkOnNext(ExecControl)} and is functionally equivalent.
   *
   * @param execControl the execution control to use to fork executions
   * @param observable the observable sequence to process each element of in a forked execution
   * @param <T> the element type
   * @return an observable
   */
  public static <T> Observable<T> forkOnNext(ExecControl execControl, Observable<T> observable) {
    return observable.lift(RxRatpack.<T>forkOnNext(execControl));
  }

  /**
   * An operator to parallelize an observable stream by forking a new execution for each omitted item.
   * This allows downstream processing to occur in concurrent executions.
   * <p>
   * To be used with the {@link Observable#lift(Observable.Operator)} method.
   * <p>
   * The {@code onCompleted()} or {@code onError()} downstream methods are guaranteed to be called <strong>after</strong> the last item has been given to the downstream {@code onNext()} method.
   * That is, the last invocation of the downstream {@code onNext()} will have returned before {@code onCompleted()} or {@code onError()} are invoked.
   * <p>
   * This is generally a more performant alternative to using plain Rx parallelization due to Ratpack's {@link ratpack.exec.Execution} semantics and use of Netty's event loop to schedule work.
   * <pre class="java">
   * import ratpack.rx.RxRatpack;
   * import ratpack.exec.ExecController;
   * import ratpack.launch.LaunchConfigBuilder;
   *
   * import rx.Observable;
   * import rx.functions.Func1;
   * import rx.functions.Action1;
   *
   * import java.util.List;
   * import java.util.Arrays;
   * import java.util.concurrent.CyclicBarrier;
   * import java.util.concurrent.BrokenBarrierException;
   *
   * public class Example {
   *
   *   public static void main(String[] args) throws Exception {
   *     RxRatpack.initialize();
   *
   *     final CyclicBarrier barrier = new CyclicBarrier(5);
   *     final ExecController execController = LaunchConfigBuilder.noBaseDir().threads(6).build().getExecController();
   *
   *     Integer[] myArray = {1, 2, 3, 4, 5};
   *     Observable&lt;Integer&gt; source = Observable.from(myArray);
   *     List&lt;Integer&gt; doubledAndSorted = source
   *       .lift(RxRatpack.&lt;Integer&gt;forkOnNext(execController.getControl()))
   *       .map(new Func1&lt;Integer, Integer&gt;() {
   *         public Integer call(Integer integer) {
   *           try {
   *             barrier.await(); // prove stream is processed concurrently
   *           } catch (InterruptedException | BrokenBarrierException e) {
   *             throw new RuntimeException(e);
   *           }
   *           return integer.intValue() * 2;
   *         }
   *       })
   *       .serialize()
   *       .toSortedList()
   *       .toBlocking()
   *       .single();
   *
   *     try {
   *       assert doubledAndSorted.equals(Arrays.asList(2, 4, 6, 8, 10));
   *     } finally {
   *       execController.close();
   *     }
   *   }
   * }
   * </pre>
   *
   * @param execControl the execution control to use to fork executions
   * @param <T> the type of item in the stream
   * @return an observable operator
   */
  public static <T> Observable.Operator<T, T> forkOnNext(final ExecControl execControl) {
    return downstream -> new Subscriber<T>(downstream) {

      private final AtomicInteger wip = new AtomicInteger(1);

      private volatile Runnable onDone;

      @Override
      public void onCompleted() {
        onDone = downstream::onCompleted;
        maybeDone();
      }

      @Override
      public void onError(final Throwable e) {
        onDone = () -> downstream.onError(e);
        maybeDone();
      }

      private void maybeDone() {
        if (wip.decrementAndGet() == 0) {
          onDone.run();
        }
      }

      @Override
      public void onNext(final T t) {
        // Avoid the overhead of creating executions if downstream is no longer interested
        if (isUnsubscribed()) {
          return;
        }

        wip.incrementAndGet();
        execControl.exec()
          .onError(this::onError)
          .start(execution -> {
            try {
              downstream.onNext(t);
            } finally {
              maybeDone();
            }
          });
      }
    };
  }

  private static class PromiseSubscribe<T, S> implements Observable.OnSubscribe<S> {

    private final Promise<T> promise;
    private final Action2<T, Subscriber<? super S>> emitter;

    public PromiseSubscribe(Promise<T> promise, Action2<T, Subscriber<? super S>> emitter) {
      this.promise = promise;
      this.emitter = emitter;
    }

    @Override
    public void call(final Subscriber<? super S> subscriber) {
      try {
        promise
          .onError(throwable -> {
            try {
              subscriber.onError(throwable);
            } catch (OnErrorNotImplementedException e) {
              throw toException(e.getCause());
            }
          })
          .then(thing -> {
            try {
              emitter.call(thing, subscriber);
              subscriber.onCompleted();
            } catch (OnErrorNotImplementedException e) {
              throw toException(e.getCause());
            }
          });
      } catch (Exception e) {
        throw ExceptionUtils.uncheck(e);
      }
    }

  }

  private static class ExecutionHook extends RxJavaObservableExecutionHook {

    @Override
    public <T> Observable.OnSubscribe<T> onSubscribeStart(Observable<? extends T> observableInstance, Observable.OnSubscribe<T> onSubscribe) {
      return ExecController.current()
        .map(e -> executionBackedOnSubscribe(onSubscribe, e))
        .orElse(onSubscribe);
    }

    private <T> Observable.OnSubscribe<T> executionBackedOnSubscribe(final Observable.OnSubscribe<T> onSubscribe, final ExecController e) {
      return (subscriber) -> onSubscribe.call(new ExecutionBackedSubscriber<>(e.getControl(), subscriber));
    }
  }

  private static class ExecutionBackedSubscriber<T> extends Subscriber<T> {
    private final Subscriber<? super T> subscriber;
    private final ExecControl execControl;

    public ExecutionBackedSubscriber(ExecControl execControl, Subscriber<? super T> subscriber) {
      super(subscriber);
      this.execControl = execControl;
      this.subscriber = subscriber;
    }

    @Override
    public void onCompleted() {
      try {
        subscriber.onCompleted();
      } catch (final OnErrorNotImplementedException e) {
        execControl.promise(f -> f.error(e.getCause())).then(Action.noop());
      }
    }

    @Override
    public void onError(Throwable e) {
      try {
        subscriber.onError(e);
      } catch (final OnErrorNotImplementedException e2) {
        execControl.promise(f -> f.error(e2.getCause())).then(Action.noop());
      }
    }

    public void onNext(T t) {
      try {
        subscriber.onNext(t);
      } catch (final OnErrorNotImplementedException e) {
        execControl.promise(fulfiller -> fulfiller.error(e.getCause())).then(Action.noop());
      }
    }
  }

}
TOP

Related Classes of ratpack.rx.RxRatpack$PromiseSubscribe

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.