package org.hibernate.test.cache.infinispan.util;
import org.infinispan.AdvancedCache;
import org.infinispan.commands.VisitableCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.InvocationContextInterceptor;
import org.infinispan.interceptors.base.BaseCustomInterceptor;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.BiPredicate;
import java.util.function.BooleanSupplier;
public class ExpectingInterceptor extends BaseCustomInterceptor {
private final static Log log = LogFactory.getLog(ExpectingInterceptor.class);
private final List<Condition> conditions = new LinkedList<>();
public static ExpectingInterceptor get(AdvancedCache cache) {
Optional<ExpectingInterceptor> self = cache.getInterceptorChain().stream().filter(ExpectingInterceptor.class::isInstance).findFirst();
if (self.isPresent()) {
return self.get();
}
ExpectingInterceptor ei = new ExpectingInterceptor();
// We are adding this after ICI because we want to handle silent failures, too
cache.addInterceptorAfter(ei, InvocationContextInterceptor.class);
return ei;
}
public static void cleanup(AdvancedCache... caches) {
for (AdvancedCache c : caches) c.removeInterceptor(ExpectingInterceptor.class);
}
public synchronized Condition when(BiPredicate<InvocationContext, VisitableCommand> predicate) {
Condition condition = new Condition(predicate, source(), null);
conditions.add(condition);
return condition;
}
public synchronized Condition whenFails(BiPredicate<InvocationContext, VisitableCommand> predicate) {
Condition condition = new Condition(predicate, source(), Boolean.FALSE);
conditions.add(condition);
return condition;
}
private static String source() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement ste = stackTrace[3];
return ste.getFileName() + ":" + ste.getLineNumber();
}
@Override
protected Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable {
boolean succeeded = false;
try {
log.tracef("Before command %s", command);
Object retval = super.handleDefault(ctx, command);
succeeded = true;
return retval;
} finally {
log.tracef("After command(successful=%s) %s", succeeded, command);
List<Runnable> toExecute = new ArrayList<>();
synchronized (this) {
for (Iterator<Condition> iterator = conditions.iterator(); iterator.hasNext(); ) {
Condition condition = iterator.next();
log.tracef("Testing condition %s", condition);
if (condition.success != null && condition.success != succeeded) {
log.trace("Condition test failed, succeeded: " + succeeded);
} else if (condition.predicate.test(ctx, command)) {
assert condition.action != null;
log.trace("Condition succeeded");
toExecute.add(condition.action);
if (condition.removeCheck == null || condition.removeCheck.getAsBoolean()) {
iterator.remove();
}
} else {
log.trace("Condition test failed");
}
}
}
// execute without holding the lock
for (Runnable runnable : toExecute) {
log.tracef("Executing %s", runnable);
runnable.run();
}
}
}
public class Condition {
private final BiPredicate<InvocationContext, VisitableCommand> predicate;
private final String source;
private final Boolean success;
private BooleanSupplier removeCheck;
private Runnable action;
public Condition(BiPredicate<InvocationContext, VisitableCommand> predicate, String source, Boolean success) {
this.predicate = predicate;
this.source = source;
this.success = success;
}
public Condition run(Runnable action) {
assert this.action == null;
this.action = action;
return this;
}
public Condition countDown(CountDownLatch latch) {
return run(() -> latch.countDown()).removeWhen(() -> latch.getCount() == 0);
}
public Condition removeWhen(BooleanSupplier check) {
assert this.removeCheck == null;
this.removeCheck = check;
return this;
}
public void cancel() {
synchronized (ExpectingInterceptor.this) {
conditions.remove(this);
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Condition{");
sb.append("source=").append(source);
sb.append(", predicate=").append(predicate);
sb.append(", success=").append(success);
sb.append(", action=").append(action);
sb.append('}');
return sb.toString();
}
}
}