/* * Copyright (C) 2015 SoftIndex LLC. * * 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 io.datakernel.rpc.client.sender; import io.datakernel.async.ResultCallback; import io.datakernel.rpc.client.RpcClientConnectionPool; import java.net.InetSocketAddress; import java.util.List; import java.util.Set; import static io.datakernel.util.Preconditions.checkArgument; import static io.datakernel.util.Preconditions.checkNotNull; public final class RpcStrategyFirstValidResult implements RpcStrategy { public interface ResultValidator<T> { boolean isValidResult(T value); } private static final ResultValidator<?> DEFAULT_RESULT_VALIDATOR = new DefaultResultValidator<>(); private final RpcStrategyList list; private final ResultValidator<?> resultValidator; private final Exception noValidResultException; private RpcStrategyFirstValidResult(RpcStrategyList list, ResultValidator<?> resultValidator, Exception noValidResultException) { this.list = list; this.resultValidator = resultValidator; this.noValidResultException = noValidResultException; } public static RpcStrategyFirstValidResult create(RpcStrategyList list) { return new RpcStrategyFirstValidResult(list, DEFAULT_RESULT_VALIDATOR, null); } public RpcStrategyFirstValidResult withResultValidator(ResultValidator<?> resultValidator) { return new RpcStrategyFirstValidResult(list, resultValidator, noValidResultException); } public RpcStrategyFirstValidResult withNoValidResultException(Exception exception) { return new RpcStrategyFirstValidResult(list, resultValidator, exception); } @Override public Set<InetSocketAddress> getAddresses() { return list.getAddresses(); } @Override public RpcSender createSender(RpcClientConnectionPool pool) { List<RpcSender> senders = list.listOfSenders(pool); if (senders.size() == 0) return null; return new Sender(senders, resultValidator, noValidResultException); } static final class Sender implements RpcSender { private final RpcSender[] subSenders; private final ResultValidator<?> resultValidator; private final Exception noValidResultException; public Sender(List<RpcSender> senders, ResultValidator<?> resultValidator, Exception noValidResultException) { checkArgument(senders != null && senders.size() > 0); this.subSenders = senders.toArray(new RpcSender[senders.size()]); this.resultValidator = checkNotNull(resultValidator); this.noValidResultException = noValidResultException; } @SuppressWarnings("unchecked") @Override public <I, O> void sendRequest(I request, int timeout, ResultCallback<O> callback) { FirstResultCallback<O> resultCallback = new FirstResultCallback<>(callback, (ResultValidator<O>) resultValidator, subSenders.length, noValidResultException); for (RpcSender sender : subSenders) { sender.sendRequest(request, timeout, resultCallback.getCallback()); } } } private static final class FirstResultCallback<T> { private final ResultCallback<T> resultCallback; private final ResultValidator<T> resultValidator; private final Exception noValidResultException; private int expectedCalls; private T result; private Exception exception; private boolean hasResult; private boolean complete; public FirstResultCallback(ResultCallback<T> resultCallback, ResultValidator<T> resultValidator, int expectedCalls, Exception noValidResultException) { checkArgument(expectedCalls > 0); this.expectedCalls = expectedCalls; this.resultCallback = checkNotNull(resultCallback); this.resultValidator = checkNotNull(resultValidator); this.noValidResultException = noValidResultException; } public ResultCallback<T> getCallback() { return new ResultCallback<T>() { @Override protected void onResult(T result) { --expectedCalls; if (!hasResult && resultValidator.isValidResult(result)) { FirstResultCallback.this.result = result; // first valid result FirstResultCallback.this.hasResult = true; } processResult(); } @Override protected void onException(Exception exception) { --expectedCalls; if (!hasResult) { FirstResultCallback.this.exception = exception; // last Exception } processResult(); } }; } private boolean resultReady() { return hasResult || expectedCalls == 0; } private void processResult() { if (complete || !resultReady()) { return; } complete = true; if (hasResult) { resultCallback.setResult(result); } else { resolveException(); if (exception == null) { resultCallback.setResult(null); } else { resultCallback.setException(exception); } } } private void resolveException() { if (exception == null && noValidResultException != null) { exception = noValidResultException; } } } private static final class DefaultResultValidator<T> implements ResultValidator<T> { @Override public boolean isValidResult(T input) { return input != null; } } }