Skip to content

Commit

Permalink
Merge pull request #157 from allenxwang/pluggable-annotations
Browse files Browse the repository at this point in the history
Fix Issue #156, #149 and #141
  • Loading branch information
Allen Wang committed Aug 25, 2014
2 parents 4bd4aa3 + b3fc37a commit d8bc3b2
Show file tree
Hide file tree
Showing 47 changed files with 838 additions and 480 deletions.
10 changes: 9 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<T> implements IClientConfigKey<T> {

Expand Down Expand Up @@ -214,6 +214,25 @@ public static IClientConfigKey[] values() {
public static Set<IClientConfigKey> 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<T> type;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<GroupBuilder, TemplateBuilder> {

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<CacheId, EvCacheProvider<?>> evCacheProviderPool = new HashMap<CacheId, EvCacheProvider<?>>();

@Override
public void process(String templateName, TemplateBuilder templateBuilder, Method method) {
EvCache annotation = method.getAnnotation(EvCache.class);
if (annotation == null) {
return;
}

Class<? extends EVCacheTranscoder<?>>[] 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) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.netflix.ribbon.proxy.processor.EVCacheAnnotationProcessor
Original file line number Diff line number Diff line change
@@ -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<AnnotationProcessor> processors = annotations.getProcessors();
boolean hasEVCacheProcessor = false;
for (AnnotationProcessor processor: processors) {
Class<?> clazz = processor.getClass();
if (clazz.equals(EVCacheAnnotationProcessor.class)) {
hasEVCacheProcessor = true;
break;
}
}
assertTrue(hasEVCacheProcessor);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ByteBuf> findMovieById(@Var("id") String id);
}
Loading

0 comments on commit d8bc3b2

Please sign in to comment.