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

com.couchbase.client.core.Reactor Maven / Gradle / Ivy

There is a newer version: 2.7.0
Show newest version
/*
 * Copyright (c) 2018 Couchbase, Inc.
 *
 * 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 com.couchbase.client.core;

import com.couchbase.client.core.error.RequestCanceledException;
import com.couchbase.client.core.msg.CancellationReason;
import com.couchbase.client.core.msg.Request;
import com.couchbase.client.core.msg.RequestContext;
import reactor.core.CoreSubscriber;
import reactor.core.Exceptions;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Operators;
import reactor.core.publisher.SignalType;
import reactor.util.context.Context;

import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.function.Supplier;

/**
 * This class provides utility methods when working with reactor.
 *
 * @since 2.0.0
 */
public class Reactor {
  private Reactor() {
    throw new AssertionError("not instantiable");
  }

  /**
   * Wraps a {@link Request} and returns it in a {@link Mono}.
   *
   * @param request the request to wrap.
   * @param response the full response to wrap, might not be the same as in the request.
   * @param propagateCancellation if a cancelled/unsubscribed mono should also cancel the
   *                              request.
   * @return the mono that wraps the request.
   */
  public static  Mono wrap(final Request request, final CompletableFuture response,
                                 final boolean propagateCancellation) {
    Mono mono = MyLittleAssemblyFactory.callOnAssembly(new SilentMonoCompletionStage<>(response));
    if (propagateCancellation) {
      mono = mono.doFinally(st -> {
        if (st == SignalType.CANCEL) {
          request.cancel(CancellationReason.STOPPED_LISTENING);
        }
      });
    }
    return mono.onErrorResume(err -> {
      if (err instanceof CompletionException) {
        return Mono.error(err.getCause());
      } else {
        return Mono.error(err);
      }
    });
  }

  /**
   * Helper method to wrap an async call into a reactive one and translate
   * exceptions appropriately.
   *
   * @param input a supplier that will be called on every subscription.
   * @return a mono that invokes the given supplier on each subscription.
   */
  public static  Mono toMono(Supplier> input) {
    return Mono.fromFuture(input)
        .onErrorMap(t -> t instanceof CompletionException ? t.getCause() : t);
  }

  /**
   * Helper method to wrap an async call into a reactive one and translate
   * exceptions appropriately.
   *
   * @param input a supplier that will be called on every subscription.
   * @return a flux that invokes the given supplier on each subscription.
   */
  public static > Flux toFlux(Supplier> input) {
    return toMono(input).flux().flatMap(Flux::fromIterable);
  }

  /**
   * Emits the value or error produced by the wrapped CompletionStage.
   * 

* Note that if Subscribers cancel their subscriptions, the CompletionStage * is not cancelled. *

* COUCHBASE NOTE: This class is an exact copy from the MonoCompletionStage that ships with reactor. The only changes * made to it are that we need to check for a specific exception when the downstream consumer is cancelled. See the * reasoning in that codeblock below. The code is copied from reactor-core version 3.3.0.RELEASE. * * @param the value type */ private static final class SilentMonoCompletionStage extends Mono implements Fuseable, Scannable { final CompletionStage future; SilentMonoCompletionStage(CompletionStage future) { this.future = Objects.requireNonNull(future, "future"); } @Override public void subscribe(CoreSubscriber actual) { Operators.MonoSubscriber sds = new Operators.MonoSubscriber<>(actual); actual.onSubscribe(sds); if (sds.isCancelled()) { return; } future.whenComplete((v, e) -> { if (sds.isCancelled()) { //nobody is interested in the Mono anymore, don't risk dropping errors Context ctx = sds.currentContext(); if (e == null || e instanceof CancellationException) { //we discard any potential value and ignore Future cancellations Operators.onDiscard(v, ctx); } else { //we make sure we keep _some_ track of a Future failure AFTER the Mono cancellation // COUCHBASE NOTE: We changed this code because in the base class we explicitly call STOPPED_LISTENING // if the downstream consumer closes. Do not call onErrorDropped in this case, since we expect this // case to be happening. Default reactor only suppresses this for cancellations, but our exception // hierachy doesn't allow for it, hence the workaround. // Note: sometimes it's a raw RequestCancelException, sometimes it's wrapped in a CompletionException. // See ReactorTest::noErrorDroppedWhenCancelledVia* tests for examples. if (e instanceof CompletionException && e.getCause() instanceof RequestCanceledException) { RequestContext requestContext = ((RequestCanceledException) (e.getCause())).context().requestContext(); if (requestContext.request().cancellationReason() != CancellationReason.STOPPED_LISTENING) { Operators.onErrorDropped(e, ctx); } } else if (e instanceof RequestCanceledException) { RequestContext requestContext = ((RequestCanceledException) e).context().requestContext(); if (requestContext.request().cancellationReason() != CancellationReason.STOPPED_LISTENING) { Operators.onErrorDropped(e, ctx); } } else { Operators.onErrorDropped(e, ctx); } //and we discard any potential value just in case both e and v are not null Operators.onDiscard(v, ctx); } return; } try { if (e instanceof CompletionException) { actual.onError(e.getCause()); } else if (e != null) { actual.onError(e); } else if (v != null) { sds.complete(v); } else { actual.onComplete(); } } catch (Throwable e1) { Operators.onErrorDropped(e1, actual.currentContext()); throw Exceptions.bubble(e1); } }); } @Override public Object scanUnsafe(Attr key) { return null; //no particular key to be represented, still useful in hooks } } /** * We have or own little pony eeeh factory because onAssembly is protected inside the mono, so we need to expose it! */ private abstract static class MyLittleAssemblyFactory extends Mono { static Mono callOnAssembly(Mono source) { return onAssembly(source); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy