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

reactor.core.publisher.MonoUsingWhen Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/*
 * Copyright (c) 2018-2023 VMware Inc. or its affiliates, All Rights Reserved.
 *
 * 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
 *
 *   https://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 reactor.core.publisher;

import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import reactor.core.CoreSubscriber;
import reactor.core.Exceptions;
import reactor.core.publisher.FluxUsingWhen.UsingWhenSubscriber;
import reactor.core.publisher.Operators.DeferredSubscription;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;

/**
 * Uses a resource, generated by a {@link Publisher} for each individual {@link Subscriber},
 * while streaming the values from a {@link Publisher} derived from the same resource.
 * Whenever the resulting sequence terminates, the relevant {@link Function} generates
 * a "cleanup" {@link Publisher} that is invoked but doesn't change the content of the
 * main sequence. Instead it just defers the termination (unless it errors, in which case
 * the error suppresses the original termination signal).
 *
 * @param  the value type streamed
 * @param  the resource type
 */
final class MonoUsingWhen extends Mono implements SourceProducer {

	final Publisher                                                     resourceSupplier;
	final Function>                 resourceClosure;
	final Function>                      asyncComplete;
	final BiFunction> asyncError;
	@Nullable
	final Function>                      asyncCancel;

	MonoUsingWhen(Publisher resourceSupplier,
			Function> resourceClosure,
			Function> asyncComplete,
			BiFunction> asyncError,
			@Nullable Function> asyncCancel) {
		this.resourceSupplier = Objects.requireNonNull(resourceSupplier, "resourceSupplier");
		this.resourceClosure = Objects.requireNonNull(resourceClosure, "resourceClosure");
		this.asyncComplete = Objects.requireNonNull(asyncComplete, "asyncComplete");
		this.asyncError = Objects.requireNonNull(asyncError, "asyncError");
		this.asyncCancel = asyncCancel;
	}

	@Override
	@SuppressWarnings("unchecked")
	public void subscribe(CoreSubscriber actual) {
		if (resourceSupplier instanceof Callable) {
			try {
				Callable resourceCallable = (Callable) resourceSupplier;
				S resource = resourceCallable.call();

				if (resource == null) {
					Operators.complete(actual);
				}
				else {
					final Mono p = deriveMonoFromResource(resource, resourceClosure);
					final UsingWhenSubscriber subscriber = prepareSubscriberForResource(resource,
							actual,
							asyncComplete,
							asyncError,
							asyncCancel,
							null);

					fromDirect(p).subscribe(subscriber);
				}
			}
			catch (Throwable e) {
				Operators.error(actual, e);
			}
			return;
		}

		// Ensure onLastOperatorHook is called by invoking Publisher::subscribe(Subscriber)
		((Publisher) Operators.toFluxOrMono(resourceSupplier)).subscribe(
				new ResourceSubscriber(actual, resourceClosure, asyncComplete, asyncError, asyncCancel, resourceSupplier instanceof Mono));
	}

	@Override
	public Object scanUnsafe(Attr key) {
		if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;
		return SourceProducer.super.scanUnsafe(key);
	}

	private static  Mono deriveMonoFromResource(
			RESOURCE resource,
			Function> resourceClosure) {

		Mono p;

		try {
			p = Objects.requireNonNull(resourceClosure.apply(resource),
					"The resourceClosure function returned a null value");
		}
		catch (Throwable e) {
			p = Mono.error(e);
		}

		return p;
	}

	private static  MonoUsingWhenSubscriber prepareSubscriberForResource(
			RESOURCE resource,
			CoreSubscriber actual,
			Function> asyncComplete,
			BiFunction> asyncError,
			@Nullable Function> asyncCancel,
			@Nullable DeferredSubscription arbiter) {
		//MonoUsingWhen cannot support ConditionalSubscriber as there's no way to defer tryOnNext
		return new MonoUsingWhenSubscriber<>(actual,
				resource,
				asyncComplete,
				asyncError,
				asyncCancel,
				arbiter);
	}

