/*
* Copyright Terracotta, Inc.
*
* 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 org.ehcache.jsr107;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.core.spi.service.ServiceUtils;
import org.ehcache.impl.config.copy.DefaultCopierConfiguration;
import org.ehcache.impl.config.copy.DefaultCopyProviderConfiguration;
import org.ehcache.impl.config.loaderwriter.DefaultCacheLoaderWriterConfiguration;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;
import org.ehcache.expiry.Expiry;
import org.ehcache.impl.copy.IdentityCopier;
import org.ehcache.jsr107.config.Jsr107Configuration;
import org.ehcache.jsr107.config.Jsr107Service;
import org.ehcache.jsr107.internal.DefaultJsr107Service;
import org.ehcache.spi.loaderwriter.CacheLoaderWriter;
import org.ehcache.spi.service.ServiceConfiguration;
import org.ehcache.spi.service.ServiceCreationConfiguration;
import org.ehcache.xml.XmlConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.internal.creation.MockSettingsImpl;
import java.io.Closeable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Factory;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.event.CacheEntryEventFilter;
import javax.cache.event.CacheEntryListener;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheWriter;
import static org.ehcache.config.builders.CacheConfigurationBuilder.newCacheConfigurationBuilder;
import static org.ehcache.config.builders.ResourcePoolsBuilder.heap;
import static org.ehcache.core.internal.util.ValueSuppliers.supplierOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* ConfigurationMergerTest
*/
@SuppressWarnings("unchecked")
public class ConfigurationMergerTest {
private ConfigurationMerger merger;
private XmlConfiguration xmlConfiguration;
private Jsr107Service jsr107Service;
private Eh107CacheLoaderWriterProvider cacheLoaderWriterFactory;
@Before
public void setUp() {
xmlConfiguration = mock(XmlConfiguration.class);
jsr107Service = mock(Jsr107Service.class);
cacheLoaderWriterFactory = mock(Eh107CacheLoaderWriterProvider.class);
merger = new ConfigurationMerger(xmlConfiguration, jsr107Service, cacheLoaderWriterFactory);
}
@Test
public void mergeConfigNoTemplateNoLoaderWriter() {
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
ConfigurationMerger.ConfigHolder<Object, Object> configHolder = merger.mergeConfigurations("cache", configuration);
assertThat(configHolder.cacheResources.getExpiryPolicy().getExpiryForCreation(42L, "Yay!"), is(Duration.INFINITE));
assertThat(configHolder.cacheResources.getCacheLoaderWriter(), nullValue());
assertThat(configHolder.useEhcacheLoaderWriter, is(false));
boolean storeByValue = false;
Collection<ServiceConfiguration<?>> serviceConfigurations = configHolder.cacheConfiguration.getServiceConfigurations();
for (ServiceConfiguration<?> serviceConfiguration : serviceConfigurations) {
if (serviceConfiguration instanceof DefaultCopierConfiguration) {
storeByValue = true;
break;
}
}
assertThat(storeByValue, is(true));
}
@Test
public void jsr107ExpiryGetsRegistered() {
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
RecordingFactory<CreatedExpiryPolicy> factory = factoryOf(new CreatedExpiryPolicy(javax.cache.expiry.Duration.FIVE_MINUTES));
configuration.setExpiryPolicyFactory(factory);
ConfigurationMerger.ConfigHolder<Object, Object> configHolder = merger.mergeConfigurations("Cache", configuration);
assertThat(factory.called, is(true));
Expiry resourcesExpiry = configHolder.cacheResources.getExpiryPolicy();
Expiry configExpiry = configHolder.cacheConfiguration.getExpiry();
assertThat(configExpiry, sameInstance(resourcesExpiry));
}
@Test
public void jsr107LoaderGetsRegistered() {
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
CacheLoader<Object, Object> mock = mock(CacheLoader.class);
RecordingFactory<CacheLoader<Object, Object>> factory = factoryOf(mock);
configuration.setReadThrough(true).setCacheLoaderFactory(factory);
merger.mergeConfigurations("cache", configuration);
assertThat(factory.called, is(true));
verify(cacheLoaderWriterFactory).registerJsr107Loader(eq("cache"), Matchers.<CacheLoaderWriter<Object, Object>>anyObject());
}
@Test
public void jsr107WriterGetsRegistered() {
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
CacheWriter<Object, Object> mock = mock(CacheWriter.class);
RecordingFactory<CacheWriter<Object, Object>> factory = factoryOf(mock);
configuration.setWriteThrough(true).setCacheWriterFactory(factory);
merger.mergeConfigurations("cache", configuration);
assertThat(factory.called, is(true));
verify(cacheLoaderWriterFactory).registerJsr107Loader(eq("cache"), Matchers.<CacheLoaderWriter<Object, Object>>anyObject());
}
@Test
public void looksUpTemplateName() {
merger.mergeConfigurations("cache", new MutableConfiguration<Object, Object>());
verify(jsr107Service).getTemplateNameForCache("cache");
}
@Test
public void loadsTemplateWhenNameFound() throws Exception {
when(jsr107Service.getTemplateNameForCache("cache")).thenReturn("cacheTemplate");
merger.mergeConfigurations("cache", new MutableConfiguration<Object, Object>());
verify(xmlConfiguration).newCacheConfigurationBuilderFromTemplate("cacheTemplate", Object.class, Object.class);
}
@Test
public void jsr107ExpiryGetsOverriddenByTemplate() throws Exception {
when(jsr107Service.getTemplateNameForCache("cache")).thenReturn("cacheTemplate");
when(xmlConfiguration.newCacheConfigurationBuilderFromTemplate("cacheTemplate", Object.class, Object.class)).thenReturn(
newCacheConfigurationBuilder(Object.class, Object.class, heap(10)).withExpiry(Expirations.timeToLiveExpiration(new Duration(5, TimeUnit.MINUTES)))
);
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
RecordingFactory<CreatedExpiryPolicy> factory = factoryOf(new CreatedExpiryPolicy(javax.cache.expiry.Duration.FIVE_MINUTES));
configuration.setExpiryPolicyFactory(factory);
ConfigurationMerger.ConfigHolder<Object, Object> configHolder = merger.mergeConfigurations("cache", configuration);
assertThat(factory.called, is(false));
Eh107Expiry<Object, Object> expiryPolicy = configHolder.cacheResources.getExpiryPolicy();
Expiry<? super Object, ? super Object> expiry = configHolder.cacheConfiguration.getExpiry();
assertThat(expiryPolicy.getExpiryForAccess(42, supplierOf("Yay")), is(expiry.getExpiryForAccess(42, supplierOf("Yay"))));
assertThat(expiryPolicy.getExpiryForUpdate(42, supplierOf("Yay"), "Lala"), is(expiry.getExpiryForUpdate(42, supplierOf("Yay"), "Lala")));
assertThat(expiryPolicy.getExpiryForCreation(42, "Yay"), is(expiry.getExpiryForCreation(42, "Yay")));
}
@Test
public void jsr107LoaderGetsOverriddenByTemplate() throws Exception {
when(jsr107Service.getTemplateNameForCache("cache")).thenReturn("cacheTemplate");
when(xmlConfiguration.newCacheConfigurationBuilderFromTemplate("cacheTemplate", Object.class, Object.class)).thenReturn(
newCacheConfigurationBuilder(Object.class, Object.class, heap(10)).add(new DefaultCacheLoaderWriterConfiguration((Class)null))
);
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
CacheLoader<Object, Object> mock = mock(CacheLoader.class);
RecordingFactory<CacheLoader<Object, Object>> factory = factoryOf(mock);
configuration.setReadThrough(true).setCacheLoaderFactory(factory);
ConfigurationMerger.ConfigHolder<Object, Object> configHolder = merger.mergeConfigurations("cache", configuration);
assertThat(factory.called, is(false));
assertThat(configHolder.cacheResources.getCacheLoaderWriter(), nullValue());
}
@Test
public void jsr107StoreByValueGetsOverriddenByTemplate() throws Exception {
CacheConfigurationBuilder<Object, Object> builder = newCacheConfigurationBuilder(Object.class, Object.class, heap(10))
.add(new DefaultCopierConfiguration<Object>((Class)IdentityCopier.class, DefaultCopierConfiguration.Type.KEY))
.add(new DefaultCopierConfiguration<Object>((Class)IdentityCopier.class, DefaultCopierConfiguration.Type.VALUE));
when(jsr107Service.getTemplateNameForCache("cache")).thenReturn("cacheTemplate");
when(xmlConfiguration.newCacheConfigurationBuilderFromTemplate("cacheTemplate", Object.class, Object.class))
.thenReturn(builder);
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>(); //store-by-value by default
ConfigurationMerger.ConfigHolder<Object, Object> configHolder = merger.mergeConfigurations("cache", configuration);
boolean storeByValue = true;
Collection<ServiceConfiguration<?>> serviceConfigurations = configHolder.cacheConfiguration.getServiceConfigurations();
for (ServiceConfiguration<?> serviceConfiguration : serviceConfigurations) {
if (serviceConfiguration instanceof DefaultCopierConfiguration) {
DefaultCopierConfiguration copierConfig = (DefaultCopierConfiguration)serviceConfiguration;
if(copierConfig.getClazz().isAssignableFrom(IdentityCopier.class))
storeByValue = false;
break;
}
}
assertThat(storeByValue, is(false));
}
@Test
public void jsr107LoaderInitFailureClosesExpiry() throws Exception {
ExpiryPolicy expiryPolicy = mock(ExpiryPolicy.class, new MockSettingsImpl<Object>().extraInterfaces(Closeable.class));
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
Factory<CacheLoader<Object, Object>> factory = throwingFactory();
configuration.setExpiryPolicyFactory(factoryOf(expiryPolicy))
.setReadThrough(true)
.setCacheLoaderFactory(factory);
try {
merger.mergeConfigurations("cache", configuration);
fail("Loader factory should have thrown");
} catch (MultiCacheException mce) {
verify((Closeable) expiryPolicy).close();
}
}
@Test
public void jsr107ListenerFactoryInitFailureClosesExpiryLoader() throws Exception {
ExpiryPolicy expiryPolicy = mock(ExpiryPolicy.class, new MockSettingsImpl<Object>().extraInterfaces(Closeable.class));
CacheLoader<Object, Object> loader = mock(CacheLoader.class, new MockSettingsImpl<Object>().extraInterfaces(Closeable.class));
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
configuration.setExpiryPolicyFactory(factoryOf(expiryPolicy))
.setReadThrough(true)
.setCacheLoaderFactory(factoryOf(loader))
.addCacheEntryListenerConfiguration(new ThrowingCacheEntryListenerConfiguration());
try {
merger.mergeConfigurations("cache", configuration);
fail("Loader factory should have thrown");
} catch (MultiCacheException mce) {
verify((Closeable) expiryPolicy).close();
verify((Closeable) loader).close();
}
}
@Test
public void jsr107LoaderInitAlways() {
CacheLoader<Object, Object> loader = mock(CacheLoader.class);
MutableConfiguration<Object, Object> configuration = new MutableConfiguration<Object, Object>();
RecordingFactory<CacheLoader<Object, Object>> factory = factoryOf(loader);
configuration.setCacheLoaderFactory(factory);
ConfigurationMerger.ConfigHolder<Object, Object> configHolder = merger.mergeConfigurations("cache", configuration);
assertThat(factory.called, is(true));
assertThat(configHolder.cacheResources.getCacheLoaderWriter(), notNullValue());
assertThat(configHolder.useEhcacheLoaderWriter, is(false));
}
@Test
public void setReadThroughWithoutLoaderFails() {
MutableConfiguration<Long, String> config = new MutableConfiguration<Long, String>();
config.setTypes(Long.class, String.class);
config.setReadThrough(true);
try {
merger.mergeConfigurations("cache", config);
fail("Expected exception as no CacheLoader factory is configured and read-through is enabled.");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("read-through"));
}
}
@Test
public void setWriteThroughWithoutWriterFails() {
MutableConfiguration<Long, String> config = new MutableConfiguration<Long, String>();
config.setTypes(Long.class, String.class);
config.setWriteThrough(true);
try {
merger.mergeConfigurations("cache", config);
fail("Expected exception as no CacheLoader factory is configured and read-through is enabled.");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("write-through"));
}
}
@Test
public void jsr107DefaultEh107IdentityCopierForImmutableTypes() {
XmlConfiguration xmlConfiguration = new XmlConfiguration(getClass().getResource("/ehcache-107-copiers-immutable-types.xml"));
final DefaultJsr107Service jsr107Service = new DefaultJsr107Service(ServiceUtils.findSingletonAmongst(Jsr107Configuration.class, xmlConfiguration.getServiceCreationConfigurations().toArray()));
merger = new ConfigurationMerger(xmlConfiguration, jsr107Service, mock(Eh107CacheLoaderWriterProvider.class));
MutableConfiguration<Long, String> stringCacheConfiguration = new MutableConfiguration<Long, String>();
stringCacheConfiguration.setTypes(Long.class, String.class);
ConfigurationMerger.ConfigHolder<Long, String> configHolder1 = merger.mergeConfigurations("stringCache", stringCacheConfiguration);
assertDefaultCopier(configHolder1.cacheConfiguration.getServiceConfigurations());
MutableConfiguration<Long, Double> doubleCacheConfiguration = new MutableConfiguration<Long, Double>();
doubleCacheConfiguration.setTypes(Long.class, Double.class);
ConfigurationMerger.ConfigHolder<Long, Double> configHolder2 = merger.mergeConfigurations("doubleCache", doubleCacheConfiguration);
assertDefaultCopier(configHolder2.cacheConfiguration.getServiceConfigurations());
MutableConfiguration<Long, Character> charCacheConfiguration = new MutableConfiguration<Long, Character>();
charCacheConfiguration.setTypes(Long.class, Character.class);
ConfigurationMerger.ConfigHolder<Long, Character> configHolder3 = merger.mergeConfigurations("charCache", charCacheConfiguration);
assertDefaultCopier(configHolder3.cacheConfiguration.getServiceConfigurations());
MutableConfiguration<Long, Float> floatCacheConfiguration = new MutableConfiguration<Long, Float>();
floatCacheConfiguration.setTypes(Long.class, Float.class);
ConfigurationMerger.ConfigHolder<Long, Float> configHolder4 = merger.mergeConfigurations("floatCache", floatCacheConfiguration);
assertDefaultCopier(configHolder4.cacheConfiguration.getServiceConfigurations());
MutableConfiguration<Long, Integer> integerCacheConfiguration = new MutableConfiguration<Long, Integer>();
integerCacheConfiguration.setTypes(Long.class, Integer.class);
ConfigurationMerger.ConfigHolder<Long, Integer> configHolder5 = merger.mergeConfigurations("integerCache", integerCacheConfiguration);
assertDefaultCopier(configHolder5.cacheConfiguration.getServiceConfigurations());
}
@Test
public void jsr107DefaultEh107IdentityCopierForImmutableTypesWithCMLevelDefaults() {
XmlConfiguration xmlConfiguration = new XmlConfiguration(getClass().getResource("/ehcache-107-immutable-types-cm-level-copiers.xml"));
final DefaultJsr107Service jsr107Service = new DefaultJsr107Service(ServiceUtils.findSingletonAmongst(Jsr107Configuration.class, xmlConfiguration.getServiceCreationConfigurations().toArray()));
merger = new ConfigurationMerger(xmlConfiguration, jsr107Service, mock(Eh107CacheLoaderWriterProvider.class));
MutableConfiguration<Long, String> stringCacheConfiguration = new MutableConfiguration<Long, String>();
stringCacheConfiguration.setTypes(Long.class, String.class);
ConfigurationMerger.ConfigHolder<Long, String> configHolder1 = merger.mergeConfigurations("stringCache", stringCacheConfiguration);
assertThat(configHolder1.cacheConfiguration.getServiceConfigurations().isEmpty(), is(true));
for (ServiceCreationConfiguration<?> serviceCreationConfiguration : xmlConfiguration.getServiceCreationConfigurations()) {
if (serviceCreationConfiguration instanceof DefaultCopyProviderConfiguration) {
DefaultCopyProviderConfiguration copierConfig = (DefaultCopyProviderConfiguration)serviceCreationConfiguration;
assertThat(copierConfig.getDefaults().size(), is(6));
assertThat(copierConfig.getDefaults().get(Long.class).getClazz().isAssignableFrom(IdentityCopier.class), is(true));
assertThat(copierConfig.getDefaults().get(String.class).getClazz().isAssignableFrom(Eh107IdentityCopier.class), is(true));
assertThat(copierConfig.getDefaults().get(Float.class).getClazz().isAssignableFrom(Eh107IdentityCopier.class), is(true));
assertThat(copierConfig.getDefaults().get(Double.class).getClazz().isAssignableFrom(Eh107IdentityCopier.class), is(true));
assertThat(copierConfig.getDefaults().get(Character.class).getClazz().isAssignableFrom(Eh107IdentityCopier.class), is(true));
assertThat(copierConfig.getDefaults().get(Integer.class).getClazz().isAssignableFrom(Eh107IdentityCopier.class), is(true));
}
}
}
@Test
public void jsr107DefaultEh107IdentityCopierForImmutableTypesWithoutTemplates() {
MutableConfiguration<Long, String> stringCacheConfiguration = new MutableConfiguration<Long, String>();
stringCacheConfiguration.setTypes(Long.class, String.class);
ConfigurationMerger.ConfigHolder<Long, String> configHolder1 = merger.mergeConfigurations("stringCache", stringCacheConfiguration);
assertDefaultCopier(configHolder1.cacheConfiguration.getServiceConfigurations());
}
private static void assertDefaultCopier(Collection<ServiceConfiguration<?>> serviceConfigurations) {
boolean noCopierConfigPresent = false;
for (ServiceConfiguration<?> serviceConfiguration : serviceConfigurations) {
if (serviceConfiguration instanceof DefaultCopierConfiguration) {
noCopierConfigPresent = true;
DefaultCopierConfiguration copierConfig = (DefaultCopierConfiguration)serviceConfiguration;
assertThat(copierConfig.getClazz().isAssignableFrom(Eh107IdentityCopier.class), is(true));
}
}
if (!noCopierConfigPresent) {
fail();
}
}
private <T> Factory<T> throwingFactory() {
return new Factory<T>() {
@Override
public T create() {
throw new UnsupportedOperationException("Boom");
}
};
}
private <T> RecordingFactory<T> factoryOf(final T instance) {
return new RecordingFactory<T>(instance);
}
private static class RecordingFactory<T> implements Factory<T> {
private final T instance;
boolean called;
RecordingFactory(T instance) {
this.instance = instance;
}
@Override
public T create() {
called = true;
return instance;
}
}
private static class ThrowingCacheEntryListenerConfiguration implements CacheEntryListenerConfiguration<Object, Object> {
@Override
public Factory<CacheEntryListener<? super Object, ? super Object>> getCacheEntryListenerFactory() {
throw new UnsupportedOperationException("BOOM");
}
@Override
public boolean isOldValueRequired() {
throw new UnsupportedOperationException("BOOM");
}
@Override
public Factory<CacheEntryEventFilter<? super Object, ? super Object>> getCacheEntryEventFilterFactory() {
throw new UnsupportedOperationException("BOOM");
}
@Override
public boolean isSynchronous() {
throw new UnsupportedOperationException("BOOM");
}
}
}