提交 6a47039b 编写于 作者: T Tomasz Bak

Make updates after code review plus a few other improvements:

A few questions after looking at MovieServiceInterfaces:
- Hystrix and external cache providers may supply different key templates. How is that handled in annotation?
- How do we handle multiple cache providers in annotation?
- for registerMovieRaw(@Content RawContentSource<Movie> rawMovieContent), @ContentTransformerClass is not required, right?
- How is header handled in annotation?
- Add more HttpMethod (PUT, DELETE, PATCH)
上级 496001e6
/*
* 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.ribbonclientextensions;
public interface CacheProviderFactory<T> {
CacheProvider<T> createCacheProvider();
}
......@@ -15,13 +15,17 @@
*/
package com.netflix.ribbonclientextensions.typedclient;
import com.netflix.ribbonclientextensions.CacheProvider;
import com.netflix.ribbonclientextensions.CacheProviderFactory;
import com.netflix.ribbonclientextensions.RibbonRequest;
import com.netflix.ribbonclientextensions.http.HttpResponseValidator;
import com.netflix.ribbonclientextensions.hystrix.FallbackHandler;
import com.netflix.ribbonclientextensions.typedclient.annotation.Cache;
import com.netflix.ribbonclientextensions.typedclient.annotation.CacheProviders;
import com.netflix.ribbonclientextensions.typedclient.annotation.CacheProviders.Provider;
import com.netflix.ribbonclientextensions.typedclient.annotation.Content;
import com.netflix.ribbonclientextensions.typedclient.annotation.ContentTransformerClass;
import com.netflix.ribbonclientextensions.typedclient.annotation.Http;
import com.netflix.ribbonclientextensions.typedclient.annotation.Http.Header;
import com.netflix.ribbonclientextensions.typedclient.annotation.Http.HttpMethod;
import com.netflix.ribbonclientextensions.typedclient.annotation.Hystrix;
import com.netflix.ribbonclientextensions.typedclient.annotation.TemplateName;
......@@ -32,7 +36,10 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
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.*;
......@@ -50,14 +57,16 @@ public class MethodTemplate {
private final String templateName;
private final Http.HttpMethod httpMethod;
private final String path;
private final Map<String, String> headers;
private final String[] paramNames;
private final int[] valueIdxs;
private final int contentArgPosition;
private final Class<? extends ContentTransformer<?>> contentTansformerClass;
private final Class<?> resultType;
private final String hystrixCacheKey;
private final FallbackHandler<?> hystrixFallbackHandler;
private final HttpResponseValidator hystrixResponseValidator;
private final String cacheKey;
private final Map<String, CacheProvider<?>> cacheProviders;
public MethodTemplate(Method method) {
this.method = method;
......@@ -65,14 +74,16 @@ public class MethodTemplate {
templateName = values.templateName;
httpMethod = values.httpMethod;
path = values.path;
headers = Collections.unmodifiableMap(values.headers);
paramNames = values.paramNames;
valueIdxs = values.valueIdxs;
contentArgPosition = values.contentArgPosition;
contentTansformerClass = values.contentTansformerClass;
resultType = values.resultType;
hystrixCacheKey = values.hystrixCacheKey;
hystrixFallbackHandler = values.hystrixFallbackHandler;
hystrixResponseValidator = values.hystrixResponseValidator;
cacheKey = values.cacheKey;
cacheProviders = Collections.unmodifiableMap(values.cacheProviders);
}
public HttpMethod getHttpMethod() {
......@@ -91,6 +102,10 @@ public class MethodTemplate {
return path;
}
public Map<String, String> getHeaders() {
return headers;
}
public String getParamName(int idx) {
return paramNames[idx];
}
......@@ -115,6 +130,10 @@ public class MethodTemplate {
return resultType;
}
public String getHystrixCacheKey() {
return hystrixCacheKey;
}
public FallbackHandler<?> getHystrixFallbackHandler() {
return hystrixFallbackHandler;
}
......@@ -123,8 +142,8 @@ public class MethodTemplate {
return hystrixResponseValidator;
}
public String getCacheKey() {
return cacheKey;
public Map<String, CacheProvider<?>> getCacheProviders() {
return cacheProviders;
}
public static <T> MethodTemplate[] from(Class<T> clientInterface) {
......@@ -146,9 +165,11 @@ public class MethodTemplate {
private int contentArgPosition;
private Class<? extends ContentTransformer<?>> contentTansformerClass;
private Class<?> resultType;
public String hystrixCacheKey;
private FallbackHandler<?> hystrixFallbackHandler;
private HttpResponseValidator hystrixResponseValidator;
public String cacheKey;
public final Map<String, String> headers = new HashMap<String, String>();
private final Map<String, CacheProvider<?>> cacheProviders = new HashMap<String, CacheProvider<?>>();
private MethodAnnotationValues(Method method) {
this.method = method;
......@@ -159,13 +180,17 @@ public class MethodTemplate {
extractContentTransformerClass();
extractResultType();
extractHystrixHandlers();
extractCacheKey();
extractCacheProviders();
}
private void extractCacheKey() {
Cache annotation = method.getAnnotation(Cache.class);
private void extractCacheProviders() {
CacheProviders annotation = method.getAnnotation(CacheProviders.class);
if (annotation != null) {
cacheKey = annotation.key();
for (Provider provider : annotation.value()) {
Class<? extends CacheProviderFactory<?>> providerClass = provider.provider();
CacheProviderFactory<?> factory = Utils.newInstance(providerClass);
cacheProviders.put(provider.key(), factory.createCacheProvider());
}
}
}
......@@ -174,8 +199,16 @@ public class MethodTemplate {
if (annotation == null) {
return;
}
hystrixFallbackHandler = Utils.newInstance(annotation.fallbackHandler());
hystrixResponseValidator = Utils.newInstance(annotation.validator());
String cacheKey = annotation.cacheKey().trim();
if (!cacheKey.isEmpty()) {
hystrixCacheKey = cacheKey;
}
if (!Hystrix.UndefFallbackHandler.class.equals(annotation.fallbackHandler())) {
hystrixFallbackHandler = Utils.newInstance(annotation.fallbackHandler());
}
if (!Hystrix.UndefHttpResponseValidator.class.equals(annotation.validator())) {
hystrixResponseValidator = Utils.newInstance(annotation.validator());
}
}
private void extractHttpAnnotation() {
......@@ -187,6 +220,9 @@ public class MethodTemplate {
}
httpMethod = annotation.method();
path = annotation.path();
for (Header h : annotation.headers()) {
headers.put(h.name(), h.value());
}
}
private void extractParamNamesWithIndexes() {
......
......@@ -15,6 +15,7 @@
*/
package com.netflix.ribbonclientextensions.typedclient;
import com.netflix.ribbonclientextensions.CacheProvider;
import com.netflix.ribbonclientextensions.RibbonRequest;
import com.netflix.ribbonclientextensions.http.HttpRequestBuilder;
import com.netflix.ribbonclientextensions.http.HttpRequestTemplate;
......@@ -44,19 +45,18 @@ public class MethodTemplateExecutor {
private static final StringTransformer STRING_TRANSFORMER = new StringTransformer();
private final HttpResourceGroup httpResourceGroup;
private final MethodTemplate methodTemplate;
private final HttpRequestTemplate<?> httpRequestTemplate;
public MethodTemplateExecutor(MethodTemplate methodTemplate) {
public MethodTemplateExecutor(HttpResourceGroup httpResourceGroup, MethodTemplate methodTemplate) {
this.httpResourceGroup = httpResourceGroup;
this.methodTemplate = methodTemplate;
httpRequestTemplate = createHttpRequestTemplate();
}
@SuppressWarnings("unchecked")
public <O> RibbonRequest<O> executeFromTemplate(HttpResourceGroup httpResourceGroup, Object[] args) {
HttpRequestTemplate<?> httpRequestTemplate = createBaseHttpRequestTemplate(httpResourceGroup);
withRequestUriBase(httpRequestTemplate);
withHystrixHandlers(httpRequestTemplate);
withCache(httpRequestTemplate);
public <O> RibbonRequest<O> executeFromTemplate(Object[] args) {
HttpRequestBuilder<?> requestBuilder = httpRequestTemplate.requestBuilder();
withParameters(requestBuilder, args);
withContent(requestBuilder, args);
......@@ -64,6 +64,15 @@ public class MethodTemplateExecutor {
return (RibbonRequest<O>) requestBuilder.build();
}
private HttpRequestTemplate<?> createHttpRequestTemplate() {
HttpRequestTemplate<?> httpRequestTemplate = createBaseHttpRequestTemplate(httpResourceGroup);
withRequestUriBase(httpRequestTemplate);
withHttpHeaders(httpRequestTemplate);
withHystrixHandlers(httpRequestTemplate);
withCacheProviders(httpRequestTemplate);
return httpRequestTemplate;
}
private HttpRequestTemplate<?> createBaseHttpRequestTemplate(HttpResourceGroup httpResourceGroup) {
HttpRequestTemplate<?> httpRequestTemplate;
if (ByteBuf.class.isAssignableFrom(methodTemplate.getResultType())) {
......@@ -82,17 +91,29 @@ public class MethodTemplateExecutor {
}
}
private void withHttpHeaders(HttpRequestTemplate<?> httpRequestTemplate) {
for (Map.Entry<String, String> header : methodTemplate.getHeaders().entrySet()) {
httpRequestTemplate.withHeader(header.getKey(), header.getValue());
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void withHystrixHandlers(HttpRequestTemplate httpRequestTemplate) {
if(methodTemplate.getHystrixFallbackHandler() != null) {
if (methodTemplate.getHystrixFallbackHandler() != null) {
httpRequestTemplate.withFallbackProvider(methodTemplate.getHystrixFallbackHandler());
httpRequestTemplate.withResponseValidator(methodTemplate.getHystrixResponseValidator());
if (methodTemplate.getHystrixCacheKey() != null) {
httpRequestTemplate.withRequestCacheKey(methodTemplate.getHystrixCacheKey());
}
}
}
private void withCache(HttpRequestTemplate<?> httpRequestTemplate) {
if(methodTemplate.getCacheKey() != null) {
httpRequestTemplate.withRequestCacheKey(methodTemplate.getCacheKey());
@SuppressWarnings({"rawtypes", "unchecked"})
private void withCacheProviders(HttpRequestTemplate<?> httpRequestTemplate) {
if (methodTemplate.getCacheProviders() != null) {
for (Map.Entry<String, CacheProvider<?>> entry : methodTemplate.getCacheProviders().entrySet()) {
httpRequestTemplate.addCacheProvider(entry.getKey(), (CacheProvider) entry.getValue());
}
}
}
......@@ -123,10 +144,10 @@ public class MethodTemplateExecutor {
}
}
public static Map<Method, MethodTemplateExecutor> from(Class<?> clientInterface) {
public static Map<Method, MethodTemplateExecutor> from(HttpResourceGroup httpResourceGroup, Class<?> clientInterface) {
Map<Method, MethodTemplateExecutor> tgm = new HashMap<Method, MethodTemplateExecutor>();
for (MethodTemplate mt : MethodTemplate.from(clientInterface)) {
tgm.put(mt.getMethod(), new MethodTemplateExecutor(mt));
tgm.put(mt.getMethod(), new MethodTemplateExecutor(httpResourceGroup, mt));
}
return tgm;
}
......
......@@ -27,24 +27,20 @@ import java.util.Map;
*/
public class RibbonDynamicProxy<T> implements InvocationHandler {
private final Map<Method, MethodTemplateExecutor> templateGeneratorMap;
private final HttpResourceGroup httpResourceGroup;
private final ClassTemplate<T> classTemplate;
public RibbonDynamicProxy(Class<T> clientInterface, HttpResourceGroup httpResourceGroup) {
classTemplate = ClassTemplate.from(clientInterface);
ClassTemplate<T> classTemplate = ClassTemplate.from(clientInterface);
if (httpResourceGroup == null) {
this.httpResourceGroup = new HttpResourceGroupFactory<T>(classTemplate).createResourceGroup();
} else {
this.httpResourceGroup = httpResourceGroup;
httpResourceGroup = new HttpResourceGroupFactory<T>(classTemplate).createResourceGroup();
}
templateGeneratorMap = MethodTemplateExecutor.from(clientInterface);
templateGeneratorMap = MethodTemplateExecutor.from(httpResourceGroup, clientInterface);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodTemplateExecutor template = templateGeneratorMap.get(method);
if (template != null) {
return template.executeFromTemplate(httpResourceGroup, args);
return template.executeFromTemplate(args);
}
// This must be one of the Object methods. Lets run it on the handler itself.
return Utils.executeOnInstance(this, method, args);
......
......@@ -15,6 +15,8 @@
*/
package com.netflix.ribbonclientextensions.typedclient.annotation;
import com.netflix.ribbonclientextensions.CacheProviderFactory;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -22,6 +24,13 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
String key();
public @interface CacheProviders {
Provider[] value() default {};
@interface Provider {
String key();
Class<? extends CacheProviderFactory<?>> provider();
}
}
......@@ -28,11 +28,22 @@ import java.lang.annotation.Target;
public @interface Http {
enum HttpMethod {
DELETE,
GET,
POST
POST,
PATCH,
PUT
}
HttpMethod method();
String path() default "";
Header[] headers() default {};
@interface Header {
String name();
String value();
}
}
......@@ -15,17 +15,42 @@
*/
package com.netflix.ribbonclientextensions.typedclient.annotation;
import com.netflix.hystrix.HystrixExecutableInfo;
import com.netflix.ribbonclientextensions.http.HttpResponseValidator;
import com.netflix.ribbonclientextensions.hystrix.FallbackHandler;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import rx.Observable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hystrix {
Class<? extends FallbackHandler<?>> fallbackHandler();
Class<? extends HttpResponseValidator> validator();
String cacheKey() default "";
Class<? extends FallbackHandler<?>> fallbackHandler() default UndefFallbackHandler.class;
Class<? extends HttpResponseValidator> validator() default UndefHttpResponseValidator.class;
/**
* Since null is not allowed as a default value in annotation, we need this marker class.
*/
final class UndefFallbackHandler implements FallbackHandler<Object> {
@Override
public Observable<Object> getFallback(HystrixExecutableInfo<?> hystrixInfo, Map<String, Object> requestProperties) {
return null;
}
}
final class UndefHttpResponseValidator implements HttpResponseValidator {
@Override
public void validate(HttpClientResponse<ByteBuf> response) {
}
}
}
package com.netflix.ribbonclientextensions.typedclient;
import com.netflix.ribbonclientextensions.CacheProvider;
import com.netflix.ribbonclientextensions.RibbonRequest;
import com.netflix.ribbonclientextensions.http.HttpRequestBuilder;
import com.netflix.ribbonclientextensions.http.HttpRequestTemplate;
......@@ -8,6 +9,7 @@ import com.netflix.ribbonclientextensions.typedclient.sample.HystrixHandlers.Mov
import com.netflix.ribbonclientextensions.typedclient.sample.HystrixHandlers.SampleHttpResponseValidator;
import com.netflix.ribbonclientextensions.typedclient.sample.Movie;
import com.netflix.ribbonclientextensions.typedclient.sample.MovieServiceInterfaces.SampleMovieService;
import com.netflix.ribbonclientextensions.typedclient.sample.MovieServiceInterfaces.ShortMovieService;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.client.RawContentSource;
import org.junit.Before;
......@@ -56,14 +58,19 @@ public class MethodTemplateExecutorTest {
expect(requestBuilderMock.withRequestProperty("id", "id123")).andReturn(requestBuilderMock);
expect(httpResourceGroupMock.newRequestTemplate("findMovieById", Movie.class)).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.withHeader("X-MyHeader1", "value1")).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.withHeader("X-MyHeader2", "value2")).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.withRequestCacheKey("findMovieById/{id}")).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.withFallbackProvider(anyObject(MovieFallbackHandler.class))).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.withResponseValidator(anyObject(SampleHttpResponseValidator.class))).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.withRequestCacheKey("movie.{id}")).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.addCacheProvider(anyObject(String.class), anyObject(CacheProvider.class))).andReturn(httpRequestTemplateMock);
replayAll();
MethodTemplateExecutor executor = createExecutor(SampleMovieService.class, "findMovieById");
RibbonRequest ribbonRequest = executor.executeFromTemplate(httpResourceGroupMock, new Object[]{"id123"});
RibbonRequest ribbonRequest = executor.executeFromTemplate(new Object[]{"id123"});
verifyAll();
assertEquals(ribbonRequestMock, ribbonRequest);
}
......@@ -74,12 +81,13 @@ public class MethodTemplateExecutorTest {
expect(requestBuilderMock.withRequestProperty("id", "id123")).andReturn(requestBuilderMock);
expect(httpResourceGroupMock.newRequestTemplate("findRawMovieById")).andReturn(httpRequestTemplateMock);
expect(httpRequestTemplateMock.withRequestCacheKey("movie.{id}")).andReturn(httpRequestTemplateMock);
replayAll();
MethodTemplateExecutor executor = createExecutor(SampleMovieService.class, "findRawMovieById");
RibbonRequest ribbonRequest = executor.executeFromTemplate(httpResourceGroupMock, new Object[]{"id123"});
RibbonRequest ribbonRequest = executor.executeFromTemplate(new Object[]{"id123"});
verifyAll();
assertEquals(ribbonRequestMock, ribbonRequest);
}
......@@ -113,16 +121,23 @@ public class MethodTemplateExecutorTest {
replayAll();
MethodTemplateExecutor executor = createExecutor(SampleMovieService.class, methodName);
RibbonRequest ribbonRequest = executor.executeFromTemplate(httpResourceGroupMock, new Object[]{contentObject});
RibbonRequest ribbonRequest = executor.executeFromTemplate(new Object[]{contentObject});
verifyAll();
assertEquals(ribbonRequestMock, ribbonRequest);
}
@Test
public void testFromFactory() throws Exception {
Map<Method, MethodTemplateExecutor> executorMap = MethodTemplateExecutor.from(SampleMovieService.class);
expect(httpResourceGroupMock.newRequestTemplate(anyObject(String.class))).andReturn(httpRequestTemplateMock).anyTimes();
expect(httpRequestTemplateMock.withMethod(anyObject(String.class))).andReturn(httpRequestTemplateMock).anyTimes();
expect(httpRequestTemplateMock.withUriTemplate(anyObject(String.class))).andReturn(httpRequestTemplateMock).anyTimes();
replayAll();
Map<Method, MethodTemplateExecutor> executorMap = MethodTemplateExecutor.from(httpResourceGroupMock, ShortMovieService.class);
assertEquals(SampleMovieService.class.getMethods().length, executorMap.size());
assertEquals(ShortMovieService.class.getMethods().length, executorMap.size());
}
private void expectUrlBase(String method, String path) {
......@@ -132,6 +147,6 @@ public class MethodTemplateExecutorTest {
private MethodTemplateExecutor createExecutor(Class<?> clientInterface, String methodName) {
MethodTemplate methodTemplate = new MethodTemplate(methodByName(clientInterface, methodName));
return new MethodTemplateExecutor(methodTemplate);
return new MethodTemplateExecutor(httpResourceGroupMock, methodTemplate);
}
}
\ No newline at end of file
package com.netflix.ribbonclientextensions.typedclient;
import com.netflix.ribbonclientextensions.CacheProvider;
import com.netflix.ribbonclientextensions.typedclient.sample.Movie;
import com.netflix.ribbonclientextensions.typedclient.sample.MovieServiceInterfaces.BrokenMovieService;
import com.netflix.ribbonclientextensions.typedclient.sample.MovieServiceInterfaces.HystrixOptionalAnnotationValues;
import com.netflix.ribbonclientextensions.typedclient.sample.MovieServiceInterfaces.SampleMovieService;
import com.netflix.ribbonclientextensions.typedclient.sample.SampleCacheProviderFactory.SampleCacheProvider;
import org.junit.Test;
import static com.netflix.ribbonclientextensions.typedclient.Utils.*;
......@@ -17,14 +20,23 @@ public class MethodTemplateTest {
public void testGetWithOneParameter() throws Exception {
MethodTemplate template = new MethodTemplate(methodByName(SampleMovieService.class, "findMovieById"));
assertEquals("id", template.getParamName(0));
assertEquals("findMovieById", template.getTemplateName());
assertEquals("/movies/{id}", template.getPath());
assertEquals("id", template.getParamName(0));
assertTrue("value1".equals(template.getHeaders().get("X-MyHeader1")));
assertTrue("value2".equals(template.getHeaders().get("X-MyHeader2")));
assertEquals(0, template.getParamPosition(0));
assertEquals(template.getResultType(), Movie.class);
assertEquals("findMovieById/{id}", template.getHystrixCacheKey());
assertNotNull(template.getHystrixFallbackHandler());
assertNotNull(template.getHystrixResponseValidator());
assertEquals(template.getResultType(), Movie.class);
assertEquals("movie.{id}", template.getCacheKey());
CacheProvider cacheProvider = template.getCacheProviders().get("findMovieById_{id}");
assertNotNull(cacheProvider);
assertTrue(cacheProvider instanceof SampleCacheProvider);
}
@Test
......@@ -39,6 +51,24 @@ public class MethodTemplateTest {
assertEquals(1, template.getParamPosition(1));
}
@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 testFromFactory() throws Exception {
MethodTemplate[] methodTemplates = MethodTemplate.from(SampleMovieService.class);
......
......@@ -5,7 +5,7 @@ import com.netflix.ribbonclientextensions.http.HttpResourceGroup;
import com.netflix.ribbonclientextensions.typedclient.sample.Movie;
import com.netflix.ribbonclientextensions.typedclient.sample.MovieServiceInterfaces.SampleMovieService;
import com.netflix.ribbonclientextensions.typedclient.sample.MovieServiceInterfaces.SampleMovieServiceWithResourceGroupNameAnnotation;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
......@@ -19,7 +19,6 @@ import static com.netflix.ribbonclientextensions.typedclient.Utils.*;
import static org.easymock.EasyMock.*;
import static org.powermock.api.easymock.PowerMock.createMock;
import static org.powermock.api.easymock.PowerMock.*;
import static org.powermock.api.easymock.PowerMock.replay;
import static org.testng.Assert.*;
/**
......@@ -29,6 +28,17 @@ import static org.testng.Assert.*;
@PrepareForTest({RibbonDynamicProxy.class, MethodTemplateExecutor.class})
public class RibbonDynamicProxyTest {
private HttpResourceGroup httpResourceGroupMock = createMock(HttpResourceGroup.class);
private HttpResourceGroupFactory httpResourceGroupFactoryMock = createMock(HttpResourceGroupFactory.class);
private RibbonRequest ribbonRequestMock = createMock(RibbonRequest.class);
@Before
public void setUp() throws Exception {
expect(httpResourceGroupFactoryMock.createResourceGroup()).andReturn(httpResourceGroupMock);
}
@Test(expected = IllegalArgumentException.class)
public void testAcceptsInterfaceOnly() throws Exception {
RibbonDynamicProxy.newInstance(Object.class, null);
......@@ -36,46 +46,27 @@ public class RibbonDynamicProxyTest {
@Test
public void testSetupWithExplicitResourceGroupObject() throws Exception {
HttpResourceGroup httpResourceGroupMock = createMock(HttpResourceGroup.class);
replay(httpResourceGroupMock);
replayAll();
RibbonDynamicProxy.newInstance(SampleMovieServiceWithResourceGroupNameAnnotation.class, httpResourceGroupMock);
}
@Test
public void testSetupWithResourceGroupNameInAnnotation() throws Exception {
HttpResourceGroup httpResourceGroupMock = createMock(HttpResourceGroup.class);
HttpResourceGroupFactory httpResourceGroupFactoryMock = createMock(HttpResourceGroupFactory.class);
expect(httpResourceGroupFactoryMock.createResourceGroup()).andReturn(httpResourceGroupMock);
mockStatic(HttpResourceGroupFactory.class);
expectNew(HttpResourceGroupFactory.class, new Class[]{ClassTemplate.class}, EasyMock.anyObject()).andReturn(httpResourceGroupFactoryMock);
expectNew(HttpResourceGroupFactory.class, new Class[]{ClassTemplate.class}, anyObject()).andReturn(httpResourceGroupFactoryMock);
replay(HttpResourceGroupFactory.class, httpResourceGroupMock, httpResourceGroupFactoryMock);
replayAll();
RibbonDynamicProxy.newInstance(SampleMovieServiceWithResourceGroupNameAnnotation.class, null);
}
@Test
public void testTypedClientGetWithPathParameter() throws Exception {
HttpResourceGroup httpResourceGroup = createMock(HttpResourceGroup.class);
RibbonRequest ribbonRequestMock = createMock(RibbonRequest.class);
MethodTemplateExecutor tgMock = createMock(MethodTemplateExecutor.class);
expect(tgMock.executeFromTemplate((com.netflix.ribbonclientextensions.http.HttpResourceGroup) EasyMock.anyObject(), (Object[]) EasyMock.anyObject())).andReturn(ribbonRequestMock);
Map<Method, MethodTemplateExecutor> tgMap = new HashMap<Method, MethodTemplateExecutor>();
tgMap.put(methodByName(SampleMovieService.class, "findMovieById"), tgMock);
mockStatic(MethodTemplateExecutor.class);
expect(MethodTemplateExecutor.from(SampleMovieService.class)).andReturn(tgMap);
initializeSampleMovieServiceMocks();
replayAll();
replay(MethodTemplateExecutor.class, tgMock, httpResourceGroup, ribbonRequestMock);
SampleMovieService service = RibbonDynamicProxy.newInstance(SampleMovieService.class, httpResourceGroup);
SampleMovieService service = RibbonDynamicProxy.newInstance(SampleMovieService.class, httpResourceGroupMock);
RibbonRequest<Movie> ribbonMovie = service.findMovieById("123");
assertNotNull(ribbonMovie);
......@@ -83,12 +74,23 @@ public class RibbonDynamicProxyTest {
@Test
public void testPlainObjectInvocations() throws Exception {
HttpResourceGroup httpResourceGroupMock = createMock(HttpResourceGroup.class);
replay(httpResourceGroupMock);
initializeSampleMovieServiceMocks();
replayAll();
SampleMovieService service = RibbonDynamicProxy.newInstance(SampleMovieService.class, httpResourceGroupMock);
assertFalse(service.equals(this));
assertEquals(service.toString(), "RibbonDynamicProxy{...}");
}
private void initializeSampleMovieServiceMocks() {
MethodTemplateExecutor tgMock = createMock(MethodTemplateExecutor.class);
expect(tgMock.executeFromTemplate(anyObject(Object[].class))).andReturn(ribbonRequestMock);
Map<Method, MethodTemplateExecutor> tgMap = new HashMap<Method, MethodTemplateExecutor>();
tgMap.put(methodByName(SampleMovieService.class, "findMovieById"), tgMock);
mockStatic(MethodTemplateExecutor.class);
expect(MethodTemplateExecutor.from(httpResourceGroupMock, SampleMovieService.class)).andReturn(tgMap);
}
}
package com.netflix.ribbonclientextensions.typedclient.sample;
import com.netflix.ribbonclientextensions.RibbonRequest;
import com.netflix.ribbonclientextensions.typedclient.annotation.Cache;
import com.netflix.ribbonclientextensions.typedclient.annotation.CacheProviders;
import com.netflix.ribbonclientextensions.typedclient.annotation.CacheProviders.Provider;
import com.netflix.ribbonclientextensions.typedclient.annotation.Content;
import com.netflix.ribbonclientextensions.typedclient.annotation.ContentTransformerClass;
import com.netflix.ribbonclientextensions.typedclient.annotation.Http;
import com.netflix.ribbonclientextensions.typedclient.annotation.Http.Header;
import com.netflix.ribbonclientextensions.typedclient.annotation.Http.HttpMethod;
import com.netflix.ribbonclientextensions.typedclient.annotation.Hystrix;
import com.netflix.ribbonclientextensions.typedclient.annotation.ResourceGroupSpec;
import com.netflix.ribbonclientextensions.typedclient.annotation.TemplateName;
import com.netflix.ribbonclientextensions.typedclient.annotation.Var;
import com.netflix.ribbonclientextensions.typedclient.sample.HystrixHandlers.GenericFallbackHandler;
import com.netflix.ribbonclientextensions.typedclient.sample.HystrixHandlers.MovieFallbackHandler;
import com.netflix.ribbonclientextensions.typedclient.sample.HystrixHandlers.SampleHttpResponseValidator;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.client.RawContentSource;
import static com.netflix.ribbonclientextensions.typedclient.sample.ResourceGroupClasses.SampleHttpResourceGroup;
import static com.netflix.ribbonclientextensions.typedclient.sample.ResourceGroupClasses.*;
/**
* @author Tomasz Bak
*/
public class MovieServiceInterfaces {
@Hystrix(validator = SampleHttpResponseValidator.class, fallbackHandler = GenericFallbackHandler.class)
public static interface SampleMovieService {
@TemplateName("findMovieById")
@Cache(key = "movie.{id}")
@Http(method = HttpMethod.GET, path = "/movies/{id}")
@Hystrix(validator = SampleHttpResponseValidator.class, fallbackHandler = MovieFallbackHandler.class)
@Http(
method = HttpMethod.GET,
path = "/movies/{id}",
headers = {
@Header(name = "X-MyHeader1", value = "value1"),
@Header(name = "X-MyHeader2", value = "value2")
})
@Hystrix(
cacheKey = "findMovieById/{id}",
validator = SampleHttpResponseValidator.class,
fallbackHandler = MovieFallbackHandler.class)
@CacheProviders({@Provider(key = "findMovieById_{id}", provider = SampleCacheProviderFactory.class)})
RibbonRequest<Movie> findMovieById(@Var("id") String id);
@TemplateName("findRawMovieById")
@Cache(key = "movie.{id}")
@Http(method = HttpMethod.GET, path = "/rawMovies/{id}")
RibbonRequest<ByteBuf> findRawMovieById(@Var("id") String id);
@TemplateName("findMovie")
@Cache(key = "movie#name={name},author={author}")
@Http(method = HttpMethod.GET, path = "/movies?name={name}&author={author}")
@Hystrix(validator = SampleHttpResponseValidator.class, fallbackHandler = MovieFallbackHandler.class)
RibbonRequest<Movie> findMovie(@Var("name") String name, @Var("author") String author);
@TemplateName("registerMovie")
......@@ -48,9 +54,14 @@ public class MovieServiceInterfaces {
@ContentTransformerClass(MovieTransformer.class)
RibbonRequest<Void> registerMovie(@Content Movie movie);
@Http(method = HttpMethod.PUT, path = "/movies/{id}")
RibbonRequest<Void> updateMovie(@Var("id") String id, @Content Movie movie);
@Http(method = HttpMethod.PATCH, path = "/movies/{id}")
RibbonRequest<Void> updateMoviePartial(@Var("id") String id, @Content Movie movie);
@TemplateName("registerMovieRaw")
@Http(method = HttpMethod.POST, path = "/movies")
@ContentTransformerClass(MovieTransformer.class)
RibbonRequest<Void> registerMovieRaw(@Content RawContentSource<Movie> rawMovieContent);
@TemplateName("registerTitle")
......@@ -60,26 +71,59 @@ public class MovieServiceInterfaces {
@TemplateName("registerBinary")
@Http(method = HttpMethod.POST, path = "/binaries")
RibbonRequest<Void> registerBinary(@Content ByteBuf binary);
@TemplateName("deleteMovie")
@Http(method = HttpMethod.DELETE, path = "/movies/{id}")
RibbonRequest<Void> deleteMovie(@Var("id") String id);
}
public static interface ShortMovieService {
@TemplateName("findMovieById")
@Http(method = HttpMethod.GET, path = "/movies/{id}")
RibbonRequest<ByteBuf> findMovieById(@Var("id") String id);
@TemplateName("findMovieById")
@Http(method = HttpMethod.GET, path = "/movies")
RibbonRequest<ByteBuf> findAll();
}
@ResourceGroupSpec(name="testResourceGroup")
@ResourceGroupSpec(name = "testResourceGroup")
public static interface SampleMovieServiceWithResourceGroupNameAnnotation {
}
@ResourceGroupSpec(resourceGroupClass=SampleHttpResourceGroup.class)
@ResourceGroupSpec(resourceGroupClass = SampleHttpResourceGroup.class)
public static interface SampleMovieServiceWithResourceGroupClassAnnotation {
}
@ResourceGroupSpec(name="testResourceGroup", resourceGroupClass=SampleHttpResourceGroup.class)
@ResourceGroupSpec(name = "testResourceGroup", resourceGroupClass = SampleHttpResourceGroup.class)
public static interface BrokenMovieServiceWithResourceGroupNameAndClassAnnotation {
}
@ResourceGroupSpec(name = "testResourceGroup")
public static interface HystrixOptionalAnnotationValues {
@TemplateName("hystrix1")
@Http(method = HttpMethod.GET, path = "/hystrix/1")
@Hystrix(cacheKey = "findMovieById/{id}")
RibbonRequest<Void> hystrixWithCacheKeyOnly();
@TemplateName("hystrix2")
@Http(method = HttpMethod.GET, path = "/hystrix/2")
@Hystrix(validator = SampleHttpResponseValidator.class)
RibbonRequest<Void> hystrixWithValidatorOnly();
@TemplateName("hystrix3")
@Http(method = HttpMethod.GET, path = "/hystrix/3")
@Hystrix(fallbackHandler = MovieFallbackHandler.class)
RibbonRequest<Void> hystrixWithFallbackHandlerOnly();
}
public static interface BrokenMovieService {
@Http(method = HttpMethod.GET)
Movie returnTypeNotRibbonRequest();
Movie missingHttpAnnotation();
@Http(method = HttpMethod.GET)
RibbonRequest<Void> multipleContentParameters(@Content Movie content1, @Content Movie content2);
......
/*
* 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.ribbonclientextensions.typedclient.sample;
import com.netflix.ribbonclientextensions.CacheProvider;
import com.netflix.ribbonclientextensions.CacheProviderFactory;
import rx.Observable;
import java.util.Map;
/**
* @author Tomasz Bak
*/
public class SampleCacheProviderFactory implements CacheProviderFactory<Object> {
@Override
public CacheProvider<Object> createCacheProvider() {
return new SampleCacheProvider();
}
public static class SampleCacheProvider implements CacheProvider<Object> {
@Override
public Observable<Object> get(String key, Map requestProperties) {
return null;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册