2

In the cache2k User Guide, §2.3. Cache Aside has a code sample and sentence about cache-aside.

Cache<String, String> routeToAirline = new Cache2kBuilder<String, String>() {}
  .name("routeToAirline")
  .build();

private String findFavoriteAirline(String origin, String destination) {
  // expensive operation to find the best airline for this route
  // for example, ask all friends...
}

public String lookupFavoirteAirline(String origin, String destination) {
  String route = origin + "-" + destination;
  String airline = routeToAirline.peek(route);
  if (airline == null) {
    airline = findFavoriteAirline(origin, destination);
    routeToAirline.put(route, airline);
  }
  return airline;
}

The above pattern is called cache aside.

And I'm like "Great....I love Cache Aside pattern". So I try to implement one using generics instead of hard coding the (value) type.

I came up with this using generics. And I inject the method that "gets" the item (Supplier). Totally reusable code!

package mypackage;

import java.util.function.Supplier;

import org.cache2k.Cache;
import org.cache2k.Cache2kBuilder;

//import mypackage.interfaces.IGenericCacheAside;

public class Cache2kGenericCacheAside<TEntity> ////implements IGenericCacheAside<TEntity> {

    public final String CacheKeyPrefix = "GenericCacheAsidePrefix";

    private volatile Cache<String, TEntity> theCache = null; /* static not allowed for TEntity */

    public TEntity GetCacheAsideItem(String uniqueIdentifier, long itemLifeMiliseconds,
            final Supplier<TEntity> valueFactory) {
        this.initiateCacheObject();
        String cacheKey = this.GetFullCacheKey(uniqueIdentifier);
        TEntity cachedOrFreshItem = this.GetFromCache(cacheKey, itemLifeMiliseconds, valueFactory);
        return cachedOrFreshItem;
    }

    public TEntity RemoveCacheAsideItem(String uniqueIdentifier) {
        TEntity removedItem = null;
        String cacheKey = this.GetFullCacheKey(uniqueIdentifier);
        if (this.theCache.containsKey(uniqueIdentifier)) {
            removedItem = this.theCache.peekAndRemove(cacheKey);
        }
        return removedItem;
    }

    private void initiateCacheObject(/* long duration, TimeUnit tu, long capacity */) {
        if (null == this.theCache) {

            theCache = new Cache2kBuilder<String, TEntity>() {
            }.name("myCache").eternal(true).build();

        }
    }



    private TEntity GetFromCache(String cacheKey, long millis, final Supplier<TEntity> valueFactory) {
        TEntity cachedOrFreshItem = theCache.peek(cacheKey);
        if (cachedOrFreshItem == null) {
            cachedOrFreshItem = valueFactory.get();
            theCache.put(cacheKey, cachedOrFreshItem);
            theCache.expireAt(cacheKey, millis);
        }

        return cachedOrFreshItem;
    }

    private String GetFullCacheKey(String uniqueIdentifier) {
        String returnValue = CacheKeyPrefix + uniqueIdentifier;
        return returnValue;
    }
}

I get a runtime error:

java.lang.IllegalArgumentException: The run time type is not available, got: TEntity

I think I may have stumbled into the this previously unknown world of type erasure.

Is there any way to implement this Generic-CacheAside code? This hidden-gem (erasure) is horrible.

<dependency>
    <groupId>org.cache2k</groupId>
    <artifactId>cache2k-api</artifactId>
    <version>1.2.0.Final</version>
</dependency>

APPEND:

Here is an example. Basically, anytime I need to cache a "Something" that isn't driven by a parameter for retrieval. In my example below, I am caching SystemSetting(s). There is no parameter to drive the retrieval.

private static int NewedUpCounter = 0;
private static int CurrentRunCacheReads = 0;