	//needed to correctly call prepareSubscriberForResource with Mono.from conversions
	static class ResourceSubscriber extends DeferredSubscription implements InnerConsumer {

		final CoreSubscriber                                        actual;
		final Function>                 resourceClosure;
		final Function>                      asyncComplete;
		final BiFunction> asyncError;
		@Nullable
		final Function>                      asyncCancel;
		final boolean                                                          isMonoSource;

		Subscription        resourceSubscription;
		boolean             resourceProvided;

		ResourceSubscriber(CoreSubscriber actual,
				Function> resourceClosure,
				Function> asyncComplete,
				BiFunction> asyncError,
				@Nullable Function> asyncCancel,
				boolean isMonoSource) {
			this.actual = Objects.requireNonNull(actual, "actual");
			this.resourceClosure = Objects.requireNonNull(resourceClosure, "resourceClosure");
			this.asyncComplete = Objects.requireNonNull(asyncComplete, "asyncComplete");
			this.asyncError = Objects.requireNonNull(asyncError, "asyncError");
			this.asyncCancel = asyncCancel;
			this.isMonoSource = isMonoSource;
		}

		@Override
		public Context currentContext() {
			return actual.currentContext();
		}

		@Override
		public void onNext(S resource) {
			if (resourceProvided) {
				Operators.onNextDropped(resource, actual.currentContext());
				return;
			}
			resourceProvided = true;

			final Mono p = deriveMonoFromResource(resource, resourceClosure);

			fromDirect(p).subscribe(MonoUsingWhen.prepareSubscriberForResource(resource,
					this.actual,
					this.asyncComplete,
					this.asyncError,
					this.asyncCancel,
					this));

			if (!isMonoSource) {
				resourceSubscription.cancel();
			}
		}

		@Override
		public void onError(Throwable throwable) {
			if (resourceProvided) {
				Operators.onErrorDropped(throwable, actual.currentContext());
				return;
			}
			//even if no resource provided, actual.onSubscribe has been called
			//let's immediately fail actual
			actual.onError(throwable);
		}

		@Override
		public void onComplete() {
			if (resourceProvided) {
				return;
			}
			//even if no resource provided, actual.onSubscribe has been called
			//let's immediately complete actual
			actual.onComplete();
		}

		@Override
		public void onSubscribe(Subscription s) {
			if (Operators.validate(this.resourceSubscription, s)) {
				this.resourceSubscription = s;
				actual.onSubscribe(this);
				s.request(Long.MAX_VALUE);
			}
		}

		@Override
		public void cancel() {
			if (!resourceProvided) {
				resourceSubscription.cancel();
			}

			super.cancel();
		}

		@Override
		public Object scanUnsafe(Attr key) {
			if (key == Attr.PARENT) return resourceSubscription;
			if (key == Attr.ACTUAL) return actual;
			if (key == Attr.PREFETCH) return Integer.MAX_VALUE;
			if (key == Attr.TERMINATED) return resourceProvided;
			if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;

			return null;
		}
	}

	static class MonoUsingWhenSubscriber extends FluxUsingWhen.UsingWhenSubscriber {

		MonoUsingWhenSubscriber(CoreSubscriber actual,
				S resource,
				Function> asyncComplete,
				BiFunction> asyncError,
				@Nullable Function> asyncCancel,
				@Nullable DeferredSubscription arbiter) {
			super(actual, resource, asyncComplete, asyncError, asyncCancel, arbiter);
		}

		T value;

		@Override
		public void onNext(T value) {
			this.value = value;
		}

		@Override
		public void deferredComplete() {
			this.error = Exceptions.TERMINATED;
			if (this.value != null) {
				actual.onNext(value);
			}
			this.actual.onComplete();
		}

		@Override
		public void deferredError(Throwable error) {
			Operators.onDiscard(this.value, actual.currentContext());
			this.error = error;
			this.actual.onError(error);
		}
	}
}