Search code examples
javaehcachejcachejava-ee-8

EE8 JCache annotation CacheResult doesn't work


I'm developing a WEB APP under JBoss EAP 7.2 (EE8) using EhCache as JCache implementation (JSR 107)

I have this code:

@Inject
CacheManager cacheManager;

@CacheResult(cacheName = "roles")
public List<RoleDTO> get(@CacheKey String id) {
    return service.getRoles(id);
}

public List<RoleDTO> getRoles(final String userId) {

    final List<RoleDTO> output = get(userId);

    return output;
}

According to this article I read, I'd expect the second time I access the get(userId) method should use cache and ignore entering the method, but is not so. I also queries injected cacheManager, and "roles" cache is always empty.

Where am I wrong ?

EDIT

I guess EhCache 3.x doesn't support jcache annotations in a standard EE environment, on the web I can only see other CDI implementations like ri o guice. However, by including one of these implementations, for example:

    <dependency>
        <groupId>org.jsr107.ri</groupId>
        <artifactId>cache-annotations-ri-cdi</artifactId>
    </dependency>

seems the new cache entry is put onto a new cachemanager (see the below code)

 Caching.getCachingProvider().getCacheManager

instead of the injected and configured CacheManager I already have

EDIT EDIT

@CacheResult(cacheName = "roles", cacheResolverFactory = AppCacheResolverFactory.class, cacheKeyGenerator = CacheKeyGeneratorFactory.class)
    public List<RoleDTO> getRoles(@CacheKey final String userId) {

This is my CacheResolverFactory class

public class AppCacheResolverFactory implements CacheResolverFactory {


    @Inject
    CacheManager cacheManager;

    @Override
    public CacheResolver getCacheResolver(CacheMethodDetails<? extends Annotation> cacheMethodDetails) {
        return new DefaultCacheResolver(cacheManager.getCache(cacheMethodDetails.getCacheName()));
    }

    @Override
    public CacheResolver getExceptionCacheResolver(CacheMethodDetails<CacheResult> cacheMethodDetails) {
        return null;
    }
}

Now I get this:

java.lang.ClassCastException: Invalid key type, expected : java.lang.String but was : org.jsr107.ri.annotations.DefaultGeneratedCacheKey

EDIT EDIT EDIT

I've created a CacheKeyGeneratorFactory and a StringCacheKey that wraps a String value:

CacheKeyGeneratorFactory

public class CacheKeyGeneratorFactory implements CacheKeyGenerator {
    @Override
    public GeneratedCacheKey generateCacheKey(CacheKeyInvocationContext<? extends Annotation> cacheKeyInvocationContext) {
        final CacheInvocationParameter[] allParameters = cacheKeyInvocationContext.getAllParameters();
        for (CacheInvocationParameter parameter : allParameters) {
            if (StringCacheKey.class.equals(parameter.getRawType())) {
                return (StringCacheKey)parameter.getValue();
            }
        }
        return null;
    }
}

StringCacheKey

public class StringCacheKey implements GeneratedCacheKey {

    private String value;

    public StringCacheKey(String param) {
        this.value = param;
    }

    @Override
    public int hashCode() {
        return this.value.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return obj != null && obj.getClass() == this.getClass() && this.value.equals(((StringCacheKey)obj).getValue());
    }

    public String getValue() {
        return value;
    }
}

Then I changed ehcache.xml configuration with this:

 <cache alias="roles">
    <key-type>mypackage.StringCacheKey</key-type>
    <value-type>java.util.List</value-type>

And the method with @CacheResult too

public List<RoleDTO> get(@CacheKey String id) 

Now it goes, however I wonder I can save all this bunch of code and class to make it work :/


Solution

  • The RI wraps keys by default into DefaultGeneratedCacheKey. You need to change your EHCache configuration to allow Object types for the key. Alternatively you can register your own key generator and use your own key type. You can of course also specify org.jsr107.ri.annotations.DefaultGeneratedCacheKey in your EHCache configuration, however, then you bind to a specific implementation.

    Unfortunately, the JSR107/JCache standard for annotations requires that a cache key implements the interface GeneratedCacheKey. That means, if you have a simple cache key like int or string, these always need to get wrapped, when stored into the cache.

    Side note: There is no technical need for the interface GeneratedCacheKey, since it does not define any new methods. This is a known problem in the standard. Unfortunately, there was no sufficient vetting of the annotations part before the standard got finalized.