diff --git a/gradle/code_quality.gradle b/gradle/code_quality.gradle index d32e0001d8..6d32dab1f9 100644 --- a/gradle/code_quality.gradle +++ b/gradle/code_quality.gradle @@ -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' diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index e99a6df4cf..725d072ef4 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -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', @@ -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', ] @@ -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', @@ -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', @@ -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}", diff --git a/guava/build.gradle b/guava/build.gradle index 488f62bee3..656ebcc539 100644 --- a/guava/build.gradle +++ b/guava/build.gradle @@ -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 diff --git a/jcache/build.gradle b/jcache/build.gradle index 327ac1e8a0..b8e8ad5162 100644 --- a/jcache/build.gradle +++ b/jcache/build.gradle @@ -10,6 +10,7 @@ dependencies { compile project(':caffeine') compile libraries.jcache compile libraries.config + compile libraries.jsr330 testCompile libraries.guava testCompile test_libraries.junit diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/FactoryCreator.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/FactoryCreator.java new file mode 100644 index 0000000000..20af8f2743 --- /dev/null +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/FactoryCreator.java @@ -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 ben.manes@gmail.com (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 the type of the instances being produced + * @return a {@link Factory} for the specified class + */ + @Nonnull + Factory factoryOf(String className); +} diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java index aa76c41290..e64b0e3526 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/configuration/TypesafeConfigurator.java @@ -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; @@ -48,6 +49,8 @@ public final class TypesafeConfigurator { static final Logger logger = Logger.getLogger(TypesafeConfigurator.class.getName()); + static FactoryCreator factoryCreator = FactoryBuilder::factoryOf; + private TypesafeConfigurator() {} /** @@ -83,6 +86,17 @@ public static Optional> 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 { final CaffeineConfiguration configuration; @@ -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"))); } } @@ -127,10 +141,10 @@ private void addListeners() { Config listener = rootConfig.getConfig(path); Factory> listenerFactory = - FactoryBuilder.factoryOf(listener.getString("class")); + factoryCreator.factoryOf(listener.getString("class")); Factory> 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"); @@ -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)); } } @@ -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)); } } diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/JCacheGuiceTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/JCacheGuiceTest.java index 4a79676a85..23dc6d453d 100644 --- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/JCacheGuiceTest.java +++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/JCacheGuiceTest.java @@ -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; @@ -39,36 +53,99 @@ * @author ben.manes@gmail.com (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 cache = cacheManager.getCache("guice"); + Map 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 { + private final Service service; + + @Inject + InjectedCacheLoader(Service service) { + this.service = service; + } + + @Override + public Integer load(Integer key) { + return ++service.times; + } + + @Override + public Map loadAll(Iterable 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 Factory factoryOf(String className) { + try { + Class clazz = (Class) 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); } } } diff --git a/jcache/src/test/resources/application.conf b/jcache/src/test/resources/application.conf index cf7d2f8154..db2e7043c6 100644 --- a/jcache/src/test/resources/application.conf +++ b/jcache/src/test/resources/application.conf @@ -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"