package com.lambdaworks.redis; import java.util.Arrays; import java.util.Collection; import java.util.function.Supplier; import com.lambdaworks.redis.api.StatefulConnection; import com.lambdaworks.redis.api.StatefulRedisConnection; import com.lambdaworks.redis.internal.LettuceAssert; import com.lambdaworks.redis.output.StreamingOutput; import com.lambdaworks.redis.protocol.CommandWrapper; import com.lambdaworks.redis.protocol.RedisCommand; import rx.Observable; import rx.Subscriber; /** * Reactive command dispatcher. * * @author Mark Paluch */ public class ReactiveCommandDispatcher<K, V, T> implements Observable.OnSubscribe<T> { private Supplier<? extends RedisCommand<K, V, T>> commandSupplier; private volatile RedisCommand<K, V, T> command; private StatefulConnection<K, V> connection; private boolean dissolve; /** * * @param staticCommand static command, must not be {@literal null} * @param connection the connection, must not be {@literal null} * @param dissolve dissolve collections into particular elements */ public ReactiveCommandDispatcher(RedisCommand<K, V, T> staticCommand, StatefulConnection<K, V> connection, boolean dissolve) { this(() -> staticCommand, connection, dissolve); } /** * * @param commandSupplier command supplier, must not be {@literal null} * @param connection the connection, must not be {@literal null} * @param dissolve dissolve collections into particular elements */ public ReactiveCommandDispatcher(Supplier<RedisCommand<K, V, T>> commandSupplier, StatefulConnection<K, V> connection, boolean dissolve) { LettuceAssert.notNull(commandSupplier, "CommandSupplier must not be null"); LettuceAssert.notNull(connection, "StatefulConnection must not be null"); this.commandSupplier = commandSupplier; this.connection = connection; this.dissolve = dissolve; this.command = commandSupplier.get(); } @Override public void call(Subscriber<? super T> subscriber) { // Reuse the first command but then discard it. RedisCommand<K, V, T> command = this.command; if (command == null) { command = commandSupplier.get(); } if (command.getOutput() instanceof StreamingOutput<?>) { StreamingOutput<T> streamingOutput = (StreamingOutput<T>) command.getOutput(); if (connection instanceof StatefulRedisConnection<?, ?> && ((StatefulRedisConnection) connection).isMulti()) { streamingOutput.setSubscriber(new DelegatingWrapper<T>( Arrays.asList(new ObservableSubscriberWrapper<>(subscriber), streamingOutput.getSubscriber()))); } else { streamingOutput.setSubscriber(new ObservableSubscriberWrapper<>(subscriber)); } } connection.dispatch(new ObservableCommand<>(command, subscriber, dissolve)); this.command = null; } private static class ObservableCommand<K, V, T> extends CommandWrapper<K, V, T> { private final Subscriber<? super T> subscriber; private final boolean dissolve; private boolean completed = false; public ObservableCommand(RedisCommand<K, V, T> command, Subscriber<? super T> subscriber, boolean dissolve) { super(command); this.subscriber = subscriber; this.dissolve = dissolve; } @Override @SuppressWarnings("unchecked") public void complete() { if (completed || subscriber.isUnsubscribed()) { return; } try { super.complete(); if (getOutput() != null) { Object result = getOutput().get(); if (!(getOutput() instanceof StreamingOutput<?>) && result != null) { if (dissolve && result instanceof Collection) { Collection<T> collection = (Collection<T>) result; for (T t : collection) { subscriber.onNext(t); } } else { subscriber.onNext((T) result); } } if (getOutput().hasError()) { subscriber.onError(new RedisCommandExecutionException(getOutput().getError())); completed = true; return; } } try { subscriber.onCompleted(); } catch (Exception e) { completeExceptionally(e); } } finally { completed = true; } } @Override public void cancel() { if (completed || subscriber.isUnsubscribed()) { return; } super.cancel(); subscriber.onCompleted(); completed = true; } @Override public boolean completeExceptionally(Throwable throwable) { if (completed || subscriber.isUnsubscribed()) { return false; } boolean b = super.completeExceptionally(throwable); subscriber.onError(throwable); completed = true; return b; } } static class ObservableSubscriberWrapper<T> implements StreamingOutput.Subscriber<T> { private Subscriber<? super T> subscriber; public ObservableSubscriberWrapper(Subscriber<? super T> subscriber) { this.subscriber = subscriber; } @Override public void onNext(T t) { if(subscriber.isUnsubscribed()) { return; } subscriber.onNext(t); } } static class DelegatingWrapper<T> implements StreamingOutput.Subscriber<T> { private Collection<StreamingOutput.Subscriber<T>> subscribers; public DelegatingWrapper(Collection<StreamingOutput.Subscriber<T>> subscribers) { this.subscribers = subscribers; } @Override public void onNext(T t) { for (StreamingOutput.Subscriber<T> subscriber : subscribers) { subscriber.onNext(t); } } } }