private static void RunCacheAsideStuff() {

    /* example ONLY. use construction-injection for "real" code */
    ////IGenericCacheAside<Collection<SystemSetting>> igca = new Cache2kGenericCacheAside<Collection<SystemSetting>>();
    /* or */
    Cache2kGenericCacheAside<Collection<SystemSetting>> igca = new Cache2kGenericCacheAside<Collection<SystemSetting>>();

    for (int i = 0; i < 20; i++) {

        /* in the below, it shows how the "time to keep in the cache" might change over time */
        int cacheMilliseconds = 2500 + (500 * i);
        System.out.println(String.format("        cacheMilliseconds=%1s",
                cacheMilliseconds));            
        Collection<SystemSetting> cacheAsideSettings = igca.GetCacheAsideItem("myuniqueIdentifier", cacheMilliseconds,
                TimeUnit.MILLISECONDS, App::CreateDummySystemSettings);

        if (null != cacheAsideSettings) {
            System.out.println("--------------");
            System.out.println(String.format("        CurrentRunCacheReads=%1s",
                    ++CurrentRunCacheReads));                   
            System.out.println(String.format("        Cached Collection<SystemSetting> Read .. size=%1s",
                    cacheAsideSettings.size()));
            for (SystemSetting sett : cacheAsideSettings) {
                System.out.println(String.format(
                        "cacheAsideSettings !! SystemSetting.Key=%1s, SystemSetting.Value = %2s, i = %3s , Time= %4s",
                        sett.getSystemSettingKey(), sett.getSettingValue(), i, LocalDateTime.now()));
            }
            System.out.println("--------------");
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    System.out.println(String.format("NewedUpCounter=%1s",
            NewedUpCounter));

}

private static Collection<SystemSetting> CreateDummySystemSettings() {

    NewedUpCounter++;
    CurrentRunCacheReads=0;

    long LOWER_RANGE = 10000; // assign lower range value
    long UPPER_RANGE = 20000; // assign upper range value
    Random random = new Random();

    long randomValue = LOWER_RANGE + (long) (random.nextDouble() * (UPPER_RANGE - LOWER_RANGE));

    Collection<SystemSetting> returnItems = new ArrayList<>();
    for (int i = 101; i < 104; i++) {
        SystemSetting newSetting = new SystemSetting();
        newSetting.setSystemSettingKey(i);
        newSetting.setSettingValue(String.format("ValueOf%1s*", randomValue));
        returnItems.add(newSetting);
    }

    System.out.println(String.format("NEW Collection<SystemSetting> CREATED !! size=%1s  ************************************************", returnItems.size()));

    return returnItems;
}

And output. A sample that shows the item/time-in-cache can change.

        cacheMilliseconds=2500
NEW Collection<SystemSetting> CREATED !! size=3  ************************************************
--------------
        CurrentRunCacheReads=1
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14000*, i =   0 , Time= 2018-09-28T13:04:14.043
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14000*, i =   0 , Time= 2018-09-28T13:04:14.052
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14000*, i =   0 , Time= 2018-09-28T13:04:14.052
--------------
        cacheMilliseconds=3000
--------------
        CurrentRunCacheReads=2
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14000*, i =   1 , Time= 2018-09-28T13:04:15.052
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14000*, i =   1 , Time= 2018-09-28T13:04:15.053
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14000*, i =   1 , Time= 2018-09-28T13:04:15.053
--------------
        cacheMilliseconds=3500
--------------
        CurrentRunCacheReads=3
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14000*, i =   2 , Time= 2018-09-28T13:04:16.054
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14000*, i =   2 , Time= 2018-09-28T13:04:16.054
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14000*, i =   2 , Time= 2018-09-28T13:04:16.054
--------------
        cacheMilliseconds=4000
NEW Collection<SystemSetting> CREATED !! size=3  ************************************************
--------------
        CurrentRunCacheReads=1
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17155*, i =   3 , Time= 2018-09-28T13:04:17.055
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17155*, i =   3 , Time= 2018-09-28T13:04:17.055
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17155*, i =   3 , Time= 2018-09-28T13:04:17.055
--------------
        cacheMilliseconds=4500
--------------
        CurrentRunCacheReads=2
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17155*, i =   4 , Time= 2018-09-28T13:04:18.056
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17155*, i =   4 , Time= 2018-09-28T13:04:18.056
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17155*, i =   4 , Time= 2018-09-28T13:04:18.056
--------------
        cacheMilliseconds=5000
--------------
        CurrentRunCacheReads=3
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17155*, i =   5 , Time= 2018-09-28T13:04:19.057
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17155*, i =   5 , Time= 2018-09-28T13:04:19.058
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17155*, i =   5 , Time= 2018-09-28T13:04:19.058
--------------
        cacheMilliseconds=5500
--------------
        CurrentRunCacheReads=4
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17155*, i =   6 , Time= 2018-09-28T13:04:20.058
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17155*, i =   6 , Time= 2018-09-28T13:04:20.058
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17155*, i =   6 , Time= 2018-09-28T13:04:20.058
--------------
        cacheMilliseconds=6000
NEW Collection<SystemSetting> CREATED !! size=3  ************************************************
--------------
        CurrentRunCacheReads=1
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17444*, i =   7 , Time= 2018-09-28T13:04:21.059
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17444*, i =   7 , Time= 2018-09-28T13:04:21.060
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17444*, i =   7 , Time= 2018-09-28T13:04:21.060
--------------
        cacheMilliseconds=6500
--------------
        CurrentRunCacheReads=2
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17444*, i =   8 , Time= 2018-09-28T13:04:22.060
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17444*, i =   8 , Time= 2018-09-28T13:04:22.060
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17444*, i =   8 , Time= 2018-09-28T13:04:22.060
--------------
        cacheMilliseconds=7000
--------------
        CurrentRunCacheReads=3
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17444*, i =   9 , Time= 2018-09-28T13:04:23.060
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17444*, i =   9 , Time= 2018-09-28T13:04:23.060
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17444*, i =   9 , Time= 2018-09-28T13:04:23.060
--------------
        cacheMilliseconds=7500
--------------
        CurrentRunCacheReads=4
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17444*, i =  10 , Time= 2018-09-28T13:04:24.060
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17444*, i =  10 , Time= 2018-09-28T13:04:24.061
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17444*, i =  10 , Time= 2018-09-28T13:04:24.061
--------------
        cacheMilliseconds=8000
--------------
        CurrentRunCacheReads=5
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17444*, i =  11 , Time= 2018-09-28T13:04:25.061
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17444*, i =  11 , Time= 2018-09-28T13:04:25.061
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17444*, i =  11 , Time= 2018-09-28T13:04:25.061
--------------
        cacheMilliseconds=8500
--------------
        CurrentRunCacheReads=6
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf17444*, i =  12 , Time= 2018-09-28T13:04:26.063
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf17444*, i =  12 , Time= 2018-09-28T13:04:26.063
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf17444*, i =  12 , Time= 2018-09-28T13:04:26.064
--------------
        cacheMilliseconds=9000
NEW Collection<SystemSetting> CREATED !! size=3  ************************************************
--------------
        CurrentRunCacheReads=1
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14680*, i =  13 , Time= 2018-09-28T13:04:27.065
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14680*, i =  13 , Time= 2018-09-28T13:04:27.065
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14680*, i =  13 , Time= 2018-09-28T13:04:27.066
--------------
        cacheMilliseconds=9500
--------------
        CurrentRunCacheReads=2
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14680*, i =  14 , Time= 2018-09-28T13:04:28.066
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14680*, i =  14 , Time= 2018-09-28T13:04:28.066
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14680*, i =  14 , Time= 2018-09-28T13:04:28.066
--------------
        cacheMilliseconds=10000
--------------
        CurrentRunCacheReads=3
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14680*, i =  15 , Time= 2018-09-28T13:04:29.067
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14680*, i =  15 , Time= 2018-09-28T13:04:29.067
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14680*, i =  15 , Time= 2018-09-28T13:04:29.067
--------------
        cacheMilliseconds=10500
--------------
        CurrentRunCacheReads=4
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14680*, i =  16 , Time= 2018-09-28T13:04:30.068
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14680*, i =  16 , Time= 2018-09-28T13:04:30.068
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14680*, i =  16 , Time= 2018-09-28T13:04:30.068
--------------
        cacheMilliseconds=11000
--------------
        CurrentRunCacheReads=5
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14680*, i =  17 , Time= 2018-09-28T13:04:31.068
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14680*, i =  17 , Time= 2018-09-28T13:04:31.068
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14680*, i =  17 , Time= 2018-09-28T13:04:31.068
--------------
        cacheMilliseconds=11500
--------------
        CurrentRunCacheReads=6
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14680*, i =  18 , Time= 2018-09-28T13:04:32.068
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14680*, i =  18 , Time= 2018-09-28T13:04:32.068
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14680*, i =  18 , Time= 2018-09-28T13:04:32.068
--------------
        cacheMilliseconds=12000
--------------
        CurrentRunCacheReads=7
        Cached Collection<SystemSetting> Read .. size=3
cacheAsideSettings !! SystemSetting.Key=101, SystemSetting.Value = ValueOf14680*, i =  19 , Time= 2018-09-28T13:04:33.069
cacheAsideSettings !! SystemSetting.Key=102, SystemSetting.Value = ValueOf14680*, i =  19 , Time= 2018-09-28T13:04:33.069
cacheAsideSettings !! SystemSetting.Key=103, SystemSetting.Value = ValueOf14680*, i =  19 , Time= 2018-09-28T13:04:33.069
--------------
NewedUpCounter=4 /* this last one not accurate since its "bailing out */
granadaCoder
  • 26,328
  • 10
  • 113
  • 146
  • 1
    You've read the next section, [§2.4. Read Through](https://cache2k.org/docs/1.0/user-guide.html#read-through), yes? The sounds a lot like the `CacheLoader` class. – John Kugelman Sep 27 '18 at 01:17
  • Yes. But the "@Override" syntax sugar needed to be defined at the construction of the "theCache " object. And I think I would have eventually tried to refactor to get there. Again, the above was my first attempt at it. But with the Generic(s) issue, never got there. But I appreciate the pointer to the convenience wire-up. – granadaCoder Sep 27 '18 at 07:31
  • TEntity could be anything and the cache has no type information. You can construct the cache with the types `String, Object` and do a cast to `Cache`. Interestingly the interface looks similar to scalacache. Isn't there any upper bound for the value type? Can you give two or three examples of how you use your interface? I'd like to see how to better support this. – cruftex Sep 28 '18 at 09:38
  • Hi. I added a sample and some commentary in the original question. Thanks for followup. – granadaCoder Sep 28 '18 at 19:23
  • Ha.! I've never heard of scalacache until you mentioned it. The above is some (authored by me) C# code I recently converted. Which used these dotnet objects. using System.Runtime.Caching; CacheItemPolicy ObjectCache cache MemoryCache.Default; new Lazy(valueFactory); And does not suffer from java's type erasure voodoo. – granadaCoder Oct 02 '18 at 23:15
  • You should follow the Java Naming Conventions: variable names and methods are always written in camelCase and class names always in PascalCase. – MC Emperor Oct 27 '18 at 07:27
  • @granadaCoder Did you happen to find a fix for this issue? Stumbled upon the exact same thing. – ameenhere Aug 13 '19 at 21:29
  • No. I used an alternative library. But at an old job (aka, no source code access). :( I'll think about it, but if I don't post something in the next 2 days, it means I cannot remember what I used. #sorry – granadaCoder Aug 13 '19 at 21:43
  • I now remember the alternate library: https://mvnrepository.com/artifact/net.jodah/expiringmap. private volatile ExpiringMap theCache = ExpiringMap.builder().variableExpiration().build(); /* * static (for thread safety) not allowed for T :( * https://docs.oracle.com/javase/tutorial/java/generics/erasure.html */ – granadaCoder Oct 17 '20 at 07:40

1 Answers1

4

The anonymous class creation expression new Cache2kBuilder<String, String>() {} is used as a "super type token" to represent a parameterized type at runtime. The way they do this is you create a subclass (typically anonymous class) which extends a parameterized type, where the type arguments are concrete types fixed (hard-coded) at compile time. Since the superclass of the class is part of the class's declaration, it is stored with the generic information in the declaration part of the class file, and this information can be retrieved at runtime via reflection.

Note that what can be retrieved from the class file at runtime is exactly what was hard-coded at compile time. This is why new Cache2kBuilder<String, TEntity>() {} doesn't work -- what was hard-coded in the source code at compile-time is that there is a type variable called TEntity, instead of a concrete class.

Cache2k provides a different way to construct a Cache2kBuilder if the class cannot be fixed at compile-time, and will only be known at runtime:

theCache = Cache2kBuilder.of(String.class, entityClass)
  .name("myCache").eternal(true).build();

where entityClass is a Class<TEntity> which is the class object of the entity class at runtime. So you will have to also pass in the Class object for the entity, not just a Supplier for it.

newacct
  • 119,665
  • 29
  • 163
  • 224