diff --git a/core/src/main/java/org/jclouds/Constants.java b/core/src/main/java/org/jclouds/Constants.java index 74e922cfe4..8b601a69d0 100644 --- a/core/src/main/java/org/jclouds/Constants.java +++ b/core/src/main/java/org/jclouds/Constants.java @@ -230,4 +230,23 @@ public interface Constants { */ public static final String PROPERTY_CREDENTIAL = "jclouds.credential"; + /** + * Long properties + *

+ * Overrides timeouts on sync interfaces. Timeout value is in ms. + * Here's an example of an override for a single method: + *

+ * + * #10 seconds
+ * jclouds.timeouts.S3Client.bucketExists=10000 + *
+ *

+ * Or for all methods: + *

+ * + * jclouds.timeouts.GridServerClient = 350000 + * + */ + public static final String PROPERTY_TIMEOUTS_PREFIX = "jclouds.timeouts."; + } diff --git a/core/src/main/java/org/jclouds/concurrent/internal/SyncProxy.java b/core/src/main/java/org/jclouds/concurrent/internal/SyncProxy.java index 42d7a83f2f..6eb6b68ae9 100644 --- a/core/src/main/java/org/jclouds/concurrent/internal/SyncProxy.java +++ b/core/src/main/java/org/jclouds/concurrent/internal/SyncProxy.java @@ -32,6 +32,8 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Named; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; import org.jclouds.concurrent.Timeout; import org.jclouds.internal.ClassMethodArgs; import org.jclouds.rest.annotations.Delegate; @@ -52,9 +54,12 @@ import com.google.inject.ProvisionException; public class SyncProxy implements InvocationHandler { @SuppressWarnings("unchecked") - public static T proxy(Class clazz, SyncProxy proxy) throws IllegalArgumentException, SecurityException, + public static T proxy(Class clazz, Object async, + @Named("sync") Cache delegateMap, + Map, Class> sync2Async, Map timeouts) throws IllegalArgumentException, SecurityException, NoSuchMethodException { - return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, proxy); + return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, + new SyncProxy(clazz, async, delegateMap, sync2Async, timeouts)); } private final Object delegate; @@ -67,8 +72,9 @@ public class SyncProxy implements InvocationHandler { private static final Set objectMethods = ImmutableSet.of(Object.class.getMethods()); @Inject - public SyncProxy(Class declaring, Object async, - @Named("sync") Cache delegateMap, Map, Class> sync2Async) + private SyncProxy(Class declaring, Object async, + @Named("sync") Cache delegateMap, Map, + Class> sync2Async, final Map timeouts) throws SecurityException, NoSuchMethodException { this.delegateMap = delegateMap; this.delegate = async; @@ -90,13 +96,7 @@ public class SyncProxy implements InvocationHandler { throw new IllegalArgumentException(String.format( "method %s has different typed exceptions than delegated method %s", method, delegatedMethod)); if (delegatedMethod.getReturnType().isAssignableFrom(ListenableFuture.class)) { - if (method.isAnnotationPresent(Timeout.class)) { - Timeout methodTimeout = method.getAnnotation(Timeout.class); - long methodNanos = convertToNanos(methodTimeout); - timeoutMap.put(method, methodNanos); - } else { - timeoutMap.put(method, typeNanos); - } + timeoutMap.put(method, getTimeout(method, typeNanos, timeouts)); methodMap.put(method, delegatedMethod); } else { syncMethodMap.put(method, delegatedMethod); @@ -105,6 +105,16 @@ public class SyncProxy implements InvocationHandler { } } + private Long getTimeout(Method method, long typeNanos, final Map timeouts) { + Long timeout = overrideTimeout(method, timeouts); + if (timeout == null && method.isAnnotationPresent(Timeout.class)) { + Timeout methodTimeout = method.getAnnotation(Timeout.class); + timeout = convertToNanos(methodTimeout); + } + return timeout != null ? timeout : typeNanos; + + } + static long convertToNanos(Timeout timeout) { long methodNanos = TimeUnit.NANOSECONDS.convert(timeout.duration(), timeout.timeUnit()); return methodNanos; @@ -139,6 +149,19 @@ public class SyncProxy implements InvocationHandler { } } + // override timeout by values configured in properties(in ms) + private Long overrideTimeout(final Method method, final Map timeouts) { + if (timeouts == null) { + return null; + } + final String className = declaring.getSimpleName(); + Long timeout = timeouts.get(className + "." + method.getName()); + if (timeout == null) { + timeout = timeouts.get(className); + } + return timeout != null ? TimeUnit.MILLISECONDS.toNanos(timeout) : null; + } + @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof SyncProxy)) diff --git a/core/src/main/java/org/jclouds/rest/RestContextBuilder.java b/core/src/main/java/org/jclouds/rest/RestContextBuilder.java index 6df2524fa2..94894c6956 100644 --- a/core/src/main/java/org/jclouds/rest/RestContextBuilder.java +++ b/core/src/main/java/org/jclouds/rest/RestContextBuilder.java @@ -36,19 +36,17 @@ import static org.jclouds.Constants.PROPERTY_ENDPOINT; import static org.jclouds.Constants.PROPERTY_IDENTITY; import static org.jclouds.Constants.PROPERTY_ISO3166_CODES; import static org.jclouds.Constants.PROPERTY_PROVIDER; +import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX; import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; +import java.util.*; import java.util.Map.Entry; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import com.google.common.collect.*; import org.jclouds.concurrent.MoreExecutors; import org.jclouds.concurrent.SingleThreaded; import org.jclouds.concurrent.config.ConfiguresExecutorService; @@ -72,10 +70,6 @@ import org.jclouds.rest.internal.RestContextImpl; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Multimap; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; @@ -117,6 +111,23 @@ public class RestContextBuilder { return LinkedHashMultimap.create(builder.build()); } + @Provides + @Singleton + @Named("TIMEOUTS") + protected Map timeouts() { + final ImmutableMap.Builder builder = ImmutableMap. builder(); + for (final Entry entry : properties.entrySet()) { + final String key = String.valueOf(entry.getKey()); + if (key.startsWith(PROPERTY_TIMEOUTS_PREFIX) && entry.getValue() != null) { + try { + final Long val = Long.valueOf(String.valueOf(entry.getValue())); + builder.put(key.replaceFirst(PROPERTY_TIMEOUTS_PREFIX, ""), val); + } catch (final Throwable t) {} + } + } + return builder.build(); + } + @Override protected void configure() { Properties toBind = new Properties(); diff --git a/core/src/main/java/org/jclouds/rest/config/ClientProvider.java b/core/src/main/java/org/jclouds/rest/config/ClientProvider.java index 643d0c1717..b89a3897b9 100644 --- a/core/src/main/java/org/jclouds/rest/config/ClientProvider.java +++ b/core/src/main/java/org/jclouds/rest/config/ClientProvider.java @@ -63,7 +63,9 @@ public class ClientProvider implements Provider { new TypeLiteral>() { }, Names.named("sync"))); try { - return (S) SyncProxy.proxy(syncClientType, new SyncProxy(syncClientType, client, delegateMap, sync2Async)); + return (S) SyncProxy.proxy(syncClientType, client, delegateMap, sync2Async, + injector.getInstance(Key.get(new TypeLiteral>() { + }, Names.named("TIMEOUTS")))); } catch (Exception e) { Throwables.propagate(e); assert false : "should have propagated"; diff --git a/core/src/main/java/org/jclouds/rest/config/CreateClientForCaller.java b/core/src/main/java/org/jclouds/rest/config/CreateClientForCaller.java index 898594ab7a..761ed6cb44 100644 --- a/core/src/main/java/org/jclouds/rest/config/CreateClientForCaller.java +++ b/core/src/main/java/org/jclouds/rest/config/CreateClientForCaller.java @@ -27,6 +27,10 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Names; import org.jclouds.concurrent.internal.SyncProxy; import org.jclouds.internal.ClassMethodArgs; @@ -42,6 +46,8 @@ import com.google.common.cache.CacheLoader; * @author Adrian Cole */ public class CreateClientForCaller extends CacheLoader { + @Inject + Injector injector; private final Cache asyncMap; private final Provider> delegateMap; Map, Class> sync2Async; @@ -61,7 +67,9 @@ public class CreateClientForCaller extends CacheLoader Object asyncClient = asyncMap.get(from); checkState(asyncClient != null, "configuration error, sync client for " + from + " not found"); try { - return SyncProxy.proxy(syncClass, new SyncProxy(syncClass, asyncClient, delegateMap.get(), sync2Async)); + return SyncProxy.proxy(syncClass, asyncClient, delegateMap.get(), sync2Async, + injector.getInstance(Key.get(new TypeLiteral>() { + }, Names.named("TIMEOUTS")))); } catch (Exception e) { Throwables.propagate(e); assert false : "should have propagated"; diff --git a/core/src/test/java/org/jclouds/concurrent/internal/SyncProxyTest.java b/core/src/test/java/org/jclouds/concurrent/internal/SyncProxyTest.java index 60fcf93f5a..5a1d2b3f03 100644 --- a/core/src/test/java/org/jclouds/concurrent/internal/SyncProxyTest.java +++ b/core/src/test/java/org/jclouds/concurrent/internal/SyncProxyTest.java @@ -77,6 +77,7 @@ public class SyncProxyTest { @Timeout(duration = 300, timeUnit = TimeUnit.MILLISECONDS) String take200MillisecondsAndOverride(); + String takeXMillisecondsPropOverride(long ms); } static ExecutorService executorService = Executors.newCachedThreadPool(); @@ -159,6 +160,21 @@ public class SyncProxyTest { return take200MillisecondsAndTimeout(); } + public ListenableFuture takeXMillisecondsPropOverride(final long ms) { + return Futures.makeListenable(executorService.submit(new Callable() { + + public String call() { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return "foo"; + } + + }), executorService); + } + } private Sync sync; @@ -166,7 +182,8 @@ public class SyncProxyTest { @BeforeTest public void setUp() throws IllegalArgumentException, SecurityException, NoSuchMethodException { Cache cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.constant(null))); - sync = SyncProxy.proxy(Sync.class, new SyncProxy(Sync.class, new Async(),cache, ImmutableMap., Class> of())); + sync = SyncProxy.proxy(Sync.class, new Async(),cache, ImmutableMap., Class> of(), + ImmutableMap.of("Sync.takeXMillisecondsPropOverride", 250L)); // just to warm up sync.string(); } @@ -198,6 +215,16 @@ public class SyncProxyTest { assertEquals(sync.take200MillisecondsAndOverride(), "foo"); } + @Test + public void testTake200MillisecondsPropOverride() { + assertEquals(sync.takeXMillisecondsPropOverride(200), "foo"); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testTake300MillisecondsPropTimeout() { + assertEquals(sync.takeXMillisecondsPropOverride(300), "foo"); + } + @Test public void testToString() { assertEquals(sync.toString(), "Sync Proxy for: Async"); @@ -229,8 +256,8 @@ public class SyncProxyTest { public void testWrongTypedException() throws IllegalArgumentException, SecurityException, NoSuchMethodException, IOException { Cache cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.constant(null))); - SyncProxy.proxy(SyncWrongException.class, new SyncProxy(SyncWrongException.class, new Async(), - cache, ImmutableMap., Class> of())); + SyncProxy.proxy(SyncWrongException.class, new Async(), cache, ImmutableMap., Class> of(), + ImmutableMap.of()); } private static interface SyncNoTimeOut { @@ -248,8 +275,30 @@ public class SyncProxyTest { public void testNoTimeOutException() throws IllegalArgumentException, SecurityException, NoSuchMethodException, IOException { Cache cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.constant(null))); - SyncProxy.proxy(SyncNoTimeOut.class, new SyncProxy(SyncNoTimeOut.class, new Async(), - cache, ImmutableMap., Class> of())); + SyncProxy.proxy(SyncNoTimeOut.class, new Async(), + cache, ImmutableMap., Class> of(), ImmutableMap.of()); } + + @Timeout(duration = 30, timeUnit = TimeUnit.SECONDS) + private static interface SyncClassOverride { + String getString(); + + String newString(); + + String getRuntimeException(); + + @Timeout(duration = 300, timeUnit = TimeUnit.MILLISECONDS) + String takeXMillisecondsPropOverride(long ms); + + } + + @Test(expectedExceptions = RuntimeException.class) + public void testClassOverridePropTimeout() throws Exception { + Cache cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.constant(null))); + final SyncClassOverride sync2 = SyncProxy.proxy(SyncClassOverride.class, new Async(), + cache, ImmutableMap., Class> of(), ImmutableMap.of("SyncClassOverride", 100L)); + + assertEquals(sync2.takeXMillisecondsPropOverride(200), "foo"); + } }