0

Trying to Implement infinispan base cache on spring boot using custom annotation:

@Aspect
@Configuration
@Slf4j
public class CacheAnnotationAspect {
    Logger logger = LoggerFactory.getLogger(CacheAnnotationAspect.class);

    @Autowired
    InfinispanCacheService cacheService;

    @Around("@annotation(com.calsoft.lib.cache.CacheResult)")
    public Object cacheResult(ProceedingJoinPoint joinPoint)throws Throwable{
        logger.info("Cache Operation :: CacheResult annotation advice invoked...");
        CacheResult cacheResult=(CacheResult) getAnnotation(joinPoint,CacheResult.class);
        CacheConfig cacheConfig=CacheConfig.from(cacheResult);
        Object resultFromCache=getFromCache(joinPoint,cacheConfig);
        if(resultFromCache!= null){
            return resultFromCache;
        }
        Object result=joinPoint.proceed(joinPoint.getArgs());
        storeInCache(result,joinPoint,cacheConfig);
        return result;
    }

    private void storeInCache(Object result, ProceedingJoinPoint joinPoint, CacheConfig cacheConfig) {
        if(result==null){
            log.info("Cache op :: null values not cached");
            return;
        }
        CacheService cacheService=getCacheService();
        if(cacheService==null){
            logger.info("Cache op :: CacheGet Failed : No CacheService available for use..");
        }
        DefaultCacheKey defaultCacheKey=getKey(joinPoint,cacheConfig);
        String cacheName=getCacheName(cacheConfig.getCacheName(),joinPoint);
        long lifeSpan=cacheConfig.getLifespan();
        if(lifeSpan== CommonConstant.CACHE_DEFAULT_LIFE){
            cacheService.put(cacheName,defaultCacheKey,result);
        }else{
            cacheService.put(cacheName,defaultCacheKey,result,lifeSpan,cacheConfig.getUnit());
        }
        logger.info("Cache Op :: Result cached :: {} ",cacheConfig);
    }

    private DefaultCacheKey getKey(ProceedingJoinPoint joinPoint, CacheConfig cacheConfig) {
        List<Object> keys=new ArrayList<>();
        Object target=joinPoint.getTarget();
        MethodSignature methodSignature=MethodSignature.class.cast(joinPoint.getSignature());
        Method method=methodSignature.getMethod();
        Annotation[][] parameterAnnotations=method.getParameterAnnotations();
        if(isEmpty(trim(cacheConfig.getKeyPrefix()))){
            keys.add(target.getClass().getName());
            keys.add(method.getName());
        }else{
            keys.add(cacheConfig.getKeyPrefix());
        }
        if(isCacheKeySpecified(parameterAnnotations)){
            keys.addAll(getCacheKeys(joinPoint,parameterAnnotations));
        }else{
            keys.addAll(Arrays.asList(joinPoint.getArgs()));
        }
        return new DefaultCacheKey(keys.toArray());
    }

    private Collection<?> getCacheKeys(ProceedingJoinPoint joinPoint, Annotation[][] parameterAnnotations) {
    Object[] args=joinPoint.getArgs();
    List<Object> result=new ArrayList<>();
    int i=0;
    for(Annotation[] annotations: parameterAnnotations){
        for(Annotation annotation: annotations){
            if(annotation instanceof CacheKey){
                result.add(args[i]);
                break;
            }
        }
        i++;
    }
    return result;
    }

    private boolean isCacheKeySpecified(Annotation[][] parameterAnnotations) {
        for(Annotation[] annotations:parameterAnnotations){
            for(Annotation annotation:annotations){
               if(annotation instanceof CacheKey) {
                return true;
               }
            }
        }
        return false;
    }

    private Object getFromCache(ProceedingJoinPoint joinPoint, CacheConfig cacheConfig) {
        CacheService cacheService = getCacheService();
        if (cacheService == null) {
            logger.info("Cache op :: CacheGet Failed : No CacheService available for use..");
        }
        String cacheName=getCacheName(cacheConfig.getCacheName(),joinPoint);
        DefaultCacheKey defaultCacheKey=getKey(joinPoint,cacheConfig);

        return cacheService.get(cacheName,defaultCacheKey);
    }

    private String getCacheName(String cacheName, ProceedingJoinPoint joinPoint) {
        boolean nameNotDefined=isEmpty(trim(cacheName));
        if(nameNotDefined){
            logger.error("Cache op :: Cache Name not defined");
        }else{
            CacheService cacheService=getCacheService();
            if(!cacheService.cacheExists(cacheName)){
                throw new RuntimeException("Cache with the name "+ cacheName+" does not exists");
            }
        }
        return cacheName;
    }

    private CacheService getCacheService() {
        return cacheService;
    }

