From f40f9fe1edafa4adca177e37d4fdd0a3ed1516e9 Mon Sep 17 00:00:00 2001 From: roman_pleshkov Date: Wed, 31 Dec 2014 13:20:44 +0300 Subject: [PATCH 01/15] #380 Caching feature for javanica: initial commit --- hystrix-contrib/hystrix-javanica/build.gradle | 1 + .../javanica/annotation/HystrixCommand.java | 1 + .../CacheKeyInvocationContextFactory.java | 35 +++++++++ .../cache/CacheResultInvocationContext.java | 76 +++++++++++++++++++ .../cache/DefaultCacheKeyGenerator.java | 40 ++++++++++ .../cache/DefaultGeneratedCacheKey.java | 56 ++++++++++++++ .../javanica/cache/HystrixCacheKey.java | 27 +++++++ .../test/spring/cache/Jsr107CacheTest.java | 32 ++++++++ 8 files changed, 268 insertions(+) create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultGeneratedCacheKey.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKey.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java diff --git a/hystrix-contrib/hystrix-javanica/build.gradle b/hystrix-contrib/hystrix-javanica/build.gradle index dea764711..dde39b7ed 100644 --- a/hystrix-contrib/hystrix-javanica/build.gradle +++ b/hystrix-contrib/hystrix-javanica/build.gradle @@ -13,6 +13,7 @@ dependencies { compile 'com.google.guava:guava:15.0' compile 'commons-collections:commons-collections:3.2.1' compile 'org.apache.commons:commons-lang3:3.1' + compile 'javax.cache:cache-api:1.0.0' compile 'com.google.code.findbugs:jsr305:2.0.0' testCompile 'junit:junit-dep:4.10' testCompile 'org.springframework:spring-core:4.0.0.RELEASE' diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java index 92f3231be..7195900b8 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java @@ -91,6 +91,7 @@ * * @return method name or empty string */ + @Deprecated String cacheKeyMethod() default ""; /** diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java new file mode 100644 index 000000000..c2fbde8dd --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheResult; +import java.lang.reflect.Method; + +/** + * // todo + * + * @author dmgcodevil + */ +public class CacheKeyInvocationContextFactory { + + public CacheKeyInvocationContext create(Method method) { + if (method.isAnnotationPresent(CacheResult.class)) { + return new CacheResultInvocationContext(); + } + return null; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java new file mode 100644 index 000000000..6fcafe077 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java @@ -0,0 +1,76 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheResult; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Set; + +/** + * // todo + * + * @author dmgcodevil + */ +public class CacheResultInvocationContext implements CacheKeyInvocationContext { + + @Override + public CacheInvocationParameter[] getKeyParameters() { + return new CacheInvocationParameter[0]; + } + + @Override + public CacheInvocationParameter getValueParameter() { + return null; + } + + @Override + public Object getTarget() { + return null; + } + + @Override + public CacheInvocationParameter[] getAllParameters() { + return new CacheInvocationParameter[0]; + } + + @Override + public T unwrap(Class cls) { + return null; + } + + @Override + public Method getMethod() { + return null; + } + + @Override + public Set getAnnotations() { + return null; + } + + @Override + public CacheResult getCacheAnnotation() { + return null; + } + + @Override + public String getCacheName() { + return null; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java new file mode 100644 index 000000000..a69865bd5 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java @@ -0,0 +1,40 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKeyGenerator; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.GeneratedCacheKey; +import java.lang.annotation.Annotation; + +/** + * // todo + * + * @author dmgcodevil + */ +public class DefaultCacheKeyGenerator implements CacheKeyGenerator { + + @Override + public GeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { + StringBuilder cacheKeyBuilder = new StringBuilder(); + for (CacheInvocationParameter parameter : cacheKeyInvocationContext.getKeyParameters()) { + cacheKeyBuilder.append(parameter.getValue()); + } + return new DefaultGeneratedCacheKey(cacheKeyBuilder.toString()); + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultGeneratedCacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultGeneratedCacheKey.java new file mode 100644 index 000000000..cf3fde762 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultGeneratedCacheKey.java @@ -0,0 +1,56 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import javax.cache.annotation.GeneratedCacheKey; + +import com.google.common.base.Objects; + +/** + * // todo + * + * @author dmgcodevil + */ +public class DefaultGeneratedCacheKey implements GeneratedCacheKey, HystrixCacheKey { + + private String cacheKey; + + public DefaultGeneratedCacheKey(String cacheKey) { + this.cacheKey = cacheKey; + } + + @Override + public String getKey() { + return cacheKey; + } + + @Override + public int hashCode() { + return Objects.hashCode(cacheKey); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + DefaultGeneratedCacheKey that = (DefaultGeneratedCacheKey) o; + + return Objects.equal(this.cacheKey, that.cacheKey); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKey.java new file mode 100644 index 000000000..77fbfbfd6 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKey.java @@ -0,0 +1,27 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +/** + * // todo + * + * @author dmgcodevil + */ +public interface HystrixCacheKey { + + String getKey(); + +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java new file mode 100644 index 000000000..fc2e60a33 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java @@ -0,0 +1,32 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.cache; + +import javax.cache.annotation.CacheResult; + +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.cache.DefaultCacheKeyGenerator; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; + +/** + * // todo + * + * @author dmgcodevil + */ +public class Jsr107CacheTest { + + // todo create test + + public static class UserService { + + @CacheResult(cacheKeyGenerator = DefaultCacheKeyGenerator.class) + @HystrixCommand(cacheKeyMethod = "getUserIdCacheKey") + public User getUser(String id, String name) { + return new User(id, name + id); // it should be network call + } + + private String getUserIdCacheKey(String id, String name) { + return id + name; + } + } + + +} From c971ca6f70e1e3a9c930c1edd4b0962147939274 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Sat, 3 Jan 2015 00:20:44 +0300 Subject: [PATCH 02/15] iss380: added implementation of CacheKeyInvocationContext --- .../cache/CacheInvocationParameterImpl.java | 65 ++++++++ .../CacheKeyInvocationContextFactory.java | 4 +- .../cache/CacheResultInvocationContext.java | 143 ++++++++++++------ 3 files changed, 166 insertions(+), 46 deletions(-) create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java new file mode 100644 index 000000000..e9960323e --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java @@ -0,0 +1,65 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import com.google.common.collect.Sets; + +import javax.cache.annotation.CacheInvocationParameter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Parameter; +import java.util.Set; + +/** + * Created by dmgcodevil on 1/2/2015. + */ +public class CacheInvocationParameterImpl implements CacheInvocationParameter { + + private Parameter parameter; + private Object value; + private Set annotations; + private Set> annotationTypes; + private int parameterPosition; + + public CacheInvocationParameterImpl(Parameter parameter, Object value, int pos) { + this.value = value; + this.annotations = Sets.newHashSet(parameter.getAnnotations()); + this.parameterPosition = pos; + } + + @Override + public Class getRawType() { + return parameter.getType(); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public Set getAnnotations() { + return annotations; + } + + @Override + public int getParameterPosition() { + return parameterPosition; + } + + public Parameter getParameter() { + return parameter; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java index c2fbde8dd..33f1410e5 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java @@ -26,9 +26,9 @@ */ public class CacheKeyInvocationContextFactory { - public CacheKeyInvocationContext create(Method method) { + public CacheKeyInvocationContext create(String cacheName, Object target, Method method, Object ...args) { if (method.isAnnotationPresent(CacheResult.class)) { - return new CacheResultInvocationContext(); + return new CacheResultInvocationContext(cacheName, target, method, args); } return null; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java index 6fcafe077..101eb8cbe 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java @@ -15,11 +15,22 @@ */ package com.netflix.hystrix.contrib.javanica.cache; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.commons.collections.CollectionUtils; + import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKey; import javax.cache.annotation.CacheKeyInvocationContext; import javax.cache.annotation.CacheResult; +import javax.cache.annotation.CacheValue; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; import java.util.Set; /** @@ -29,48 +40,92 @@ */ public class CacheResultInvocationContext implements CacheKeyInvocationContext { - @Override - public CacheInvocationParameter[] getKeyParameters() { - return new CacheInvocationParameter[0]; - } - - @Override - public CacheInvocationParameter getValueParameter() { - return null; - } - - @Override - public Object getTarget() { - return null; - } - - @Override - public CacheInvocationParameter[] getAllParameters() { - return new CacheInvocationParameter[0]; - } - - @Override - public T unwrap(Class cls) { - return null; - } - - @Override - public Method getMethod() { - return null; - } - - @Override - public Set getAnnotations() { - return null; - } - - @Override - public CacheResult getCacheAnnotation() { - return null; - } - - @Override - public String getCacheName() { - return null; - } + + private Method method; + private String cacheName; + private Object target; + private List keyParameters; + private List cacheInvocationParameters; + private CacheInvocationParameter valueParameter; + + public CacheResultInvocationContext(String cacheName, Object target, Method method, Object... args) { + this.method = method; + this.target = target; + this.cacheName = cacheName; + Parameter[] parameters = method.getParameters(); + cacheInvocationParameters = new ArrayList(parameters.length); + for (int pos = 0; pos < method.getParameterCount(); pos++) { + Parameter param = parameters[pos]; + Object val = args[pos]; + cacheInvocationParameters.add(new CacheInvocationParameterImpl(param, val, pos)); + } + // get key parameters + keyParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, new Predicate() { + @Override + public boolean apply(CacheInvocationParameter input) { + CacheInvocationParameterImpl invocationParameter = (CacheInvocationParameterImpl) input; + return invocationParameter.getParameter().isAnnotationPresent(CacheKey.class); + } + })); + List valueParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, + new Predicate() { + @Override + public boolean apply(CacheInvocationParameter input) { + CacheInvocationParameterImpl invocationParameter = (CacheInvocationParameterImpl) input; + return invocationParameter.getParameter().isAnnotationPresent(CacheValue.class); + } + })); + if (valueParameters.size() > 1) { + throw new RuntimeException("only one method parameter can be annotated with CacheValue annotation"); + } + if (CollectionUtils.isNotEmpty(valueParameters)) { + valueParameter = valueParameters.get(0); + } + + } + + @Override + public CacheInvocationParameter[] getKeyParameters() { + return keyParameters.toArray(new CacheInvocationParameter[keyParameters.size()]); + } + + @Override + public CacheInvocationParameter getValueParameter() { + return valueParameter; + } + + @Override + public Object getTarget() { + return target; + } + + @Override + public CacheInvocationParameter[] getAllParameters() { + return cacheInvocationParameters.toArray(new CacheInvocationParameter[cacheInvocationParameters.size()]); + } + + @Override + public T unwrap(Class cls) { + return null; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public Set getAnnotations() { + return Sets.newHashSet(method.getAnnotations()); + } + + @Override + public CacheResult getCacheAnnotation() { + return method.getAnnotation(CacheResult.class); + } + + @Override + public String getCacheName() { + return cacheName; + } } From 439054225862dfd5003d0aafc097100b1ac67917 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Sat, 3 Jan 2015 23:28:42 +0300 Subject: [PATCH 03/15] iss380: initial working version --- .../aop/aspectj/HystrixCacheAspect.java | 58 +++++ .../AbstractCacheKeyInvocationContext.java | 172 +++++++++++++ .../cache/CacheInvocationParameterImpl.java | 29 ++- .../cache/CacheKeyGeneratorFactory.java | 78 ++++++ .../CacheKeyInvocationContextFactory.java | 53 +++- .../CacheRemoveCacheKeyInvocationContext.java | 37 +++ .../cache/CacheResultInvocationContext.java | 106 +------- .../cache/DefaultCacheKeyGenerator.java | 33 ++- .../DefaultHystrixGeneratedCacheKey.java | 59 +++++ ...Key.java => HystrixCacheKeyGenerator.java} | 12 +- ...Key.java => HystrixGeneratedCacheKey.java} | 47 ++-- .../cache/HystrixRequestCacheManager.java | 55 ++++ .../command/AbstractHystrixCommand.java | 56 +++-- .../AbstractHystrixCommandFactory.java | 24 +- .../javanica/command/BatchHystrixCommand.java | 12 +- .../command/BatchHystrixCommandFactory.java | 17 +- .../javanica/command/GenericCommand.java | 18 +- .../command/GenericHystrixCommandFactory.java | 16 +- .../command/HystrixCommandBuilder.java | 162 ++++++++++++ .../contrib/javanica/command/MetaHolder.java | 2 +- .../HystrixCacheKeyGeneratorException.java | 39 +++ .../hystrix/contrib/javanica/CommonUtils.java | 9 + .../CacheKeyInvocationContextFactoryTest.java | 95 +++++++ .../test/spring/cache/Jsr107CacheTest.java | 234 ++++++++++++++++-- 24 files changed, 1155 insertions(+), 268 deletions(-) create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultHystrixGeneratedCacheKey.java rename hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/{HystrixCacheKey.java => HystrixCacheKeyGenerator.java} (61%) rename hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/{DefaultGeneratedCacheKey.java => HystrixGeneratedCacheKey.java} (52%) create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGeneratorException.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java new file mode 100644 index 000000000..051bd92f5 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java @@ -0,0 +1,58 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.aop.aspectj; + +import com.netflix.hystrix.contrib.javanica.cache.CacheKeyInvocationContextFactory; +import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.apache.commons.lang3.Validate; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; +import java.lang.reflect.Method; + +import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; + +/** + * AspectJ aspect to process methods which annotated with annotations from javax.cache.annotation package. + * + * @author dmgcodevil + */ +@Aspect +public class HystrixCacheAspect { + + @Pointcut("@annotation(javax.cache.annotation.CacheRemove) && !@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") + public void cacheRemoveAnnotationPointcut() { + } + + @Around("cacheRemoveAnnotationPointcut()") + public Object methodsAnnotatedWithCacheRemove(final ProceedingJoinPoint joinPoint) throws Throwable { + Method method = getMethodFromTarget(joinPoint); + Object obj = joinPoint.getTarget(); + Object[] args = joinPoint.getArgs(); + Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint); + MetaHolder metaHolder = MetaHolder.builder() + .args(args).method(method).obj(obj).build(); + CacheKeyInvocationContext context = CacheKeyInvocationContextFactory + .createCacheRemoveInvocationContext(metaHolder); + HystrixRequestCacheManager.getInstance().clearCache(context); + return joinPoint.proceed(); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java new file mode 100644 index 000000000..8d7129490 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java @@ -0,0 +1,172 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.commons.collections.CollectionUtils; + +import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKey; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheValue; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Base implementation of {@link CacheKeyInvocationContext} interface. + * + * @author dmgcodevil + */ +public abstract class AbstractCacheKeyInvocationContext implements CacheKeyInvocationContext { + + private Method method; + private Object target; + private A cacheAnnotation; + private List keyParameters; + private List cacheInvocationParameters; + private CacheInvocationParameter valueParameter; + + /** + * Creates CacheKeyInvocationContext. + * + * @param cacheAnnotation the cache annotation, for example: {@link javax.cache.annotation.CacheResult} or + * {@link javax.cache.annotation.CacheRemove} + * @param target the target object + * @param method the current method + * @param args the arguments of the current method + */ + public AbstractCacheKeyInvocationContext(A cacheAnnotation, Object target, Method method, Object... args) { + this.method = method; + this.target = target; + this.cacheAnnotation = cacheAnnotation; + Class[] parametersTypes = method.getParameterTypes(); + Annotation[][] parametersAnnotations = method.getParameterAnnotations(); + int parameterCount = parametersTypes.length; + cacheInvocationParameters = new ArrayList(parameterCount); + for (int pos = 0; pos < parameterCount; pos++) { + Class paramType = parametersTypes[pos]; + Object val = args[pos]; + cacheInvocationParameters.add(new CacheInvocationParameterImpl(paramType, val, parametersAnnotations[pos], pos)); + } + // get key parameters + keyParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, new Predicate() { + @Override + public boolean apply(CacheInvocationParameter input) { + return isAnnotationPresent(input, CacheKey.class); + } + })); + List valueParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, + new Predicate() { + @Override + public boolean apply(CacheInvocationParameter input) { + return isAnnotationPresent(input, CacheValue.class); + } + })); + if (valueParameters.size() > 1) { + throw new RuntimeException("only one method parameter can be annotated with CacheValue annotation"); + } + if (CollectionUtils.isNotEmpty(valueParameters)) { + valueParameter = valueParameters.get(0); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public CacheInvocationParameter[] getKeyParameters() { + return keyParameters.toArray(new CacheInvocationParameter[keyParameters.size()]); + } + + /** + * {@inheritDoc} + */ + @Override + public CacheInvocationParameter getValueParameter() { + return valueParameter; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTarget() { + return target; + } + + /** + * {@inheritDoc} + */ + @Override + public CacheInvocationParameter[] getAllParameters() { + return cacheInvocationParameters.toArray(new CacheInvocationParameter[cacheInvocationParameters.size()]); + } + + /** + * {@inheritDoc} + */ + @Override + public T unwrap(Class cls) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Method getMethod() { + return method; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getAnnotations() { + return Sets.newHashSet(method.getAnnotations()); + } + + /** + * {@inheritDoc} + */ + @Override + public A getCacheAnnotation() { + return cacheAnnotation; + } + + /** + * {@inheritDoc} + */ + @Override + public abstract String getCacheName(); + + private boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { + return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { + @Override + public boolean apply(Annotation input) { + return input.annotationType().equals(annotation); + } + }).isPresent(); + } + +} \ No newline at end of file diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java index e9960323e..0fce43f7e 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java @@ -19,47 +19,58 @@ import javax.cache.annotation.CacheInvocationParameter; import java.lang.annotation.Annotation; -import java.lang.reflect.Parameter; import java.util.Set; /** - * Created by dmgcodevil on 1/2/2015. + * Implementation of {@link CacheInvocationParameter}. + * + * @author dmgcodevil */ public class CacheInvocationParameterImpl implements CacheInvocationParameter { - private Parameter parameter; + private Class rawType; private Object value; private Set annotations; private Set> annotationTypes; private int parameterPosition; - public CacheInvocationParameterImpl(Parameter parameter, Object value, int pos) { + public CacheInvocationParameterImpl(Class paramType, Object value, Annotation[] annotations, int pos) { this.value = value; - this.annotations = Sets.newHashSet(parameter.getAnnotations()); + this.rawType = paramType; + this.annotations = Sets.newHashSet(annotations); this.parameterPosition = pos; } + /** + * {@inheritDoc} + */ @Override public Class getRawType() { - return parameter.getType(); + return rawType; } + /** + * {@inheritDoc} + */ @Override public Object getValue() { return value; } + /** + * {@inheritDoc} + */ @Override public Set getAnnotations() { return annotations; } + /** + * {@inheritDoc} + */ @Override public int getParameterPosition() { return parameterPosition; } - public Parameter getParameter() { - return parameter; - } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java new file mode 100644 index 000000000..4581f7fdc --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java @@ -0,0 +1,78 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCacheKeyGeneratorException; + +import javax.cache.annotation.CacheKeyGenerator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Factory to create {@link HystrixCacheKeyGenerator}. + * + * @author dmgcodevil + */ +public class CacheKeyGeneratorFactory { + + private static final CacheKeyGeneratorFactory INSTANCE = new CacheKeyGeneratorFactory(); + + private CacheKeyGeneratorFactory() { + } + + public static CacheKeyGeneratorFactory getInstance() { + return INSTANCE; + } + + private Supplier defaultCacheKeyGenerator = Suppliers.memoize(new Supplier() { + @Override + public HystrixCacheKeyGenerator get() { + return new DefaultCacheKeyGenerator(); + } + }); + + private ConcurrentMap, HystrixCacheKeyGenerator> generators + = new ConcurrentHashMap, HystrixCacheKeyGenerator>(); + + /** + * Instantiates new instance of the given type or returns exiting + * one if it's present in the factory. + * + * @param cacheKeyGenerator the certain class type that extends {@link HystrixCacheKeyGenerator} interface. + * @return new instance with specified type or already existing one + * @throws HystrixCacheKeyGeneratorException + */ + public HystrixCacheKeyGenerator create(Class cacheKeyGenerator) throws HystrixCacheKeyGeneratorException { + if (cacheKeyGenerator == null || CacheKeyGenerator.class.equals(cacheKeyGenerator)) { + return defaultCacheKeyGenerator.get(); + } + try { + HystrixCacheKeyGenerator generator = generators.putIfAbsent(cacheKeyGenerator, + (HystrixCacheKeyGenerator) cacheKeyGenerator.newInstance()); + if (generator == null) { + generator = generators.get(cacheKeyGenerator); + } + return generator; + } catch (InstantiationException e) { + throw new HystrixCacheKeyGeneratorException(e); + } catch (IllegalAccessException e) { + throw new HystrixCacheKeyGeneratorException(e); + } + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java index 33f1410e5..1528c093a 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java @@ -15,21 +15,56 @@ */ package com.netflix.hystrix.contrib.javanica.cache; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; + import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; import javax.cache.annotation.CacheResult; -import java.lang.reflect.Method; /** - * // todo + * Factory to create specific {@link CacheKeyInvocationContext} instances. * * @author dmgcodevil */ -public class CacheKeyInvocationContextFactory { +public final class CacheKeyInvocationContextFactory { + + private CacheKeyInvocationContextFactory() { + throw new UnsupportedOperationException("it's prohibited to create instances of this class"); + } + + /** + * Creates new instance of {@link CacheResultInvocationContext} if {@link CacheResult} annotation + * is present for the specified method, see {@link MetaHolder#getMethod()}. + * + * @param metaHolder the meta holder contains information about current executable method, see {@link MetaHolder#getMethod()} + * @return new instance of {@link CacheResultInvocationContext} or null if the given method doesn't have {@link CacheResult} annotation + */ + public static CacheKeyInvocationContext createCacheResultInvocationContext(MetaHolder metaHolder) { + if (metaHolder.getMethod().isAnnotationPresent(CacheResult.class)) { + return new CacheResultInvocationContext( + metaHolder.getMethod().getAnnotation(CacheResult.class), + metaHolder.getObj(), + metaHolder.getMethod(), + metaHolder.getArgs()); + } + return null; + } - public CacheKeyInvocationContext create(String cacheName, Object target, Method method, Object ...args) { - if (method.isAnnotationPresent(CacheResult.class)) { - return new CacheResultInvocationContext(cacheName, target, method, args); - } - return null; - } + /** + * Creates new instance of {@link CacheRemoveCacheKeyInvocationContext} if {@link CacheRemove} annotation + * is present for the specified method, see {@link MetaHolder#getMethod()}. + * + * @param metaHolder the meta holder contains information about current executable method, see {@link MetaHolder#getMethod()}. + * @return {@link CacheRemoveCacheKeyInvocationContext} or null if the given method doesn't have {@link CacheRemove} annotation + */ + public static CacheKeyInvocationContext createCacheRemoveInvocationContext(MetaHolder metaHolder) { + if (metaHolder.getMethod().isAnnotationPresent(CacheRemove.class)) { + return new CacheRemoveCacheKeyInvocationContext( + metaHolder.getMethod().getAnnotation(CacheRemove.class), + metaHolder.getObj(), + metaHolder.getMethod(), + metaHolder.getArgs()); + } + return null; + } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java new file mode 100644 index 000000000..46afc92de --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java @@ -0,0 +1,37 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import javax.cache.annotation.CacheRemove; +import java.lang.reflect.Method; + +/** + * Concrete implementation of {@link AbstractCacheKeyInvocationContext} provides information of invocation + * context for {@link CacheRemove} annotation. + * + * @author dmgcodevil + */ +public class CacheRemoveCacheKeyInvocationContext extends AbstractCacheKeyInvocationContext { + + public CacheRemoveCacheKeyInvocationContext(CacheRemove cacheAnnotation, Object target, Method method, Object... args) { + super(cacheAnnotation, target, method, args); + } + + @Override + public String getCacheName() { + return getCacheAnnotation().cacheName(); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java index 101eb8cbe..85923f092 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java @@ -15,117 +15,23 @@ */ package com.netflix.hystrix.contrib.javanica.cache; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.apache.commons.collections.CollectionUtils; - -import javax.cache.annotation.CacheInvocationParameter; -import javax.cache.annotation.CacheKey; -import javax.cache.annotation.CacheKeyInvocationContext; import javax.cache.annotation.CacheResult; -import javax.cache.annotation.CacheValue; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; /** - * // todo + * Concrete implementation of {@link AbstractCacheKeyInvocationContext} provides information of invocation + * context for {@link CacheResult} annotation. * * @author dmgcodevil */ -public class CacheResultInvocationContext implements CacheKeyInvocationContext { - - - private Method method; - private String cacheName; - private Object target; - private List keyParameters; - private List cacheInvocationParameters; - private CacheInvocationParameter valueParameter; - - public CacheResultInvocationContext(String cacheName, Object target, Method method, Object... args) { - this.method = method; - this.target = target; - this.cacheName = cacheName; - Parameter[] parameters = method.getParameters(); - cacheInvocationParameters = new ArrayList(parameters.length); - for (int pos = 0; pos < method.getParameterCount(); pos++) { - Parameter param = parameters[pos]; - Object val = args[pos]; - cacheInvocationParameters.add(new CacheInvocationParameterImpl(param, val, pos)); - } - // get key parameters - keyParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, new Predicate() { - @Override - public boolean apply(CacheInvocationParameter input) { - CacheInvocationParameterImpl invocationParameter = (CacheInvocationParameterImpl) input; - return invocationParameter.getParameter().isAnnotationPresent(CacheKey.class); - } - })); - List valueParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, - new Predicate() { - @Override - public boolean apply(CacheInvocationParameter input) { - CacheInvocationParameterImpl invocationParameter = (CacheInvocationParameterImpl) input; - return invocationParameter.getParameter().isAnnotationPresent(CacheValue.class); - } - })); - if (valueParameters.size() > 1) { - throw new RuntimeException("only one method parameter can be annotated with CacheValue annotation"); - } - if (CollectionUtils.isNotEmpty(valueParameters)) { - valueParameter = valueParameters.get(0); - } - - } - - @Override - public CacheInvocationParameter[] getKeyParameters() { - return keyParameters.toArray(new CacheInvocationParameter[keyParameters.size()]); - } - - @Override - public CacheInvocationParameter getValueParameter() { - return valueParameter; - } - - @Override - public Object getTarget() { - return target; - } +public class CacheResultInvocationContext extends AbstractCacheKeyInvocationContext { - @Override - public CacheInvocationParameter[] getAllParameters() { - return cacheInvocationParameters.toArray(new CacheInvocationParameter[cacheInvocationParameters.size()]); - } - - @Override - public T unwrap(Class cls) { - return null; - } - - @Override - public Method getMethod() { - return method; - } - - @Override - public Set getAnnotations() { - return Sets.newHashSet(method.getAnnotations()); - } - - @Override - public CacheResult getCacheAnnotation() { - return method.getAnnotation(CacheResult.class); + public CacheResultInvocationContext(CacheResult cacheAnnotation, Object target, Method method, Object... args) { + super(cacheAnnotation, target, method, args); } @Override public String getCacheName() { - return cacheName; + return null; } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java index a69865bd5..61583c008 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java @@ -16,25 +16,34 @@ package com.netflix.hystrix.contrib.javanica.cache; import javax.cache.annotation.CacheInvocationParameter; -import javax.cache.annotation.CacheKeyGenerator; import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.GeneratedCacheKey; import java.lang.annotation.Annotation; /** - * // todo + * Default implementation of {@link HystrixCacheKeyGenerator} creates cache keys with {@link DefaultHystrixGeneratedCacheKey} type. * * @author dmgcodevil */ -public class DefaultCacheKeyGenerator implements CacheKeyGenerator { +public class DefaultCacheKeyGenerator implements HystrixCacheKeyGenerator { - @Override - public GeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { - StringBuilder cacheKeyBuilder = new StringBuilder(); - for (CacheInvocationParameter parameter : cacheKeyInvocationContext.getKeyParameters()) { - cacheKeyBuilder.append(parameter.getValue()); - } - return new DefaultGeneratedCacheKey(cacheKeyBuilder.toString()); - } + /** + * Calls toString() method for each parameter annotated with {@link javax.cache.annotation.CacheKey} annotation and + * gathers results together into single string which subsequently is used as hystrix cache key. + * + * @param cacheKeyInvocationContext runtime information about an intercepted method invocation for a method + * annotated with {@link javax.cache.annotation.CacheResult} or {@link javax.cache.annotation.CacheRemove} + * @return see {@link DefaultHystrixGeneratedCacheKey} + */ + @Override + public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { + if (cacheKeyInvocationContext.getKeyParameters() == null || cacheKeyInvocationContext.getKeyParameters().length == 0) { + return DefaultHystrixGeneratedCacheKey.EMPTY; + } + StringBuilder cacheKeyBuilder = new StringBuilder(); + for (CacheInvocationParameter parameter : cacheKeyInvocationContext.getKeyParameters()) { + cacheKeyBuilder.append(parameter.getValue()); + } + return new DefaultHystrixGeneratedCacheKey(cacheKeyBuilder.toString()); + } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultHystrixGeneratedCacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultHystrixGeneratedCacheKey.java new file mode 100644 index 000000000..e694b71ad --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultHystrixGeneratedCacheKey.java @@ -0,0 +1,59 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import com.google.common.base.Objects; + +/** + * Default implementation of {@link HystrixGeneratedCacheKey}. + * + * @author dmgcodevil + */ +public class DefaultHystrixGeneratedCacheKey implements HystrixGeneratedCacheKey { + + /** + * Means "do not cache". + */ + public static final DefaultHystrixGeneratedCacheKey EMPTY = new DefaultHystrixGeneratedCacheKey(null); + + private String cacheKey; + + public DefaultHystrixGeneratedCacheKey(String cacheKey) { + this.cacheKey = cacheKey; + } + + @Override + public String getCacheKey() { + return cacheKey; + } + + @Override + public int hashCode() { + return Objects.hashCode(cacheKey); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + DefaultHystrixGeneratedCacheKey that = (DefaultHystrixGeneratedCacheKey) o; + + return Objects.equal(this.cacheKey, that.cacheKey); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java similarity index 61% rename from hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKey.java rename to hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java index 77fbfbfd6..cb25284d3 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKey.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java @@ -15,13 +15,17 @@ */ package com.netflix.hystrix.contrib.javanica.cache; +import javax.cache.annotation.CacheKeyGenerator; +import javax.cache.annotation.CacheKeyInvocationContext; +import java.lang.annotation.Annotation; + /** - * // todo + * Specific interface to adopt {@link CacheKeyGenerator} for Hystrix environment. * * @author dmgcodevil */ -public interface HystrixCacheKey { - - String getKey(); +public interface HystrixCacheKeyGenerator extends CacheKeyGenerator { + @Override + HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultGeneratedCacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java similarity index 52% rename from hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultGeneratedCacheKey.java rename to hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java index cf3fde762..61b868ab7 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultGeneratedCacheKey.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java @@ -17,40 +17,23 @@ import javax.cache.annotation.GeneratedCacheKey; -import com.google.common.base.Objects; - /** - * // todo + * Specific interface to adopt {@link GeneratedCacheKey} for Hystrix environment. * * @author dmgcodevil */ -public class DefaultGeneratedCacheKey implements GeneratedCacheKey, HystrixCacheKey { - - private String cacheKey; - - public DefaultGeneratedCacheKey(String cacheKey) { - this.cacheKey = cacheKey; - } - - @Override - public String getKey() { - return cacheKey; - } - - @Override - public int hashCode() { - return Objects.hashCode(cacheKey); - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - DefaultGeneratedCacheKey that = (DefaultGeneratedCacheKey) o; - - return Objects.equal(this.cacheKey, that.cacheKey); - } +public interface HystrixGeneratedCacheKey extends GeneratedCacheKey { + + /** + * Key to be used for request caching. + *

+ * By default this returns null which means "do not cache". + *

+ * To enable caching override this method and return a string key uniquely representing the state of a command instance. + *

+ * If multiple command instances in the same request scope match keys then only the first will be executed and all others returned from cache. + * + * @return cacheKey + */ + String getCacheKey(); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java new file mode 100644 index 000000000..479bdaebc --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java @@ -0,0 +1,55 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.cache; + +import com.netflix.hystrix.HystrixCommandKey; +import com.netflix.hystrix.HystrixRequestCache; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault; + +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; + +/** + * Cache manager to work with {@link HystrixRequestCache}. + * + * @author dmgcodevil + */ +public final class HystrixRequestCacheManager { + + private static final HystrixRequestCacheManager INSTANCE = new HystrixRequestCacheManager(); + + private HystrixRequestCacheManager() { + } + + public static HystrixRequestCacheManager getInstance() { + return INSTANCE; + } + + /** + * Clears the cache for a given cacheKey context. + * + * @param context the runtime information about an intercepted method invocation for a method + * annotated with {@link CacheRemove} annotation + */ + public void clearCache(CacheKeyInvocationContext context) { + String cacheName = context.getCacheName(); + HystrixCacheKeyGenerator keyGenerator = CacheKeyGeneratorFactory.getInstance() + .create(context.getCacheAnnotation().cacheKeyGenerator()); + String key = keyGenerator.generateCacheKey(context).getCacheKey(); + HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey(cacheName), + HystrixConcurrencyStrategyDefault.getInstance()).clear(key); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java index 111d71c33..ea9dc0165 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java @@ -18,12 +18,18 @@ import com.google.common.base.Throwables; import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.cache.CacheKeyGeneratorFactory; +import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.HystrixCacheKeyGenerator; import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.exception.HystrixRuntimeException; import javax.annotation.concurrent.ThreadSafe; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; +import javax.cache.annotation.CacheResult; import java.util.Collection; import java.util.Map; @@ -37,32 +43,22 @@ public abstract class AbstractHystrixCommand extends com.netflix.hystrix.Hyst private CommandActions commandActions; private final Map commandProperties; + private CacheKeyInvocationContext cacheResultInvocationContext; + private CacheKeyInvocationContext cacheRemoveInvocationContext; private final Collection> collapsedRequests; private final Class[] ignoreExceptions; private final ExecutionType executionType; + private final CacheKeyGeneratorFactory cacheKeyGeneratorFactory = CacheKeyGeneratorFactory.getInstance(); - /** - * Constructor with parameters. - * - * @param setterBuilder the builder to build {@link com.netflix.hystrix.HystrixCommand.Setter} - * @param commandActions the command actions {@link CommandActions} - * @param commandProperties the command properties - * @param collapsedRequests the collapsed requests - * @param ignoreExceptions the exceptions which should be ignored and wrapped to throw in {@link HystrixBadRequestException} - * @param executionType the execution type {@link ExecutionType} - */ - protected AbstractHystrixCommand(CommandSetterBuilder setterBuilder, - CommandActions commandActions, - Map commandProperties, - Collection> collapsedRequests, - final Class[] ignoreExceptions, - ExecutionType executionType) { - super(setterBuilder.build()); - this.commandActions = commandActions; - this.commandProperties = commandProperties; - this.collapsedRequests = collapsedRequests; - this.ignoreExceptions = ignoreExceptions; - this.executionType = executionType; + protected AbstractHystrixCommand(HystrixCommandBuilder builder) { + super(builder.getSetterBuilder().build()); + this.commandActions = builder.getCommandActions(); + this.commandProperties = builder.getCommandProperties(); + this.collapsedRequests = builder.getCollapsedRequests(); + this.cacheResultInvocationContext = builder.getCacheResultInvocationContext(); + this.cacheRemoveInvocationContext = builder.getCacheRemoveInvocationContext(); + this.ignoreExceptions = builder.getIgnoreExceptions(); + this.executionType = builder.getExecutionType(); HystrixPropertiesManager.setCommandProperties(commandProperties, getCommandKey().name()); } @@ -130,6 +126,12 @@ public ExecutionType getExecutionType() { @Override protected String getCacheKey() { String key; + if (cacheResultInvocationContext != null) { + HystrixCacheKeyGenerator keyGenerator = cacheKeyGeneratorFactory + .create(cacheResultInvocationContext.getCacheAnnotation().cacheKeyGenerator()); + return keyGenerator.generateCacheKey(cacheResultInvocationContext).getCacheKey(); + } + // deprecated approach if (commandActions.getCacheKeyAction() != null) { key = String.valueOf(commandActions.getCacheKeyAction().execute(executionType)); } else { @@ -170,6 +172,7 @@ Object process(Action action) throws Exception { Object result; try { result = action.execute(); + flushCache(); } catch (CommandActionExecutionException throwable) { Throwable cause = throwable.getCause(); if (isIgnorable(cause)) { @@ -198,6 +201,15 @@ protected T getFallback() { throw new RuntimeException("No fallback available.", getFailedExecutionException()); } + /** + * Clears cache for the specified hystrix command. + */ + protected void flushCache() { + if (cacheRemoveInvocationContext != null) { + HystrixRequestCacheManager.getInstance().clearCache(cacheRemoveInvocationContext); + } + } + /** * Common action. */ diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java index 4716232d7..e296b3f05 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java @@ -18,9 +18,7 @@ import com.google.common.base.Throwables; import com.google.common.collect.Maps; -import com.netflix.config.ConfigurationManager; import com.netflix.hystrix.HystrixCollapser; -import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; @@ -32,6 +30,9 @@ import java.util.Collections; import java.util.Map; +import static com.netflix.hystrix.contrib.javanica.cache.CacheKeyInvocationContextFactory.createCacheRemoveInvocationContext; +import static com.netflix.hystrix.contrib.javanica.cache.CacheKeyInvocationContextFactory.createCacheResultInvocationContext; + /** * Base implementation of {@link HystrixCommandFactory} interface. * @@ -66,8 +67,16 @@ public T create(MetaHolder metaHolder, CommandAction cacheKeyAction = createCacheKeyAction(metaHolder); CommandActions commandActions = CommandActions.builder().commandAction(commandAction) .fallbackAction(fallbackAction).cacheKeyAction(cacheKeyAction).build(); - return create(setterBuilder, commandActions, commandProperties, collapsedRequests, - metaHolder.getHystrixCommand().ignoreExceptions(), metaHolder.getExecutionType()); + + HystrixCommandBuilder hystrixCommandBuilder = new HystrixCommandBuilder().setterBuilder(setterBuilder) + .commandActions(commandActions) + .commandProperties(commandProperties) + .collapsedRequests(collapsedRequests) + .cacheResultInvocationContext(createCacheResultInvocationContext(metaHolder)) + .cacheRemoveInvocationContext(createCacheRemoveInvocationContext(metaHolder)) + .ignoreExceptions(metaHolder.getHystrixCommand().ignoreExceptions()) + .executionType(metaHolder.getExecutionType()); + return create(hystrixCommandBuilder); } CommandAction createFallbackAction(MetaHolder metaHolder, @@ -100,12 +109,7 @@ CommandAction createFallbackAction(MetaHolder metaHolder, return fallbackAction; } - abstract T create(CommandSetterBuilder setterBuilder, - CommandActions commandActions, - Map commandProperties, - Collection> collapsedRequests, - Class[] ignoreExceptions, - ExecutionType executionType); + abstract T create(HystrixCommandBuilder hystrixCommandBuilder); private CommandAction createCacheKeyAction(MetaHolder metaHolder) { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java index 9a1e5089e..56393d514 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommand.java @@ -45,16 +45,8 @@ public class BatchHystrixCommand extends AbstractHystrixCommand commandProperties, - Collection> collapsedRequests, - Class[] ignoreExceptions, - ExecutionType executionType) { - super(setterBuilder, commandActions, commandProperties, collapsedRequests, - ignoreExceptions, executionType); + protected BatchHystrixCommand(HystrixCommandBuilder builder) { + super(builder); } public boolean isFallbackEnabled() { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommandFactory.java index d6a35d6f8..51f5b6d20 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommandFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/BatchHystrixCommandFactory.java @@ -15,11 +15,6 @@ */ package com.netflix.hystrix.contrib.javanica.command; -import com.netflix.hystrix.HystrixCollapser; - -import java.util.Collection; -import java.util.Map; - /** * Specific implementation of {@link HystrixCommandFactory} interface to create {@link BatchHystrixCommand} instances. */ @@ -32,15 +27,9 @@ public static HystrixCommandFactory getInstance() { return COMMAND_FACTORY; } + @Override - BatchHystrixCommand create(CommandSetterBuilder setterBuilder, - CommandActions commandActions, - Map commandProperties, - Collection> collapsedRequests, - Class[] ignoreExceptions, - ExecutionType executionType) { - BatchHystrixCommand batchHystrixCommand = new BatchHystrixCommand(setterBuilder, commandActions, - commandProperties, collapsedRequests, ignoreExceptions, executionType); - return batchHystrixCommand; + BatchHystrixCommand create(HystrixCommandBuilder hystrixCommandBuilder) { + return new BatchHystrixCommand(hystrixCommandBuilder); } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java index 1682ee749..6cd29283a 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericCommand.java @@ -15,16 +15,11 @@ */ package com.netflix.hystrix.contrib.javanica.command; -import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.contrib.javanica.exception.FallbackInvocationException; -import com.netflix.hystrix.exception.HystrixBadRequestException; -import com.netflix.hystrix.exception.HystrixRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.concurrent.ThreadSafe; -import java.util.Collection; -import java.util.Map; /** * Implementation of AbstractHystrixCommand which returns an Object as result. @@ -34,17 +29,8 @@ public class GenericCommand extends AbstractHystrixCommand { private static final Logger LOGGER = LoggerFactory.getLogger(GenericCommand.class); - /** - * {@inheritDoc} - */ - protected GenericCommand(CommandSetterBuilder setterBuilder, - CommandActions commandActions, - Map commandProperties, - Collection> collapsedRequests, - Class[] ignoreExceptions, - ExecutionType executionType) { - super(setterBuilder, commandActions, commandProperties, collapsedRequests, - ignoreExceptions, executionType); + public GenericCommand(HystrixCommandBuilder builder) { + super(builder); } /** diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericHystrixCommandFactory.java index bf3efdad9..9891cfe95 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericHystrixCommandFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/GenericHystrixCommandFactory.java @@ -15,11 +15,6 @@ */ package com.netflix.hystrix.contrib.javanica.command; -import com.netflix.hystrix.HystrixCollapser; - -import java.util.Collection; -import java.util.Map; - /** * Specific implementation of {@link HystrixCommandFactory} interface to create {@link GenericCommand} instances. */ @@ -33,15 +28,8 @@ public static HystrixCommandFactory getInstance() { } @Override - GenericCommand create(CommandSetterBuilder setterBuilder, - CommandActions commandActions, - Map commandProperties, - Collection> collapsedRequests, - Class[] ignoreExceptions, - ExecutionType executionType) { - GenericCommand genericCommand = new GenericCommand(setterBuilder, commandActions, commandProperties, - collapsedRequests, ignoreExceptions, executionType); - return genericCommand; + GenericCommand create(HystrixCommandBuilder hystrixCommandBuilder) { + return new GenericCommand(hystrixCommandBuilder); } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java new file mode 100644 index 000000000..34fb63787 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java @@ -0,0 +1,162 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.command; + +import com.netflix.hystrix.HystrixCollapser; + +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; +import javax.cache.annotation.CacheResult; +import java.util.Collection; +import java.util.Map; + +/** + * Builder contains all necessary information required to create specific hystrix command. + * + * @author dmgcodevil + */ +public class HystrixCommandBuilder { + + private CommandSetterBuilder setterBuilder; + private CommandActions commandActions; + private Map commandProperties; + private CacheKeyInvocationContext cacheResultInvocationContext; + private CacheKeyInvocationContext cacheRemoveInvocationContext; + private Collection> collapsedRequests; + private Class[] ignoreExceptions; + private ExecutionType executionType; + + public CommandSetterBuilder getSetterBuilder() { + return setterBuilder; + } + + public CommandActions getCommandActions() { + return commandActions; + } + + public Map getCommandProperties() { + return commandProperties; + } + + public CacheKeyInvocationContext getCacheResultInvocationContext() { + return cacheResultInvocationContext; + } + + public CacheKeyInvocationContext getCacheRemoveInvocationContext() { + return cacheRemoveInvocationContext; + } + + public Collection> getCollapsedRequests() { + return collapsedRequests; + } + + public Class[] getIgnoreExceptions() { + return ignoreExceptions; + } + + public ExecutionType getExecutionType() { + return executionType; + } + + /** + * Sets the builder to create {@link com.netflix.hystrix.HystrixCommand.Setter}. + * + * @param pSetterBuilder the builder to create {@link com.netflix.hystrix.HystrixCommand.Setter} + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder setterBuilder(CommandSetterBuilder pSetterBuilder) { + this.setterBuilder = pSetterBuilder; + return this; + } + + /** + * Sets command actions {@link CommandActions}. + * + * @param pCommandActions the command actions + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder commandActions(CommandActions pCommandActions) { + this.commandActions = pCommandActions; + return this; + } + + /** + * Sets command properties. + * + * @param pCommandProperties the command properties + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder commandProperties(Map pCommandProperties) { + this.commandProperties = pCommandProperties; + return this; + } + + /** + * Sets CacheResult invocation context, see {@link CacheKeyInvocationContext} and {@link CacheResult}. + * + * @param pCacheResultInvocationContext the CacheResult invocation context + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder cacheResultInvocationContext(CacheKeyInvocationContext pCacheResultInvocationContext) { + this.cacheResultInvocationContext = pCacheResultInvocationContext; + return this; + } + + /** + * Sets CacheRemove invocation context, see {@link CacheKeyInvocationContext} and {@link CacheRemove}. + * + * @param pCacheRemoveInvocationContext the CacheRemove invocation context + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder cacheRemoveInvocationContext(CacheKeyInvocationContext pCacheRemoveInvocationContext) { + this.cacheRemoveInvocationContext = pCacheRemoveInvocationContext; + return this; + } + + /** + * Sets collapsed requests. + * + * @param pCollapsedRequests the collapsed requests + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder collapsedRequests(Collection> pCollapsedRequests) { + this.collapsedRequests = pCollapsedRequests; + return this; + } + + /** + * Sets exceptions that should be ignored and wrapped to throw in {@link com.netflix.hystrix.exception.HystrixBadRequestException}. + * + * @param pIgnoreExceptions the exceptions to be ignored + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder ignoreExceptions(Class[] pIgnoreExceptions) { + this.ignoreExceptions = pIgnoreExceptions; + return this; + } + + /** + * Sets execution type, see {@link ExecutionType}. + * + * @param pExecutionType the execution type + * @return this {@link HystrixCommandBuilder} + */ + public HystrixCommandBuilder executionType(ExecutionType pExecutionType) { + this.executionType = pExecutionType; + return this; + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java index f9ef8ab69..5098f509c 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/MetaHolder.java @@ -24,7 +24,7 @@ import javax.annotation.concurrent.Immutable; /** - * Simple immutable holder to keep all necessary information to build Hystrix command. + * Simple immutable holder to keep all necessary information about current method to build Hystrix command. */ @Immutable public class MetaHolder { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGeneratorException.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGeneratorException.java new file mode 100644 index 000000000..e65d04e7a --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGeneratorException.java @@ -0,0 +1,39 @@ +/** + * Copyright 2012 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.hystrix.contrib.javanica.exception; + +/** + * Indicates that something is going wrong with caching logic. + * + * @author dmgcodevil + */ +public class HystrixCacheKeyGeneratorException extends RuntimeException { + + public HystrixCacheKeyGeneratorException() { + } + + public HystrixCacheKeyGeneratorException(String message) { + super(message); + } + + public HystrixCacheKeyGeneratorException(String message, Throwable cause) { + super(message, cause); + } + + public HystrixCacheKeyGeneratorException(Throwable cause) { + super(cause); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/CommonUtils.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/CommonUtils.java index 59d30c7ac..1947f0a1d 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/CommonUtils.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/CommonUtils.java @@ -2,10 +2,12 @@ import com.google.common.base.Function; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandMetrics; +import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.HystrixRequestLog; import javax.annotation.Nullable; @@ -20,6 +22,13 @@ public HystrixCommandMetrics getMetrics(String commandKey) { return HystrixCommandMetrics.getInstance(HystrixCommandKey.Factory.asKey(commandKey)); } + + public static HystrixInvokableInfo getLastExecutedCommand() { + Collection> executedCommands = + HystrixRequestLog.getCurrentRequest().getAllExecutedCommands(); + return Iterables.getLast(executedCommands); + } + public static void assertExecutedCommands(String... commands) { Collection> executedCommands = HystrixRequestLog.getCurrentRequest().getExecutedCommands(); diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java new file mode 100644 index 000000000..d9eb9a852 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java @@ -0,0 +1,95 @@ +package com.netflix.hystrix.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.junit.Test; + +import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKey; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; +import javax.cache.annotation.CacheResult; + +import java.lang.annotation.Annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Unit test for {@link CacheKeyInvocationContextFactory}. + * + * @author dmgcodevil + */ +public class CacheKeyInvocationContextFactoryTest { + + @Test + public void testCreateCacheResultInvocationContext_givenMethodAnnotatedWithCacheResult_shouldCreateCorrectCacheKeyInvocationContext() + throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + String param2 = "val_2"; + Integer param3 = 3; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, String.class, Integer.class)) + .args(new Object[]{param1, param2, param3}) + .obj(testCacheClass).build(); + // when + CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + + // then + assertNotNull(context.getKeyParameters()); + assertEquals(2, context.getKeyParameters().length); + assertEquals(String.class, context.getKeyParameters()[0].getRawType()); + assertEquals(0, context.getKeyParameters()[0].getParameterPosition()); + assertEquals(param1, context.getKeyParameters()[0].getValue()); + assertTrue(isAnnotationPresent(context.getKeyParameters()[0], CacheKey.class)); + + assertEquals(Integer.class, context.getKeyParameters()[1].getRawType()); + assertEquals(2, context.getKeyParameters()[1].getParameterPosition()); + assertEquals(param3, context.getKeyParameters()[1].getValue()); + assertTrue(isAnnotationPresent(context.getKeyParameters()[1], CacheKey.class)); + } + + @Test + public void testCreateCacheRemoveInvocationContext_givenMethodAnnotatedWithCacheRemove_shouldCreateCorrectCacheKeyInvocationContext() + throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheRemoveMethod", String.class)) + .args(new Object[]{param1}) + .obj(testCacheClass).build(); + // when + CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheRemoveInvocationContext(metaHolder); + + // then + assertNotNull(context.getKeyParameters()); + assertEquals(0, context.getKeyParameters().length); + } + + public static class TestCacheClass { + + @CacheResult + public Object cacheResultMethod(@CacheKey String param1, String param2, @CacheKey Integer param3) { + return null; + } + + @CacheRemove + public Object cacheRemoveMethod(String param1) { + return null; + } + } + + private static boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { + return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { + @Override + public boolean apply(Annotation input) { + return input.annotationType().equals(annotation); + } + }).isPresent(); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java index fc2e60a33..95734e6ad 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java @@ -1,32 +1,236 @@ package com.netflix.hystrix.contrib.javanica.test.spring.cache; -import javax.cache.annotation.CacheResult; - +import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.cache.DefaultCacheKeyGenerator; +import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCacheAspect; +import com.netflix.hystrix.contrib.javanica.cache.DefaultHystrixGeneratedCacheKey; +import com.netflix.hystrix.contrib.javanica.cache.HystrixCacheKeyGenerator; +import com.netflix.hystrix.contrib.javanica.cache.HystrixGeneratedCacheKey; +import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.annotation.PostConstruct; +import javax.cache.annotation.CacheInvocationParameter; +import javax.cache.annotation.CacheKey; +import javax.cache.annotation.CacheKeyInvocationContext; +import javax.cache.annotation.CacheRemove; +import javax.cache.annotation.CacheResult; +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static com.netflix.hystrix.contrib.javanica.CommonUtils.getLastExecutedCommand; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** - * // todo + * Test to check cache implementation based on JSR-107. * * @author dmgcodevil */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {AopCglibConfig.class, Jsr107CacheTest.CacheTestConfig.class}) public class Jsr107CacheTest { - // todo create test + private UserService userService; + + @Autowired + private ApplicationContext applicationContext; + + @Before + public void setUp() throws Exception { + userService = applicationContext.getBean(UserService.class); + } + + /** + * Get-Set-Get with Request Cache Invalidation Test. + *

+ * given: + * command to get user by id, see {@link UserService#getUserById(String)} + * command to update user, see {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.spring.domain.User)} + *

+ * when: + * 1. call {@link UserService#getUserById(String)} + * 2. call {@link UserService#getUserById(String)} + * 3. call {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.spring.domain.User)} + * 4. call {@link UserService#getUserById(String)} + *

+ * then: + * at the first time "getUserById" command shouldn't retrieve value from cache + * at the second time "getUserById" command should retrieve value from cache + * "update" method should update an user and flush cache related to "getUserById" command + * after "update" method execution "getUserById" command shouldn't retrieve value from cache + */ + @Test + public void testGetSetGetUserCache_givenTwoCommands() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); // initial name value + + user = userService.getUserById("1"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("name", user.getName()); // same name + + // create new user with same id but with new name + user = new User("1", "new_name"); + userService.update(user); // update the user + + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("new_name", user.getName()); + + + } finally { + context.shutdown(); + } + + // start a new request context + context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } finally { + context.shutdown(); + } + } + + @Test + public void testGetSetGetUserCache_givenOneCommandAndOneMethodAnnotatedWithCacheRemove() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + + // given + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); // initial name value + + user = userService.getUserById("1"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("name", user.getName()); // same name + + // when + userService.updateName("1", "new_name"); // update the user name + + // then + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("new_name", user.getName()); + + + } finally { + context.shutdown(); + } + + // start a new request context + context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } finally { + context.shutdown(); + } + } + + public static class UserService { + private Map storage = new ConcurrentHashMap(); + + @PostConstruct + private void init() { + storage.put("1", new User("1", "name")); + } + + @CacheResult + @HystrixCommand + public User getUserById(@CacheKey String id) { + return storage.get(id); + } + + @CacheRemove(cacheName = "getUserById", cacheKeyGenerator = UserCacheKeyGenerator.class) + @HystrixCommand + public void update(@CacheKey User user) { + storage.put(user.getId(), user); + } + + @CacheRemove(cacheName = "getUserById") + public void updateName(@CacheKey String id, String name) { + storage.get(id).setName(name); + } - public static class UserService { + } - @CacheResult(cacheKeyGenerator = DefaultCacheKeyGenerator.class) - @HystrixCommand(cacheKeyMethod = "getUserIdCacheKey") - public User getUser(String id, String name) { - return new User(id, name + id); // it should be network call - } + public static class UserCacheKeyGenerator implements HystrixCacheKeyGenerator { + @Override + public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { + CacheInvocationParameter cacheInvocationParameter = cacheKeyInvocationContext.getKeyParameters()[0]; + User user = (User) cacheInvocationParameter.getValue(); + return new DefaultHystrixGeneratedCacheKey(user.getId()); + } + } - private String getUserIdCacheKey(String id, String name) { - return id + name; - } - } + /** + * Spring configuration. + */ + @Configurable + public static class CacheTestConfig { + @Bean + @Scope(value = "prototype") + public UserService userService() { + return new UserService(); + } + @Bean + public HystrixCacheAspect hystrixCacheAspect() { + return new HystrixCacheAspect(); + } + } } From c876952e28bb76b5dc75b69302530a711d52a981 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Sun, 4 Jan 2015 23:26:13 +0300 Subject: [PATCH 04/15] added todos --- .../javanica/cache/AbstractCacheKeyInvocationContext.java | 1 + .../hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java | 1 + 2 files changed, 2 insertions(+) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java index 8d7129490..829233225 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java @@ -68,6 +68,7 @@ public AbstractCacheKeyInvocationContext(A cacheAnnotation, Object target, Metho cacheInvocationParameters.add(new CacheInvocationParameterImpl(paramType, val, parametersAnnotations[pos], pos)); } // get key parameters + // todo: If no parameters are annotated with {@link CacheKey} or {@link CacheValue} then all parameters are included keyParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, new Predicate() { @Override public boolean apply(CacheInvocationParameter input) { diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java index 61583c008..8d8cf53e9 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java @@ -40,6 +40,7 @@ public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext Date: Mon, 5 Jan 2015 23:06:26 +0300 Subject: [PATCH 05/15] #iss380: If no CacheKey annotation exists then all parameters are included --- .../AbstractCacheKeyInvocationContext.java | 25 +++++-------------- .../cache/DefaultCacheKeyGenerator.java | 1 - .../CacheKeyInvocationContextFactoryTest.java | 3 ++- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java index 829233225..b2b9a36b1 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java @@ -24,7 +24,6 @@ import javax.cache.annotation.CacheInvocationParameter; import javax.cache.annotation.CacheKey; import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheValue; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; @@ -43,7 +42,6 @@ public abstract class AbstractCacheKeyInvocationContext im private A cacheAnnotation; private List keyParameters; private List cacheInvocationParameters; - private CacheInvocationParameter valueParameter; /** * Creates CacheKeyInvocationContext. @@ -68,27 +66,12 @@ public AbstractCacheKeyInvocationContext(A cacheAnnotation, Object target, Metho cacheInvocationParameters.add(new CacheInvocationParameterImpl(paramType, val, parametersAnnotations[pos], pos)); } // get key parameters - // todo: If no parameters are annotated with {@link CacheKey} or {@link CacheValue} then all parameters are included keyParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, new Predicate() { @Override public boolean apply(CacheInvocationParameter input) { return isAnnotationPresent(input, CacheKey.class); } })); - List valueParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, - new Predicate() { - @Override - public boolean apply(CacheInvocationParameter input) { - return isAnnotationPresent(input, CacheValue.class); - } - })); - if (valueParameters.size() > 1) { - throw new RuntimeException("only one method parameter can be annotated with CacheValue annotation"); - } - if (CollectionUtils.isNotEmpty(valueParameters)) { - valueParameter = valueParameters.get(0); - } - } /** @@ -96,7 +79,11 @@ public boolean apply(CacheInvocationParameter input) { */ @Override public CacheInvocationParameter[] getKeyParameters() { - return keyParameters.toArray(new CacheInvocationParameter[keyParameters.size()]); + if (CollectionUtils.isEmpty(keyParameters)) { + return getAllParameters(); + } else { + return keyParameters.toArray(new CacheInvocationParameter[keyParameters.size()]); + } } /** @@ -104,7 +91,7 @@ public CacheInvocationParameter[] getKeyParameters() { */ @Override public CacheInvocationParameter getValueParameter() { - return valueParameter; + return null; } /** diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java index 8d8cf53e9..61583c008 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java @@ -40,7 +40,6 @@ public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext Date: Sat, 10 Jan 2015 00:19:12 +0300 Subject: [PATCH 06/15] iss380: javanica cache implementation --- hystrix-contrib/hystrix-javanica/build.gradle | 1 - .../aop/aspectj/HystrixCacheAspect.java | 19 ++- .../AbstractCacheKeyInvocationContext.java | 160 ------------------ .../cache/CacheInvocationContext.java | 124 ++++++++++++++ .../cache/CacheInvocationContextFactory.java | 76 +++++++++ .../cache/CacheInvocationParameter.java | 81 +++++++++ .../cache/CacheInvocationParameterImpl.java | 76 --------- .../cache/CacheKeyGeneratorFactory.java | 78 --------- .../CacheKeyInvocationContextFactory.java | 70 -------- .../CacheRemoveCacheKeyInvocationContext.java | 37 ---- .../cache/CacheResultInvocationContext.java | 37 ---- .../cache/DefaultCacheKeyGenerator.java | 49 ------ .../cache/HystrixCacheKeyGenerator.java | 77 ++++++++- .../cache/HystrixGeneratedCacheKey.java | 8 +- .../cache/HystrixRequestCacheManager.java | 16 +- .../javanica/cache/annotation/CacheKey.java | 35 ++++ .../cache/annotation/CacheRemove.java | 37 ++++ .../cache/annotation/CacheResult.java | 35 ++++ .../command/AbstractHystrixCommand.java | 27 +-- .../AbstractHystrixCommandFactory.java | 4 +- .../command/HystrixCommandBuilder.java | 23 +-- ...> HystrixCacheKeyGenerationException.java} | 10 +- .../exception/HystrixCachingException.java | 22 +++ .../CacheKeyInvocationContextFactoryTest.java | 143 ++++++++-------- .../test/spring/cache/Jsr107CacheTest.java | 152 ++++++++++++++--- .../javanica/test/spring/domain/Profile.java | 16 ++ .../javanica/test/spring/domain/User.java | 10 ++ 27 files changed, 762 insertions(+), 661 deletions(-) delete mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java rename hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/{HystrixCacheKeyGeneratorException.java => HystrixCacheKeyGenerationException.java} (71%) create mode 100644 hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/Profile.java diff --git a/hystrix-contrib/hystrix-javanica/build.gradle b/hystrix-contrib/hystrix-javanica/build.gradle index dde39b7ed..dea764711 100644 --- a/hystrix-contrib/hystrix-javanica/build.gradle +++ b/hystrix-contrib/hystrix-javanica/build.gradle @@ -13,7 +13,6 @@ dependencies { compile 'com.google.guava:guava:15.0' compile 'commons-collections:commons-collections:3.2.1' compile 'org.apache.commons:commons-lang3:3.1' - compile 'javax.cache:cache-api:1.0.0' compile 'com.google.code.findbugs:jsr305:2.0.0' testCompile 'junit:junit-dep:4.10' testCompile 'org.springframework:spring-core:4.0.0.RELEASE' diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java index 051bd92f5..734202a69 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java @@ -1,5 +1,5 @@ /** - * Copyright 2012 Netflix, Inc. + * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,20 @@ */ package com.netflix.hystrix.contrib.javanica.aop.aspectj; -import com.netflix.hystrix.contrib.javanica.cache.CacheKeyInvocationContextFactory; + +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory; import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.command.ExecutionType; import com.netflix.hystrix.contrib.javanica.command.MetaHolder; import org.apache.commons.lang3.Validate; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; + -import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheRemove; import java.lang.reflect.Method; import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; @@ -38,7 +41,7 @@ @Aspect public class HystrixCacheAspect { - @Pointcut("@annotation(javax.cache.annotation.CacheRemove) && !@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") + @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove) && !@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)") public void cacheRemoveAnnotationPointcut() { } @@ -49,8 +52,10 @@ public Object methodsAnnotatedWithCacheRemove(final ProceedingJoinPoint joinPoin Object[] args = joinPoint.getArgs(); Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint); MetaHolder metaHolder = MetaHolder.builder() - .args(args).method(method).obj(obj).build(); - CacheKeyInvocationContext context = CacheKeyInvocationContextFactory + .args(args).method(method).obj(obj) + .executionType(ExecutionType.SYNCHRONOUS) + .build(); + CacheInvocationContext context = CacheInvocationContextFactory .createCacheRemoveInvocationContext(metaHolder); HystrixRequestCacheManager.getInstance().clearCache(context); return joinPoint.proceed(); diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java deleted file mode 100644 index b2b9a36b1..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/AbstractCacheKeyInvocationContext.java +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright 2012 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.hystrix.contrib.javanica.cache; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.apache.commons.collections.CollectionUtils; - -import javax.cache.annotation.CacheInvocationParameter; -import javax.cache.annotation.CacheKey; -import javax.cache.annotation.CacheKeyInvocationContext; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * Base implementation of {@link CacheKeyInvocationContext} interface. - * - * @author dmgcodevil - */ -public abstract class AbstractCacheKeyInvocationContext implements CacheKeyInvocationContext { - - private Method method; - private Object target; - private A cacheAnnotation; - private List keyParameters; - private List cacheInvocationParameters; - - /** - * Creates CacheKeyInvocationContext. - * - * @param cacheAnnotation the cache annotation, for example: {@link javax.cache.annotation.CacheResult} or - * {@link javax.cache.annotation.CacheRemove} - * @param target the target object - * @param method the current method - * @param args the arguments of the current method - */ - public AbstractCacheKeyInvocationContext(A cacheAnnotation, Object target, Method method, Object... args) { - this.method = method; - this.target = target; - this.cacheAnnotation = cacheAnnotation; - Class[] parametersTypes = method.getParameterTypes(); - Annotation[][] parametersAnnotations = method.getParameterAnnotations(); - int parameterCount = parametersTypes.length; - cacheInvocationParameters = new ArrayList(parameterCount); - for (int pos = 0; pos < parameterCount; pos++) { - Class paramType = parametersTypes[pos]; - Object val = args[pos]; - cacheInvocationParameters.add(new CacheInvocationParameterImpl(paramType, val, parametersAnnotations[pos], pos)); - } - // get key parameters - keyParameters = Lists.newArrayList(Iterables.filter(cacheInvocationParameters, new Predicate() { - @Override - public boolean apply(CacheInvocationParameter input) { - return isAnnotationPresent(input, CacheKey.class); - } - })); - } - - /** - * {@inheritDoc} - */ - @Override - public CacheInvocationParameter[] getKeyParameters() { - if (CollectionUtils.isEmpty(keyParameters)) { - return getAllParameters(); - } else { - return keyParameters.toArray(new CacheInvocationParameter[keyParameters.size()]); - } - } - - /** - * {@inheritDoc} - */ - @Override - public CacheInvocationParameter getValueParameter() { - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public Object getTarget() { - return target; - } - - /** - * {@inheritDoc} - */ - @Override - public CacheInvocationParameter[] getAllParameters() { - return cacheInvocationParameters.toArray(new CacheInvocationParameter[cacheInvocationParameters.size()]); - } - - /** - * {@inheritDoc} - */ - @Override - public T unwrap(Class cls) { - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public Method getMethod() { - return method; - } - - /** - * {@inheritDoc} - */ - @Override - public Set getAnnotations() { - return Sets.newHashSet(method.getAnnotations()); - } - - /** - * {@inheritDoc} - */ - @Override - public A getCacheAnnotation() { - return cacheAnnotation; - } - - /** - * {@inheritDoc} - */ - @Override - public abstract String getCacheName(); - - private boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { - return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { - @Override - public boolean apply(Annotation input) { - return input.annotationType().equals(annotation); - } - }).isPresent(); - } - -} \ No newline at end of file diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java new file mode 100644 index 000000000..19d7ac25b --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java @@ -0,0 +1,124 @@ +/** + * Copyright 2015 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.hystrix.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.contrib.javanica.command.ExecutionType; +import com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction; +import org.apache.commons.collections.CollectionUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +/** + * // todo + * + * @author dmgcodevil + */ +public class CacheInvocationContext { + + private Method method; + private Object target; + private MethodExecutionAction cacheKeyMethod; + private ExecutionType executionType; + private A cacheAnnotation; + + private List parameters = Collections.emptyList(); + private List keyParameters = Collections.emptyList(); + + public CacheInvocationContext(A cacheAnnotation, MethodExecutionAction cacheKeyMethod, + ExecutionType executionType, Object target, Method method, Object... args) { + this.method = method; + this.target = target; + this.cacheKeyMethod = cacheKeyMethod; + this.cacheAnnotation = cacheAnnotation; + this.executionType = executionType; + Class[] parametersTypes = method.getParameterTypes(); + Annotation[][] parametersAnnotations = method.getParameterAnnotations(); + int parameterCount = parametersTypes.length; + if (parameterCount > 0) { + ImmutableList.Builder parametersBuilder = ImmutableList.builder(); + for (int pos = 0; pos < parameterCount; pos++) { + Class paramType = parametersTypes[pos]; + Object val = args[pos]; + parametersBuilder.add(new CacheInvocationParameter(paramType, val, parametersAnnotations[pos], pos)); + } + parameters = parametersBuilder.build(); + // get key parameters + Iterable filtered = Iterables.filter(parameters, new Predicate() { + @Override + public boolean apply(CacheInvocationParameter input) { + return input.hasCacheKeyAnnotation(); + } + }); + if (filtered.iterator().hasNext()) { + keyParameters = ImmutableList.builder().addAll(filtered).build(); + } else { + keyParameters = parameters; + } + } + } + + public Method getMethod() { + return method; + } + + public Object getTarget() { + return target; + } + + public A getCacheAnnotation() { + return cacheAnnotation; + } + + /** + * todo + * + * @return immutable list of {@link CacheInvocationParameter} objects + */ + public List getAllParameters() { + return parameters; + } + + /** + * // todo + * + * @return immutable list of {@link CacheInvocationParameter} objects + */ + public List getKeyParameters() { + return keyParameters; + } + + public boolean hasKeyParameters() { + return CollectionUtils.isNotEmpty(keyParameters); + } + + public String getCacheKeyMethodName() { + return cacheKeyMethod != null ? cacheKeyMethod.getMethod().getName() : null; + } + + public MethodExecutionAction getCacheKeyMethod() { + return cacheKeyMethod; + } + + public ExecutionType getExecutionType() { + return executionType; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java new file mode 100644 index 000000000..b3388ddcd --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java @@ -0,0 +1,76 @@ +/** + * Copyright 2015 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.hystrix.contrib.javanica.cache; + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Method; + +import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getDeclaredMethod; + +/** + * // todo + * + * @author dmgcodevil + */ +public class CacheInvocationContextFactory { + + public static CacheInvocationContext createCacheResultInvocationContext(MetaHolder metaHolder) { + if (metaHolder.getMethod().isAnnotationPresent(CacheResult.class)) { + Method method = metaHolder.getMethod(); + CacheResult cacheResult = method.getAnnotation(CacheResult.class); + MethodExecutionAction cacheKeyMethod = createCacheKeyAction(cacheResult.cacheKeyMethod(), metaHolder); + return new CacheInvocationContext(cacheResult, cacheKeyMethod, + metaHolder.getExecutionType(), metaHolder.getObj(), method, metaHolder.getArgs()); + } + return null; + } + + public static CacheInvocationContext createCacheRemoveInvocationContext(MetaHolder metaHolder) { + if (metaHolder.getMethod().isAnnotationPresent(CacheRemove.class)) { + Method method = metaHolder.getMethod(); + CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class); + MethodExecutionAction cacheKeyMethod = createCacheKeyAction(cacheRemove.cacheKeyMethod(), metaHolder); + return new CacheInvocationContext(cacheRemove, cacheKeyMethod, + metaHolder.getExecutionType(), metaHolder.getObj(), method, metaHolder.getArgs()); + } + return null; + } + + private static MethodExecutionAction createCacheKeyAction(String method, MetaHolder metaHolder) { + MethodExecutionAction cacheKeyAction = null; + if (StringUtils.isNotBlank(method)) { + Method cacheKeyMethod = getDeclaredMethod(metaHolder.getObj().getClass(), method, + metaHolder.getMethod().getParameterTypes()); + if (cacheKeyMethod == null) { + throw new HystrixCachingException("method with name '" + method + "' doesn't exist in class '" + + metaHolder.getObj().getClass() + "'"); + } + if (!cacheKeyMethod.getReturnType().equals(String.class)) { + throw new HystrixCachingException("return type of cacheKey method must be String. Method: '" + method + "', Class: '" + + metaHolder.getObj().getClass() + "'"); + } + cacheKeyAction = new MethodExecutionAction(metaHolder.getObj(), cacheKeyMethod, metaHolder.getArgs()); + } + return cacheKeyAction; + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java new file mode 100644 index 000000000..0130800cd --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java @@ -0,0 +1,81 @@ +/** + * Copyright 2015 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.hystrix.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Set; + +/** + * // todo + * + * @author dmgcodevil + */ +public class CacheInvocationParameter { + + private final Class rawType; + private final Object value; + private final CacheKey cacheKeyAnnotation; + private final Set annotations; + private final int position; + + public CacheInvocationParameter(Class rawType, Object value, Annotation[] annotations, int position) { + this.rawType = rawType; + this.value = value; + this.annotations = ImmutableSet.builder().addAll(Arrays.asList(annotations)).build(); + this.position = position; + this.cacheKeyAnnotation = (CacheKey) cacheKeyAnnotation(); + } + + public Set getAnnotations() { + return annotations; + } + + public CacheKey getCacheKeyAnnotation() { + return cacheKeyAnnotation; + } + + public boolean hasCacheKeyAnnotation() { + return cacheKeyAnnotation != null; + } + + public Class getRawType() { + return rawType; + } + + public Object getValue() { + return value; + } + + public int getPosition() { + return position; + } + + private Annotation cacheKeyAnnotation() { + return Iterables.tryFind(annotations, new Predicate() { + @Override + public boolean apply(Annotation input) { + return input.annotationType().equals(CacheKey.class); + } + }).orNull(); + } + +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java deleted file mode 100644 index 0fce43f7e..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2012 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.hystrix.contrib.javanica.cache; - -import com.google.common.collect.Sets; - -import javax.cache.annotation.CacheInvocationParameter; -import java.lang.annotation.Annotation; -import java.util.Set; - -/** - * Implementation of {@link CacheInvocationParameter}. - * - * @author dmgcodevil - */ -public class CacheInvocationParameterImpl implements CacheInvocationParameter { - - private Class rawType; - private Object value; - private Set annotations; - private Set> annotationTypes; - private int parameterPosition; - - public CacheInvocationParameterImpl(Class paramType, Object value, Annotation[] annotations, int pos) { - this.value = value; - this.rawType = paramType; - this.annotations = Sets.newHashSet(annotations); - this.parameterPosition = pos; - } - - /** - * {@inheritDoc} - */ - @Override - public Class getRawType() { - return rawType; - } - - /** - * {@inheritDoc} - */ - @Override - public Object getValue() { - return value; - } - - /** - * {@inheritDoc} - */ - @Override - public Set getAnnotations() { - return annotations; - } - - /** - * {@inheritDoc} - */ - @Override - public int getParameterPosition() { - return parameterPosition; - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java deleted file mode 100644 index 4581f7fdc..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyGeneratorFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright 2012 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.hystrix.contrib.javanica.cache; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.netflix.hystrix.contrib.javanica.exception.HystrixCacheKeyGeneratorException; - -import javax.cache.annotation.CacheKeyGenerator; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * Factory to create {@link HystrixCacheKeyGenerator}. - * - * @author dmgcodevil - */ -public class CacheKeyGeneratorFactory { - - private static final CacheKeyGeneratorFactory INSTANCE = new CacheKeyGeneratorFactory(); - - private CacheKeyGeneratorFactory() { - } - - public static CacheKeyGeneratorFactory getInstance() { - return INSTANCE; - } - - private Supplier defaultCacheKeyGenerator = Suppliers.memoize(new Supplier() { - @Override - public HystrixCacheKeyGenerator get() { - return new DefaultCacheKeyGenerator(); - } - }); - - private ConcurrentMap, HystrixCacheKeyGenerator> generators - = new ConcurrentHashMap, HystrixCacheKeyGenerator>(); - - /** - * Instantiates new instance of the given type or returns exiting - * one if it's present in the factory. - * - * @param cacheKeyGenerator the certain class type that extends {@link HystrixCacheKeyGenerator} interface. - * @return new instance with specified type or already existing one - * @throws HystrixCacheKeyGeneratorException - */ - public HystrixCacheKeyGenerator create(Class cacheKeyGenerator) throws HystrixCacheKeyGeneratorException { - if (cacheKeyGenerator == null || CacheKeyGenerator.class.equals(cacheKeyGenerator)) { - return defaultCacheKeyGenerator.get(); - } - try { - HystrixCacheKeyGenerator generator = generators.putIfAbsent(cacheKeyGenerator, - (HystrixCacheKeyGenerator) cacheKeyGenerator.newInstance()); - if (generator == null) { - generator = generators.get(cacheKeyGenerator); - } - return generator; - } catch (InstantiationException e) { - throw new HystrixCacheKeyGeneratorException(e); - } catch (IllegalAccessException e) { - throw new HystrixCacheKeyGeneratorException(e); - } - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java deleted file mode 100644 index 1528c093a..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactory.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2012 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.hystrix.contrib.javanica.cache; - -import com.netflix.hystrix.contrib.javanica.command.MetaHolder; - -import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheRemove; -import javax.cache.annotation.CacheResult; - -/** - * Factory to create specific {@link CacheKeyInvocationContext} instances. - * - * @author dmgcodevil - */ -public final class CacheKeyInvocationContextFactory { - - private CacheKeyInvocationContextFactory() { - throw new UnsupportedOperationException("it's prohibited to create instances of this class"); - } - - /** - * Creates new instance of {@link CacheResultInvocationContext} if {@link CacheResult} annotation - * is present for the specified method, see {@link MetaHolder#getMethod()}. - * - * @param metaHolder the meta holder contains information about current executable method, see {@link MetaHolder#getMethod()} - * @return new instance of {@link CacheResultInvocationContext} or null if the given method doesn't have {@link CacheResult} annotation - */ - public static CacheKeyInvocationContext createCacheResultInvocationContext(MetaHolder metaHolder) { - if (metaHolder.getMethod().isAnnotationPresent(CacheResult.class)) { - return new CacheResultInvocationContext( - metaHolder.getMethod().getAnnotation(CacheResult.class), - metaHolder.getObj(), - metaHolder.getMethod(), - metaHolder.getArgs()); - } - return null; - } - - /** - * Creates new instance of {@link CacheRemoveCacheKeyInvocationContext} if {@link CacheRemove} annotation - * is present for the specified method, see {@link MetaHolder#getMethod()}. - * - * @param metaHolder the meta holder contains information about current executable method, see {@link MetaHolder#getMethod()}. - * @return {@link CacheRemoveCacheKeyInvocationContext} or null if the given method doesn't have {@link CacheRemove} annotation - */ - public static CacheKeyInvocationContext createCacheRemoveInvocationContext(MetaHolder metaHolder) { - if (metaHolder.getMethod().isAnnotationPresent(CacheRemove.class)) { - return new CacheRemoveCacheKeyInvocationContext( - metaHolder.getMethod().getAnnotation(CacheRemove.class), - metaHolder.getObj(), - metaHolder.getMethod(), - metaHolder.getArgs()); - } - return null; - } -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java deleted file mode 100644 index 46afc92de..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheRemoveCacheKeyInvocationContext.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012 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.hystrix.contrib.javanica.cache; - -import javax.cache.annotation.CacheRemove; -import java.lang.reflect.Method; - -/** - * Concrete implementation of {@link AbstractCacheKeyInvocationContext} provides information of invocation - * context for {@link CacheRemove} annotation. - * - * @author dmgcodevil - */ -public class CacheRemoveCacheKeyInvocationContext extends AbstractCacheKeyInvocationContext { - - public CacheRemoveCacheKeyInvocationContext(CacheRemove cacheAnnotation, Object target, Method method, Object... args) { - super(cacheAnnotation, target, method, args); - } - - @Override - public String getCacheName() { - return getCacheAnnotation().cacheName(); - } -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java deleted file mode 100644 index 85923f092..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheResultInvocationContext.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012 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.hystrix.contrib.javanica.cache; - -import javax.cache.annotation.CacheResult; -import java.lang.reflect.Method; - -/** - * Concrete implementation of {@link AbstractCacheKeyInvocationContext} provides information of invocation - * context for {@link CacheResult} annotation. - * - * @author dmgcodevil - */ -public class CacheResultInvocationContext extends AbstractCacheKeyInvocationContext { - - public CacheResultInvocationContext(CacheResult cacheAnnotation, Object target, Method method, Object... args) { - super(cacheAnnotation, target, method, args); - } - - @Override - public String getCacheName() { - return null; - } -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java deleted file mode 100644 index 61583c008..000000000 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/DefaultCacheKeyGenerator.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2012 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.hystrix.contrib.javanica.cache; - -import javax.cache.annotation.CacheInvocationParameter; -import javax.cache.annotation.CacheKeyInvocationContext; -import java.lang.annotation.Annotation; - -/** - * Default implementation of {@link HystrixCacheKeyGenerator} creates cache keys with {@link DefaultHystrixGeneratedCacheKey} type. - * - * @author dmgcodevil - */ -public class DefaultCacheKeyGenerator implements HystrixCacheKeyGenerator { - - /** - * Calls toString() method for each parameter annotated with {@link javax.cache.annotation.CacheKey} annotation and - * gathers results together into single string which subsequently is used as hystrix cache key. - * - * @param cacheKeyInvocationContext runtime information about an intercepted method invocation for a method - * annotated with {@link javax.cache.annotation.CacheResult} or {@link javax.cache.annotation.CacheRemove} - * @return see {@link DefaultHystrixGeneratedCacheKey} - */ - @Override - public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { - if (cacheKeyInvocationContext.getKeyParameters() == null || cacheKeyInvocationContext.getKeyParameters().length == 0) { - return DefaultHystrixGeneratedCacheKey.EMPTY; - } - StringBuilder cacheKeyBuilder = new StringBuilder(); - for (CacheInvocationParameter parameter : cacheKeyInvocationContext.getKeyParameters()) { - cacheKeyBuilder.append(parameter.getValue()); - } - return new DefaultHystrixGeneratedCacheKey(cacheKeyBuilder.toString()); - } - -} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java index cb25284d3..3c7fe41e2 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright 2012 Netflix, Inc. + * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,80 @@ */ package com.netflix.hystrix.contrib.javanica.cache; -import javax.cache.annotation.CacheKeyGenerator; -import javax.cache.annotation.CacheKeyInvocationContext; + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCacheKeyGenerationException; +import org.apache.commons.lang3.StringUtils; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.List; /** - * Specific interface to adopt {@link CacheKeyGenerator} for Hystrix environment. + * Creates cache keys with {@link DefaultHystrixGeneratedCacheKey} type. * * @author dmgcodevil */ -public interface HystrixCacheKeyGenerator extends CacheKeyGenerator { +public class HystrixCacheKeyGenerator { + + private static final HystrixCacheKeyGenerator INSTANCE = new HystrixCacheKeyGenerator(); + + public static HystrixCacheKeyGenerator getInstance() { + return INSTANCE; + } + + public HystrixGeneratedCacheKey generateCacheKey(CacheInvocationContext cacheInvocationContext) throws HystrixCacheKeyGenerationException { + MethodExecutionAction cacheKeyMethod = cacheInvocationContext.getCacheKeyMethod(); + if (cacheKeyMethod != null) { + try { + HystrixGeneratedCacheKey cacheKey = new DefaultHystrixGeneratedCacheKey((String) cacheKeyMethod.execute(cacheInvocationContext.getExecutionType())); + return cacheKey; + } catch (Throwable throwable) { + throw new HystrixCacheKeyGenerationException(throwable); + } + } else { + if (cacheInvocationContext.hasKeyParameters()) { + StringBuilder cacheKeyBuilder = new StringBuilder(); + for (CacheInvocationParameter parameter : cacheInvocationContext.getKeyParameters()) { + CacheKey cacheKey = parameter.getCacheKeyAnnotation(); + if (StringUtils.isNotBlank(cacheKey.value())) { + Object value = getPropertyValue(Arrays.asList(StringUtils.split(cacheKey.value(), ".")), parameter.getValue()); + cacheKeyBuilder.append(value); + } else { + cacheKeyBuilder.append(parameter.getValue()); + } + } + return new DefaultHystrixGeneratedCacheKey(cacheKeyBuilder.toString()); + } else { + return DefaultHystrixGeneratedCacheKey.EMPTY; + } + } + } + + private Object getPropertyValue(List names, Object obj) throws HystrixCacheKeyGenerationException { + for (String name : names) { + if (obj != null) { + obj = getPropertyValue(name, obj); + } + } + return obj; + } + + private Object getPropertyValue(String name, Object obj) throws HystrixCacheKeyGenerationException { + try { + return new PropertyDescriptor(name, obj.getClass()) + .getReadMethod().invoke(obj); + } catch (IllegalAccessException e) { + throw new HystrixCacheKeyGenerationException(e); + } catch (InvocationTargetException e) { + throw new HystrixCacheKeyGenerationException(e); + } catch (IntrospectionException e) { + throw new HystrixCacheKeyGenerationException(e); + } + } - @Override - HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java index 61b868ab7..101e443f9 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixGeneratedCacheKey.java @@ -1,5 +1,5 @@ /** - * Copyright 2012 Netflix, Inc. + * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,12 @@ */ package com.netflix.hystrix.contrib.javanica.cache; -import javax.cache.annotation.GeneratedCacheKey; - /** - * Specific interface to adopt {@link GeneratedCacheKey} for Hystrix environment. + * Specific interface to adopt {@link HystrixGeneratedCacheKey} for Hystrix environment. * * @author dmgcodevil */ -public interface HystrixGeneratedCacheKey extends GeneratedCacheKey { +public interface HystrixGeneratedCacheKey { /** * Key to be used for request caching. diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java index 479bdaebc..db9c356f8 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixRequestCacheManager.java @@ -1,5 +1,5 @@ /** - * Copyright 2012 Netflix, Inc. + * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,9 @@ import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixRequestCache; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault; -import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheRemove; /** * Cache manager to work with {@link HystrixRequestCache}. @@ -44,11 +43,12 @@ public static HystrixRequestCacheManager getInstance() { * @param context the runtime information about an intercepted method invocation for a method * annotated with {@link CacheRemove} annotation */ - public void clearCache(CacheKeyInvocationContext context) { - String cacheName = context.getCacheName(); - HystrixCacheKeyGenerator keyGenerator = CacheKeyGeneratorFactory.getInstance() - .create(context.getCacheAnnotation().cacheKeyGenerator()); - String key = keyGenerator.generateCacheKey(context).getCacheKey(); + public void clearCache(CacheInvocationContext context) { + HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); + String cacheName = context.getCacheAnnotation().commandKey(); + HystrixGeneratedCacheKey hystrixGeneratedCacheKey = + defaultCacheKeyGenerator.generateCacheKey(context); + String key = hystrixGeneratedCacheKey.getCacheKey(); HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey(cacheName), HystrixConcurrencyStrategyDefault.getInstance()).clear(key); } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java new file mode 100644 index 000000000..90bc9fab8 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java @@ -0,0 +1,35 @@ +/** + * Copyright 2015 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.hystrix.contrib.javanica.cache.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * // todo + * + * @author dmgcodevil + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CacheKey { + + public String value() default ""; +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java new file mode 100644 index 000000000..4467a5011 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java @@ -0,0 +1,37 @@ +/** + * Copyright 2015 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.hystrix.contrib.javanica.cache.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * // todo + * + * @author dmgcodevil + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface CacheRemove { + + String commandKey(); + + String cacheKeyMethod() default ""; +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java new file mode 100644 index 000000000..d7deaa0f4 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java @@ -0,0 +1,35 @@ +/** + * Copyright 2015 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.hystrix.contrib.javanica.cache.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * // todo + * + * @author dmgcodevil + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CacheResult { + String cacheKeyMethod() default ""; + +} diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java index ea9dc0165..d4c3da9a8 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java @@ -18,18 +18,19 @@ import com.google.common.base.Throwables; import com.netflix.hystrix.HystrixCollapser; -import com.netflix.hystrix.contrib.javanica.cache.CacheKeyGeneratorFactory; -import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; import com.netflix.hystrix.contrib.javanica.cache.HystrixCacheKeyGenerator; +import com.netflix.hystrix.contrib.javanica.cache.HystrixGeneratedCacheKey; +import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; import com.netflix.hystrix.contrib.javanica.exception.CommandActionExecutionException; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.exception.HystrixRuntimeException; import javax.annotation.concurrent.ThreadSafe; -import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheRemove; -import javax.cache.annotation.CacheResult; + import java.util.Collection; import java.util.Map; @@ -43,12 +44,13 @@ public abstract class AbstractHystrixCommand extends com.netflix.hystrix.Hyst private CommandActions commandActions; private final Map commandProperties; - private CacheKeyInvocationContext cacheResultInvocationContext; - private CacheKeyInvocationContext cacheRemoveInvocationContext; + private CacheInvocationContext cacheResultInvocationContext; + private CacheInvocationContext cacheRemoveInvocationContext; private final Collection> collapsedRequests; private final Class[] ignoreExceptions; private final ExecutionType executionType; - private final CacheKeyGeneratorFactory cacheKeyGeneratorFactory = CacheKeyGeneratorFactory.getInstance(); + //private final CacheKeyGeneratorFactory cacheKeyGeneratorFactory = CacheKeyGeneratorFactory.getInstance(); + HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); protected AbstractHystrixCommand(HystrixCommandBuilder builder) { super(builder.getSetterBuilder().build()); @@ -125,12 +127,13 @@ public ExecutionType getExecutionType() { */ @Override protected String getCacheKey() { - String key; + String key = null; if (cacheResultInvocationContext != null) { - HystrixCacheKeyGenerator keyGenerator = cacheKeyGeneratorFactory - .create(cacheResultInvocationContext.getCacheAnnotation().cacheKeyGenerator()); - return keyGenerator.generateCacheKey(cacheResultInvocationContext).getCacheKey(); + HystrixGeneratedCacheKey hystrixGeneratedCacheKey = + defaultCacheKeyGenerator.generateCacheKey(cacheResultInvocationContext); + return hystrixGeneratedCacheKey.getCacheKey(); } + // deprecated approach if (commandActions.getCacheKeyAction() != null) { key = String.valueOf(commandActions.getCacheKeyAction().execute(executionType)); diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java index e296b3f05..777078f24 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java @@ -30,8 +30,8 @@ import java.util.Collections; import java.util.Map; -import static com.netflix.hystrix.contrib.javanica.cache.CacheKeyInvocationContextFactory.createCacheRemoveInvocationContext; -import static com.netflix.hystrix.contrib.javanica.cache.CacheKeyInvocationContextFactory.createCacheResultInvocationContext; +import static com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory.createCacheRemoveInvocationContext; +import static com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory.createCacheResultInvocationContext; /** * Base implementation of {@link HystrixCommandFactory} interface. diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java index 34fb63787..04458c7ba 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/HystrixCommandBuilder.java @@ -16,10 +16,11 @@ package com.netflix.hystrix.contrib.javanica.command; import com.netflix.hystrix.HystrixCollapser; +import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; + -import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheRemove; -import javax.cache.annotation.CacheResult; import java.util.Collection; import java.util.Map; @@ -33,8 +34,8 @@ public class HystrixCommandBuilder { private CommandSetterBuilder setterBuilder; private CommandActions commandActions; private Map commandProperties; - private CacheKeyInvocationContext cacheResultInvocationContext; - private CacheKeyInvocationContext cacheRemoveInvocationContext; + private CacheInvocationContext cacheResultInvocationContext; + private CacheInvocationContext cacheRemoveInvocationContext; private Collection> collapsedRequests; private Class[] ignoreExceptions; private ExecutionType executionType; @@ -51,11 +52,11 @@ public Map getCommandProperties() { return commandProperties; } - public CacheKeyInvocationContext getCacheResultInvocationContext() { + public CacheInvocationContext getCacheResultInvocationContext() { return cacheResultInvocationContext; } - public CacheKeyInvocationContext getCacheRemoveInvocationContext() { + public CacheInvocationContext getCacheRemoveInvocationContext() { return cacheRemoveInvocationContext; } @@ -105,23 +106,23 @@ public HystrixCommandBuilder commandProperties(Map pCommandPrope } /** - * Sets CacheResult invocation context, see {@link CacheKeyInvocationContext} and {@link CacheResult}. + * Sets CacheResult invocation context, see {@link CacheInvocationContext} and {@link CacheResult}. * * @param pCacheResultInvocationContext the CacheResult invocation context * @return this {@link HystrixCommandBuilder} */ - public HystrixCommandBuilder cacheResultInvocationContext(CacheKeyInvocationContext pCacheResultInvocationContext) { + public HystrixCommandBuilder cacheResultInvocationContext(CacheInvocationContext pCacheResultInvocationContext) { this.cacheResultInvocationContext = pCacheResultInvocationContext; return this; } /** - * Sets CacheRemove invocation context, see {@link CacheKeyInvocationContext} and {@link CacheRemove}. + * Sets CacheRemove invocation context, see {@link CacheInvocationContext} and {@link CacheRemove}. * * @param pCacheRemoveInvocationContext the CacheRemove invocation context * @return this {@link HystrixCommandBuilder} */ - public HystrixCommandBuilder cacheRemoveInvocationContext(CacheKeyInvocationContext pCacheRemoveInvocationContext) { + public HystrixCommandBuilder cacheRemoveInvocationContext(CacheInvocationContext pCacheRemoveInvocationContext) { this.cacheRemoveInvocationContext = pCacheRemoveInvocationContext; return this; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGeneratorException.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java similarity index 71% rename from hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGeneratorException.java rename to hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java index e65d04e7a..3fa8baa89 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGeneratorException.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java @@ -20,20 +20,20 @@ * * @author dmgcodevil */ -public class HystrixCacheKeyGeneratorException extends RuntimeException { +public class HystrixCacheKeyGenerationException extends RuntimeException { - public HystrixCacheKeyGeneratorException() { + public HystrixCacheKeyGenerationException() { } - public HystrixCacheKeyGeneratorException(String message) { + public HystrixCacheKeyGenerationException(String message) { super(message); } - public HystrixCacheKeyGeneratorException(String message, Throwable cause) { + public HystrixCacheKeyGenerationException(String message, Throwable cause) { super(message, cause); } - public HystrixCacheKeyGeneratorException(Throwable cause) { + public HystrixCacheKeyGenerationException(Throwable cause) { super(cause); } } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java new file mode 100644 index 000000000..2fee8c7ea --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java @@ -0,0 +1,22 @@ +package com.netflix.hystrix.contrib.javanica.exception; + +/** + * Created by dmgcodevil on 1/9/2015. + */ +public class HystrixCachingException extends RuntimeException { + + public HystrixCachingException() { + } + + public HystrixCachingException(String message) { + super(message); + } + + public HystrixCachingException(String message, Throwable cause) { + super(message, cause); + } + + public HystrixCachingException(Throwable cause) { + super(cause); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java index 9290e2e01..f46675f54 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java @@ -5,11 +5,6 @@ import com.netflix.hystrix.contrib.javanica.command.MetaHolder; import org.junit.Test; -import javax.cache.annotation.CacheInvocationParameter; -import javax.cache.annotation.CacheKey; -import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheRemove; -import javax.cache.annotation.CacheResult; import java.lang.annotation.Annotation; @@ -24,73 +19,73 @@ */ public class CacheKeyInvocationContextFactoryTest { - @Test - public void testCreateCacheResultInvocationContext_givenMethodAnnotatedWithCacheResult_shouldCreateCorrectCacheKeyInvocationContext() - throws NoSuchMethodException { - // given - TestCacheClass testCacheClass = new TestCacheClass(); - String param1 = "val_1"; - String param2 = "val_2"; - Integer param3 = 3; - MetaHolder metaHolder = MetaHolder.builder() - .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, String.class, Integer.class)) - .args(new Object[]{param1, param2, param3}) - .obj(testCacheClass).build(); - // when - CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheResultInvocationContext(metaHolder); - - // then - assertNotNull(context.getKeyParameters()); - assertEquals(2, context.getKeyParameters().length); - assertEquals(String.class, context.getKeyParameters()[0].getRawType()); - assertEquals(0, context.getKeyParameters()[0].getParameterPosition()); - assertEquals(param1, context.getKeyParameters()[0].getValue()); - assertTrue(isAnnotationPresent(context.getKeyParameters()[0], CacheKey.class)); - - assertEquals(Integer.class, context.getKeyParameters()[1].getRawType()); - assertEquals(2, context.getKeyParameters()[1].getParameterPosition()); - assertEquals(param3, context.getKeyParameters()[1].getValue()); - assertTrue(isAnnotationPresent(context.getKeyParameters()[1], CacheKey.class)); - } - - @Test - public void testCreateCacheRemoveInvocationContext_givenMethodAnnotatedWithCacheRemove_shouldCreateCorrectCacheKeyInvocationContext() - throws NoSuchMethodException { - // given - TestCacheClass testCacheClass = new TestCacheClass(); - String param1 = "val_1"; - MetaHolder metaHolder = MetaHolder.builder() - .method(TestCacheClass.class.getMethod("cacheRemoveMethod", String.class)) - .args(new Object[]{param1}) - .obj(testCacheClass).build(); - // when - CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheRemoveInvocationContext(metaHolder); - - // then - assertNotNull(context.getKeyParameters()); - assertEquals(1, context.getKeyParameters().length); - assertEquals(String.class, context.getKeyParameters()[0].getRawType()); - } - - public static class TestCacheClass { - - @CacheResult - public Object cacheResultMethod(@CacheKey String param1, String param2, @CacheKey Integer param3) { - return null; - } - - @CacheRemove - public Object cacheRemoveMethod(String param1) { - return null; - } - } - - private static boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { - return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { - @Override - public boolean apply(Annotation input) { - return input.annotationType().equals(annotation); - } - }).isPresent(); - } +// @Test +// public void testCreateCacheResultInvocationContext_givenMethodAnnotatedWithCacheResult_shouldCreateCorrectCacheKeyInvocationContext() +// throws NoSuchMethodException { +// // given +// TestCacheClass testCacheClass = new TestCacheClass(); +// String param1 = "val_1"; +// String param2 = "val_2"; +// Integer param3 = 3; +// MetaHolder metaHolder = MetaHolder.builder() +// .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, String.class, Integer.class)) +// .args(new Object[]{param1, param2, param3}) +// .obj(testCacheClass).build(); +// // when +// CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheResultInvocationContext(metaHolder); +// +// // then +// assertNotNull(context.getKeyParameters()); +// assertEquals(2, context.getKeyParameters().length); +// assertEquals(String.class, context.getKeyParameters()[0].getRawType()); +// assertEquals(0, context.getKeyParameters()[0].getParameterPosition()); +// assertEquals(param1, context.getKeyParameters()[0].getValue()); +// assertTrue(isAnnotationPresent(context.getKeyParameters()[0], CacheKey.class)); +// +// assertEquals(Integer.class, context.getKeyParameters()[1].getRawType()); +// assertEquals(2, context.getKeyParameters()[1].getParameterPosition()); +// assertEquals(param3, context.getKeyParameters()[1].getValue()); +// assertTrue(isAnnotationPresent(context.getKeyParameters()[1], CacheKey.class)); +// } +// +// @Test +// public void testCreateCacheRemoveInvocationContext_givenMethodAnnotatedWithCacheRemove_shouldCreateCorrectCacheKeyInvocationContext() +// throws NoSuchMethodException { +// // given +// TestCacheClass testCacheClass = new TestCacheClass(); +// String param1 = "val_1"; +// MetaHolder metaHolder = MetaHolder.builder() +// .method(TestCacheClass.class.getMethod("cacheRemoveMethod", String.class)) +// .args(new Object[]{param1}) +// .obj(testCacheClass).build(); +// // when +// CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheRemoveInvocationContext(metaHolder); +// +// // then +// assertNotNull(context.getKeyParameters()); +// assertEquals(1, context.getKeyParameters().length); +// assertEquals(String.class, context.getKeyParameters()[0].getRawType()); +// } +// +// public static class TestCacheClass { +// +// @CacheResult +// public Object cacheResultMethod(@CacheKey String param1, String param2, @CacheKey Integer param3) { +// return null; +// } +// +// @CacheRemove +// public Object cacheRemoveMethod(String param1) { +// return null; +// } +// } +// +// private static boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { +// return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { +// @Override +// public boolean apply(Annotation input) { +// return input.annotationType().equals(annotation); +// } +// }).isPresent(); +// } } diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java index 95734e6ad..c52e9bfcf 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java @@ -1,12 +1,16 @@ package com.netflix.hystrix.contrib.javanica.test.spring.cache; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCacheAspect; -import com.netflix.hystrix.contrib.javanica.cache.DefaultHystrixGeneratedCacheKey; -import com.netflix.hystrix.contrib.javanica.cache.HystrixCacheKeyGenerator; -import com.netflix.hystrix.contrib.javanica.cache.HystrixGeneratedCacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.Profile; import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import org.junit.Before; @@ -21,12 +25,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.PostConstruct; -import javax.cache.annotation.CacheInvocationParameter; -import javax.cache.annotation.CacheKey; -import javax.cache.annotation.CacheKeyInvocationContext; -import javax.cache.annotation.CacheRemove; -import javax.cache.annotation.CacheResult; -import java.lang.annotation.Annotation; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -126,6 +124,63 @@ public void testGetSetGetUserCache_givenTwoCommands() { } } + @Test + public void testGetSetGetUserCache_givenGetUserByEmailAndUpdateProfile() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + + User user = userService.getUserByEmail("email"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); + assertEquals("email", user.getProfile().getEmail()); // initial email value + + user = userService.getUserByEmail("email"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("email", user.getProfile().getEmail()); // same email + + // create new user with same id but with new email + Profile profile = new Profile(); + profile.setEmail("new_email"); + user.setProfile(profile); + userService.updateProfile(user); // update the user profile + + user = userService.getUserByEmail("new_email"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "updateProfile" + // method was invoked and a cache for "getUserByEmail" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); + assertEquals("new_email", user.getProfile().getEmail()); + + + } finally { + context.shutdown(); + } + + // start a new request context + context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserByEmail("new_email"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } finally { + context.shutdown(); + } + } + @Test public void testGetSetGetUserCache_givenOneCommandAndOneMethodAnnotatedWithCacheRemove() { HystrixRequestContext context = HystrixRequestContext.initializeContext(); @@ -180,12 +235,37 @@ public void testGetSetGetUserCache_givenOneCommandAndOneMethodAnnotatedWithCache } } + + @Test(expected = HystrixCachingException.class) + public void testGetUser_givenWrongCacheKeyMethodReturnType_shouldThrowException() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserByName("name"); + } finally { + context.shutdown(); + } + } + + @Test(expected = HystrixCachingException.class) + public void testGetUserByName_givenNonexistentCacheKeyMethod_shouldThrowException() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUser(); + } finally { + context.shutdown(); + } + } + public static class UserService { private Map storage = new ConcurrentHashMap(); @PostConstruct private void init() { - storage.put("1", new User("1", "name")); + User user = new User("1", "name"); + Profile profile = new Profile(); + profile.setEmail("email"); + user.setProfile(profile); + storage.put("1", user); } @CacheResult @@ -194,28 +274,56 @@ public User getUserById(@CacheKey String id) { return storage.get(id); } - @CacheRemove(cacheName = "getUserById", cacheKeyGenerator = UserCacheKeyGenerator.class) + @CacheResult(cacheKeyMethod = "getUserByNameCacheKey") @HystrixCommand - public void update(@CacheKey User user) { + public User getUserByName(String name) { + return null; + } + + private Long getUserByNameCacheKey() { + return 0L; + } + + @CacheResult(cacheKeyMethod = "nonexistent") + @HystrixCommand + public User getUser() { + return null; + } + + @CacheResult(cacheKeyMethod = "getUserByEmailCacheKey") + @HystrixCommand + public User getUserByEmail(final String email) { + return Iterables.tryFind(storage.values(), new Predicate() { + @Override + public boolean apply(User input) { + return input.getProfile().getEmail().equalsIgnoreCase(email); + } + }).orNull(); + } + + private String getUserByEmailCacheKey(String email) { + return email; + } + + @CacheRemove(commandKey = "getUserById") + @HystrixCommand + public void update(@CacheKey("id") User user) { storage.put(user.getId(), user); } - @CacheRemove(cacheName = "getUserById") + @CacheRemove(commandKey = "getUserByEmail") + @HystrixCommand + public void updateProfile(@CacheKey("profile.email") User user) { + storage.get(user.getId()).setProfile(user.getProfile()); + } + + @CacheRemove(commandKey = "getUserById") public void updateName(@CacheKey String id, String name) { storage.get(id).setName(name); } } - public static class UserCacheKeyGenerator implements HystrixCacheKeyGenerator { - @Override - public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { - CacheInvocationParameter cacheInvocationParameter = cacheKeyInvocationContext.getKeyParameters()[0]; - User user = (User) cacheInvocationParameter.getValue(); - return new DefaultHystrixGeneratedCacheKey(user.getId()); - } - } - /** * Spring configuration. */ diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/Profile.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/Profile.java new file mode 100644 index 000000000..5b26d1c90 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/Profile.java @@ -0,0 +1,16 @@ +package com.netflix.hystrix.contrib.javanica.test.spring.domain; + +/** + * Created by dmgcodevil on 1/9/2015. + */ +public class Profile { + private String email; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/User.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/User.java index bc79a6ae6..f17221f3e 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/User.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/domain/User.java @@ -7,6 +7,8 @@ public class User { private String id; private String name; + private Profile profile; + public User() { } @@ -32,6 +34,14 @@ public void setName(String name) { this.name = name; } + public Profile getProfile() { + return profile; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + @Override public String toString() { final StringBuilder sb = new StringBuilder("User{"); From 8a1ba322cd0acd1e41f331082a9e12de76cae6e0 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Sat, 10 Jan 2015 18:28:21 +0300 Subject: [PATCH 07/15] #iss380: added javadocs --- .../aop/aspectj/HystrixCacheAspect.java | 6 +- .../cache/CacheInvocationContext.java | 72 ++++++++++++++++--- .../cache/CacheInvocationContextFactory.java | 20 ++++-- .../cache/CacheInvocationParameter.java | 33 ++++++++- .../cache/HystrixCacheKeyGenerator.java | 5 +- .../javanica/cache/annotation/CacheKey.java | 16 ++++- .../cache/annotation/CacheRemove.java | 20 +++++- .../cache/annotation/CacheResult.java | 17 ++++- .../command/AbstractHystrixCommand.java | 3 +- .../javanica/command/CommandActions.java | 6 +- 10 files changed, 169 insertions(+), 29 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java index 734202a69..bae61592c 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCacheAspect.java @@ -19,6 +19,7 @@ import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContext; import com.netflix.hystrix.contrib.javanica.cache.CacheInvocationContextFactory; import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; import com.netflix.hystrix.contrib.javanica.command.ExecutionType; import com.netflix.hystrix.contrib.javanica.command.MetaHolder; import org.apache.commons.lang3.Validate; @@ -26,15 +27,14 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; -import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; - import java.lang.reflect.Method; import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget; /** - * AspectJ aspect to process methods which annotated with annotations from javax.cache.annotation package. + * AspectJ aspect to process methods which annotated with annotations from + * com.netflix.hystrix.contrib.javanica.cache.annotation package. * * @author dmgcodevil */ diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java index 19d7ac25b..7dce4c61f 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContext.java @@ -28,28 +28,37 @@ import java.util.List; /** - * // todo + * Runtime information about an intercepted method invocation for a method + * annotated with {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult}, + * {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove} annotations. * * @author dmgcodevil */ public class CacheInvocationContext { - private Method method; - private Object target; - private MethodExecutionAction cacheKeyMethod; - private ExecutionType executionType; - private A cacheAnnotation; + private final Method method; + private final Object target; + private final MethodExecutionAction cacheKeyMethod; + private final ExecutionType executionType = ExecutionType.SYNCHRONOUS; + private final A cacheAnnotation; private List parameters = Collections.emptyList(); private List keyParameters = Collections.emptyList(); - public CacheInvocationContext(A cacheAnnotation, MethodExecutionAction cacheKeyMethod, - ExecutionType executionType, Object target, Method method, Object... args) { + /** + * Constructor to create CacheInvocationContext based on passed parameters. + * + * @param cacheAnnotation the caching annotation, like {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult} + * @param cacheKeyMethod the method to generate cache key + * @param target the current instance of intercepted method + * @param method the method annotated with on of caching annotations + * @param args the method arguments + */ + public CacheInvocationContext(A cacheAnnotation, MethodExecutionAction cacheKeyMethod, Object target, Method method, Object... args) { this.method = method; this.target = target; this.cacheKeyMethod = cacheKeyMethod; this.cacheAnnotation = cacheAnnotation; - this.executionType = executionType; Class[] parametersTypes = method.getParameterTypes(); Annotation[][] parametersAnnotations = method.getParameterAnnotations(); int parameterCount = parametersTypes.length; @@ -76,10 +85,20 @@ public boolean apply(CacheInvocationParameter input) { } } + /** + * Gets intercepted method that annotated with caching annotation. + * + * @return method + */ public Method getMethod() { return method; } + /** + * Gets current instance that can be used to invoke {@link #cacheKeyMethod} or for another needs. + * + * @return current instance + */ public Object getTarget() { return target; } @@ -89,7 +108,7 @@ public A getCacheAnnotation() { } /** - * todo + * Gets all method parameters. * * @return immutable list of {@link CacheInvocationParameter} objects */ @@ -98,7 +117,18 @@ public List getAllParameters() { } /** - * // todo + * Returns a clone of the array of all method parameters annotated with + * {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey} annotation to be used by the + * {@link HystrixCacheKeyGenerator} in creating a {@link HystrixGeneratedCacheKey}. The returned array + * may be the same as or a subset of the array returned by {@link #getAllParameters()}. + *

+ * Parameters in this array are selected by the following rules: + *

* * @return immutable list of {@link CacheInvocationParameter} objects */ @@ -106,18 +136,38 @@ public List getKeyParameters() { return keyParameters; } + /** + * Checks whether any method argument annotated with {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey} annotation. + * + * @return true if at least one method argument with {@link com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey} annotation + */ public boolean hasKeyParameters() { return CollectionUtils.isNotEmpty(keyParameters); } + /** + * Gets method name to be used to get a key for request caching. + * + * @return method name + */ public String getCacheKeyMethodName() { return cacheKeyMethod != null ? cacheKeyMethod.getMethod().getName() : null; } + /** + * Gets action that invokes cache key method, the result of execution is used as cache key. + * + * @return cache key method execution action, see {@link MethodExecutionAction}. + */ public MethodExecutionAction getCacheKeyMethod() { return cacheKeyMethod; } + /** + * Gets execution type of cache key action. + * + * @return execution type + */ public ExecutionType getExecutionType() { return executionType; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java index b3388ddcd..e0c6c4737 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactory.java @@ -27,30 +27,40 @@ import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getDeclaredMethod; /** - * // todo + * Factory to create certain {@link CacheInvocationContext}. * * @author dmgcodevil */ public class CacheInvocationContextFactory { + /** + * Create {@link CacheInvocationContext} parametrized with {@link CacheResult} annotation. + * + * @param metaHolder the meta holder, see {@link com.netflix.hystrix.contrib.javanica.command.MetaHolder} + * @return initialized and configured {@link CacheInvocationContext} + */ public static CacheInvocationContext createCacheResultInvocationContext(MetaHolder metaHolder) { if (metaHolder.getMethod().isAnnotationPresent(CacheResult.class)) { Method method = metaHolder.getMethod(); CacheResult cacheResult = method.getAnnotation(CacheResult.class); MethodExecutionAction cacheKeyMethod = createCacheKeyAction(cacheResult.cacheKeyMethod(), metaHolder); - return new CacheInvocationContext(cacheResult, cacheKeyMethod, - metaHolder.getExecutionType(), metaHolder.getObj(), method, metaHolder.getArgs()); + return new CacheInvocationContext(cacheResult, cacheKeyMethod, metaHolder.getObj(), method, metaHolder.getArgs()); } return null; } + /** + * Create {@link CacheInvocationContext} parametrized with {@link CacheRemove} annotation. + * + * @param metaHolder the meta holder, see {@link com.netflix.hystrix.contrib.javanica.command.MetaHolder} + * @return initialized and configured {@link CacheInvocationContext} + */ public static CacheInvocationContext createCacheRemoveInvocationContext(MetaHolder metaHolder) { if (metaHolder.getMethod().isAnnotationPresent(CacheRemove.class)) { Method method = metaHolder.getMethod(); CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class); MethodExecutionAction cacheKeyMethod = createCacheKeyAction(cacheRemove.cacheKeyMethod(), metaHolder); - return new CacheInvocationContext(cacheRemove, cacheKeyMethod, - metaHolder.getExecutionType(), metaHolder.getObj(), method, metaHolder.getArgs()); + return new CacheInvocationContext(cacheRemove, cacheKeyMethod, metaHolder.getObj(), method, metaHolder.getArgs()); } return null; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java index 0130800cd..5ac2efe71 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameter.java @@ -25,7 +25,8 @@ import java.util.Set; /** - * // todo + * A parameter to an intercepted method invocation. Contains the parameter value + * as well static type and annotation information about the parameter. * * @author dmgcodevil */ @@ -45,26 +46,56 @@ public CacheInvocationParameter(Class rawType, Object value, Annotation[] ann this.cacheKeyAnnotation = (CacheKey) cacheKeyAnnotation(); } + /** + * Returns an immutable Set of all Annotations on this method parameter, never null. + * + * @return set of {@link Annotation} + */ public Set getAnnotations() { return annotations; } + /** + * Gets {@link CacheKey} for the parameter. + * + * @return {@link CacheKey} annotation or null if the parameter isn't annotated with {@link CacheKey}. + */ public CacheKey getCacheKeyAnnotation() { return cacheKeyAnnotation; } + /** + * Checks whether the parameter annotated with {@link CacheKey} or not. + * + * @return true if parameter annotated with {@link CacheKey} otherwise - false + */ public boolean hasCacheKeyAnnotation() { return cacheKeyAnnotation != null; } + /** + * Gets the parameter type as declared on the method. + * + * @return parameter type + */ public Class getRawType() { return rawType; } + /** + * Gets the parameter value + * + * @return parameter value + */ public Object getValue() { return value; } + /** + * Gets index of the parameter in the original parameter array. + * + * @return index of the parameter + */ public int getPosition() { return position; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java index 3c7fe41e2..1aba8aad8 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java @@ -29,7 +29,10 @@ import java.util.List; /** - * Creates cache keys with {@link DefaultHystrixGeneratedCacheKey} type. + * Generates a {@link HystrixGeneratedCacheKey} based on + * a {@link CacheInvocationContext}. + *

+ * Implementation is thread-safe. * * @author dmgcodevil */ diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java index 90bc9fab8..2f9d1babe 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheKey.java @@ -22,7 +22,10 @@ import java.lang.annotation.Target; /** - * // todo + * Marks a method argument as part of the cache key. + * If no arguments are marked all arguments are used. + * If {@link CacheResult} or {@link CacheRemove} annotation has specified cacheKeyMethod then + * a method arguments will not be used to build cache key even if they annotated with {@link CacheKey}. * * @author dmgcodevil */ @@ -31,5 +34,14 @@ @Documented public @interface CacheKey { - public String value() default ""; + /** + * Allows specify name of a certain argument property. + * for example: @CacheKey("id") User user, + * or in case composite property: @CacheKey("profile.name") User user. + * null properties are ignored, i.e. if profile is null + * then result of @CacheKey("profile.name") User user will be empty string. + * + * @return name of an argument property + */ + String value() default ""; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java index 4467a5011..aef445971 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheRemove.java @@ -22,7 +22,8 @@ import java.lang.annotation.Target; /** - * // todo + * Marks methods used to invalidate cache of a command. + * Generated cache key must be same as key generated within {@link CacheResult} context. * * @author dmgcodevil */ @@ -31,7 +32,24 @@ @Documented public @interface CacheRemove { + /** + * Command name is used to find appropriate Hystrix command that cache should be cleared. + * + * @return command name + */ String commandKey(); + /** + * Method name to be used to get a key for request caching. + * The command and cache key method should be placed in the same class and have same method signature except + * cache key method return type, that should be String. + *

+ * cacheKeyMethod has higher priority than an arguments of a method, that means what actual arguments + * of a method that annotated with {@link CacheResult} will not be used to generate cache key, instead specified + * cacheKeyMethod fully assigns to itself responsibility for cache key generation. + * By default this returns empty string which means "do not use cache method". + * + * @return method name or empty string + */ String cacheKeyMethod() default ""; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java index d7deaa0f4..dfa52f3fa 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/annotation/CacheResult.java @@ -22,7 +22,8 @@ import java.lang.annotation.Target; /** - * // todo + * Marks a methods that results should be cached for a Hystrix command. + * This annotation must be used in conjunction with {@link com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand} annotation. * * @author dmgcodevil */ @@ -30,6 +31,18 @@ @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CacheResult { - String cacheKeyMethod() default ""; + /** + * Method name to be used to get a key for request caching. + * The command and cache key method should be placed in the same class and have same method signature except + * cache key method return type, that should be String. + *

+ * cacheKeyMethod has higher priority than an arguments of a method, that means what actual arguments + * of a method that annotated with {@link CacheResult} will not be used to generate cache key, instead specified + * cacheKeyMethod fully assigns to itself responsibility for cache key generation. + * By default this returns empty string which means "do not use cache method". + * + * @return method name or empty string + */ + String cacheKeyMethod() default ""; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java index d4c3da9a8..8656a2261 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java @@ -49,8 +49,7 @@ public abstract class AbstractHystrixCommand extends com.netflix.hystrix.Hyst private final Collection> collapsedRequests; private final Class[] ignoreExceptions; private final ExecutionType executionType; - //private final CacheKeyGeneratorFactory cacheKeyGeneratorFactory = CacheKeyGeneratorFactory.getInstance(); - HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); + private HystrixCacheKeyGenerator defaultCacheKeyGenerator = HystrixCacheKeyGenerator.getInstance(); protected AbstractHystrixCommand(HystrixCommandBuilder builder) { super(builder.getSetterBuilder().build()); diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java index 83d1af0e4..d2c8f5db8 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java @@ -15,7 +15,11 @@ */ package com.netflix.hystrix.contrib.javanica.command; - +/** + * Wrapper for command actions combines different actions together. + * + * @author dmgcodevil + */ public class CommandActions { private final CommandAction commandAction; From 9bbc11b6c2e51ddf1b2981f5c5523da3bc777b23 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Sat, 10 Jan 2015 19:31:07 +0300 Subject: [PATCH 08/15] #iss380: Added tests --- .../cache/HystrixCacheKeyGenerator.java | 8 +- .../HystrixCacheKeyGenerationException.java | 2 +- .../exception/HystrixCachingException.java | 19 +- .../CacheInvocationContextFactoryTest.java | 145 ++++++++ .../cache/CacheInvocationParameterTest.java | 45 +++ .../CacheKeyInvocationContextFactoryTest.java | 91 ----- .../cache/HystrixCacheKeyGeneratorTest.java | 125 +++++++ .../javanica/test/spring/cache/CacheTest.java | 282 +++++++++++++- .../test/spring/cache/Jsr107CacheTest.java | 344 ------------------ 9 files changed, 602 insertions(+), 459 deletions(-) create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactoryTest.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterTest.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java create mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGeneratorTest.java delete mode 100644 hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java index 1aba8aad8..2a7f492d8 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGenerator.java @@ -59,8 +59,7 @@ public HystrixGeneratedCacheKey generateCacheKey(CacheInvocationContext names, Object obj) throws HystrixCacheKeyGenerationException { + private Object appendPropertyValue(StringBuilder cacheKeyBuilder, List names, Object obj) throws HystrixCacheKeyGenerationException { for (String name : names) { if (obj != null) { obj = getPropertyValue(name, obj); } } + if (obj != null) { + cacheKeyBuilder.append(obj); + } return obj; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java index 3fa8baa89..dc53c9322 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCacheKeyGenerationException.java @@ -16,7 +16,7 @@ package com.netflix.hystrix.contrib.javanica.exception; /** - * Indicates that something is going wrong with caching logic. + * Indicates that something is going wrong with cache key generation logic. * * @author dmgcodevil */ diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java index 2fee8c7ea..0c330a8e9 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/exception/HystrixCachingException.java @@ -1,7 +1,24 @@ +/** + * Copyright 2015 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.hystrix.contrib.javanica.exception; /** - * Created by dmgcodevil on 1/9/2015. + * Indicates that something is going wrong with caching logic. + * + * @author dmgcodevil */ public class HystrixCachingException extends RuntimeException { diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactoryTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactoryTest.java new file mode 100644 index 000000000..a25be6320 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationContextFactoryTest.java @@ -0,0 +1,145 @@ +package com.netflix.hystrix.contrib.javanica.cache; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; +import org.junit.Test; + + +import java.lang.annotation.Annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Unit test for {@link CacheInvocationContextFactory}. + * + * @author dmgcodevil + */ +public class CacheInvocationContextFactoryTest { + + @Test + public void testCreateCacheResultInvocationContext_givenMethodAnnotatedWithCacheResult_shouldCreateCorrectCacheKeyInvocationContext() + throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + String param2 = "val_2"; + Integer param3 = 3; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, String.class, Integer.class)) + .args(new Object[]{param1, param2, param3}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + + // then + assertNotNull(context.getKeyParameters()); + assertEquals(2, context.getKeyParameters().size()); + assertEquals(String.class, context.getKeyParameters().get(0).getRawType()); + assertEquals(0, context.getKeyParameters().get(0).getPosition()); + assertEquals(param1, context.getKeyParameters().get(0).getValue()); + assertTrue(isAnnotationPresent(context.getKeyParameters().get(0), CacheKey.class)); + + assertEquals(Integer.class, context.getKeyParameters().get(1).getRawType()); + assertEquals(2, context.getKeyParameters().get(1).getPosition()); + assertEquals(param3, context.getKeyParameters().get(1).getValue()); + assertTrue(isAnnotationPresent(context.getKeyParameters().get(1), CacheKey.class)); + } + + @Test + public void testCreateCacheRemoveInvocationContext_givenMethodAnnotatedWithCacheRemove_shouldCreateCorrectCacheKeyInvocationContext() + throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheRemoveMethod", String.class)) + .args(new Object[]{param1}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheRemoveInvocationContext(metaHolder); + + // then + assertNotNull(context.getKeyParameters()); + assertEquals(1, context.getKeyParameters().size()); + CacheInvocationParameter actual = context.getKeyParameters().get(0); + assertEquals(String.class, actual.getRawType()); + assertEquals(param1, actual.getValue()); + assertEquals(0, actual.getPosition()); + } + + @Test(expected = HystrixCachingException.class) + public void testCacheResultMethodWithWrongCacheKeyMethodSignature_givenWrongCacheKeyMethod_shouldThrowException() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethodWithWrongCacheKeyMethodSignature", String.class)) + .args(new Object[]{param1}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + // then expected HystrixCachingException + } + + @Test(expected = HystrixCachingException.class) + public void testCacheResultMethodWithCacheKeyMethodWithWrongReturnType_givenCacheKeyMethodWithWrongReturnType_shouldThrowException() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String param1 = "val_1"; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethodWithCacheKeyMethodWithWrongReturnType", String.class, String.class)) + .args(new Object[]{param1}) + .obj(testCacheClass).build(); + // when + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + System.out.println(context); + // then expected HystrixCachingException + } + + public static class TestCacheClass { + + @CacheResult + public Object cacheResultMethod(@CacheKey String param1, String param2, @CacheKey Integer param3) { + return null; + } + + @CacheRemove(commandKey = "test") + public Object cacheRemoveMethod(String param1) { + return null; + } + + @CacheResult(cacheKeyMethod = "cacheKeyMethodSignature") + public Object cacheResultMethodWithWrongCacheKeyMethodSignature(String param2) { + return null; + } + + private String cacheKeyMethodSignature(String param1, String param2) { + return null; + } + + @CacheResult(cacheKeyMethod = "cacheKeyMethodWithWrongReturnType") + public Object cacheResultMethodWithCacheKeyMethodWithWrongReturnType(String param1, String param2) { + return null; + } + + private Long cacheKeyMethodWithWrongReturnType(String param1, String param2) { + return null; + } + } + + private static boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { + return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { + @Override + public boolean apply(Annotation input) { + return input.annotationType().equals(annotation); + } + }).isPresent(); + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterTest.java new file mode 100644 index 000000000..6e5a0c835 --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheInvocationParameterTest.java @@ -0,0 +1,45 @@ +package com.netflix.hystrix.contrib.javanica.cache; + + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class CacheInvocationParameterTest { + + @Test + public void testCacheInvocationParameterConstructor() throws NoSuchMethodException { + // given + Class rawType = String.class; + Object value = "test"; + Method method = CacheInvocationParameterTest.class.getDeclaredMethod("stabMethod", String.class); + method.setAccessible(true); + Annotation[] annotations = method.getParameterAnnotations()[0]; + int position = 0; + // when + CacheInvocationParameter cacheInvocationParameter = new CacheInvocationParameter(rawType, value, annotations, position); + // then + assertEquals(rawType, cacheInvocationParameter.getRawType()); + assertEquals(value, cacheInvocationParameter.getValue()); + assertEquals(annotations[0], cacheInvocationParameter.getCacheKeyAnnotation()); + assertTrue(cacheInvocationParameter.hasCacheKeyAnnotation()); + assertTrue(cacheInvocationParameter.getAnnotations().contains(annotations[0])); + + try { + cacheInvocationParameter.getAnnotations().clear(); + fail(); + } catch (Throwable e) { + // getAnnotations should return immutable set. + } + } + + private static void stabMethod(@CacheKey String val) { + + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java deleted file mode 100644 index f46675f54..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/CacheKeyInvocationContextFactoryTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.cache; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.netflix.hystrix.contrib.javanica.command.MetaHolder; -import org.junit.Test; - - -import java.lang.annotation.Annotation; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -/** - * Unit test for {@link CacheKeyInvocationContextFactory}. - * - * @author dmgcodevil - */ -public class CacheKeyInvocationContextFactoryTest { - -// @Test -// public void testCreateCacheResultInvocationContext_givenMethodAnnotatedWithCacheResult_shouldCreateCorrectCacheKeyInvocationContext() -// throws NoSuchMethodException { -// // given -// TestCacheClass testCacheClass = new TestCacheClass(); -// String param1 = "val_1"; -// String param2 = "val_2"; -// Integer param3 = 3; -// MetaHolder metaHolder = MetaHolder.builder() -// .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, String.class, Integer.class)) -// .args(new Object[]{param1, param2, param3}) -// .obj(testCacheClass).build(); -// // when -// CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheResultInvocationContext(metaHolder); -// -// // then -// assertNotNull(context.getKeyParameters()); -// assertEquals(2, context.getKeyParameters().length); -// assertEquals(String.class, context.getKeyParameters()[0].getRawType()); -// assertEquals(0, context.getKeyParameters()[0].getParameterPosition()); -// assertEquals(param1, context.getKeyParameters()[0].getValue()); -// assertTrue(isAnnotationPresent(context.getKeyParameters()[0], CacheKey.class)); -// -// assertEquals(Integer.class, context.getKeyParameters()[1].getRawType()); -// assertEquals(2, context.getKeyParameters()[1].getParameterPosition()); -// assertEquals(param3, context.getKeyParameters()[1].getValue()); -// assertTrue(isAnnotationPresent(context.getKeyParameters()[1], CacheKey.class)); -// } -// -// @Test -// public void testCreateCacheRemoveInvocationContext_givenMethodAnnotatedWithCacheRemove_shouldCreateCorrectCacheKeyInvocationContext() -// throws NoSuchMethodException { -// // given -// TestCacheClass testCacheClass = new TestCacheClass(); -// String param1 = "val_1"; -// MetaHolder metaHolder = MetaHolder.builder() -// .method(TestCacheClass.class.getMethod("cacheRemoveMethod", String.class)) -// .args(new Object[]{param1}) -// .obj(testCacheClass).build(); -// // when -// CacheKeyInvocationContext context = CacheKeyInvocationContextFactory.createCacheRemoveInvocationContext(metaHolder); -// -// // then -// assertNotNull(context.getKeyParameters()); -// assertEquals(1, context.getKeyParameters().length); -// assertEquals(String.class, context.getKeyParameters()[0].getRawType()); -// } -// -// public static class TestCacheClass { -// -// @CacheResult -// public Object cacheResultMethod(@CacheKey String param1, String param2, @CacheKey Integer param3) { -// return null; -// } -// -// @CacheRemove -// public Object cacheRemoveMethod(String param1) { -// return null; -// } -// } -// -// private static boolean isAnnotationPresent(CacheInvocationParameter parameter, final Class annotation) { -// return Iterables.tryFind(parameter.getAnnotations(), new Predicate() { -// @Override -// public boolean apply(Annotation input) { -// return input.annotationType().equals(annotation); -// } -// }).isPresent(); -// } -} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGeneratorTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGeneratorTest.java new file mode 100644 index 000000000..57b781d0a --- /dev/null +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/cache/HystrixCacheKeyGeneratorTest.java @@ -0,0 +1,125 @@ +package com.netflix.hystrix.contrib.javanica.cache; + + +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.command.MetaHolder; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class HystrixCacheKeyGeneratorTest { + + @Test + public void testGenerateCacheKey_givenUser_shouldReturnCorrectCacheKey() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String id = "1"; + User user = new User(); + user.setId(id); + Profile profile = new Profile("user name"); + user.setProfile(profile); + String expectedKey = id + user.getProfile().getName(); + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, User.class)) + .args(new Object[]{id, user}) + .obj(testCacheClass).build(); + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + HystrixCacheKeyGenerator keyGenerator = HystrixCacheKeyGenerator.getInstance(); + // when + String actual = keyGenerator.generateCacheKey(context).getCacheKey(); + // then + assertEquals(expectedKey, actual); + } + + @Test + public void testGenerateCacheKey_givenUserWithNullProfile_shouldReturnCorrectCacheKey() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + String id = "1"; + User user = new User(); + user.setId(id); + user.setProfile(null); + String expectedKey = id; + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod", String.class, User.class)) + .args(new Object[]{id, user}) + .obj(testCacheClass).build(); + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + HystrixCacheKeyGenerator keyGenerator = HystrixCacheKeyGenerator.getInstance(); + // when + String actual = keyGenerator.generateCacheKey(context).getCacheKey(); + // then + assertEquals(expectedKey, actual); + } + + @Test + public void testGenerateCacheKey_givenCacheKeyMethodWithNoArguments_shouldReturnEmptyCacheKey() throws NoSuchMethodException { + // given + TestCacheClass testCacheClass = new TestCacheClass(); + MetaHolder metaHolder = MetaHolder.builder() + .method(TestCacheClass.class.getMethod("cacheResultMethod")) + .args(new Object[]{}) + .obj(testCacheClass).build(); + CacheInvocationContext context = CacheInvocationContextFactory.createCacheResultInvocationContext(metaHolder); + HystrixCacheKeyGenerator keyGenerator = HystrixCacheKeyGenerator.getInstance(); + // when + HystrixGeneratedCacheKey actual = keyGenerator.generateCacheKey(context); + // then + assertEquals(DefaultHystrixGeneratedCacheKey.EMPTY, actual); + } + + public static class TestCacheClass { + + @CacheResult + public Object cacheResultMethod(@CacheKey String id, @CacheKey("profile.name") User user) { + return "test"; + } + + @CacheResult + public Object cacheResultMethod() { + return "test"; + } + + } + + public static class User { + private String id; + private Profile profile; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Profile getProfile() { + return profile; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + } + + public static class Profile { + private String name; + + public Profile() { + } + + public Profile(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/CacheTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/CacheTest.java index ea78d82bd..41aa0add3 100644 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/CacheTest.java +++ b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/CacheTest.java @@ -1,56 +1,168 @@ package com.netflix.hystrix.contrib.javanica.test.spring.cache; +import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.netflix.hystrix.HystrixInvokableInfo; -import com.netflix.hystrix.HystrixRequestLog; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; +import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCacheAspect; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; +import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; +import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; +import com.netflix.hystrix.contrib.javanica.test.spring.domain.Profile; import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import java.util.Collection; +import javax.annotation.PostConstruct; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import static com.netflix.hystrix.contrib.javanica.CommonUtils.getLastExecutedCommand; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** - * This test covers "Request cache" functionality. - * Link: https://github.com/Netflix/Hystrix/wiki/How-To-Use#Caching + * Test to check cache implementation based on JSR-107. + * + * @author dmgcodevil */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {AopCglibConfig.class, CacheTest.CacheTestConfig.class}) public class CacheTest { - @Autowired private UserService userService; + @Autowired + private ApplicationContext applicationContext; + + @Before + public void setUp() throws Exception { + userService = applicationContext.getBean(UserService.class); + } + + /** + * Get-Set-Get with Request Cache Invalidation Test. + *

+ * given: + * command to get user by id, see {@link UserService#getUserById(String)} + * command to update user, see {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.spring.domain.User)} + *

+ * when: + * 1. call {@link UserService#getUserById(String)} + * 2. call {@link UserService#getUserById(String)} + * 3. call {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.spring.domain.User)} + * 4. call {@link UserService#getUserById(String)} + *

+ * then: + * at the first time "getUserById" command shouldn't retrieve value from cache + * at the second time "getUserById" command should retrieve value from cache + * "update" method should update an user and flush cache related to "getUserById" command + * after "update" method execution "getUserById" command shouldn't retrieve value from cache + */ @Test - public void testGetUserCache() { + public void testGetSetGetUserCache_givenTwoCommands() { HystrixRequestContext context = HystrixRequestContext.initializeContext(); try { - User user = userService.getUser("1", "name"); + User user = userService.getUserById("1"); HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); // initial name value + + user = userService.getUserById("1"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("name", user.getName()); // same name + + // create new user with same id but with new name + user = new User("1", "new_name"); + userService.update(user); // update the user + + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); assertEquals("1", user.getId()); + assertEquals("new_name", user.getName()); + + + } finally { + context.shutdown(); + } + // start a new request context + context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } finally { + context.shutdown(); + } + } + + @Test + public void testGetSetGetUserCache_givenGetUserByEmailAndUpdateProfile() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + + User user = userService.getUserByEmail("email"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); // this is the first time we've executed this command with // the value of "1" so it should not be from cache assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); + assertEquals("email", user.getProfile().getEmail()); // initial email value - user = userService.getUser("1", "name"); + user = userService.getUserByEmail("email"); assertEquals("1", user.getId()); getUserByIdCommand = getLastExecutedCommand(); // this is the second time we've executed this command with // the same value so it should return from cache assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("email", user.getProfile().getEmail()); // same email + + // create new user with same id but with new email + Profile profile = new Profile(); + profile.setEmail("new_email"); + user.setProfile(profile); + userService.updateProfile(user); // update the user profile + + user = userService.getUserByEmail("new_email"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "updateProfile" + // method was invoked and a cache for "getUserByEmail" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); + assertEquals("new_email", user.getProfile().getEmail()); + + } finally { context.shutdown(); } @@ -58,7 +170,7 @@ public void testGetUserCache() { // start a new request context context = HystrixRequestContext.initializeContext(); try { - User user = userService.getUser("1", "name"); + User user = userService.getUserByEmail("new_email"); HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); assertEquals("1", user.getId()); // this is a new request context so this @@ -69,21 +181,147 @@ public void testGetUserCache() { } } - private HystrixInvokableInfo getLastExecutedCommand() { - Collection> executedCommands = - HystrixRequestLog.getCurrentRequest().getAllExecutedCommands(); - return Iterables.getLast(executedCommands); + @Test + public void testGetSetGetUserCache_givenOneCommandAndOneMethodAnnotatedWithCacheRemove() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + + // given + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("name", user.getName()); // initial name value + + user = userService.getUserById("1"); + assertEquals("1", user.getId()); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + assertEquals("name", user.getName()); // same name + + // when + userService.updateName("1", "new_name"); // update the user name + + // then + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + assertEquals("1", user.getId()); + assertEquals("new_name", user.getName()); + + + } finally { + context.shutdown(); + } + + // start a new request context + context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + assertEquals("1", user.getId()); + // this is a new request context so this + // should not come from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } finally { + context.shutdown(); + } + } + + + @Test(expected = HystrixCachingException.class) + public void testGetUser_givenWrongCacheKeyMethodReturnType_shouldThrowException() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUserByName("name"); + } finally { + context.shutdown(); + } + } + + @Test(expected = HystrixCachingException.class) + public void testGetUserByName_givenNonexistentCacheKeyMethod_shouldThrowException() { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + User user = userService.getUser(); + } finally { + context.shutdown(); + } } public static class UserService { - @HystrixCommand(cacheKeyMethod = "getUserIdCacheKey") - public User getUser(String id, String name) { - return new User(id, name + id); // it should be network call + private Map storage = new ConcurrentHashMap(); + + @PostConstruct + private void init() { + User user = new User("1", "name"); + Profile profile = new Profile(); + profile.setEmail("email"); + user.setProfile(profile); + storage.put("1", user); + } + + @CacheResult + @HystrixCommand + public User getUserById(@CacheKey String id) { + return storage.get(id); + } + + @CacheResult(cacheKeyMethod = "getUserByNameCacheKey") + @HystrixCommand + public User getUserByName(String name) { + return null; } - private String getUserIdCacheKey(String id, String name) { - return id + name; + private Long getUserByNameCacheKey() { + return 0L; } + + @CacheResult(cacheKeyMethod = "nonexistent") + @HystrixCommand + public User getUser() { + return null; + } + + @CacheResult(cacheKeyMethod = "getUserByEmailCacheKey") + @HystrixCommand + public User getUserByEmail(final String email) { + return Iterables.tryFind(storage.values(), new Predicate() { + @Override + public boolean apply(User input) { + return input.getProfile().getEmail().equalsIgnoreCase(email); + } + }).orNull(); + } + + private String getUserByEmailCacheKey(String email) { + return email; + } + + @CacheRemove(commandKey = "getUserById") + @HystrixCommand + public void update(@CacheKey("id") User user) { + storage.put(user.getId(), user); + } + + @CacheRemove(commandKey = "getUserByEmail") + @HystrixCommand + public void updateProfile(@CacheKey("profile.email") User user) { + storage.get(user.getId()).setProfile(user.getProfile()); + } + + @CacheRemove(commandKey = "getUserById") + public void updateName(@CacheKey String id, String name) { + storage.get(id).setName(name); + } + } /** @@ -92,9 +330,15 @@ private String getUserIdCacheKey(String id, String name) { @Configurable public static class CacheTestConfig { @Bean + @Scope(value = "prototype") public UserService userService() { return new UserService(); } + + @Bean + public HystrixCacheAspect hystrixCacheAspect() { + return new HystrixCacheAspect(); + } } -} +} \ No newline at end of file diff --git a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java b/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java deleted file mode 100644 index c52e9bfcf..000000000 --- a/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/spring/cache/Jsr107CacheTest.java +++ /dev/null @@ -1,344 +0,0 @@ -package com.netflix.hystrix.contrib.javanica.test.spring.cache; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.netflix.hystrix.HystrixInvokableInfo; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCacheAspect; -import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey; -import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove; -import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult; -import com.netflix.hystrix.contrib.javanica.exception.HystrixCachingException; -import com.netflix.hystrix.contrib.javanica.test.spring.conf.AopCglibConfig; -import com.netflix.hystrix.contrib.javanica.test.spring.domain.Profile; -import com.netflix.hystrix.contrib.javanica.test.spring.domain.User; -import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Configurable; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Scope; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import javax.annotation.PostConstruct; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static com.netflix.hystrix.contrib.javanica.CommonUtils.getLastExecutedCommand; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * Test to check cache implementation based on JSR-107. - * - * @author dmgcodevil - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {AopCglibConfig.class, Jsr107CacheTest.CacheTestConfig.class}) -public class Jsr107CacheTest { - - private UserService userService; - - @Autowired - private ApplicationContext applicationContext; - - @Before - public void setUp() throws Exception { - userService = applicationContext.getBean(UserService.class); - } - - /** - * Get-Set-Get with Request Cache Invalidation Test. - *

- * given: - * command to get user by id, see {@link UserService#getUserById(String)} - * command to update user, see {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.spring.domain.User)} - *

- * when: - * 1. call {@link UserService#getUserById(String)} - * 2. call {@link UserService#getUserById(String)} - * 3. call {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.spring.domain.User)} - * 4. call {@link UserService#getUserById(String)} - *

- * then: - * at the first time "getUserById" command shouldn't retrieve value from cache - * at the second time "getUserById" command should retrieve value from cache - * "update" method should update an user and flush cache related to "getUserById" command - * after "update" method execution "getUserById" command shouldn't retrieve value from cache - */ - @Test - public void testGetSetGetUserCache_givenTwoCommands() { - HystrixRequestContext context = HystrixRequestContext.initializeContext(); - try { - - User user = userService.getUserById("1"); - HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); - // this is the first time we've executed this command with - // the value of "1" so it should not be from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - assertEquals("1", user.getId()); - assertEquals("name", user.getName()); // initial name value - - user = userService.getUserById("1"); - assertEquals("1", user.getId()); - getUserByIdCommand = getLastExecutedCommand(); - // this is the second time we've executed this command with - // the same value so it should return from cache - assertTrue(getUserByIdCommand.isResponseFromCache()); - assertEquals("name", user.getName()); // same name - - // create new user with same id but with new name - user = new User("1", "new_name"); - userService.update(user); // update the user - - user = userService.getUserById("1"); - getUserByIdCommand = getLastExecutedCommand(); - // this is the first time we've executed this command after "update" - // method was invoked and a cache for "getUserById" command was flushed - // so the response should not be from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - assertEquals("1", user.getId()); - assertEquals("new_name", user.getName()); - - - } finally { - context.shutdown(); - } - - // start a new request context - context = HystrixRequestContext.initializeContext(); - try { - User user = userService.getUserById("1"); - HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); - assertEquals("1", user.getId()); - // this is a new request context so this - // should not come from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - } finally { - context.shutdown(); - } - } - - @Test - public void testGetSetGetUserCache_givenGetUserByEmailAndUpdateProfile() { - HystrixRequestContext context = HystrixRequestContext.initializeContext(); - try { - - User user = userService.getUserByEmail("email"); - HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); - // this is the first time we've executed this command with - // the value of "1" so it should not be from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - assertEquals("1", user.getId()); - assertEquals("name", user.getName()); - assertEquals("email", user.getProfile().getEmail()); // initial email value - - user = userService.getUserByEmail("email"); - assertEquals("1", user.getId()); - getUserByIdCommand = getLastExecutedCommand(); - // this is the second time we've executed this command with - // the same value so it should return from cache - assertTrue(getUserByIdCommand.isResponseFromCache()); - assertEquals("email", user.getProfile().getEmail()); // same email - - // create new user with same id but with new email - Profile profile = new Profile(); - profile.setEmail("new_email"); - user.setProfile(profile); - userService.updateProfile(user); // update the user profile - - user = userService.getUserByEmail("new_email"); - getUserByIdCommand = getLastExecutedCommand(); - // this is the first time we've executed this command after "updateProfile" - // method was invoked and a cache for "getUserByEmail" command was flushed - // so the response should not be from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - assertEquals("1", user.getId()); - assertEquals("name", user.getName()); - assertEquals("new_email", user.getProfile().getEmail()); - - - } finally { - context.shutdown(); - } - - // start a new request context - context = HystrixRequestContext.initializeContext(); - try { - User user = userService.getUserByEmail("new_email"); - HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); - assertEquals("1", user.getId()); - // this is a new request context so this - // should not come from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - } finally { - context.shutdown(); - } - } - - @Test - public void testGetSetGetUserCache_givenOneCommandAndOneMethodAnnotatedWithCacheRemove() { - HystrixRequestContext context = HystrixRequestContext.initializeContext(); - try { - - // given - User user = userService.getUserById("1"); - HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); - // this is the first time we've executed this command with - // the value of "1" so it should not be from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - assertEquals("1", user.getId()); - assertEquals("name", user.getName()); // initial name value - - user = userService.getUserById("1"); - assertEquals("1", user.getId()); - getUserByIdCommand = getLastExecutedCommand(); - // this is the second time we've executed this command with - // the same value so it should return from cache - assertTrue(getUserByIdCommand.isResponseFromCache()); - assertEquals("name", user.getName()); // same name - - // when - userService.updateName("1", "new_name"); // update the user name - - // then - user = userService.getUserById("1"); - getUserByIdCommand = getLastExecutedCommand(); - // this is the first time we've executed this command after "update" - // method was invoked and a cache for "getUserById" command was flushed - // so the response should not be from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - assertEquals("1", user.getId()); - assertEquals("new_name", user.getName()); - - - } finally { - context.shutdown(); - } - - // start a new request context - context = HystrixRequestContext.initializeContext(); - try { - User user = userService.getUserById("1"); - HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); - assertEquals("1", user.getId()); - // this is a new request context so this - // should not come from cache - assertFalse(getUserByIdCommand.isResponseFromCache()); - } finally { - context.shutdown(); - } - } - - - @Test(expected = HystrixCachingException.class) - public void testGetUser_givenWrongCacheKeyMethodReturnType_shouldThrowException() { - HystrixRequestContext context = HystrixRequestContext.initializeContext(); - try { - User user = userService.getUserByName("name"); - } finally { - context.shutdown(); - } - } - - @Test(expected = HystrixCachingException.class) - public void testGetUserByName_givenNonexistentCacheKeyMethod_shouldThrowException() { - HystrixRequestContext context = HystrixRequestContext.initializeContext(); - try { - User user = userService.getUser(); - } finally { - context.shutdown(); - } - } - - public static class UserService { - private Map storage = new ConcurrentHashMap(); - - @PostConstruct - private void init() { - User user = new User("1", "name"); - Profile profile = new Profile(); - profile.setEmail("email"); - user.setProfile(profile); - storage.put("1", user); - } - - @CacheResult - @HystrixCommand - public User getUserById(@CacheKey String id) { - return storage.get(id); - } - - @CacheResult(cacheKeyMethod = "getUserByNameCacheKey") - @HystrixCommand - public User getUserByName(String name) { - return null; - } - - private Long getUserByNameCacheKey() { - return 0L; - } - - @CacheResult(cacheKeyMethod = "nonexistent") - @HystrixCommand - public User getUser() { - return null; - } - - @CacheResult(cacheKeyMethod = "getUserByEmailCacheKey") - @HystrixCommand - public User getUserByEmail(final String email) { - return Iterables.tryFind(storage.values(), new Predicate() { - @Override - public boolean apply(User input) { - return input.getProfile().getEmail().equalsIgnoreCase(email); - } - }).orNull(); - } - - private String getUserByEmailCacheKey(String email) { - return email; - } - - @CacheRemove(commandKey = "getUserById") - @HystrixCommand - public void update(@CacheKey("id") User user) { - storage.put(user.getId(), user); - } - - @CacheRemove(commandKey = "getUserByEmail") - @HystrixCommand - public void updateProfile(@CacheKey("profile.email") User user) { - storage.get(user.getId()).setProfile(user.getProfile()); - } - - @CacheRemove(commandKey = "getUserById") - public void updateName(@CacheKey String id, String name) { - storage.get(id).setName(name); - } - - } - - /** - * Spring configuration. - */ - @Configurable - public static class CacheTestConfig { - @Bean - @Scope(value = "prototype") - public UserService userService() { - return new UserService(); - } - - @Bean - public HystrixCacheAspect hystrixCacheAspect() { - return new HystrixCacheAspect(); - } - } - -} From 0f9b08425181a6c3d7a09e02d5df9b3602a75e53 Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sat, 10 Jan 2015 20:17:42 +0300 Subject: [PATCH 09/15] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 92 ++++++++++++++++------ 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 75f083dd0..5b31348a4 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -175,21 +175,76 @@ If `userResource.getUserById(id);` throws an exception which type is _BadRequest ## Request Cache -Request caching is enabled by defining the _get cache key_ method like in example below: -```java - @HystrixCommand(cacheKeyMethod = "getUserIdCacheKey") - public User getUserById(String id) { - return userResource.getUserById(id); - } +Javanica provides specific annotations in order to enable and manage request caching. This annotations look very similar to [JSR107](https://github.com/jsr107/jsr107spec) but less extensive than those, by other hand Hystrix doesn't provide independent and complex caching system therefore there is no need to have such diversity of annotations as in JSR107. +Javanica has only three annotations dedicated for request caching. - private String getUserIdCacheKey(String id){ - return id; - } + +| Annotation | Description | Properties | +| ------------- |-------------| -----| +| @CacheResult | Marks a methods that results should be cached for a Hystrix command.This annotation must be used in conjunction with HystrixCommand annotation. | cacheKeyMethod | +| @CacheRemove | Marks methods used to invalidate cache of a command. Generated cache key must be same as key generated within link CacheResult context | commandKey, cacheKeyMethod | +| @CacheKey | Marks a method argument as part of the cache key. If no arguments are marked all arguments are used. If _@CacheResult_ or _@CacheRemove_ annotation has specified _cacheKeyMethod_ then a method arguments will not be used to build cache key even if they annotated with _@CacheKey_ | value | + +**cacheKeyMethod** - a method name to be used to get a key for request caching. The command and cache key method should be placed in the same class and have same method signature except cache key method return type that should be _String_. +_cacheKeyMethod_ has higher priority than an arguments of a method, that means what actual arguments +of a method that annotated with _@CacheResult_ will not be used to generate cache key, instead specified +_cacheKeyMethod_ fully assigns to itself responsibility for cache key generation. +By default this returns empty string which means "do not use cache method. + +**Cache key generator** + +**HystrixCacheKeyGenerator** generates a **HystrixGeneratedCacheKey** based on **CacheInvocationContext**. Implementation is thread-safe. +Parameters of an annotated method are selected by the following rules: +- If no parameters are annotated with _@CacheKey_ then all parameters are included +- If one or more _@CacheKey_ annotations exist only those parameters with the _@CacheKey_ annotation are included + +**Note**: If _CacheResult_ or _CacheRemove_ annotation has specified **cacheKeyMethod** then a method arguments **will not be used to build cache key** even if they annotated with _CacheKey_. + +**@CacheKey and value property** +This annotation has one property by default that allows specify name of a certain argument property. for example: ```@CacheKey("id") User user```, or in case composite property: ```@CacheKey("profile.name") User user```. Null properties are ignored, i.e. if ```profile``` is ```null``` then result of ```@CacheKey("profile.name") User user``` will be empty string. + +Examples: + +```java + @CacheResult + @HystrixCommand + public User getUserById(@CacheKey String id) { + return storage.get(id); + } + + // -------------------------------------------------- + @CacheResult(cacheKeyMethod = "getUserByNameCacheKey") + @HystrixCommand + public User getUserByName(String name) { + return storage.getByName(name); + } + private Long getUserByNameCacheKey(String name) { + return name; + } + // -------------------------------------------------- + @CacheResult + @HystrixCommand + public void getUserByProfileName(@CacheKey("profile.email") User user) { + storage.getUserByProfileName(user.getProfile().getName()); + } + ``` -If "getUserIdCacheKey" returns `null` then the cache key will be "null" (string). +**Get-Set-Get pattern** +Example: +```java + @CacheResult + @HystrixCommand + public User getUserById(@CacheKey String id) { + return storage.get(id); + } -**_Its important to remember that Hystrix command and "get cache key" method should be placed in the same class and have same method signature_**. + @CacheRemove(commandKey = "getUserById") + @HystrixCommand + public void update(@CacheKey("id") User user) { + storage.put(user.getId(), user); + } +``` ## Configuration ### Command Properties @@ -305,17 +360,4 @@ All requests that were processed successfully or failed will be returned to an u #Development Status and Future -Todo list: - -1. Add new annotation that can be applied for types in order to define default command properties for all commands that are specified in a class. For example specify default group key or default properties for all commands. -2.Add Cache annotation to mark some parameters which should participate in building cache key, for example: - -```java -@HystrixCommand -User getUserByParams(@Cache String id, String name, @Cache Integer age) -``` -`toString()` method will be invoked for parameters: id and age to build cache key: -Pseudo code -``` -String cacheKey = id.toString()+age.toString() -``` +Please create an issue if you need a feature or you detected some bugs. Thanks From bda7a6b35775405402c8a7cc95f0c5d2b3ac1ae7 Mon Sep 17 00:00:00 2001 From: dmgcodevil Date: Sat, 10 Jan 2015 20:35:03 +0300 Subject: [PATCH 10/15] iss380: remove cacheKeyMethod from HystrixCommand annotation --- .../javanica/annotation/HystrixCommand.java | 11 ----------- .../aop/aspectj/HystrixCommandAspect.java | 3 +-- .../command/AbstractHystrixCommand.java | 17 +---------------- .../command/AbstractHystrixCommandFactory.java | 3 +-- .../javanica/command/CommandActions.java | 12 ------------ 5 files changed, 3 insertions(+), 43 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java index 7195900b8..c3d388197 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/annotation/HystrixCommand.java @@ -83,17 +83,6 @@ */ String fallbackMethod() default ""; - /** - * Method name to be used to get a key for request caching. - * The command and get cache key method should be placed in the same class. - *

- * By default this returns empty string which means "do not cache". - * - * @return method name or empty string - */ - @Deprecated - String cacheKeyMethod() default ""; - /** * Specifies command properties. * diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java index dab692193..dcf56784f 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/aop/aspectj/HystrixCommandAspect.java @@ -54,10 +54,9 @@ public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinP HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class); HystrixCollapser hystrixCollapser = method.getAnnotation(HystrixCollapser.class); ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType()); - Method cacheKeyMethod = getMethodFromTarget(joinPoint, hystrixCommand.cacheKeyMethod()); MetaHolder metaHolder = MetaHolder.builder() .args(args).method(method).obj(obj).proxyObj(joinPoint.getThis()) - .cacheKeyMethod(cacheKeyMethod).executionType(executionType) + .executionType(executionType) .hystrixCommand(hystrixCommand).hystrixCollapser(hystrixCollapser) .defaultCommandKey(method.getName()) .defaultCollapserKey(method.getName()) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java index 8656a2261..0f559e448 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommand.java @@ -81,14 +81,6 @@ CommandAction getFallbackAction() { return commandActions.getFallbackAction(); } - /** - * Gets key action. - * - * @return key action - */ - CommandAction getCacheKeyAction() { - return commandActions.getCacheKeyAction(); - } /** * Gets command properties. @@ -130,14 +122,7 @@ protected String getCacheKey() { if (cacheResultInvocationContext != null) { HystrixGeneratedCacheKey hystrixGeneratedCacheKey = defaultCacheKeyGenerator.generateCacheKey(cacheResultInvocationContext); - return hystrixGeneratedCacheKey.getCacheKey(); - } - - // deprecated approach - if (commandActions.getCacheKeyAction() != null) { - key = String.valueOf(commandActions.getCacheKeyAction().execute(executionType)); - } else { - key = super.getCacheKey(); + key = hystrixGeneratedCacheKey.getCacheKey(); } return key; } diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java index 777078f24..112716eda 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/AbstractHystrixCommandFactory.java @@ -64,9 +64,8 @@ public T create(MetaHolder metaHolder, Map commandProperties = getCommandProperties(metaHolder.getHystrixCommand()); CommandAction commandAction = new MethodExecutionAction(metaHolder.getObj(), metaHolder.getMethod(), metaHolder.getArgs()); CommandAction fallbackAction = createFallbackAction(metaHolder, collapsedRequests); - CommandAction cacheKeyAction = createCacheKeyAction(metaHolder); CommandActions commandActions = CommandActions.builder().commandAction(commandAction) - .fallbackAction(fallbackAction).cacheKeyAction(cacheKeyAction).build(); + .fallbackAction(fallbackAction).build(); HystrixCommandBuilder hystrixCommandBuilder = new HystrixCommandBuilder().setterBuilder(setterBuilder) .commandActions(commandActions) diff --git a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java index d2c8f5db8..d4621a656 100644 --- a/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java +++ b/hystrix-contrib/hystrix-javanica/src/main/java/com/netflix/hystrix/contrib/javanica/command/CommandActions.java @@ -24,12 +24,10 @@ public class CommandActions { private final CommandAction commandAction; private final CommandAction fallbackAction; - private final CommandAction cacheKeyAction; public CommandActions(Builder builder) { this.commandAction = builder.commandAction; this.fallbackAction = builder.fallbackAction; - this.cacheKeyAction = builder.cacheKeyAction; } public static Builder builder() { @@ -44,14 +42,9 @@ public CommandAction getFallbackAction() { return fallbackAction; } - public CommandAction getCacheKeyAction() { - return cacheKeyAction; - } - public static class Builder { private CommandAction commandAction; private CommandAction fallbackAction; - private CommandAction cacheKeyAction; public Builder commandAction(CommandAction pCommandAction) { this.commandAction = pCommandAction; @@ -63,11 +56,6 @@ public Builder fallbackAction(CommandAction pFallbackAction) { return this; } - public Builder cacheKeyAction(CommandAction pCacheKeyAction) { - this.cacheKeyAction = pCacheKeyAction; - return this; - } - public CommandActions build() { return new CommandActions(this); } From 6da380931468b3eb8c7b52c1263e4d287b4206de Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sat, 10 Jan 2015 20:57:31 +0300 Subject: [PATCH 11/15] Update README.md Javanica cache --- hystrix-contrib/hystrix-javanica/README.md | 49 +++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 5b31348a4..fe93f3040 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -190,10 +190,57 @@ _cacheKeyMethod_ has higher priority than an arguments of a method, that means w of a method that annotated with _@CacheResult_ will not be used to generate cache key, instead specified _cacheKeyMethod_ fully assigns to itself responsibility for cache key generation. By default this returns empty string which means "do not use cache method. +You can consider _cacheKeyMethod_ as a replacement for common key generators (for example [JSR170-CacheKeyGenerator](https://github.com/jsr107/jsr107spec/blob/master/src/main/java/javax/cache/annotation/CacheKeyGenerator.java)) but with _cacheKeyMethod_ cache key generation becomes more convenient and simple. Not to be unfounded let's compare the two approaches: +JSR107 +```java + + @CacheRemove(cacheName = "getUserById", cacheKeyGenerator = UserCacheKeyGenerator.class) + @HystrixCommand + public void update(@CacheKey User user) { + storage.put(user.getId(), user); + } + + public static class UserCacheKeyGenerator implements HystrixCacheKeyGenerator { + @Override + public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext cacheKeyInvocationContext) { + CacheInvocationParameter cacheInvocationParameter = cacheKeyInvocationContext.getKeyParameters()[0]; + User user = (User) cacheInvocationParameter.getValue(); + return new DefaultHystrixGeneratedCacheKey(user.getId()); + } + } +``` + +Javanica cacheKeyMethod +```java + @CacheRemove(commandKey = "getUserById", cacheKeyMethod=) + @HystrixCommand + public void update(User user) { + storage.put(user.getId(), user); + } + private String cacheKeyMethod(User user) { + return user.getId(); + } + +``` +or even just +```java + @CacheRemove(commandKey = "getUserById") + @HystrixCommand + public void update(@CacheKey("id") User user) { + storage.put(user.getId(), user); + } +``` +You don't need to create new classes, also approach with cacheKeyMethod helps during refactoring if you will give correct names for cache key methods. It is recommended to append prefix "cacheKeyMethod" to the real method name, for example: +```java +public User getUserById(@CacheKey String id); +``` +```java +private User getUserByIdCacheKeyMethod(String id); +``` **Cache key generator** -**HystrixCacheKeyGenerator** generates a **HystrixGeneratedCacheKey** based on **CacheInvocationContext**. Implementation is thread-safe. +Jacanica has only one cache key generator **HystrixCacheKeyGenerator** that generates a _HystrixGeneratedCacheKey_ based on _CacheInvocationContext_. Implementation is thread-safe. Parameters of an annotated method are selected by the following rules: - If no parameters are annotated with _@CacheKey_ then all parameters are included - If one or more _@CacheKey_ annotations exist only those parameters with the _@CacheKey_ annotation are included From 21f53b396eb85eca74eeed30fd9b04c2021c20d5 Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sun, 11 Jan 2015 11:04:53 +0300 Subject: [PATCH 12/15] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 31 ++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index fe93f3040..9d612ac37 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -278,19 +278,46 @@ Examples: ``` **Get-Set-Get pattern** +To get more about this pattern you can read [this](https://github.com/Netflix/Hystrix/wiki/How-To-Use#get-set-get-with-request-cache-invalidation) chapter Example: ```java + public class UserService { @CacheResult @HystrixCommand - public User getUserById(@CacheKey String id) { + public User getUserById(@CacheKey String id) { // GET return storage.get(id); } @CacheRemove(commandKey = "getUserById") @HystrixCommand - public void update(@CacheKey("id") User user) { + public void update(@CacheKey("id") User user) { // SET storage.put(user.getId(), user); } + } + + // test app + + public void test(){ + User user = userService.getUserById("1"); + HystrixInvokableInfo getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command with + // the value of "1" so it should not be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the second time we've executed this command with + // the same value so it should return from cache + assertTrue(getUserByIdCommand.isResponseFromCache()); + + user = new User("1", "new_name"); + userService.update(user); // update the user + user = userService.getUserById("1"); + getUserByIdCommand = getLastExecutedCommand(); + // this is the first time we've executed this command after "update" + // method was invoked and a cache for "getUserById" command was flushed + // so the response shouldn't be from cache + assertFalse(getUserByIdCommand.isResponseFromCache()); + } ``` ## Configuration From b4435818e5c02137a34676e486e425482f8518cf Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sun, 11 Jan 2015 11:14:07 +0300 Subject: [PATCH 13/15] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 9d612ac37..19ad34019 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -320,6 +320,22 @@ Example: } ``` +**Note**: You can use @CacheRemove annotation in conjunction with @HystrixCommand or without. If you want annotate not command method with @CacheRemove annotation then you need to add HystrixCacheAspect aspect to your configuration: + +```xml + + ... + + ... + + + + + + + +``` + ## Configuration ### Command Properties From 8a4955a5b72e18b82d28dad9573e71dfe952a70c Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sun, 11 Jan 2015 11:33:47 +0300 Subject: [PATCH 14/15] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 19ad34019..6917d7054 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -451,3 +451,4 @@ All requests that were processed successfully or failed will be returned to an u #Development Status and Future Please create an issue if you need a feature or you detected some bugs. Thanks +**Note**: Javaniva 1.4.+ is more stable than 1.3.+ All fixes initially are added in 1.4.+ and after if it's not much efforts merged to 1.3. It's recommended to use Javaniva 1.4.+ From 92f04c58b558d91e654e0b329619ea9eae4081fd Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sun, 11 Jan 2015 11:34:39 +0300 Subject: [PATCH 15/15] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 6917d7054..d5bea981b 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -451,4 +451,5 @@ All requests that were processed successfully or failed will be returned to an u #Development Status and Future Please create an issue if you need a feature or you detected some bugs. Thanks -**Note**: Javaniva 1.4.+ is more stable than 1.3.+ All fixes initially are added in 1.4.+ and after if it's not much efforts merged to 1.3. It's recommended to use Javaniva 1.4.+ + +**Note**: Javaniva 1.4.+ is more stable than 1.3.+ All fixes initially are added in 1.4.+ and after if it's not much efforts then merged to 1.3. **It's recommended to use Javaniva 1.4.+**