Skip to content

Commit

Permalink
Pluggable JCache Factory for dependency injectors (fixes #117)
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-manes committed Aug 27, 2016
1 parent 8973141 commit 49c012c
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 19 deletions.
2 changes: 1 addition & 1 deletion gradle/code_quality.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ tasks.withType(Test) {
jvmArgs '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'
}
options {
jvmArgs '-XX:SoftRefLRUPolicyMSPerMB=0', '-noverify'
jvmArgs '-XX:SoftRefLRUPolicyMSPerMB=0', '-XX:+UseParallelGC', '-noverify'
}
if (System.env.'CI') {
maxHeapSize = '512m'
Expand Down
10 changes: 6 additions & 4 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*/
ext {
versions = [
akka: '2.4.9-RC2',
akka: '2.4.9',
commons_compress: '1.12',
commons_lang3: '3.4',
config: '1.3.0',
Expand All @@ -35,8 +35,9 @@ ext {
javapoet: '1.7.0',
jcache: '1.0.0',
jsr305: '3.0.1',
jsr330: '1',
stream: '2.9.5',
univocity_parsers: '2.2.0',
univocity_parsers: '2.2.1',
ycsb: '0.10.0',
xz: '1.5',
]
Expand All @@ -47,7 +48,7 @@ ext {
jcache_tck: '1.0.1',
jctools: '1.2.1',
junit: '4.12',
mockito: '2.0.106-beta',
mockito: '2.0.111-beta',
pax_exam: '4.9.1',
testng: '6.9.12',
truth: '0.24',
Expand All @@ -59,7 +60,7 @@ ext {
ehcache3: '3.1.1',
elastic_search: '5.0.0-alpha5',
infinispan: '9.0.0.Alpha4',
jackrabbit: '1.5.7',
jackrabbit: '1.5.8',
jamm: '0.3.1',
java_object_layout: '0.5',
koloboke: '0.6.8',
Expand Down Expand Up @@ -91,6 +92,7 @@ ext {
javapoet: "com.squareup:javapoet:${versions.javapoet}",
jcache: "javax.cache:cache-api:${versions.jcache}",
jsr305: "com.google.code.findbugs:jsr305:${versions.jsr305}",
jsr330: "javax.inject:javax.inject:${versions.jsr330}",
stream: "com.clearspring.analytics:stream:${versions.stream}",
univocity_parsers: "com.univocity:univocity-parsers:${versions.univocity_parsers}",
ycsb: "com.github.brianfrankcooper.ycsb:core:${versions.ycsb}",
Expand Down
2 changes: 0 additions & 2 deletions guava/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ task osgiTests(type: Test, group: 'Cache tests', description: 'Isolated OSGi tes
}

tasks.withType(Test) {
enabled = !JavaVersion.current().isJava9Compatible()

systemProperty 'guava.osgi.version', versions.guava
systemProperty 'caffeine.osgi.jar', project(':caffeine').jar.archivePath.path
systemProperty 'caffeine-guava.osgi.jar', project(':guava').jar.archivePath.path
Expand Down
1 change: 1 addition & 0 deletions jcache/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
compile project(':caffeine')
compile libraries.jcache
compile libraries.config
compile libraries.jsr330

testCompile libraries.guava
testCompile test_libraries.junit
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2016 Ben Manes. All Rights Reserved.
*
* 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.github.benmanes.caffeine.jcache.configuration;

import javax.annotation.Nonnull;
import javax.cache.configuration.Factory;

/**
* An object capable of providing factories that produce an instance for a given class name.
*
* @author [email protected] (Ben Manes)
*/
@FunctionalInterface
public interface FactoryCreator {

/**
* Returns a {@link Factory} that will produce instances of the specified class.
*
* @param className the fully qualified name of the desired class
* @param <T> the type of the instances being produced
* @return a {@link Factory} for the specified class
*/
@Nonnull
<T> Factory<T> factoryOf(String className);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import javax.cache.expiry.Duration;
import javax.cache.expiry.EternalExpiryPolicy;
import javax.cache.expiry.ExpiryPolicy;
import javax.inject.Inject;

import com.github.benmanes.caffeine.jcache.expiry.JCacheExpiryPolicy;
import com.typesafe.config.Config;
Expand All @@ -48,6 +49,8 @@
public final class TypesafeConfigurator {
static final Logger logger = Logger.getLogger(TypesafeConfigurator.class.getName());

static FactoryCreator factoryCreator = FactoryBuilder::factoryOf;

private TypesafeConfigurator() {}

/**
Expand Down Expand Up @@ -83,6 +86,17 @@ public static <K, V> Optional<CaffeineConfiguration<K, V>> from(Config config, S
return Optional.ofNullable(configuration);
}

/**
* Specifies how {@link Factory} instances are created for a given class name. The default
* strategy uses {@link Class#newInstance()} and requires the class has a no-args constructor.
*
* @param factoryCreator the strategy for creating a factory
*/
@Inject
public static void setFactoryBuilder(FactoryCreator factoryCreator) {
TypesafeConfigurator.factoryCreator = requireNonNull(factoryCreator);
}

/** A one-shot builder for creating a configuration instance. */
private static final class Configurator<K, V> {
final CaffeineConfiguration<K, V> configuration;
Expand Down Expand Up @@ -116,7 +130,7 @@ private void addStoreByValue() {
boolean enabled = config.getBoolean("store-by-value.enabled");
configuration.setStoreByValue(enabled);
if (config.hasPath("store-by-value.strategy")) {
configuration.setCopierFactory(FactoryBuilder.factoryOf(
configuration.setCopierFactory(factoryCreator.factoryOf(
config.getString("store-by-value.strategy")));
}
}
Expand All @@ -127,10 +141,10 @@ private void addListeners() {
Config listener = rootConfig.getConfig(path);

Factory<? extends CacheEntryListener<? super K, ? super V>> listenerFactory =
FactoryBuilder.factoryOf(listener.getString("class"));
factoryCreator.factoryOf(listener.getString("class"));
Factory<? extends CacheEntryEventFilter<? super K, ? super V>> filterFactory = null;
if (listener.hasPath("filter")) {
filterFactory = FactoryBuilder.factoryOf(listener.getString("filter"));
filterFactory = factoryCreator.factoryOf(listener.getString("filter"));
}
boolean oldValueRequired = listener.getBoolean("old-value-required");
boolean synchronous = listener.getBoolean("synchronous");
Expand All @@ -147,7 +161,7 @@ private void addReadThrough() {
configuration.setReadThrough(isReadThrough);
if (config.hasPath("read-through.loader")) {
String loaderClass = config.getString("read-through.loader");
configuration.setCacheLoaderFactory(FactoryBuilder.factoryOf(loaderClass));
configuration.setCacheLoaderFactory(factoryCreator.factoryOf(loaderClass));
}
}

Expand All @@ -157,7 +171,7 @@ private void addWriteThrough() {
configuration.setWriteThrough(isWriteThrough);
if (config.hasPath("write-through.writer")) {
String writerClass = config.getString("write-through.writer");
configuration.setCacheWriterFactory(FactoryBuilder.factoryOf(writerClass));
configuration.setCacheWriterFactory(factoryCreator.factoryOf(writerClass));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,33 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import java.util.Map;

import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.annotation.CacheResolverFactory;
import javax.cache.annotation.CacheResult;
import javax.cache.configuration.Factory;
import javax.cache.configuration.FactoryBuilder;
import javax.cache.integration.CacheLoader;
import javax.cache.spi.CachingProvider;

import org.jsr107.ri.annotations.DefaultCacheResolverFactory;
import org.jsr107.ri.annotations.guice.module.CacheAnnotationsModule;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.github.benmanes.caffeine.jcache.configuration.FactoryCreator;
import com.github.benmanes.caffeine.jcache.configuration.TypesafeConfigurator;
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;
Expand All @@ -39,36 +53,99 @@
* @author [email protected] (Ben Manes)
*/
public final class JCacheGuiceTest {
@Inject CacheManager cacheManager;
@Inject Service service;

@Test
public void sanity() {
@BeforeMethod
public void beforeMethod() {
Module module = Modules.override(new CacheAnnotationsModule()).with(new CaffeineJCacheModule());
Injector injector = Guice.createInjector(module);
Service service = injector.getInstance(Service.class);
Guice.createInjector(module).injectMembers(this);
}

@AfterClass
public void afterClass() {
TypesafeConfigurator.setFactoryBuilder(FactoryBuilder::factoryOf);
}

@Test
public void factory() {
Cache<Integer, Integer> cache = cacheManager.getCache("guice");
Map<Integer, Integer> result = cache.getAll(ImmutableSet.of(1, 2, 3));
assertThat(result, is(ImmutableMap.of(1, 1, 2, 2, 3, 3)));
}

@Test
public void annotations() {
for (int i = 0; i < 10; i++) {
assertThat(service.get(), is(1));
}
assertThat(service.times, is(1));
}

public static class Service {
static class Service {
int times;

@CacheResult(cacheName = "guice")
@CacheResult(cacheName = "annotations")
public Integer get() {
return ++times;
}
}

/** Resolves the annotations to the Caffeine provider as multiple are on the IDE classpath. */
public static final class InjectedCacheLoader implements CacheLoader<Integer, Integer> {
private final Service service;

@Inject
InjectedCacheLoader(Service service) {
this.service = service;
}

@Override
public Integer load(Integer key) {
return ++service.times;
}

@Override
public Map<Integer, Integer> loadAll(Iterable<? extends Integer> keys) {
return Maps.toMap(ImmutableSet.copyOf(keys), this::load);
}
}

static final class GuiceFactoryCreator implements FactoryCreator {
final Injector injector;

@Inject
GuiceFactoryCreator(Injector injector) {
this.injector = injector;
}

@Override
@SuppressWarnings("unchecked")
public <T> Factory<T> factoryOf(String className) {
try {
Class<T> clazz = (Class<T>) Class.forName(className);
return injector.getProvider(clazz)::get;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

static final class CaffeineJCacheModule extends AbstractModule {

@Override protected void configure() {
configureCachingProvider();
requestStaticInjection(TypesafeConfigurator.class);
bind(FactoryCreator.class).to(GuiceFactoryCreator.class);
}

/** Resolves the annotations to the provider as multiple are on the IDE's classpath. */
void configureCachingProvider() {
CachingProvider provider = Caching.getCachingProvider(
CaffeineCachingProvider.class.getName());
CacheManager cacheManager = provider.getCacheManager(
provider.getDefaultURI(), provider.getDefaultClassLoader());
bind(CacheResolverFactory.class).toInstance(new DefaultCacheResolverFactory(cacheManager));
bind(CacheManager.class).toInstance(cacheManager);
}
}
}
7 changes: 7 additions & 0 deletions jcache/src/test/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ caffeine.jcache {
policy.maximum.size = 1000
}

guice {
read-through {
enabled = true
loader = "com.github.benmanes.caffeine.jcache.JCacheGuiceTest$InjectedCacheLoader"
}
}

listeners {
test-listener {
class = "com.github.benmanes.caffeine.jcache.configuration.TestCacheEntryListener"
Expand Down

0 comments on commit 49c012c

Please sign in to comment.