    private Annotation getAnnotation(ProceedingJoinPoint joinPoint, Class type) {
        MethodSignature methodSignature=MethodSignature.class.cast(joinPoint.getSignature());
        Method method=methodSignature.getMethod();
        return method.getAnnotation(type);
    }

}

Above class << CacheAnnotationAspect >> is custom annotation @CacheResult Aspect implementation where it will first try to retrieve from cache and if not found will make actual dao call and then store in cache.

Below is the of the implementation of InfinispanCacheService which invokes cachemager to get/put cache entries.

@Service
public class InfinispanCacheService implements CacheService {

    Logger logger = LoggerFactory.getLogger(InfinispanCacheService.class);
    @Autowired
    private DefaultCacheManagerWrapper cacheManagerWrapper;
    private DefaultCacheManager infiniCacheManager;

    private DefaultCacheManager initializeCacheManager(){
        if(infiniCacheManager==null){
            infiniCacheManager=cacheManagerWrapper.getCacheManager();
        }
        return infiniCacheManager;
    }
    @PostConstruct
    public void start(){
        logger.info("Initializing...InifinispanCacheService ....");
        initializeCacheManager();
        for(String cacheName : infiniCacheManager.getCacheNames()){
            infiniCacheManager.startCache(cacheName);
        }
    }
    @Override
    public Object get(String cacheName, Object key) {
        return getCache(cacheName).get(key);
    }

    @Override
    public void put(String cacheName, Object key, Object value, long lifespan, TimeUnit unit) {
        Cache cache=getCache(cacheName);
        cache.put(key,value,lifespan,unit);
    }

    @Override
    public void put(String cacheName, Object key, Object value) {
        Cache cache=getCache(cacheName);
        cache.put(key,value);
    }

    private Cache<Object,Object> getCache(String cacheName) {
        Cache<Object,Object> cache;
        if(isEmpty(trim(cacheName))){
            cache=infiniCacheManager.getCache();
        }else{
            cache=infiniCacheManager.getCache(cacheName,false);
        }
        return cache;
    }

    @Override
    public boolean cacheExists(String cacheName) {
        return infiniCacheManager.cacheExists(cacheName);
    }
}

<<<<<< The DefaultCacheManager below is one which during startup initializes the DefaultCacheManager by loading the infispan.xml configuration >>>>>

@Component
public class DefaultCacheManagerWrapper {

    Logger logger = LoggerFactory.getLogger(DefaultCacheManagerWrapper.class);
//    @Value("${classpath:spring.cache.infinispan.config}")
    private String fileName="file:\\calsoft\\devlabs\\ecom2\\ecom-svc-admin\\src\\main\\resources\\infinispan.xml";
    private DefaultCacheManager infiniCacheManager;

    @PostConstruct
    public void start(){
        logger.info(" Received File Name :: {} ",fileName);
        try{
            URL fileUrl=new URL(fileName);
            URLConnection urlConnection=fileUrl.openConnection();
            InputStream inputStream=urlConnection.getInputStream();
            infiniCacheManager=new DefaultCacheManager(inputStream);
            infiniCacheManager.start();
            logger.info("Cache Manager Initialized....");
        }catch(MalformedURLException mue){
            logger.error("Error creating file url ",mue.getMessage());
        } catch (IOException e) {
            logger.error("Error creating file url ",e.getMessage());
        }
    }
    public void stop() { infiniCacheManager.stop();}
    public DefaultCacheManager getCacheManager(){
        return infiniCacheManager;
    }
}

<<<< Infinispan.xml configuration >>

   <?xml version="1.0" encoding="UTF-8"?>
<infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:scehmaLocation="
            urn:infinispan:config:7.2
            http://www.infinispan.org/schemas/infinispan-config-7.2.xsd"
            xmlns="urn:infinispan:config:7.2">
    <cache-container default-cache="attributeset-cache">
        <!-- template configurations -->
        <local-cache-configuration name="local-template">
            <expiration interval="10000" lifespan="50000" max-idle="50000"/>
        </local-cache-configuration>

        <!-- cache definitions -->
        <local-cache name="attributeset-cache" configuration="local-template"/>
    </cache-container>
</infinispan>

Annotation at controller level:

@CacheResult(cacheName= CommonConstant.ATTRIBUTE_SET_CACHE,lifespan=10,unit = TimeUnit.MINUTES)
    @GetMapping("/eavattributeset")
    public List<EavAttributeSet> fetchAllAttributes() {
            return eavAttributeService.fetchAllEavattributesets();
    }

<< EavAttributeService >>

@Service
public class EavAttributeService {

