/** * Copyright 2009 Google Inc. * * 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 org.waveprotocol.box.server.util; import com.google.common.base.Preconditions; import org.waveprotocol.wave.model.util.Pair; import org.waveprotocol.wave.util.logging.Log; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * A {@code SuccessFailCallback} which supports blocking for a response. Note that neither * {@link #onSuccess} not {@link #onFailure} will accept a null argument. * * TODO: the behaviour for unexpected conditions is a bit harsh, should log not crash (but it * requires more work, currently unwarranted). */ public class BlockingSuccessFailCallback<S, F> implements SuccessFailCallback<S, F> { private final static Log LOG = Log.get(BlockingSuccessFailCallback.class); private final AtomicReference<S> successResult = new AtomicReference<S>(null); private final AtomicReference<F> failureResult = new AtomicReference<F>(null); private final CountDownLatch awaitLatch = new CountDownLatch(1); private final String description; private BlockingSuccessFailCallback(String description) { this.description = description; } public static <S, F> BlockingSuccessFailCallback<S, F> create() { StackTraceElement caller = new Throwable().getStackTrace()[1]; return new BlockingSuccessFailCallback<S, F>("Created at " + caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber()); } /** * Wait for either {@link #onSuccess} or {@link #onFailure} to be called and return both results. * Either the first of the pair (success value) or second of the pair (failure value) will hold * a not-null result indicating which method was run, while the other is guaranteed to contain * null. * * The return result may be null if the await did not return within the timeout. * * @param timeout * @param unit * @return pair of (success value, failure value) where exactly one will be not-null, or null * if the await timed out */ public Pair<S, F> await(long timeout, TimeUnit unit) { try { long startMs = System.currentTimeMillis(); if (!awaitLatch.await(timeout, unit)) { LOG.warning(description + ": timed out while waiting for " + timeout + " " + unit); return null; } LOG.fine(description + ": await took " + (System.currentTimeMillis() - startMs) + " ms"); } catch (InterruptedException e) { LOG.severe(description + ": interrupted while waiting", e); throw new IllegalStateException(e); } return Pair.of(successResult.get(), failureResult.get()); } @Override public void onFailure(F failureValue) { LOG.warning(description + ": onFailure(" + failureValue + ")"); Preconditions.checkArgument(failureValue != null); Preconditions.checkState(failureResult.getAndSet(failureValue) == null); Preconditions.checkState(successResult.get() == null); awaitLatch.countDown(); } @Override public void onSuccess(S successValue) { LOG.fine(description + ": onSuccess(" + successValue + ")"); Preconditions.checkArgument(successResult != null); Preconditions.checkState(successResult.getAndSet(successValue) == null); Preconditions.checkState(failureResult.get() == null); awaitLatch.countDown(); } }