| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.jclouds.rest.suppliers; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Throwables.propagate; |
| import static org.jclouds.util.Throwables2.getFirstThrowableOfType; |
| |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.jclouds.rest.AuthorizationException; |
| |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Supplier; |
| import com.google.common.cache.CacheBuilder; |
| import com.google.common.cache.CacheLoader; |
| import com.google.common.cache.LoadingCache; |
| import com.google.common.collect.ForwardingObject; |
| import com.google.common.util.concurrent.UncheckedExecutionException; |
| |
| /** |
| * This will retry the supplier if it encounters a timeout exception, but not if it encounters an |
| * AuthorizationException. |
| * <p/> |
| * A shared exception reference is used so that anyone who encounters an authorizationexception will be short-circuited. |
| * This prevents accounts from being locked out. |
| * |
| * <h3>details</h3> |
| * http://code.google.com/p/google-guice/issues/detail?id=483 guice doesn't remember when singleton providers throw |
| * exceptions. in this case, if the supplier fails with an authorization exception, it is called again for each provider |
| * method that depends on it. To short-circuit this, we remember the last exception trusting that guice is |
| * single-threaded. |
| * |
| * Note this implementation is folded into the same class, vs being decorated as stacktraces are exceptionally long and |
| * difficult to grok otherwise. We use {@link LoadingCache} to deal with concurrency issues related to the supplier. |
| */ |
| public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> extends ForwardingObject implements |
| Supplier<T> { |
| |
| static class SetAndThrowAuthorizationExceptionSupplierBackedLoader<V> extends CacheLoader<String, Optional<V>> { |
| |
| private final Supplier<V> delegate; |
| private final AtomicReference<AuthorizationException> authException; |
| private final ValueLoadedCallback<V> valueLoadedCallback; |
| |
| public SetAndThrowAuthorizationExceptionSupplierBackedLoader(Supplier<V> delegate, |
| AtomicReference<AuthorizationException> authException, ValueLoadedCallback<V> valueLoadedCallback) { |
| this.delegate = checkNotNull(delegate, "delegate"); |
| this.authException = checkNotNull(authException, "authException"); |
| this.valueLoadedCallback = checkNotNull(valueLoadedCallback, "valueLoadedCallback"); |
| } |
| |
| @Override |
| public Optional<V> load(String key) { |
| if (authException.get() != null) |
| throw authException.get(); |
| try { |
| Optional<V> value = Optional.fromNullable(delegate.get()); |
| valueLoadedCallback.valueLoaded(value); |
| return value; |
| } catch (Exception e) { |
| AuthorizationException aex = getFirstThrowableOfType(e, AuthorizationException.class); |
| if (aex != null) { |
| authException.set(aex); |
| throw aex; |
| } |
| throw propagate(e); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this).add("delegate", delegate).toString(); |
| } |
| } |
| |
| public static class ValueLoadedEvent<V> { |
| private final Object eventKey; |
| private final Optional<V> value; |
| |
| public ValueLoadedEvent(Object eventKey, Optional<V> value) { |
| this.eventKey = checkNotNull(eventKey, "eventKey"); |
| this.value = checkNotNull(value, "value"); |
| } |
| |
| public Object getEventKey() { |
| return eventKey; |
| } |
| |
| public Optional<V> getValue() { |
| return value; |
| } |
| } |
| |
| private final Supplier<T> delegate; |
| private final long duration; |
| private final TimeUnit unit; |
| private final LoadingCache<String, Optional<T>> cache; |
| |
| public static <T> MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> create( |
| AtomicReference<AuthorizationException> authException, Supplier<T> delegate, long duration, TimeUnit unit) { |
| return new MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T>(authException, delegate, duration, |
| unit, new ValueLoadedCallback.NoOpCallback<T>()); |
| } |
| |
| /** |
| * Creates a memoized supplier that calls the given callback each time values are loaded. |
| */ |
| public static <T> MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> create( |
| AtomicReference<AuthorizationException> authException, Supplier<T> delegate, long duration, TimeUnit unit, |
| ValueLoadedCallback<T> valueLoadedCallback) { |
| return new MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T>(authException, delegate, duration, |
| unit, valueLoadedCallback); |
| } |
| |
| MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier(AtomicReference<AuthorizationException> authException, |
| Supplier<T> delegate, long duration, TimeUnit unit, ValueLoadedCallback<T> valueLoadedCallback) { |
| this.delegate = delegate; |
| this.duration = duration; |
| this.unit = unit; |
| this.cache = CacheBuilder.newBuilder().expireAfterWrite(duration, unit) |
| .build(new SetAndThrowAuthorizationExceptionSupplierBackedLoader<T>(delegate, authException, valueLoadedCallback)); |
| } |
| |
| @Override |
| protected Supplier<T> delegate() { |
| return delegate; |
| } |
| |
| @Override |
| public T get() { |
| try { |
| return cache.get("FOO").orNull(); |
| } catch (UncheckedExecutionException e) { |
| throw propagate(e.getCause()); |
| } catch (ExecutionException e) { |
| throw propagate(e.getCause()); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this).add("delegate", delegate).add("duration", duration).add("unit", unit) |
| .toString(); |
| } |
| |
| } |