
io.lettuce.core.protocol.CommandWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lettuce-core Show documentation
Show all versions of lettuce-core Show documentation
Advanced and thread-safe Java Redis client for synchronous, asynchronous, and
reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs
and much more.
The newest version!
/*
* Copyright 2011-Present, Redis Ltd. and Contributors
* All rights reserved.
*
* Licensed under the MIT License.
*
* This file contains contributions from third-party contributors
* 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 io.lettuce.core.protocol;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import io.lettuce.core.output.CommandOutput;
import io.netty.buffer.ByteBuf;
/**
* Wrapper for a command.
*
* @author Mark Paluch
*/
public class CommandWrapper implements RedisCommand, CompleteableCommand, DecoratedCommand {
@SuppressWarnings({ "rawtypes" })
private static final AtomicReferenceFieldUpdater ONCOMPLETE = AtomicReferenceFieldUpdater
.newUpdater(CommandWrapper.class, Object[].class, "onComplete");
private static final Object[] EMPTY = new Object[0];
private static final Object[] COMPLETE = new Object[0];
protected final RedisCommand command;
// accessed via AtomicReferenceFieldUpdater.
@SuppressWarnings("unused")
private volatile Object[] onComplete = EMPTY;
public CommandWrapper(RedisCommand command) {
this.command = command;
}
@Override
public CommandOutput getOutput() {
return command.getOutput();
}
@Override
public void complete() {
Object[] consumers = ONCOMPLETE.get(this);
if (consumers != COMPLETE && ONCOMPLETE.compareAndSet(this, consumers, COMPLETE)) {
command.complete();
doOnComplete();
notifyConsumers(consumers);
}
}
/**
* Callback method called after successful completion and before notifying downstream consumers.
*
* @since 6.0.2
*/
protected void doOnComplete() {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void notifyConsumers(Object[] consumers) {
for (Object callback : consumers) {
if (callback instanceof Consumer) {
Consumer consumer = (Consumer) callback;
if (getOutput() != null) {
consumer.accept(getOutput().get());
} else {
consumer.accept(null);
}
}
if (callback instanceof BiConsumer) {
BiConsumer consumer = (BiConsumer) callback;
if (getOutput() != null) {
consumer.accept(getOutput().get(), null);
} else {
consumer.accept(null, null);
}
}
}
}
@Override
public void cancel() {
Object[] consumers = ONCOMPLETE.get(this);
if (consumers != COMPLETE && ONCOMPLETE.compareAndSet(this, consumers, COMPLETE)) {
command.cancel();
CancellationException exception = new CancellationException();
doOnError(exception);
notifyBiConsumer(consumers, exception);
}
}
@Override
public boolean completeExceptionally(Throwable throwable) {
Object[] consumers = ONCOMPLETE.get(this);
boolean result = false;
if (consumers != COMPLETE && ONCOMPLETE.compareAndSet(this, consumers, COMPLETE)) {
result = command.completeExceptionally(throwable);
doOnError(throwable);
notifyBiConsumer(consumers, throwable);
}
return result;
}
/**
* Callback method called after error completion and before notifying downstream consumers.
*
* @param throwable
* @since 6.0.2
*/
protected void doOnError(Throwable throwable) {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void notifyBiConsumer(Object[] consumers, Throwable exception) {
for (Object callback : consumers) {
if (!(callback instanceof BiConsumer)) {
continue;
}
BiConsumer consumer = (BiConsumer) callback;
if (getOutput() != null) {
consumer.accept(getOutput().get(), exception);
} else {
consumer.accept(null, exception);
}
}
}
@Override
public CommandArgs getArgs() {
return command.getArgs();
}
@Override
public ProtocolKeyword getType() {
return command.getType();
}
@Override
public void encode(ByteBuf buf) {
command.encode(buf);
}
@Override
public boolean isCancelled() {
return command.isCancelled();
}
@Override
public void setOutput(CommandOutput output) {
command.setOutput(output);
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void onComplete(Consumer super T> action) {
if (command instanceof CompleteableCommand) {
((CompleteableCommand) command).onComplete(action);
} else {
addOnComplete(action);
}
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void onComplete(BiConsumer super T, Throwable> action) {
if (command instanceof CompleteableCommand) {
((CompleteableCommand) command).onComplete(action);
} else {
addOnComplete(action);
}
}
private void addOnComplete(Object action) {
for (;;) {
Object[] existing = ONCOMPLETE.get(this);
Object[] updated = new Object[existing.length + 1];
System.arraycopy(existing, 0, updated, 0, existing.length);
updated[existing.length] = action;
if (ONCOMPLETE.compareAndSet(this, existing, updated)) {
return;
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [type=").append(getType());
sb.append(", output=").append(getOutput());
sb.append(", commandType=").append(command.getClass().getName());
sb.append(']');
return sb.toString();
}
@Override
public boolean isDone() {
return command.isDone();
}
@Override
public RedisCommand getDelegate() {
return command;
}
/**
* Unwrap a wrapped command.
*
* @param wrapped
* @return
*/
@SuppressWarnings("unchecked")
public static RedisCommand unwrap(RedisCommand wrapped) {
RedisCommand result = wrapped;
while (result instanceof DecoratedCommand, ?, ?>) {
result = ((DecoratedCommand) result).getDelegate();
}
return result;
}
/**
* Returns an object that implements the given interface to allow access to non-standard methods, or standard methods not
* exposed by the proxy.
*
* If the receiver implements the interface then the result is the receiver or a proxy for the receiver. If the receiver is
* a wrapper and the wrapped object implements the interface then the result is the wrapped object or a proxy for the
* wrapped object. Otherwise return the result of calling unwrap
recursively on the wrapped object or a proxy
* for that result. If the receiver is not a wrapper and does not implement the interface, then an {@code null} is returned.
*
* @param wrapped
* @param iface A Class defining an interface that the result must implement.
* @return the unwrapped instance or {@code null}.
* @since 5.1
*/
@SuppressWarnings("unchecked")
public static R unwrap(RedisCommand wrapped, Class iface) {
RedisCommand result = wrapped;
if (iface.isInstance(wrapped)) {
return iface.cast(wrapped);
}
while (result instanceof DecoratedCommand, ?, ?>) {
result = ((DecoratedCommand) result).getDelegate();
if (iface.isInstance(result)) {
return iface.cast(result);
}
}
return null;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof RedisCommand)) {
return false;
}
RedisCommand, ?, ?> left = command;
while (left instanceof DecoratedCommand) {
left = CommandWrapper.unwrap(left);
}
RedisCommand, ?, ?> right = (RedisCommand, ?, ?>) o;
while (right instanceof DecoratedCommand) {
right = CommandWrapper.unwrap(right);
}
return left == right;
}
@Override
public int hashCode() {
RedisCommand, ?, ?> toHash = command;
while (toHash instanceof DecoratedCommand) {
toHash = CommandWrapper.unwrap(toHash);
}
return toHash != null ? toHash.hashCode() : 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy