/**
* Copyright 2016 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.test.common.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.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.common.BasicHystrixTest;
import com.netflix.hystrix.contrib.javanica.test.common.domain.Profile;
import com.netflix.hystrix.contrib.javanica.test.common.domain.User;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.junit.Before;
import org.junit.Test;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getLastExecutedCommand;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Created by dmgcodevil
*/
public abstract class BasicCacheTest extends BasicHystrixTest {
private UserService userService;
@Before
public void setUp() throws Exception {
userService = createUserService();
}
protected abstract UserService createUserService();
/**
* Get-Set-Get with Request Cache Invalidation Test.
* <p/>
* 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.common.domain.User)}
* <p/>
* when:
* 1. call {@link UserService#getUserById(String)}
* 2. call {@link UserService#getUserById(String)}
* 3. call {@link UserService#update(com.netflix.hystrix.contrib.javanica.test.common.domain.User)}
* 4. call {@link UserService#getUserById(String)}
* <p/>
* 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() {
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());
// start a new request context
resetContext();
user = userService.getUserById("1");
getUserByIdCommand = getLastExecutedCommand();
assertEquals("1", user.getId());
// this is a new request context so this
// should not come from cache
assertFalse(getUserByIdCommand.isResponseFromCache());
}
@Test
public void testGetSetGetUserCache_givenGetUserByEmailAndUpdateProfile() {
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());
// start a new request context
resetContext();
user = userService.getUserByEmail("new_email");
getUserByIdCommand = getLastExecutedCommand();
assertEquals("1", user.getId());
// this is a new request context so this
// should not come from cache
assertFalse(getUserByIdCommand.isResponseFromCache());
}
@Test
public void testGetSetGetUserCache_givenOneCommandAndOneMethodAnnotatedWithCacheRemove() {
// 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());
// start a new request context
resetContext();
user = userService.getUserById("1");
getUserByIdCommand = getLastExecutedCommand();
assertEquals("1", user.getId());
// this is a new request context so this
// should not come from cache
assertFalse(getUserByIdCommand.isResponseFromCache());
}
@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<String, User> storage = new ConcurrentHashMap<String, User>();
@PostConstruct
public 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<User>() {
@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);
}
}
}