    Logger logger = LoggerFactory.getLogger(EavAttributeService.class);
    @Autowired
    private EavAttributeJpaRepository eavAttributeJpaRepository;

    @Autowired
    EavAttributeSetJpaRepository eavAttributeSetJpaRepository;  

    public List<EavAttributeSet> fetchAllEavattributesets() {

        return eavAttributeSetJpaRepository.findAll();

    }
}

<< CacheConfig >>

@Data
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class CacheConfig {
    private String cacheName;
    private long lifespan;
    private TimeUnit unit;
    private String keyPrefix;

    public static CacheConfig from(CacheResult cacheResult) {
        return new CacheConfig(cacheResult.cacheName(), cacheResult.lifespan(), cacheResult.unit(), cacheResult.keyPrefix());
    }

}

Issue : The data is not getting cache, Wherever @CacheResult annotation is used the CacheAnnotationAspect is getting invoked and the check for data also happens in cache but when it tries to store the data in cache it does not cache and every subsequent call of this method does not return any data.

Shriram
  • 1
  • 1
  • Are you sure the values are not cached, and they don't expire after 10 milliseconds as configured? – Dan Berindei Nov 21 '19 at 11:52
  • @DanBerindei thanks for the reply, I even increased it to 60000 milliseconds but still it does not seems to cache, as every method hit would call the dao. – Shriram Nov 22 '19 at 01:40
  • also i have lifespan as one of the attributes `@CacheResult(cacheName= CommonConstant.ATTRIBUTE_SET_CACHE,lifespan=10,unit = TimeUnit.MINUTES) @GetMapping("/eavattributeset") public List fetchAllAttributes() { return eavAttributeService.fetchAllEavattributesets(); }` – Shriram Nov 22 '19 at 01:41
  • Sorry, I missed the explicit timeout on the `@CacheResult` annotation. – Dan Berindei Nov 22 '19 at 15:39
  • its fine @DanBerindei , any suggestions/tips to resolve the issue ?? – Shriram Nov 22 '19 at 16:28
  • unfortunately I don't know much about Spring and especially about implementing interceptors in Spring, so the only advice I can give is to add a breakpoint when the interceptor accesses the cache and check that it's using the same key and same cache instance every time. – Dan Berindei Nov 25 '19 at 14:53

1 Answers1

0

It works fine when i try with below configuration on infinispan.xml.

<expiration lifespan="50000"/>

Can you try with the above config and see if it using the cached data.

I guess it could be the max-idle timeout (10 milliseconds) which could be issue.

Sam
  • 143
  • 1
  • 8
  • I tried increasing the lifespan ( refer above update infinispan.xml) but did not resolve the issue. It still does not cache. – Shriram Nov 23 '19 at 12:32
  • Actually it works for me with your updated configuration as well.. can you share the CacheConfig code – Sam Nov 23 '19 at 14:56
  • added << CacheConfig >> to above code snippet, also is this working for you on spring boot + Embedded Tomcat/Standalone Tomcat – Shriram Nov 24 '19 at 05:03
  • yeah working with springboot + embedded tomcat. i have uploaded my sample in https://github.com/samuel-coder/infinispanaspectsample. please give it a try. – Sam Nov 24 '19 at 07:32
  • Sorry for the delay, I am getting below error while try to run the application..Sorry for the delay, I am getting below error while try to run the samples .. `BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Advice must be declared inside an aspect type: Offending method 'public java.lang.Object com.samples.CacheAnnotationAspect.cacheResult(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable' in class com.samples.CacheAnnotationAspect] at` – Shriram Dec 08 '19 at 17:18
  • weird why i was not seeing that error earlier, anyways fixed now. can you do a git pull and try again – Sam Dec 09 '19 at 17:19
  • it works fine, I am trying to reproduce the issue in your code by creating list of 1000 bikes and 1000 bikes but i am not able to reproduce. I noticed that in my case the joinpoint.proceed() call returns back even before the actually work is completed. So what happens is the storeInCache() is invoked much before the method has actually returned the result from jointpoint.proceed. I tried it putting it storeInCache() in finally but still same issue. – Shriram Dec 10 '19 at 01:41
  • can you check if eavAttributeService.fetchAllEavattributesets() is async. Also can you add code snippet of eavAttributeService code here? – Sam Dec 10 '19 at 03:31
  • Its not async, added << EavAttributeService >> code snippnet for your reference...Its a simple jpa call to load EavAttributeSet entity. The EavAttributeSet has some nested relationship association, which takes time to load and convert into json but that should not cause @around to return from joinpoint.proceed to return before completion ? – Shriram Dec 10 '19 at 16:41
  • Yeah looking at EavAttributeService, i dont see any async. sorry not sure how to help you further. – Sam Dec 14 '19 at 06:53