diff --git a/build.gradle b/build.gradle index 9e8f2763..1fc60051 100644 --- a/build.gradle +++ b/build.gradle @@ -146,7 +146,6 @@ project(':ribbon') { dependencies { compile 'com.netflix.hystrix:hystrix-core:1.4.0-RC4' - compile 'com.netflix.evcache:evcache-client:1.0.5' compile 'javax.inject:javax.inject:1' compile project(':ribbon-transport') testCompile project(':ribbon-test') @@ -155,6 +154,15 @@ project(':ribbon') { } } +project(':ribbon-evcache') { + dependencies { + compile project(':ribbon') + compile 'com.netflix.evcache:evcache-client:1.0.5' + testCompile project(':ribbon-test') + testCompile project(':ribbon').sourceSets.test.output + } +} + project(':ribbon-guice') { dependencies { compile project(':ribbon') diff --git a/ribbon-core/src/main/java/com/netflix/client/config/ClientConfigFactory.java b/ribbon-core/src/main/java/com/netflix/client/config/ClientConfigFactory.java index 7e1ac728..6d265c1f 100644 --- a/ribbon-core/src/main/java/com/netflix/client/config/ClientConfigFactory.java +++ b/ribbon-core/src/main/java/com/netflix/client/config/ClientConfigFactory.java @@ -26,10 +26,8 @@ public static class DefaultClientConfigFactory implements ClientConfigFactory { @Override public IClientConfig newConfig() { IClientConfig config = new DefaultClientConfigImpl(); - config.loadDefaultValues(); return config; - - } + } } public static final ClientConfigFactory DEFAULT = new DefaultClientConfigFactory(); diff --git a/ribbon-core/src/main/java/com/netflix/client/config/CommonClientConfigKey.java b/ribbon-core/src/main/java/com/netflix/client/config/CommonClientConfigKey.java index 3cb4a41e..84af56cc 100644 --- a/ribbon-core/src/main/java/com/netflix/client/config/CommonClientConfigKey.java +++ b/ribbon-core/src/main/java/com/netflix/client/config/CommonClientConfigKey.java @@ -17,7 +17,7 @@ */ package com.netflix.client.config; -import static com.google.common.base.Preconditions.checkArgument; +import com.google.common.reflect.TypeToken; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -26,7 +26,7 @@ import java.util.HashSet; import java.util.Set; -import com.google.common.reflect.TypeToken; +import static com.google.common.base.Preconditions.checkArgument; public abstract class CommonClientConfigKey implements IClientConfigKey { @@ -214,6 +214,25 @@ public static IClientConfigKey[] values() { public static Set keys() { return keys; } + + public static IClientConfigKey valueOf(final String name) { + for (IClientConfigKey key: keys()) { + if (key.key().equals(name)) { + return key; + } + } + return new IClientConfigKey() { + @Override + public String key() { + return name; + } + + @Override + public Class type() { + return String.class; + } + }; + } private final String configKey; private final Class type; diff --git a/ribbon/src/main/java/com/netflix/ribbon/evache/CacheFaultException.java b/ribbon-evcache/src/main/java/com/netflix/ribbon/evache/CacheFaultException.java similarity index 100% rename from ribbon/src/main/java/com/netflix/ribbon/evache/CacheFaultException.java rename to ribbon-evcache/src/main/java/com/netflix/ribbon/evache/CacheFaultException.java diff --git a/ribbon/src/main/java/com/netflix/ribbon/evache/CacheMissException.java b/ribbon-evcache/src/main/java/com/netflix/ribbon/evache/CacheMissException.java similarity index 100% rename from ribbon/src/main/java/com/netflix/ribbon/evache/CacheMissException.java rename to ribbon-evcache/src/main/java/com/netflix/ribbon/evache/CacheMissException.java diff --git a/ribbon/src/main/java/com/netflix/ribbon/evache/EvCacheOptions.java b/ribbon-evcache/src/main/java/com/netflix/ribbon/evache/EvCacheOptions.java similarity index 100% rename from ribbon/src/main/java/com/netflix/ribbon/evache/EvCacheOptions.java rename to ribbon-evcache/src/main/java/com/netflix/ribbon/evache/EvCacheOptions.java diff --git a/ribbon/src/main/java/com/netflix/ribbon/evache/EvCacheProvider.java b/ribbon-evcache/src/main/java/com/netflix/ribbon/evache/EvCacheProvider.java similarity index 100% rename from ribbon/src/main/java/com/netflix/ribbon/evache/EvCacheProvider.java rename to ribbon-evcache/src/main/java/com/netflix/ribbon/evache/EvCacheProvider.java diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/EvCache.java b/ribbon-evcache/src/main/java/com/netflix/ribbon/proxy/annotation/EvCache.java similarity index 100% rename from ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/EvCache.java rename to ribbon-evcache/src/main/java/com/netflix/ribbon/proxy/annotation/EvCache.java diff --git a/ribbon-evcache/src/main/java/com/netflix/ribbon/proxy/processor/EVCacheAnnotationProcessor.java b/ribbon-evcache/src/main/java/com/netflix/ribbon/proxy/processor/EVCacheAnnotationProcessor.java new file mode 100644 index 00000000..b792658e --- /dev/null +++ b/ribbon-evcache/src/main/java/com/netflix/ribbon/proxy/processor/EVCacheAnnotationProcessor.java @@ -0,0 +1,97 @@ +package com.netflix.ribbon.proxy.processor; + +import com.netflix.evcache.EVCacheTranscoder; +import com.netflix.ribbon.ResourceGroup.GroupBuilder; +import com.netflix.ribbon.ResourceGroup.TemplateBuilder; +import com.netflix.ribbon.RibbonResourceFactory; +import com.netflix.ribbon.evache.EvCacheOptions; +import com.netflix.ribbon.evache.EvCacheProvider; +import com.netflix.ribbon.proxy.ProxyAnnotationException; +import com.netflix.ribbon.proxy.Utils; +import com.netflix.ribbon.proxy.annotation.EvCache; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Allen Wang + */ +public class EVCacheAnnotationProcessor implements AnnotationProcessor { + + private static final class CacheId { + private final String appName; + private final String cacheName; + + CacheId(String appName, String cacheName) { + this.appName = appName; + this.cacheName = cacheName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CacheId cacheId = (CacheId) o; + + if (!appName.equals(cacheId.appName)) { + return false; + } + return cacheName.equals(cacheId.cacheName); + } + + @Override + public int hashCode() { + int result = appName.hashCode(); + result = 31 * result + cacheName.hashCode(); + return result; + } + } + + private Map> evCacheProviderPool = new HashMap>(); + + @Override + public void process(String templateName, TemplateBuilder templateBuilder, Method method) { + EvCache annotation = method.getAnnotation(EvCache.class); + if (annotation == null) { + return; + } + + Class>[] transcoderClasses = annotation.transcoder(); + EVCacheTranscoder transcoder; + if (transcoderClasses.length == 0) { + transcoder = null; + } else if (transcoderClasses.length > 1) { + throw new ProxyAnnotationException("Multiple transcoders defined on method " + method.getName()); + } else { + transcoder = Utils.newInstance(transcoderClasses[0]); + } + + EvCacheOptions evCacheOptions = new EvCacheOptions( + annotation.appName(), + annotation.name(), + annotation.enableZoneFallback(), + annotation.ttl(), + transcoder, + annotation.key()); + if (evCacheOptions != null) { + CacheId cacheId = new CacheId(evCacheOptions.getAppName(), evCacheOptions.getCacheName()); + EvCacheProvider provider = evCacheProviderPool.get(cacheId); + if (provider == null) { + provider = new EvCacheProvider(evCacheOptions); + evCacheProviderPool.put(cacheId, provider); + } + templateBuilder.withCacheProvider(evCacheOptions.getCacheKeyTemplate(), provider); + } + + } + + @Override + public void process(String groupName, GroupBuilder groupBuilder, RibbonResourceFactory factory, Class interfaceClass) { + } +} diff --git a/ribbon-evcache/src/main/resources/META-INF/services/com.netflix.ribbon.proxy.processor.AnnotationProcessor b/ribbon-evcache/src/main/resources/META-INF/services/com.netflix.ribbon.proxy.processor.AnnotationProcessor new file mode 100644 index 00000000..39bc5865 --- /dev/null +++ b/ribbon-evcache/src/main/resources/META-INF/services/com.netflix.ribbon.proxy.processor.AnnotationProcessor @@ -0,0 +1 @@ +com.netflix.ribbon.proxy.processor.EVCacheAnnotationProcessor diff --git a/ribbon/src/test/java/com/netflix/ribbon/evache/EvCacheProviderTest.java b/ribbon-evcache/src/test/java/com/netflix/ribbon/evache/EvCacheProviderTest.java similarity index 100% rename from ribbon/src/test/java/com/netflix/ribbon/evache/EvCacheProviderTest.java rename to ribbon-evcache/src/test/java/com/netflix/ribbon/evache/EvCacheProviderTest.java diff --git a/ribbon-evcache/src/test/java/com/netflix/ribbon/evache/ServiceLoaderTest.java b/ribbon-evcache/src/test/java/com/netflix/ribbon/evache/ServiceLoaderTest.java new file mode 100644 index 00000000..30972f7b --- /dev/null +++ b/ribbon-evcache/src/test/java/com/netflix/ribbon/evache/ServiceLoaderTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Netflix, 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 com.netflix.ribbon.evache; + +import com.netflix.ribbon.proxy.processor.AnnotationProcessor; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; +import com.netflix.ribbon.proxy.processor.EVCacheAnnotationProcessor; +import org.junit.Test; + +import java.util.List; + +import static junit.framework.TestCase.assertTrue; + +/** + * @author Allen Wang + */ +public class ServiceLoaderTest { + @Test + public void testServiceLoader() { + AnnotationProcessorsProvider annotations = AnnotationProcessorsProvider.DEFAULT; + List processors = annotations.getProcessors(); + boolean hasEVCacheProcessor = false; + for (AnnotationProcessor processor: processors) { + Class clazz = processor.getClass(); + if (clazz.equals(EVCacheAnnotationProcessor.class)) { + hasEVCacheProcessor = true; + break; + } + } + assertTrue(hasEVCacheProcessor); + } +} diff --git a/ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/EvCacheAnnotationTest.java b/ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/EvCacheAnnotationTest.java new file mode 100644 index 00000000..594937db --- /dev/null +++ b/ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/EvCacheAnnotationTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2014 Netflix, 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 com.netflix.ribbon.proxy; + +import com.netflix.ribbon.CacheProvider; +import com.netflix.ribbon.RibbonRequest; +import com.netflix.ribbon.evache.EvCacheProvider; +import com.netflix.ribbon.http.HttpRequestBuilder; +import com.netflix.ribbon.http.HttpRequestTemplate; +import com.netflix.ribbon.http.HttpRequestTemplate.Builder; +import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; +import com.netflix.ribbon.proxy.sample.HystrixHandlers.MovieFallbackHandler; +import com.netflix.ribbon.proxy.sample.HystrixHandlers.SampleHttpResponseValidator; +import com.netflix.ribbon.proxy.sample.SampleMovieServiceWithEVCache; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.easymock.annotation.Mock; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static com.netflix.ribbon.proxy.Utils.methodByName; +import static junit.framework.Assert.assertEquals; +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.expect; +import static org.powermock.api.easymock.PowerMock.*; + +/** + * @author Tomasz Bak + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MethodTemplateExecutor.class}) +@PowerMockIgnore("javax.management.*") +public class EvCacheAnnotationTest { + + @Mock + private RibbonRequest ribbonRequestMock = createMock(RibbonRequest.class); + + @Mock + private HttpRequestBuilder requestBuilderMock = createMock(HttpRequestBuilder.class); + + @Mock + private Builder httpRequestTemplateBuilderMock = createMock(Builder.class); + + @Mock + private HttpRequestTemplate httpRequestTemplateMock = createMock(HttpRequestTemplate.class); + + @Mock + private HttpResourceGroup httpResourceGroupMock = createMock(HttpResourceGroup.class); + + @BeforeClass + public static void setup() { + RibbonDynamicProxy.registerAnnotationProcessors(AnnotationProcessorsProvider.DEFAULT); + } + + @Before + public void setUp() throws Exception { + expect(requestBuilderMock.build()).andReturn(ribbonRequestMock); + expect(httpRequestTemplateBuilderMock.build()).andReturn(httpRequestTemplateMock); + expect(httpRequestTemplateMock.requestBuilder()).andReturn(requestBuilderMock); + } + + @Test + public void testGetQueryWithDomainObjectResult() throws Exception { + expectUrlBase("GET", "/movies/{id}"); + + expect(requestBuilderMock.withRequestProperty("id", "id123")).andReturn(requestBuilderMock); + expect(httpResourceGroupMock.newTemplateBuilder("findMovieById")).andReturn(httpRequestTemplateBuilderMock); + + expect(httpRequestTemplateBuilderMock.withHeader("X-MyHeader1", "value1.1")).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withHeader("X-MyHeader1", "value1.2")).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withHeader("X-MyHeader2", "value2")).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withRequestCacheKey("findMovieById/{id}")).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withFallbackProvider(anyObject(MovieFallbackHandler.class))).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withResponseValidator(anyObject(SampleHttpResponseValidator.class))).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withCacheProvider(anyObject(String.class), anyObject(CacheProvider.class))).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withCacheProvider(anyObject(String.class), anyObject(EvCacheProvider.class))).andReturn(httpRequestTemplateBuilderMock); + + replayAll(); + + MethodTemplateExecutor executor = createExecutor(SampleMovieServiceWithEVCache.class, "findMovieById"); + RibbonRequest ribbonRequest = executor.executeFromTemplate(new Object[]{"id123"}); + + verifyAll(); + + assertEquals(ribbonRequestMock, ribbonRequest); + } + + private void expectUrlBase(String method, String path) { + expect(httpRequestTemplateBuilderMock.withMethod(method)).andReturn(httpRequestTemplateBuilderMock); + expect(httpRequestTemplateBuilderMock.withUriTemplate(path)).andReturn(httpRequestTemplateBuilderMock); + } + + private MethodTemplateExecutor createExecutor(Class clientInterface, String methodName) { + MethodTemplate methodTemplate = new MethodTemplate(methodByName(clientInterface, methodName)); + return new MethodTemplateExecutor(httpResourceGroupMock, methodTemplate, AnnotationProcessorsProvider.DEFAULT); + } +} diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/EvCacheClasses.java b/ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/sample/EvCacheClasses.java similarity index 100% rename from ribbon/src/test/java/com/netflix/ribbon/proxy/sample/EvCacheClasses.java rename to ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/sample/EvCacheClasses.java diff --git a/ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/sample/SampleMovieServiceWithEVCache.java b/ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/sample/SampleMovieServiceWithEVCache.java new file mode 100644 index 00000000..142c7c19 --- /dev/null +++ b/ribbon-evcache/src/test/java/com/netflix/ribbon/proxy/sample/SampleMovieServiceWithEVCache.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Netflix, 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 com.netflix.ribbon.proxy.sample; + +import com.netflix.ribbon.RibbonRequest; +import com.netflix.ribbon.proxy.annotation.CacheProvider; +import com.netflix.ribbon.proxy.annotation.EvCache; +import com.netflix.ribbon.proxy.annotation.Http; +import com.netflix.ribbon.proxy.annotation.Http.Header; +import com.netflix.ribbon.proxy.annotation.Http.HttpMethod; +import com.netflix.ribbon.proxy.annotation.Hystrix; +import com.netflix.ribbon.proxy.annotation.TemplateName; +import com.netflix.ribbon.proxy.annotation.Var; +import com.netflix.ribbon.proxy.sample.EvCacheClasses.SampleEVCacheTranscoder; +import com.netflix.ribbon.proxy.sample.HystrixHandlers.MovieFallbackHandler; +import com.netflix.ribbon.proxy.sample.HystrixHandlers.SampleHttpResponseValidator; +import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieService; +import io.netty.buffer.ByteBuf; + +/** + * @author Allen Wang + */ +public interface SampleMovieServiceWithEVCache extends SampleMovieService { + @TemplateName("findMovieById") + @Http( + method = HttpMethod.GET, + uri = "/movies/{id}", + headers = { + @Header(name = "X-MyHeader1", value = "value1.1"), + @Header(name = "X-MyHeader1", value = "value1.2"), + @Header(name = "X-MyHeader2", value = "value2") + }) + @Hystrix( + cacheKey = "findMovieById/{id}", + validator = SampleHttpResponseValidator.class, + fallbackHandler = MovieFallbackHandler.class) + @CacheProvider(key = "findMovieById_{id}", provider = SampleCacheProviderFactory.class) + + @EvCache(name = "movie-cache", appName = "movieService", key = "movie-{id}", ttl = 50, + enableZoneFallback = true, transcoder = SampleEVCacheTranscoder.class) + RibbonRequest findMovieById(@Var("id") String id); +} diff --git a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java index aaac989f..347aad7a 100644 --- a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java +++ b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java @@ -23,6 +23,8 @@ import com.netflix.ribbon.examples.rx.common.RecommendationServiceResponseValidator; import com.netflix.ribbon.examples.rx.common.RxMovieTransformer; import com.netflix.ribbon.proxy.annotation.CacheProvider; +import com.netflix.ribbon.proxy.annotation.ClientProperties; +import com.netflix.ribbon.proxy.annotation.ClientProperties.Property; import com.netflix.ribbon.proxy.annotation.Content; import com.netflix.ribbon.proxy.annotation.ContentTransformerClass; import com.netflix.ribbon.proxy.annotation.Http; @@ -36,6 +38,11 @@ /** * @author Tomasz Bak */ +@ClientProperties(properties = { + @Property(name="ReadTimeout", value="2000"), + @Property(name="ConnectTimeout", value="1000"), + @Property(name="MaxAutoRetriesNextServer", value="2") +}, exportToArchaius = true) public interface MovieService { @TemplateName("recommendationsByUserId") diff --git a/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonModule.java b/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonModule.java index 3f328c17..1713e272 100644 --- a/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonModule.java +++ b/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonModule.java @@ -23,6 +23,8 @@ import com.netflix.ribbon.RibbonResourceFactory; import com.netflix.ribbon.RibbonTransportFactory; import com.netflix.ribbon.RibbonTransportFactory.DefaultRibbonTransportFactory; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider.DefaultAnnotationProcessorsProvider; /** * Default bindings for Ribbon @@ -35,6 +37,7 @@ public class RibbonModule extends AbstractModule { protected void configure() { bind(ClientConfigFactory.class).to(DefaultClientConfigFactory.class).in(Scopes.SINGLETON); bind(RibbonTransportFactory.class).to(DefaultRibbonTransportFactory.class).in(Scopes.SINGLETON); + bind(AnnotationProcessorsProvider.class).to(DefaultAnnotationProcessorsProvider.class).in(Scopes.SINGLETON); bind(RibbonResourceFactory.class).to(DefaultResourceFactory.class).in(Scopes.SINGLETON); } } diff --git a/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonResourceProvider.java b/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonResourceProvider.java index 39dd2a4c..b7278fd6 100644 --- a/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonResourceProvider.java +++ b/ribbon-guice/src/main/java/com/netflix/ribbon/guice/RibbonResourceProvider.java @@ -58,7 +58,7 @@ public V acceptExtensionVisitor( @Inject @Toolable - void initialize(RibbonResourceFactory factory) { + protected void initialize(RibbonResourceFactory factory) { this.factory = factory; } } diff --git a/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/RxMovieProxyExampleTest.java b/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/RxMovieProxyExampleTest.java index 0ee42b6c..b18201be 100644 --- a/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/RxMovieProxyExampleTest.java +++ b/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/RxMovieProxyExampleTest.java @@ -33,6 +33,8 @@ import com.netflix.ribbon.examples.rx.proxy.RxMovieProxyExample; import com.netflix.ribbon.guice.RibbonModule; import com.netflix.ribbon.guice.RibbonResourceProvider; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider.DefaultAnnotationProcessorsProvider; import io.netty.buffer.ByteBuf; import io.reactivex.netty.protocol.http.client.HttpClient; import org.junit.Test; @@ -83,6 +85,7 @@ public void shouldBindCustomClientConfigFactory() { protected void configure() { bind(RibbonResourceFactory.class).to(DefaultResourceFactory.class).in(Scopes.SINGLETON); bind(RibbonTransportFactory.class).to(DefaultRibbonTransportFactory.class).in(Scopes.SINGLETON); + bind(AnnotationProcessorsProvider.class).to(DefaultAnnotationProcessorsProvider.class).in(Scopes.SINGLETON); bind(ClientConfigFactory.class).to(MyClientConfigFactory.class).in(Scopes.SINGLETON); } }, diff --git a/ribbon-transport/src/main/java/com/netflix/client/netty/LoadBalancingRxClient.java b/ribbon-transport/src/main/java/com/netflix/client/netty/LoadBalancingRxClient.java index 6d3e5721..42b8869a 100644 --- a/ribbon-transport/src/main/java/com/netflix/client/netty/LoadBalancingRxClient.java +++ b/ribbon-transport/src/main/java/com/netflix/client/netty/LoadBalancingRxClient.java @@ -116,7 +116,7 @@ public LoadBalancingRxClient(ILoadBalancer lb, IClientConfig config, RetryHandle public IClientConfig getClientConfig() { return clientConfig; } - + public int getResponseTimeOut() { int maxRetryNextServer = 0; int maxRetrySameServer = 0; diff --git a/ribbon-transport/src/main/java/com/netflix/client/netty/udp/LoadBalancingUdpClient.java b/ribbon-transport/src/main/java/com/netflix/client/netty/udp/LoadBalancingUdpClient.java index 0ae1cca4..2c77f063 100644 --- a/ribbon-transport/src/main/java/com/netflix/client/netty/udp/LoadBalancingUdpClient.java +++ b/ribbon-transport/src/main/java/com/netflix/client/netty/udp/LoadBalancingUdpClient.java @@ -17,7 +17,11 @@ */ package com.netflix.client.netty.udp; -import rx.Subscription; +import com.netflix.client.RetryHandler; +import com.netflix.client.config.IClientConfig; +import com.netflix.client.netty.LoadBalancingRxClient; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; import io.reactivex.netty.RxNetty; import io.reactivex.netty.client.ClientMetricsEvent; import io.reactivex.netty.client.RxClient; @@ -26,12 +30,6 @@ import io.reactivex.netty.protocol.udp.client.UdpClientBuilder; import io.reactivex.netty.servo.udp.UdpClientListener; -import com.netflix.client.RetryHandler; -import com.netflix.client.config.IClientConfig; -import com.netflix.client.netty.LoadBalancingRxClient; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; - public class LoadBalancingUdpClient extends LoadBalancingRxClient> implements RxClient { public LoadBalancingUdpClient(IClientConfig config, diff --git a/ribbon/src/main/java/com/netflix/ribbon/DefaultResourceFactory.java b/ribbon/src/main/java/com/netflix/ribbon/DefaultResourceFactory.java index 80f4257f..6e2b80c5 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/DefaultResourceFactory.java +++ b/ribbon/src/main/java/com/netflix/ribbon/DefaultResourceFactory.java @@ -16,13 +16,19 @@ package com.netflix.ribbon; import com.netflix.client.config.ClientConfigFactory; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; import javax.inject.Inject; public class DefaultResourceFactory extends RibbonResourceFactory { @Inject + public DefaultResourceFactory(ClientConfigFactory clientConfigFactory, RibbonTransportFactory transportFactory, + AnnotationProcessorsProvider annotationProcessorsProvider) { + super(clientConfigFactory, transportFactory, annotationProcessorsProvider); + } + public DefaultResourceFactory(ClientConfigFactory clientConfigFactory, RibbonTransportFactory transportFactory) { - super(clientConfigFactory, transportFactory); + super(clientConfigFactory, transportFactory, AnnotationProcessorsProvider.DEFAULT); } } diff --git a/ribbon/src/main/java/com/netflix/ribbon/ResourceGroup.java b/ribbon/src/main/java/com/netflix/ribbon/ResourceGroup.java index b620c00f..babf5db0 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/ResourceGroup.java +++ b/ribbon/src/main/java/com/netflix/ribbon/ResourceGroup.java @@ -27,6 +27,12 @@ public abstract class ResourceGroup> { protected final ClientConfigFactory configFactory; protected final RibbonTransportFactory transportFactory; + public static abstract class GroupBuilder { + public abstract T build(); + + public abstract GroupBuilder withClientOptions(ClientOptions options); + } + public static abstract class TemplateBuilder> { public abstract TemplateBuilder withFallbackProvider(FallbackHandler fallbackProvider); diff --git a/ribbon/src/main/java/com/netflix/ribbon/Ribbon.java b/ribbon/src/main/java/com/netflix/ribbon/Ribbon.java index 4c9e411d..697919a4 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/Ribbon.java +++ b/ribbon/src/main/java/com/netflix/ribbon/Ribbon.java @@ -18,6 +18,7 @@ import com.netflix.client.config.ClientConfigFactory; import com.netflix.ribbon.http.HttpResourceGroup; import com.netflix.ribbon.http.HttpResourceGroup.Builder; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; /** * A class that can be used to create {@link com.netflix.ribbon.http.HttpResourceGroup}, {@link com.netflix.ribbon.http.HttpResourceGroup.Builder}, @@ -26,7 +27,7 @@ * */ public final class Ribbon { - private static final RibbonResourceFactory factory = new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT); + private static final RibbonResourceFactory factory = new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT, AnnotationProcessorsProvider.DEFAULT); private Ribbon() { } diff --git a/ribbon/src/main/java/com/netflix/ribbon/RibbonResourceFactory.java b/ribbon/src/main/java/com/netflix/ribbon/RibbonResourceFactory.java index 81f76657..bb8669a7 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/RibbonResourceFactory.java +++ b/ribbon/src/main/java/com/netflix/ribbon/RibbonResourceFactory.java @@ -19,9 +19,11 @@ import com.netflix.ribbon.http.HttpResourceGroup; import com.netflix.ribbon.http.HttpResourceGroup.Builder; import com.netflix.ribbon.proxy.RibbonDynamicProxy; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; /** - * Factory for creating an HttpResourceGroup. For DI either bind DefaultHttpResourceGroupFactory + * Factory for creating an HttpResourceGroup or dynamic proxy from an annotated interface. + * For DI either bind DefaultHttpResourceGroupFactory * or implement your own to customize or override HttpResourceGroup. * * @author elandau @@ -29,12 +31,15 @@ public abstract class RibbonResourceFactory { protected final ClientConfigFactory clientConfigFactory; protected final RibbonTransportFactory transportFactory; + protected final AnnotationProcessorsProvider annotationProcessors; - public static final RibbonResourceFactory DEFAULT = new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT); + public static final RibbonResourceFactory DEFAULT = new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT, + AnnotationProcessorsProvider.DEFAULT); - public RibbonResourceFactory(ClientConfigFactory configFactory, RibbonTransportFactory transportFactory) { + public RibbonResourceFactory(ClientConfigFactory configFactory, RibbonTransportFactory transportFactory, AnnotationProcessorsProvider processors) { this.clientConfigFactory = configFactory; this.transportFactory = transportFactory; + this.annotationProcessors = processors; } public Builder createHttpResourceGroupBuilder(String name) { @@ -49,7 +54,7 @@ public HttpResourceGroup createHttpResourceGroup(String name) { public T from(Class classType) { - return RibbonDynamicProxy.newInstance(classType, this, clientConfigFactory, transportFactory); + return RibbonDynamicProxy.newInstance(classType, this, clientConfigFactory, transportFactory, annotationProcessors); } public HttpResourceGroup createHttpResourceGroup(String name, ClientOptions options) { @@ -57,4 +62,12 @@ public HttpResourceGroup createHttpResourceGroup(String name, ClientOptions opti builder.withClientOptions(options); return builder.build(); } + + public final RibbonTransportFactory getTransportFactory() { + return transportFactory; + } + + public final ClientConfigFactory getClientConfigFactory() { + return clientConfigFactory; + } } diff --git a/ribbon/src/main/java/com/netflix/ribbon/http/HttpResourceGroup.java b/ribbon/src/main/java/com/netflix/ribbon/http/HttpResourceGroup.java index 14a1bb9b..113ac91f 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/http/HttpResourceGroup.java +++ b/ribbon/src/main/java/com/netflix/ribbon/http/HttpResourceGroup.java @@ -28,7 +28,7 @@ public class HttpResourceGroup extends ResourceGroup> { private final HttpClient client; private final HttpHeaders headers; - public static class Builder { + public static class Builder extends GroupBuilder { private ClientOptions clientOptions; private HttpHeaders httpHeaders = new DefaultHttpHeaders(); private ClientConfigFactory clientConfigFactory; @@ -45,6 +45,7 @@ public static Builder newBuilder(String groupName, ClientConfigFactory configFac return new Builder(groupName, configFactory, transportFactory); } + @Override public Builder withClientOptions(ClientOptions options) { this.clientOptions = options; return this; @@ -55,6 +56,7 @@ public Builder withHeader(String name, String value) { return this; } + @Override public HttpResourceGroup build() { return new HttpResourceGroup(name, clientOptions, clientConfigFactory, transportFactory, httpHeaders); } diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/EvCacheProviderPool.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/EvCacheProviderPool.java deleted file mode 100644 index 611063c7..00000000 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/EvCacheProviderPool.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2014 Netflix, 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 com.netflix.ribbon.proxy; - -import com.netflix.ribbon.CacheProvider; -import com.netflix.ribbon.evache.EvCacheOptions; -import com.netflix.ribbon.evache.EvCacheProvider; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Tomasz Bak - */ -class EvCacheProviderPool { - - private final Map> pool; - - EvCacheProviderPool(MethodTemplate[] methodTemplates) { - pool = createEvCachePool(methodTemplates); - } - - public CacheProvider getMatching(EvCacheOptions evCacheOptions) { - return pool.get(new CacheId(evCacheOptions.getAppName(), evCacheOptions.getCacheName())); - } - - private static Map> createEvCachePool(MethodTemplate[] methodTemplates) { - Map> newPool = new HashMap>(); - for (MethodTemplate template : methodTemplates) { - EvCacheOptions options = template.getEvCacheOptions(); - if (options != null) { - CacheId id = new CacheId(options.getAppName(), options.getCacheName()); - if (!newPool.containsKey(id)) { - newPool.put(id, new EvCacheProvider(options)); - } - } - } - return newPool; - } - - private static final class CacheId { - - private final String appName; - private final String cacheName; - - CacheId(String appName, String cacheName) { - this.appName = appName; - this.cacheName = cacheName; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - CacheId cacheId = (CacheId) o; - - if (!appName.equals(cacheId.appName)) { - return false; - } - return cacheName.equals(cacheId.cacheName); - } - - @Override - public int hashCode() { - int result = appName.hashCode(); - result = 31 * result + cacheName.hashCode(); - return result; - } - } -} diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java index cd747ecd..eb5bcd81 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java @@ -15,20 +15,9 @@ */ package com.netflix.ribbon.proxy; -import com.netflix.evcache.EVCacheTranscoder; -import com.netflix.ribbon.CacheProviderFactory; import com.netflix.ribbon.RibbonRequest; -import com.netflix.ribbon.evache.EvCacheOptions; -import com.netflix.ribbon.http.HttpResponseValidator; -import com.netflix.ribbon.hystrix.FallbackHandler; -import com.netflix.ribbon.proxy.annotation.CacheProvider; import com.netflix.ribbon.proxy.annotation.Content; import com.netflix.ribbon.proxy.annotation.ContentTransformerClass; -import com.netflix.ribbon.proxy.annotation.EvCache; -import com.netflix.ribbon.proxy.annotation.Http; -import com.netflix.ribbon.proxy.annotation.Http.Header; -import com.netflix.ribbon.proxy.annotation.Http.HttpMethod; -import com.netflix.ribbon.proxy.annotation.Hystrix; import com.netflix.ribbon.proxy.annotation.TemplateName; import com.netflix.ribbon.proxy.annotation.Var; import io.netty.buffer.ByteBuf; @@ -40,12 +29,9 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import static java.lang.String.*; +import static java.lang.String.format; /** * Extracts information from Ribbon annotated method, to automatically populate the Ribbon request template. @@ -60,43 +46,23 @@ class MethodTemplate { private final Method method; private final String templateName; - private final Http.HttpMethod httpMethod; - private final String uriTemplate; - private final Map> headers; private final String[] paramNames; private final int[] valueIdxs; private final int contentArgPosition; private final Class> contentTansformerClass; private final Class resultType; private final Class genericContentType; - private final String hystrixCacheKey; - private final FallbackHandler hystrixFallbackHandler; - private final HttpResponseValidator hystrixResponseValidator; - private final List cacheProviders; - private final EvCacheOptions evCacheOptions; MethodTemplate(Method method) { this.method = method; MethodAnnotationValues values = new MethodAnnotationValues(method); templateName = values.templateName; - httpMethod = values.httpMethod; - uriTemplate = values.uriTemplate; - headers = Collections.unmodifiableMap(values.headers); paramNames = values.paramNames; valueIdxs = values.valueIdxs; contentArgPosition = values.contentArgPosition; contentTansformerClass = values.contentTansformerClass; resultType = values.resultType; genericContentType = values.genericContentType; - hystrixCacheKey = values.hystrixCacheKey; - hystrixFallbackHandler = values.hystrixFallbackHandler; - hystrixResponseValidator = values.hystrixResponseValidator; - cacheProviders = Collections.unmodifiableList(values.cacheProviders); - evCacheOptions = values.evCacheOptions; - } - - public HttpMethod getHttpMethod() { - return httpMethod; } public String getTemplateName() { @@ -107,14 +73,6 @@ public Method getMethod() { return method; } - public String getUriTemplate() { - return uriTemplate; - } - - public Map> getHeaders() { - return headers; - } - public String getParamName(int idx) { return paramNames[idx]; } @@ -143,26 +101,6 @@ public Class getGenericContentType() { return genericContentType; } - public String getHystrixCacheKey() { - return hystrixCacheKey; - } - - public FallbackHandler getHystrixFallbackHandler() { - return hystrixFallbackHandler; - } - - public HttpResponseValidator getHystrixResponseValidator() { - return hystrixResponseValidator; - } - - public List getCacheProviders() { - return cacheProviders; - } - - public EvCacheOptions getEvCacheOptions() { - return evCacheOptions; - } - public static MethodTemplate[] from(Class clientInterface) { List list = new ArrayList(clientInterface.getMethods().length); for (Method m : clientInterface.getMethods()) { @@ -193,79 +131,20 @@ private static class MethodAnnotationValues { private final Method method; private String templateName; - private Http.HttpMethod httpMethod; - private String uriTemplate; private String[] paramNames; private int[] valueIdxs; private int contentArgPosition; private Class> contentTansformerClass; private Class resultType; private Class genericContentType; - public String hystrixCacheKey; - private FallbackHandler hystrixFallbackHandler; - private HttpResponseValidator hystrixResponseValidator; - public final Map> headers = new HashMap>(); - private final List cacheProviders = new ArrayList(); - private EvCacheOptions evCacheOptions; private MethodAnnotationValues(Method method) { this.method = method; extractTemplateName(); - extractHttpAnnotation(); extractParamNamesWithIndexes(); extractContentArgPosition(); extractContentTransformerClass(); extractResultType(); - extractHystrixHandlers(); - extractCacheProvider(); - extractEvCacheOptions(); - } - - private void extractCacheProvider() { - CacheProvider annotation = method.getAnnotation(CacheProvider.class); - if (annotation != null) { - CacheProviderFactory factory = Utils.newInstance(annotation.provider()); - cacheProviders.add(new CacheProviderEntry(annotation.key(), factory.createCacheProvider())); - } - } - - private void extractHystrixHandlers() { - Hystrix annotation = method.getAnnotation(Hystrix.class); - if (annotation == null) { - return; - } - String cacheKey = annotation.cacheKey().trim(); - if (!cacheKey.isEmpty()) { - hystrixCacheKey = cacheKey; - } - if (annotation.fallbackHandler().length == 1) { - hystrixFallbackHandler = Utils.newInstance(annotation.fallbackHandler()[0]); - } else if (annotation.fallbackHandler().length > 1) { - throw new ProxyAnnotationException(format("more than one fallback handler defined for method %s", methodName())); - } - if (annotation.validator().length == 1) { - hystrixResponseValidator = Utils.newInstance(annotation.validator()[0]); - } else if (annotation.validator().length > 1) { - throw new ProxyAnnotationException(format("more than one validator defined for method %s", methodName())); - } - } - - private void extractHttpAnnotation() { - Http annotation = method.getAnnotation(Http.class); - if (null == annotation) { - throw new ProxyAnnotationException(format("Method %s misses @Http annotation", methodName())); - } - httpMethod = annotation.method(); - uriTemplate = annotation.uri(); - for (Header h : annotation.headers()) { - if (!headers.containsKey(h.name())) { - ArrayList values = new ArrayList(); - values.add(h.value()); - headers.put(h.name(), values); - } else { - headers.get(h.name()).add(h.value()); - } - } } private void extractParamNamesWithIndexes() { @@ -362,31 +241,6 @@ private void extractResultType() { } } - private void extractEvCacheOptions() { - EvCache annotation = method.getAnnotation(EvCache.class); - if (annotation == null) { - return; - } - - Class>[] transcoderClasses = annotation.transcoder(); - EVCacheTranscoder transcoder; - if (transcoderClasses.length == 0) { - transcoder = null; - } else if (transcoderClasses.length > 1) { - throw new ProxyAnnotationException("Multiple transcoders defined on method " + methodName()); - } else { - transcoder = Utils.newInstance(transcoderClasses[0]); - } - - evCacheOptions = new EvCacheOptions( - annotation.appName(), - annotation.name(), - annotation.enableZoneFallback(), - annotation.ttl(), - transcoder, - annotation.key()); - } - private String methodName() { return method.getDeclaringClass().getSimpleName() + '.' + method.getName(); } diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java index 06628678..33ec1a00 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java @@ -15,13 +15,12 @@ */ package com.netflix.ribbon.proxy; -import com.netflix.ribbon.CacheProvider; import com.netflix.ribbon.RibbonRequest; -import com.netflix.ribbon.evache.EvCacheOptions; -import com.netflix.ribbon.evache.EvCacheProvider; import com.netflix.ribbon.http.HttpRequestBuilder; import com.netflix.ribbon.http.HttpRequestTemplate.Builder; import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.processor.AnnotationProcessor; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.reactivex.netty.channel.ContentTransformer; @@ -30,9 +29,7 @@ import java.lang.reflect.Method; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * @author Tomasz Bak @@ -60,13 +57,14 @@ public ByteBuf call(byte[] toTransform, ByteBufAllocator byteBufAllocator) { private final HttpResourceGroup httpResourceGroup; private final MethodTemplate methodTemplate; private final Builder httpRequestTemplateBuilder; - private final EvCacheProviderPool evCacheProviderPool; - MethodTemplateExecutor(HttpResourceGroup httpResourceGroup, MethodTemplate methodTemplate, EvCacheProviderPool evCacheProviderPool) { + MethodTemplateExecutor(HttpResourceGroup httpResourceGroup, MethodTemplate methodTemplate, AnnotationProcessorsProvider annotations) { this.httpResourceGroup = httpResourceGroup; this.methodTemplate = methodTemplate; - this.evCacheProviderPool = evCacheProviderPool; httpRequestTemplateBuilder = createHttpRequestTemplateBuilder(); + for (AnnotationProcessor processor: annotations.getProcessors()) { + processor.process(methodTemplate.getTemplateName(), httpRequestTemplateBuilder, methodTemplate.getMethod()); + } } @SuppressWarnings("unchecked") @@ -80,10 +78,6 @@ public RibbonRequest executeFromTemplate(Object[] args) { private Builder createHttpRequestTemplateBuilder() { Builder httpRequestTemplateBuilder = createBaseHttpRequestTemplate(httpResourceGroup); - withRequestUriBase(httpRequestTemplateBuilder); - withHttpHeaders(httpRequestTemplateBuilder); - withHystrixHandlers(httpRequestTemplateBuilder); - withCacheProviders(httpRequestTemplateBuilder); return httpRequestTemplateBuilder; } @@ -98,48 +92,6 @@ private Builder createBaseHttpRequestTemplate(HttpResourceGroup httpResourceG return httpRequestTemplate; } - private void withRequestUriBase(Builder httpRequestTemplate) { - httpRequestTemplate.withMethod(methodTemplate.getHttpMethod().name()); - if (methodTemplate.getUriTemplate() != null) { - httpRequestTemplate.withUriTemplate(methodTemplate.getUriTemplate()); - } - } - - private void withHttpHeaders(Builder httpRequestTemplate) { - for (Entry> header : methodTemplate.getHeaders().entrySet()) { - String key = header.getKey(); - for (String value : header.getValue()) { - httpRequestTemplate.withHeader(key, value); - } - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - private void withHystrixHandlers(Builder httpRequestTemplate) { - if (methodTemplate.getHystrixFallbackHandler() != null) { - httpRequestTemplate.withFallbackProvider(methodTemplate.getHystrixFallbackHandler()); - } - if (methodTemplate.getHystrixResponseValidator() != null) { - httpRequestTemplate.withResponseValidator(methodTemplate.getHystrixResponseValidator()); - } - if (methodTemplate.getHystrixCacheKey() != null) { - httpRequestTemplate.withRequestCacheKey(methodTemplate.getHystrixCacheKey()); - } - } - - @SuppressWarnings({"rawtypes", "unchecked", "OverlyStrongTypeCast"}) - private void withCacheProviders(Builder httpRequestTemplate) { - if (methodTemplate.getCacheProviders() != null) { - for (MethodTemplate.CacheProviderEntry entry : methodTemplate.getCacheProviders()) { - httpRequestTemplate.withCacheProvider(entry.getKey(), (CacheProvider) entry.getCacheProvider()); - } - } - EvCacheOptions evCacheOptions = methodTemplate.getEvCacheOptions(); - if (evCacheOptions != null) { - httpRequestTemplate.withCacheProvider(evCacheOptions.getCacheKeyTemplate(), (EvCacheProvider) evCacheProviderPool.getMatching(evCacheOptions)); - } - } - private void withParameters(HttpRequestBuilder requestBuilder, Object[] args) { int length = methodTemplate.getParamSize(); for (int i = 0; i < length; i++) { @@ -174,12 +126,11 @@ private void withContent(HttpRequestBuilder requestBuilder, Object[] args) { } } - public static Map from(HttpResourceGroup httpResourceGroup, Class clientInterface) { + public static Map from(HttpResourceGroup httpResourceGroup, Class clientInterface, AnnotationProcessorsProvider annotations) { MethodTemplate[] methodTemplates = MethodTemplate.from(clientInterface); - EvCacheProviderPool evCacheProviderPool = new EvCacheProviderPool(methodTemplates); Map tgm = new HashMap(); for (MethodTemplate mt : methodTemplates) { - tgm.put(mt.getMethod(), new MethodTemplateExecutor(httpResourceGroup, mt, evCacheProviderPool)); + tgm.put(mt.getMethod(), new MethodTemplateExecutor(httpResourceGroup, mt, annotations)); } return tgm; } diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/ProxyHttpResourceGroupFactory.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/ProxyHttpResourceGroupFactory.java index 2c26a1c7..3b40763e 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/ProxyHttpResourceGroupFactory.java +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/ProxyHttpResourceGroupFactory.java @@ -16,31 +16,31 @@ package com.netflix.ribbon.proxy; import com.netflix.client.config.ClientConfigFactory; -import com.netflix.client.config.IClientConfig; -import com.netflix.ribbon.ClientOptions; import com.netflix.ribbon.DefaultResourceFactory; import com.netflix.ribbon.RibbonResourceFactory; import com.netflix.ribbon.RibbonTransportFactory; import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.processor.AnnotationProcessor; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; /** * @author Tomasz Bak */ -class ProxyHttpResourceGroupFactory { +public class ProxyHttpResourceGroupFactory { private final ClassTemplate classTemplate; private final RibbonResourceFactory httpResourceGroupFactory; - private final IClientConfig clientConfig; + private final AnnotationProcessorsProvider processors; ProxyHttpResourceGroupFactory(ClassTemplate classTemplate) { - this(classTemplate, new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT), - ClientConfigFactory.DEFAULT.newConfig(), RibbonTransportFactory.DEFAULT); + this(classTemplate, new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT, AnnotationProcessorsProvider.DEFAULT), + AnnotationProcessorsProvider.DEFAULT); } - - ProxyHttpResourceGroupFactory(ClassTemplate classTemplate, RibbonResourceFactory httpResourceGroupFactory, IClientConfig clientConfig, - RibbonTransportFactory transportFactory) { + + ProxyHttpResourceGroupFactory(ClassTemplate classTemplate, RibbonResourceFactory httpResourceGroupFactory, + AnnotationProcessorsProvider processors) { this.classTemplate = classTemplate; this.httpResourceGroupFactory = httpResourceGroupFactory; - this.clientConfig = clientConfig; + this.processors = processors; } public HttpResourceGroup createResourceGroup() { @@ -52,8 +52,11 @@ public HttpResourceGroup createResourceGroup() { if (name == null) { name = classTemplate.getClientInterface().getSimpleName(); } - clientConfig.loadProperties(name); - return httpResourceGroupFactory.createHttpResourceGroupBuilder(name).withClientOptions(ClientOptions.from(clientConfig)).build(); + HttpResourceGroup.Builder builder = httpResourceGroupFactory.createHttpResourceGroupBuilder(name); + for (AnnotationProcessor processor: processors.getProcessors()) { + processor.process(name, builder, httpResourceGroupFactory, classTemplate.getClientInterface()); + } + return builder.build(); } } } diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/RibbonDynamicProxy.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/RibbonDynamicProxy.java index caba0422..2ea76b8d 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/RibbonDynamicProxy.java +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/RibbonDynamicProxy.java @@ -16,11 +16,15 @@ package com.netflix.ribbon.proxy; import com.netflix.client.config.ClientConfigFactory; -import com.netflix.client.config.IClientConfig; import com.netflix.ribbon.DefaultResourceFactory; import com.netflix.ribbon.RibbonResourceFactory; import com.netflix.ribbon.RibbonTransportFactory; import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; +import com.netflix.ribbon.proxy.processor.CacheProviderAnnotationProcessor; +import com.netflix.ribbon.proxy.processor.ClientPropertiesProcessor; +import com.netflix.ribbon.proxy.processor.HttpAnnotationProcessor; +import com.netflix.ribbon.proxy.processor.HystrixAnnotationProcessor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -35,20 +39,26 @@ public class RibbonDynamicProxy implements InvocationHandler { private final Map templateExecutorMap; RibbonDynamicProxy(Class clientInterface, HttpResourceGroup httpResourceGroup) { + AnnotationProcessorsProvider processors = AnnotationProcessorsProvider.DEFAULT; + registerAnnotationProcessors(processors); lifeCycle = new ProxyLifecycleImpl(httpResourceGroup); - templateExecutorMap = MethodTemplateExecutor.from(httpResourceGroup, clientInterface); + templateExecutorMap = MethodTemplateExecutor.from(httpResourceGroup, clientInterface, processors); } - public RibbonDynamicProxy(Class clientInterface, RibbonResourceFactory resourceGroupFactory, ClientConfigFactory configFactory, RibbonTransportFactory transportFactory) { + public RibbonDynamicProxy(Class clientInterface, RibbonResourceFactory resourceGroupFactory, ClientConfigFactory configFactory, + RibbonTransportFactory transportFactory, AnnotationProcessorsProvider processors) { + registerAnnotationProcessors(processors); ClassTemplate classTemplate = ClassTemplate.from(clientInterface); - IClientConfig config = createClientConfig(classTemplate, configFactory); - HttpResourceGroup httpResourceGroup = new ProxyHttpResourceGroupFactory(classTemplate, resourceGroupFactory, config, transportFactory).createResourceGroup(); - templateExecutorMap = MethodTemplateExecutor.from(httpResourceGroup, clientInterface); + HttpResourceGroup httpResourceGroup = new ProxyHttpResourceGroupFactory(classTemplate, resourceGroupFactory, processors).createResourceGroup(); + templateExecutorMap = MethodTemplateExecutor.from(httpResourceGroup, clientInterface, processors); lifeCycle = new ProxyLifecycleImpl(httpResourceGroup); } - private IClientConfig createClientConfig(ClassTemplate classTemplate, ClientConfigFactory configFactory) { - return configFactory.newConfig(); + static void registerAnnotationProcessors(AnnotationProcessorsProvider processors) { + processors.register(new HttpAnnotationProcessor()); + processors.register(new HystrixAnnotationProcessor()); + processors.register(new CacheProviderAnnotationProcessor()); + processors.register(new ClientPropertiesProcessor()); } @Override @@ -102,10 +112,10 @@ public synchronized void shutdown() { } public static T newInstance(Class clientInterface) { - return newInstance(clientInterface, new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT), - ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT); + return newInstance(clientInterface, new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT, AnnotationProcessorsProvider.DEFAULT), + ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT, AnnotationProcessorsProvider.DEFAULT); } - + @SuppressWarnings("unchecked") static T newInstance(Class clientInterface, HttpResourceGroup httpResourceGroup) { if (!clientInterface.isInterface()) { @@ -120,16 +130,22 @@ static T newInstance(Class clientInterface, HttpResourceGroup httpResourc new RibbonDynamicProxy(clientInterface, httpResourceGroup) ); } - + @SuppressWarnings("unchecked") - public static T newInstance(Class clientInterface, RibbonResourceFactory resourceGroupFactory, ClientConfigFactory configFactory, RibbonTransportFactory transportFactory) { + public static T newInstance(Class clientInterface, RibbonResourceFactory resourceGroupFactory, + ClientConfigFactory configFactory, RibbonTransportFactory transportFactory, AnnotationProcessorsProvider processors) { if (!clientInterface.isInterface()) { throw new IllegalArgumentException(clientInterface.getName() + " is a class not interface"); } return (T) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[]{clientInterface, ProxyLifeCycle.class}, - new RibbonDynamicProxy(clientInterface, resourceGroupFactory, configFactory, transportFactory) + new RibbonDynamicProxy(clientInterface, resourceGroupFactory, configFactory, transportFactory, processors) ); } + + public static T newInstance(Class clientInterface, RibbonResourceFactory resourceGroupFactory, + ClientConfigFactory configFactory, RibbonTransportFactory transportFactory) { + return newInstance(clientInterface, resourceGroupFactory, configFactory, transportFactory, AnnotationProcessorsProvider.DEFAULT); + } } diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/Utils.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/Utils.java index d859d93a..9a5fe625 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/Utils.java +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/Utils.java @@ -24,7 +24,7 @@ * * @author Tomasz Bak */ -final class Utils { +public final class Utils { private Utils() { } diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/ClientProperties.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/ClientProperties.java new file mode 100644 index 00000000..bb4a5386 --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/ClientProperties.java @@ -0,0 +1,22 @@ +package com.netflix.ribbon.proxy.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Allen Wang + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ClientProperties { + @interface Property { + String name(); + String value(); + } + + Property[] properties() default {}; + + boolean exportToArchaius() default false; +} diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/AnnotationProcessor.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/AnnotationProcessor.java new file mode 100644 index 00000000..4aafb410 --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/AnnotationProcessor.java @@ -0,0 +1,17 @@ +package com.netflix.ribbon.proxy.processor; + +import com.netflix.ribbon.ResourceGroup.GroupBuilder; +import com.netflix.ribbon.ResourceGroup.TemplateBuilder; +import com.netflix.ribbon.RibbonResourceFactory; + +import java.lang.reflect.Method; + +/** + * @author Tomasz Bak + */ +public interface AnnotationProcessor { + + void process(String templateName, S templateBuilder, Method method); + + void process(String groupName, T groupBuilder, RibbonResourceFactory resourceFactory, Class interfaceClass); +} diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/AnnotationProcessorsProvider.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/AnnotationProcessorsProvider.java new file mode 100644 index 00000000..3cf9dc97 --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/AnnotationProcessorsProvider.java @@ -0,0 +1,34 @@ +package com.netflix.ribbon.proxy.processor; + +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Tomasz Bak + */ +public abstract class AnnotationProcessorsProvider { + + public static final AnnotationProcessorsProvider DEFAULT = new DefaultAnnotationProcessorsProvider(); + private final List processors = new CopyOnWriteArrayList(); + + public static class DefaultAnnotationProcessorsProvider extends AnnotationProcessorsProvider { + protected DefaultAnnotationProcessorsProvider() { + ServiceLoader loader = ServiceLoader.load(AnnotationProcessor.class); + Iterator iterator = loader.iterator(); + while (iterator.hasNext()) { + register(iterator.next()); + } + } + + } + + public void register(AnnotationProcessor processor) { + processors.add(processor); + } + + public List getProcessors() { + return processors; + } +} diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/CacheProviderAnnotationProcessor.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/CacheProviderAnnotationProcessor.java new file mode 100644 index 00000000..70e3c67f --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/CacheProviderAnnotationProcessor.java @@ -0,0 +1,28 @@ +package com.netflix.ribbon.proxy.processor; + +import com.netflix.ribbon.CacheProviderFactory; +import com.netflix.ribbon.ResourceGroup.GroupBuilder; +import com.netflix.ribbon.ResourceGroup.TemplateBuilder; +import com.netflix.ribbon.RibbonResourceFactory; +import com.netflix.ribbon.proxy.Utils; +import com.netflix.ribbon.proxy.annotation.CacheProvider; + +import java.lang.reflect.Method; + +/** + * @author Allen Wang + */ +public class CacheProviderAnnotationProcessor implements AnnotationProcessor { + @Override + public void process(String templateName, TemplateBuilder templateBuilder, Method method) { + CacheProvider annotation = method.getAnnotation(CacheProvider.class); + if (annotation != null) { + CacheProviderFactory factory = Utils.newInstance(annotation.provider()); + templateBuilder.withCacheProvider(annotation.key(), factory.createCacheProvider()); + } + } + + @Override + public void process(String groupName, GroupBuilder groupBuilder, RibbonResourceFactory resourceFactory, Class interfaceClass) { + } +} diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/ClientPropertiesProcessor.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/ClientPropertiesProcessor.java new file mode 100644 index 00000000..ca5b3831 --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/ClientPropertiesProcessor.java @@ -0,0 +1,60 @@ +package com.netflix.ribbon.proxy.processor; + +import com.netflix.client.config.CommonClientConfigKey; +import com.netflix.client.config.IClientConfig; +import com.netflix.config.AggregatedConfiguration; +import com.netflix.config.ConcurrentMapConfiguration; +import com.netflix.config.ConfigurationManager; +import com.netflix.ribbon.ClientOptions; +import com.netflix.ribbon.ResourceGroup.GroupBuilder; +import com.netflix.ribbon.ResourceGroup.TemplateBuilder; +import com.netflix.ribbon.RibbonResourceFactory; +import com.netflix.ribbon.proxy.annotation.ClientProperties; +import com.netflix.ribbon.proxy.annotation.ClientProperties.Property; +import org.apache.commons.configuration.AbstractConfiguration; +import org.apache.commons.configuration.Configuration; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * @author Allen Wang + */ +public class ClientPropertiesProcessor implements AnnotationProcessor { + @Override + public void process(String templateName, TemplateBuilder templateBuilder, Method method) { + } + + @Override + public void process(String groupName, GroupBuilder groupBuilder, RibbonResourceFactory resourceFactory, Class interfaceClass) { + ClientProperties properties = interfaceClass.getAnnotation(ClientProperties.class); + if (properties != null) { + IClientConfig config = resourceFactory.getClientConfigFactory().newConfig(); + for (Property prop : properties.properties()) { + String name = prop.name(); + config.set(CommonClientConfigKey.valueOf(name), prop.value()); + } + ClientOptions options = ClientOptions.from(config); + groupBuilder.withClientOptions(options); + if (properties.exportToArchaius()) { + exportPropertiesToArchaius(groupName, config, interfaceClass.getName()); + } + } + } + + private void exportPropertiesToArchaius(String groupName, IClientConfig config, String configName) { + Map map = config.getProperties(); + Configuration configuration = ConfigurationManager.getConfigInstance(); + if (configuration instanceof AggregatedConfiguration) { + AggregatedConfiguration ac = (AggregatedConfiguration) configuration; + configuration = ac.getConfiguration(configName); + if (configuration == null) { + configuration = new ConcurrentMapConfiguration(); + ac.addConfiguration((AbstractConfiguration) configuration, configName); + } + } + for (Map.Entry entry : map.entrySet()) { + configuration.setProperty(groupName + "." + config.getNameSpace() + "." + entry.getKey(), entry.getValue()); + } + } +} diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/HttpAnnotationProcessor.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/HttpAnnotationProcessor.java new file mode 100644 index 00000000..2c0f6d33 --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/HttpAnnotationProcessor.java @@ -0,0 +1,65 @@ +package com.netflix.ribbon.proxy.processor; + +import com.netflix.ribbon.RibbonResourceFactory; +import com.netflix.ribbon.http.HttpRequestTemplate.Builder; +import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.ProxyAnnotationException; +import com.netflix.ribbon.proxy.annotation.Http; +import com.netflix.ribbon.proxy.annotation.Http.Header; +import com.netflix.ribbon.proxy.annotation.Http.HttpMethod; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import static java.lang.String.format; + +/** + * Http annotation + */ +public class HttpAnnotationProcessor implements AnnotationProcessor { + + @Override + public void process(String templateName, Builder templateBuilder, Method method) { + Http annotation = method.getAnnotation(Http.class); + if (null == annotation) { + throw new ProxyAnnotationException(format("Method %s misses @Http annotation", method.getName())); + } + final HttpMethod httpMethod = annotation.method(); + final String uriTemplate = annotation.uri(); + final Map> headers = annotation.headers().length == 0 ? null : new HashMap>(); + for (Header h : annotation.headers()) { + if (!headers.containsKey(h.name())) { + ArrayList values = new ArrayList(); + values.add(h.value()); + headers.put(h.name(), values); + } else { + headers.get(h.name()).add(h.value()); + } + } + templateBuilder.withMethod(httpMethod.name()); + + // uri + if (uriTemplate != null) { + templateBuilder.withUriTemplate(uriTemplate); + } + + // headers + if (headers != null) { + for (Entry> header : headers.entrySet()) { + String key = header.getKey(); + for (String value : header.getValue()) { + templateBuilder.withHeader(key, value); + } + } + } + + } + + @Override + public void process(String groupName, HttpResourceGroup.Builder groupBuilder, RibbonResourceFactory resourceFactory, Class interfaceClass) { + } +} diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/HystrixAnnotationProcessor.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/HystrixAnnotationProcessor.java new file mode 100644 index 00000000..a14b3cf8 --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/processor/HystrixAnnotationProcessor.java @@ -0,0 +1,47 @@ +package com.netflix.ribbon.proxy.processor; + +import com.netflix.ribbon.RibbonResourceFactory; +import com.netflix.ribbon.http.HttpRequestTemplate.Builder; +import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.http.HttpResponseValidator; +import com.netflix.ribbon.hystrix.FallbackHandler; +import com.netflix.ribbon.proxy.ProxyAnnotationException; +import com.netflix.ribbon.proxy.Utils; +import com.netflix.ribbon.proxy.annotation.Hystrix; + +import java.lang.reflect.Method; + +import static java.lang.String.format; + +/** + * @author Allen Wang + */ +public class HystrixAnnotationProcessor implements AnnotationProcessor { + @Override + public void process(String templateName, Builder templateBuilder, Method method) { + Hystrix annotation = method.getAnnotation(Hystrix.class); + if (annotation == null) { + return; + } + String cacheKey = annotation.cacheKey().trim(); + if (!cacheKey.isEmpty()) { + templateBuilder.withRequestCacheKey(cacheKey); + } + if (annotation.fallbackHandler().length == 1) { + FallbackHandler hystrixFallbackHandler = Utils.newInstance(annotation.fallbackHandler()[0]); + templateBuilder.withFallbackProvider(hystrixFallbackHandler); + } else if (annotation.fallbackHandler().length > 1) { + throw new ProxyAnnotationException(format("more than one fallback handler defined for method %s", method.getName())); + } + if (annotation.validator().length == 1) { + HttpResponseValidator hystrixResponseValidator = Utils.newInstance(annotation.validator()[0]); + templateBuilder.withResponseValidator(hystrixResponseValidator); + } else if (annotation.validator().length > 1) { + throw new ProxyAnnotationException(format("more than one validator defined for method %s", method.getName())); + } + } + + @Override + public void process(String groupName, HttpResourceGroup.Builder groupBuilder, RibbonResourceFactory resourceFactory, Class interfaceClass) { + } +} diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/ClientPropertiesTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/ClientPropertiesTest.java new file mode 100644 index 00000000..afe7ed7d --- /dev/null +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/ClientPropertiesTest.java @@ -0,0 +1,84 @@ +package com.netflix.ribbon.proxy; + +import com.netflix.client.config.ClientConfigFactory; +import com.netflix.client.config.IClientConfig; +import com.netflix.client.config.IClientConfigKey.Keys; +import com.netflix.config.ConfigurationManager; +import com.netflix.ribbon.DefaultResourceFactory; +import com.netflix.ribbon.RibbonResourceFactory; +import com.netflix.ribbon.RibbonTransportFactory.DefaultRibbonTransportFactory; +import com.netflix.ribbon.proxy.annotation.ClientProperties; +import com.netflix.ribbon.proxy.annotation.ClientProperties.Property; +import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieService; +import io.netty.buffer.ByteBuf; +import io.reactivex.netty.protocol.http.client.HttpClient; +import org.apache.commons.configuration.Configuration; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * @author Allen Wang + */ +public class ClientPropertiesTest { + + private static class MyTransportFactory extends DefaultRibbonTransportFactory { + private IClientConfig config; + + public MyTransportFactory(ClientConfigFactory clientConfigFactory) { + super(clientConfigFactory); + } + + @Override + public HttpClient newHttpClient(IClientConfig config) { + this.config = config; + return super.newHttpClient(config); + } + + public IClientConfig getClientConfig() { + return this.config; + } + } + + @ClientProperties(properties = { + @Property(name="ReadTimeout", value="3000"), + @Property(name="ConnectTimeout", value="1000"), + @Property(name="MaxAutoRetriesNextServer", value="0") + }) + public static interface MovieService extends SampleMovieService { + } + + @Test + public void testAnnotation() { + MyTransportFactory transportFactory = new MyTransportFactory(ClientConfigFactory.DEFAULT); + RibbonResourceFactory resourceFactory = new DefaultResourceFactory(ClientConfigFactory.DEFAULT, transportFactory); + RibbonDynamicProxy.newInstance(SampleMovieService.class, resourceFactory, ClientConfigFactory.DEFAULT, transportFactory); + IClientConfig clientConfig = transportFactory.getClientConfig(); + assertEquals(1000, clientConfig.get(Keys.ConnectTimeout).longValue()); + assertEquals(2000, clientConfig.get(Keys.ReadTimeout).longValue()); + + Configuration config = ConfigurationManager.getConfigInstance(); + assertEquals("2000", config.getProperty("SampleMovieService.ribbon.ReadTimeout")); + assertEquals("1000", config.getProperty("SampleMovieService.ribbon.ConnectTimeout")); + + config.setProperty("SampleMovieService.ribbon.ReadTimeout", "5000"); + assertEquals(5000, clientConfig.get(Keys.ReadTimeout).longValue()); + } + + @Test + public void testNoExportToArchaius() { + MyTransportFactory transportFactory = new MyTransportFactory(ClientConfigFactory.DEFAULT); + RibbonResourceFactory resourceFactory = new DefaultResourceFactory(ClientConfigFactory.DEFAULT, transportFactory); + RibbonDynamicProxy.newInstance(MovieService.class, resourceFactory, ClientConfigFactory.DEFAULT, transportFactory); + IClientConfig clientConfig = transportFactory.getClientConfig(); + assertEquals(1000, clientConfig.get(Keys.ConnectTimeout).longValue()); + assertEquals(3000, clientConfig.get(Keys.ReadTimeout).longValue()); + assertEquals(0, clientConfig.get(Keys.MaxAutoRetriesNextServer).longValue()); + + Configuration config = ConfigurationManager.getConfigInstance(); + assertNull(config.getProperty("MovieService.ribbon.ReadTimeout")); + config.setProperty("MovieService.ribbon.ReadTimeout", "5000"); + assertEquals(5000, clientConfig.get(Keys.ReadTimeout).longValue()); + } +} diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/EvCacheProviderPoolTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/EvCacheProviderPoolTest.java deleted file mode 100644 index ffed8afb..00000000 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/EvCacheProviderPoolTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2014 Netflix, 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 com.netflix.ribbon.proxy; - -import com.netflix.ribbon.CacheProvider; -import com.netflix.ribbon.evache.EvCacheOptions; -import com.netflix.ribbon.evache.EvCacheProvider; -import com.netflix.ribbon.proxy.EvCacheProviderPool; -import com.netflix.ribbon.proxy.MethodTemplate; -import com.netflix.ribbon.proxy.Utils; -import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieService; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.easymock.annotation.Mock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import static junit.framework.Assert.*; -import static org.easymock.EasyMock.*; -import static org.powermock.api.easymock.PowerMock.*; - -/** - * @author Tomasz Bak - */ -@RunWith(PowerMockRunner.class) -@PrepareForTest({EvCacheProviderPool.class}) -public class EvCacheProviderPoolTest { - - @Mock - private EvCacheProvider evCacheProviderMock; - - @Before - public void setUp() throws Exception { - mockStatic(EvCacheProvider.class); - expectNew(EvCacheProvider.class, new Class[]{EvCacheOptions.class}, anyObject(EvCacheOptions.class)).andReturn(evCacheProviderMock); - } - - @Test - public void testCreate() throws Exception { - replayAll(); - EvCacheProviderPool pool = new EvCacheProviderPool(MethodTemplate.from(SampleMovieService.class)); - MethodTemplate findById = new MethodTemplate(Utils.methodByName(SampleMovieService.class, "findMovieById")); - CacheProvider cacheProvider = pool.getMatching(findById.getEvCacheOptions()); - - assertEquals(evCacheProviderMock, cacheProvider); - } -} \ No newline at end of file diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/HttpResourceGroupFactoryTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/HttpResourceGroupFactoryTest.java index 16dc55b7..72273930 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/HttpResourceGroupFactoryTest.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/HttpResourceGroupFactoryTest.java @@ -20,6 +20,7 @@ import com.netflix.ribbon.DefaultResourceFactory; import com.netflix.ribbon.RibbonTransportFactory; import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieService; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieServiceWithResourceGroupClassAnnotation; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieServiceWithResourceGroupNameAnnotation; @@ -37,8 +38,8 @@ public class HttpResourceGroupFactoryTest { @Test public void testResourceGroupAnnotationMissing() throws Exception { ClassTemplate classTemplate = new ClassTemplate(SampleMovieService.class); - new ProxyHttpResourceGroupFactory(classTemplate, new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT), - ClientConfigFactory.DEFAULT.newConfig(), RibbonTransportFactory.DEFAULT).createResourceGroup(); + new ProxyHttpResourceGroupFactory(classTemplate, new DefaultResourceFactory(ClientConfigFactory.DEFAULT, RibbonTransportFactory.DEFAULT, AnnotationProcessorsProvider.DEFAULT), + AnnotationProcessorsProvider.DEFAULT).createResourceGroup(); } @Test diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java index 1f0f3bd6..6ab11116 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java @@ -17,12 +17,11 @@ import com.netflix.ribbon.CacheProvider; import com.netflix.ribbon.RibbonRequest; -import com.netflix.ribbon.evache.EvCacheOptions; -import com.netflix.ribbon.evache.EvCacheProvider; import com.netflix.ribbon.http.HttpRequestBuilder; import com.netflix.ribbon.http.HttpRequestTemplate; import com.netflix.ribbon.http.HttpRequestTemplate.Builder; import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; import com.netflix.ribbon.proxy.sample.HystrixHandlers.MovieFallbackHandler; import com.netflix.ribbon.proxy.sample.HystrixHandlers.SampleHttpResponseValidator; import com.netflix.ribbon.proxy.sample.Movie; @@ -31,9 +30,11 @@ import io.netty.buffer.ByteBuf; import io.reactivex.netty.channel.ContentTransformer; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.annotation.Mock; +import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import rx.Observable; @@ -52,6 +53,7 @@ */ @RunWith(PowerMockRunner.class) @PrepareForTest({MethodTemplateExecutor.class}) +@PowerMockIgnore("javax.management.*") public class MethodTemplateExecutorTest { @Mock @@ -69,11 +71,10 @@ public class MethodTemplateExecutorTest { @Mock private HttpResourceGroup httpResourceGroupMock = createMock(HttpResourceGroup.class); - @Mock - private EvCacheProviderPool evCacheProviderPoolMock = createMock(EvCacheProviderPool.class); - - @Mock - private EvCacheProvider evCacheProviderMock = createMock(EvCacheProvider.class); + @BeforeClass + public static void setup() { + RibbonDynamicProxy.registerAnnotationProcessors(AnnotationProcessorsProvider.DEFAULT); + } @Before public void setUp() throws Exception { @@ -96,8 +97,6 @@ public void testGetQueryWithDomainObjectResult() throws Exception { expect(httpRequestTemplateBuilderMock.withFallbackProvider(anyObject(MovieFallbackHandler.class))).andReturn(httpRequestTemplateBuilderMock); expect(httpRequestTemplateBuilderMock.withResponseValidator(anyObject(SampleHttpResponseValidator.class))).andReturn(httpRequestTemplateBuilderMock); expect(httpRequestTemplateBuilderMock.withCacheProvider(anyObject(String.class), anyObject(CacheProvider.class))).andReturn(httpRequestTemplateBuilderMock); - expect(httpRequestTemplateBuilderMock.withCacheProvider(anyObject(String.class), anyObject(EvCacheProvider.class))).andReturn(httpRequestTemplateBuilderMock); - expect(evCacheProviderPoolMock.getMatching(anyObject(EvCacheOptions.class))).andReturn(evCacheProviderMock); replayAll(); @@ -108,7 +107,6 @@ public void testGetQueryWithDomainObjectResult() throws Exception { assertEquals(ribbonRequestMock, ribbonRequest); } - @Test public void testGetQueryWithByteBufResult() throws Exception { expectUrlBase("GET", "/rawMovies/{id}"); @@ -166,16 +164,12 @@ private void doTestPostWith(String uriTemplate, String methodName, Object conten @Test public void testFromFactory() throws Exception { - mockStatic(EvCacheProviderPool.class); - expectNew(EvCacheProviderPool.class, new Class[]{MethodTemplate[].class}, anyObject(MethodTemplate[].class)).andReturn(evCacheProviderPoolMock); - expect(evCacheProviderPoolMock.getMatching(anyObject(EvCacheOptions.class))).andReturn(evCacheProviderMock).anyTimes(); - expect(httpResourceGroupMock.newTemplateBuilder(anyObject(String.class))).andReturn(httpRequestTemplateBuilderMock).anyTimes(); expect(httpRequestTemplateBuilderMock.withMethod(anyObject(String.class))).andReturn(httpRequestTemplateBuilderMock).anyTimes(); expect(httpRequestTemplateBuilderMock.withUriTemplate(anyObject(String.class))).andReturn(httpRequestTemplateBuilderMock).anyTimes(); replayAll(); - Map executorMap = MethodTemplateExecutor.from(httpResourceGroupMock, ShortMovieService.class); + Map executorMap = MethodTemplateExecutor.from(httpResourceGroupMock, ShortMovieService.class, AnnotationProcessorsProvider.DEFAULT); assertEquals(ShortMovieService.class.getMethods().length, executorMap.size()); } @@ -187,6 +181,6 @@ private void expectUrlBase(String method, String path) { private MethodTemplateExecutor createExecutor(Class clientInterface, String methodName) { MethodTemplate methodTemplate = new MethodTemplate(methodByName(clientInterface, methodName)); - return new MethodTemplateExecutor(httpResourceGroupMock, methodTemplate, evCacheProviderPoolMock); + return new MethodTemplateExecutor(httpResourceGroupMock, methodTemplate, AnnotationProcessorsProvider.DEFAULT); } -} \ No newline at end of file +} diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java index f2b863c5..eaa74b09 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java @@ -15,21 +15,16 @@ */ package com.netflix.ribbon.proxy; -import com.netflix.ribbon.evache.EvCacheOptions; -import com.netflix.ribbon.proxy.MethodTemplate.CacheProviderEntry; -import com.netflix.ribbon.proxy.sample.EvCacheClasses.SampleEVCacheTranscoder; import com.netflix.ribbon.proxy.sample.Movie; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.BrokenMovieService; -import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.HystrixOptionalAnnotationValues; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.PostsWithDifferentContentTypes; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieService; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.TemplateNameDerivedFromMethodName; import com.netflix.ribbon.proxy.sample.MovieTransformer; -import com.netflix.ribbon.proxy.sample.SampleCacheProviderFactory.SampleCacheProvider; import io.netty.buffer.ByteBuf; import org.junit.Test; -import static com.netflix.ribbon.proxy.Utils.*; +import static com.netflix.ribbon.proxy.Utils.methodByName; import static org.junit.Assert.*; /** @@ -43,30 +38,10 @@ public void testGetWithOneParameter() throws Exception { assertEquals("id", template.getParamName(0)); assertEquals("findMovieById", template.getTemplateName()); - assertEquals("/movies/{id}", template.getUriTemplate()); - assertTrue("value1.1".equals(template.getHeaders().get("X-MyHeader1").get(0))); - assertTrue("value1.2".equals(template.getHeaders().get("X-MyHeader1").get(1))); - assertTrue("value2".equals(template.getHeaders().get("X-MyHeader2").get(0))); assertEquals(0, template.getParamPosition(0)); assertEquals(template.getResultType(), ByteBuf.class); - - assertEquals("findMovieById/{id}", template.getHystrixCacheKey()); - assertNotNull(template.getHystrixFallbackHandler()); - assertNotNull(template.getHystrixResponseValidator()); - - CacheProviderEntry cacheProviderEntry = template.getCacheProviders().get(0); - assertNotNull(cacheProviderEntry); - assertTrue(cacheProviderEntry.getCacheProvider() instanceof SampleCacheProvider); - - EvCacheOptions evOpts = template.getEvCacheOptions(); - assertNotNull(evOpts); - assertEquals("movie-cache", evOpts.getCacheName()); - assertEquals("movieService", evOpts.getAppName()); - assertEquals("movie-{id}", evOpts.getCacheKeyTemplate()); - assertEquals(50, evOpts.getTimeToLive()); - assertTrue(evOpts.getTranscoder() instanceof SampleEVCacheTranscoder); } @Test @@ -74,7 +49,6 @@ public void testGetWithTwoParameters() throws Exception { MethodTemplate template = new MethodTemplate(methodByName(SampleMovieService.class, "findMovie")); assertEquals("findMovie", template.getTemplateName()); - assertEquals("/movies?name={name}&author={author}", template.getUriTemplate()); assertEquals("name", template.getParamName(0)); assertEquals(0, template.getParamPosition(0)); assertEquals("author", template.getParamName(1)); @@ -87,24 +61,6 @@ public void testTemplateNameCanBeDerivedFromMethodName() throws Exception { assertEquals("myTemplateName", template.getTemplateName()); } - @Test - public void testHystrixOptionalParameters() throws Exception { - MethodTemplate template = new MethodTemplate(methodByName(HystrixOptionalAnnotationValues.class, "hystrixWithCacheKeyOnly")); - assertNotNull(template.getHystrixCacheKey()); - assertNull(template.getHystrixResponseValidator()); - assertNull(template.getHystrixFallbackHandler()); - - template = new MethodTemplate(methodByName(HystrixOptionalAnnotationValues.class, "hystrixWithValidatorOnly")); - assertNull(template.getHystrixCacheKey()); - assertNotNull(template.getHystrixResponseValidator()); - assertNull(template.getHystrixFallbackHandler()); - - template = new MethodTemplate(methodByName(HystrixOptionalAnnotationValues.class, "hystrixWithFallbackHandlerOnly")); - assertNull(template.getHystrixCacheKey()); - assertNull(template.getHystrixResponseValidator()); - assertNotNull(template.getHystrixFallbackHandler()); - } - @Test public void testWithRawContentSourceContent() throws Exception { MethodTemplate methodTemplate = new MethodTemplate(methodByName(PostsWithDifferentContentTypes.class, "postwithRawContentSource")); diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/RibbonDynamicProxyTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/RibbonDynamicProxyTest.java index 9a8bc340..c8bebe5f 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/RibbonDynamicProxyTest.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/RibbonDynamicProxyTest.java @@ -15,11 +15,10 @@ */ package com.netflix.ribbon.proxy; -import com.netflix.client.config.IClientConfig; import com.netflix.ribbon.RibbonRequest; import com.netflix.ribbon.RibbonResourceFactory; -import com.netflix.ribbon.RibbonTransportFactory; import com.netflix.ribbon.http.HttpResourceGroup; +import com.netflix.ribbon.proxy.processor.AnnotationProcessorsProvider; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieService; import com.netflix.ribbon.proxy.sample.MovieServiceInterfaces.SampleMovieServiceWithResourceGroupNameAnnotation; import io.netty.buffer.ByteBuf; @@ -81,8 +80,8 @@ public void testSetupWithExplicitResourceGroupObject() throws Exception { public void testSetupWithResourceGroupNameInAnnotation() throws Exception { mockStatic(ProxyHttpResourceGroupFactory.class); expectNew(ProxyHttpResourceGroupFactory.class, new Class[]{ClassTemplate.class, - RibbonResourceFactory.class, IClientConfig.class, - RibbonTransportFactory.class}, anyObject(), anyObject(), anyObject(), anyObject()).andReturn(httpResourceGroupFactoryMock); + RibbonResourceFactory.class, AnnotationProcessorsProvider.class + }, anyObject(), anyObject(), anyObject()).andReturn(httpResourceGroupFactoryMock); replayAll(); @@ -119,6 +118,6 @@ private void initializeSampleMovieServiceMocks() { tgMap.put(methodByName(SampleMovieService.class, "findMovieById"), tgMock); mockStatic(MethodTemplateExecutor.class); - expect(MethodTemplateExecutor.from(httpResourceGroupMock, SampleMovieService.class)).andReturn(tgMap); + expect(MethodTemplateExecutor.from(httpResourceGroupMock, SampleMovieService.class, AnnotationProcessorsProvider.DEFAULT)).andReturn(tgMap); } } diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java index 33d722be..c82b3796 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java @@ -2,9 +2,10 @@ import com.netflix.ribbon.RibbonRequest; import com.netflix.ribbon.proxy.annotation.CacheProvider; +import com.netflix.ribbon.proxy.annotation.ClientProperties; +import com.netflix.ribbon.proxy.annotation.ClientProperties.Property; import com.netflix.ribbon.proxy.annotation.Content; import com.netflix.ribbon.proxy.annotation.ContentTransformerClass; -import com.netflix.ribbon.proxy.annotation.EvCache; import com.netflix.ribbon.proxy.annotation.Http; import com.netflix.ribbon.proxy.annotation.Http.Header; import com.netflix.ribbon.proxy.annotation.Http.HttpMethod; @@ -12,7 +13,6 @@ import com.netflix.ribbon.proxy.annotation.ResourceGroup; import com.netflix.ribbon.proxy.annotation.TemplateName; import com.netflix.ribbon.proxy.annotation.Var; -import com.netflix.ribbon.proxy.sample.EvCacheClasses.SampleEVCacheTranscoder; import com.netflix.ribbon.proxy.sample.HystrixHandlers.MovieFallbackHandler; import com.netflix.ribbon.proxy.sample.HystrixHandlers.SampleHttpResponseValidator; import io.netty.buffer.ByteBuf; @@ -20,13 +20,18 @@ import java.util.concurrent.atomic.AtomicReference; -import static com.netflix.ribbon.proxy.sample.ResourceGroupClasses.*; +import static com.netflix.ribbon.proxy.sample.ResourceGroupClasses.SampleHttpResourceGroup; /** * @author Tomasz Bak */ public class MovieServiceInterfaces { + @ClientProperties(properties = { + @Property(name="ReadTimeout", value="2000"), + @Property(name="ConnectTimeout", value="1000"), + @Property(name="MaxAutoRetriesNextServer", value="2") + }, exportToArchaius = true) public static interface SampleMovieService { @TemplateName("findMovieById") @@ -43,8 +48,6 @@ public static interface SampleMovieService { validator = SampleHttpResponseValidator.class, fallbackHandler = MovieFallbackHandler.class) @CacheProvider(key = "findMovieById_{id}", provider = SampleCacheProviderFactory.class) - @EvCache(name = "movie-cache", appName = "movieService", key = "movie-{id}", ttl = 50, - enableZoneFallback = true, transcoder = SampleEVCacheTranscoder.class) RibbonRequest findMovieById(@Var("id") String id); @TemplateName("findRawMovieById") diff --git a/settings.gradle b/settings.gradle index 9f5e0aaa..5ed29661 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ rootProject.name='ribbon' -include 'ribbon-core', 'ribbon-loadbalancer', 'ribbon-httpclient', 'ribbon-eureka', 'ribbon-transport', 'ribbon-examples', 'ribbon-test', 'ribbon-guice', 'ribbon' +include 'ribbon-core', 'ribbon-loadbalancer', 'ribbon-httpclient', 'ribbon-eureka', 'ribbon-transport', 'ribbon-examples', 'ribbon-test', 'ribbon-guice', 'ribbon', 'ribbon-evcache'