/*
* 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.impl.internal.store.tiering;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.EvictionAdvisor;
import org.ehcache.config.ResourcePools;
import org.ehcache.config.ResourceType;
import org.ehcache.core.internal.store.StoreConfigurationImpl;
import org.ehcache.config.SizedResourcePool;
import org.ehcache.core.spi.service.DiskResourceService;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.CachePersistenceException;
import org.ehcache.expiry.Expirations;
import org.ehcache.expiry.Expiry;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.ehcache.impl.copy.IdentityCopier;
import org.ehcache.core.events.NullStoreEventDispatcher;
import org.ehcache.impl.internal.events.TestStoreEventDispatcher;
import org.ehcache.impl.internal.executor.OnDemandExecutionService;
import org.ehcache.impl.internal.persistence.TestDiskResourceService;
import org.ehcache.impl.internal.sizeof.NoopSizeOfEngine;
import org.ehcache.impl.internal.store.disk.OffHeapDiskStore;
import org.ehcache.impl.internal.store.disk.OffHeapDiskStoreSPITest;
import org.ehcache.impl.internal.store.heap.OnHeapStore;
import org.ehcache.impl.internal.store.heap.OnHeapStoreByValueSPITest;
import org.ehcache.core.spi.time.SystemTimeSource;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.impl.serialization.JavaSerializer;
import org.ehcache.internal.store.StoreFactory;
import org.ehcache.internal.store.StoreSPITest;
import org.ehcache.core.internal.service.ServiceLocator;
import org.ehcache.spi.service.ServiceProvider;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.spi.store.tiering.AuthoritativeTier;
import org.ehcache.core.spi.store.tiering.CachingTier;
import org.ehcache.spi.copy.Copier;
import org.ehcache.spi.serialization.Serializer;
import org.ehcache.core.spi.service.FileBasedPersistenceContext;
import org.ehcache.spi.service.Service;
import org.ehcache.spi.service.ServiceConfiguration;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder;
import static org.ehcache.config.units.MemoryUnit.MB;
import static org.ehcache.core.internal.service.ServiceLocator.dependencySet;
import static org.ehcache.impl.config.store.disk.OffHeapDiskStoreConfiguration.DEFAULT_DISK_SEGMENTS;
import static org.ehcache.impl.config.store.disk.OffHeapDiskStoreConfiguration.DEFAULT_WRITER_CONCURRENCY;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test the {@link TieredStore} compliance to the
* {@link Store} contract.
*/
public class TieredStoreSPITest extends StoreSPITest<String, String> {
private StoreFactory<String, String> storeFactory;
private final TieredStore.Provider provider = new TieredStore.Provider();
private final Map<Store<String, String>, String> createdStores = new ConcurrentHashMap<Store<String, String>, String>();
@Rule
public final TemporaryFolder folder = new TemporaryFolder();
@Rule
public TestDiskResourceService diskResourceService = new TestDiskResourceService();
@Override
protected StoreFactory<String, String> getStoreFactory() {
return storeFactory;
}
@Before
public void setUp() throws IOException {
storeFactory = new StoreFactory<String, String>() {
final AtomicInteger aliasCounter = new AtomicInteger();
@Override
public Store<String, String> newStore() {
return newStore(null, null, Expirations.noExpiration(), SystemTimeSource.INSTANCE);
}
@Override
public Store<String, String> newStoreWithCapacity(long capacity) {
return newStore(capacity, null, Expirations.noExpiration(), SystemTimeSource.INSTANCE);
}
@Override
public Store<String, String> newStoreWithExpiry(Expiry<? super String, ? super String> expiry, TimeSource timeSource) {
return newStore(null, null, expiry, timeSource);
}
@Override
public Store<String, String> newStoreWithEvictionAdvisor(EvictionAdvisor<String, String> evictionAdvisor) {
return newStore(null, evictionAdvisor, Expirations.noExpiration(), SystemTimeSource.INSTANCE);
}
private Store<String, String> newStore(Long capacity, EvictionAdvisor<String, String> evictionAdvisor, Expiry<? super String, ? super String> expiry, TimeSource timeSource) {
Serializer<String> keySerializer = new JavaSerializer<String>(getClass().getClassLoader());
Serializer<String> valueSerializer = new JavaSerializer<String>(getClass().getClassLoader());
Store.Configuration<String, String> config = new StoreConfigurationImpl<String, String>(getKeyType(), getValueType(),
evictionAdvisor, getClass().getClassLoader(), expiry, buildResourcePools(capacity), 0, keySerializer, valueSerializer);
@SuppressWarnings("unchecked")
final Copier<String> defaultCopier = new IdentityCopier();
OnHeapStore<String, String> onHeapStore = new OnHeapStore<String, String>(config, timeSource, defaultCopier, defaultCopier, new NoopSizeOfEngine(), NullStoreEventDispatcher.<String, String>nullStoreEventDispatcher());
try {
CacheConfiguration cacheConfiguration = mock(CacheConfiguration.class);
when(cacheConfiguration.getResourcePools()).thenReturn(newResourcePoolsBuilder().disk(1, MB, false).build());
String spaceName = "alias-" + aliasCounter.getAndIncrement();
DiskResourceService.PersistenceSpaceIdentifier space = diskResourceService.getPersistenceSpaceIdentifier(spaceName, cacheConfiguration);
FileBasedPersistenceContext persistenceContext = diskResourceService.createPersistenceContextWithin(space, "store");
SizedResourcePool diskPool = config.getResourcePools().getPoolForResource(ResourceType.Core.DISK);
MemoryUnit unit = (MemoryUnit) diskPool.getUnit();
long sizeInBytes = unit.toBytes(diskPool.getSize());
OffHeapDiskStore<String, String> diskStore = new OffHeapDiskStore<String, String>(
persistenceContext,
new OnDemandExecutionService(), null, DEFAULT_WRITER_CONCURRENCY, DEFAULT_DISK_SEGMENTS,
config, timeSource,
new TestStoreEventDispatcher<String, String>(),
sizeInBytes);
TieredStore<String, String> tieredStore = new TieredStore<String, String>(onHeapStore, diskStore);
provider.registerStore(tieredStore, new CachingTier.Provider() {
@Override
public <K, V> CachingTier<K, V> createCachingTier(final Store.Configuration<K, V> storeConfig, final ServiceConfiguration<?>... serviceConfigs) {
throw new UnsupportedOperationException("Implement me!");
}
@Override
public void releaseCachingTier(final CachingTier<?, ?> resource) {
OnHeapStoreByValueSPITest.closeStore((OnHeapStore)resource);
}
@Override
public void initCachingTier(final CachingTier<?, ?> resource) {
// no op
}
@Override
public int rankCachingTier(Set<ResourceType<?>> resourceTypes, Collection<ServiceConfiguration<?>> serviceConfigs) {
throw new UnsupportedOperationException("Implement me!");
}
@Override
public void start(final ServiceProvider<Service> serviceProvider) {
throw new UnsupportedOperationException("Implement me!");
}
@Override
public void stop() {
throw new UnsupportedOperationException("Implement me!");
}
}, new AuthoritativeTier.Provider() {
@Override
public <K, V> AuthoritativeTier<K, V> createAuthoritativeTier(final Store.Configuration<K, V> storeConfig, final ServiceConfiguration<?>... serviceConfigs) {
throw new UnsupportedOperationException("Implement me!");
}
@Override
public void releaseAuthoritativeTier(final AuthoritativeTier<?, ?> resource) {
OffHeapDiskStoreSPITest.closeStore((OffHeapDiskStore<?, ?>)resource);
}
@Override
public void initAuthoritativeTier(final AuthoritativeTier<?, ?> resource) {
OffHeapDiskStoreSPITest.initStore((OffHeapDiskStore<?, ?>)resource);
}
@Override
public int rankAuthority(ResourceType<?> authorityResource, Collection<ServiceConfiguration<?>> serviceConfigs) {
throw new UnsupportedOperationException("Implement me!");
}
@Override
public void start(final ServiceProvider<Service> serviceProvider) {
throw new UnsupportedOperationException("Implement me!");
}
@Override
public void stop() {
throw new UnsupportedOperationException("Implement me!");
}
});
provider.initStore(tieredStore);
createdStores.put(tieredStore, spaceName);
return tieredStore;
} catch (CachePersistenceException e) {
throw new RuntimeException("Error creation persistence context", e);
}
}
@Override
public Store.ValueHolder<String> newValueHolder(final String value) {
final long creationTime = SystemTimeSource.INSTANCE.getTimeMillis();
return new Store.ValueHolder<String>() {
@Override
public String value() {
return value;
}
@Override
public long creationTime(TimeUnit unit) {
return creationTime;
}
@Override
public long expirationTime(TimeUnit unit) {
return 0;
}
@Override
public boolean isExpired(long expirationTime, TimeUnit unit) {
return false;
}
@Override
public long lastAccessTime(TimeUnit unit) {
return 0;
}
@Override
public float hitRate(long now, TimeUnit unit) {
return 0;
}
@Override
public long hits() {
throw new UnsupportedOperationException("Implement me!");
}
@Override
public long getId() {
throw new UnsupportedOperationException("Implement me!");
}
};
}
@Override
public Class<String> getKeyType() {
return String.class;
}
@Override
public Class<String> getValueType() {
return String.class;
}
@Override
public ServiceConfiguration<?>[] getServiceConfigurations() {
return new ServiceConfiguration[0];
}
@Override
public String createKey(long seed) {
return Long.toString(seed);
}
@Override
public String createValue(long seed) {
char[] chars = new char[400 * 1024];
Arrays.fill(chars, (char) (0x1 + (seed & 0x7e)));
return new String(chars);
}
@Override
public void close(final Store<String, String> store) {
String spaceName = createdStores.get(store);
provider.releaseStore(store);
try {
diskResourceService.destroy(spaceName);
} catch (CachePersistenceException e) {
throw new AssertionError(e);
} finally {
createdStores.remove(store);
}
}
@Override
public ServiceProvider<Service> getServiceProvider() {
ServiceLocator.DependencySet dependencySet = dependencySet();
dependencySet.with(new FakeCachingTierProvider());
dependencySet.with(new FakeAuthoritativeTierProvider());
return dependencySet.build();
}
};
}
@After
public void tearDown() throws CachePersistenceException {
for (Map.Entry<Store<String, String>, String> entry : createdStores.entrySet()) {
provider.releaseStore(entry.getKey());
diskResourceService.destroy(entry.getValue());
}
}
private ResourcePools buildResourcePools(Comparable<Long> capacityConstraint) {
if (capacityConstraint == null) {
capacityConstraint = 16L;
}
return newResourcePoolsBuilder().heap(5, EntryUnit.ENTRIES).disk((Long)capacityConstraint, MB).build();
}
public static class FakeCachingTierProvider implements CachingTier.Provider {
@Override
@SuppressWarnings("unchecked")
public <K, V> CachingTier<K, V> createCachingTier(Store.Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs) {
return mock(CachingTier.class);
}
@Override
public void releaseCachingTier(CachingTier<?, ?> resource) {
throw new UnsupportedOperationException();
}
@Override
public void initCachingTier(CachingTier<?, ?> resource) {
throw new UnsupportedOperationException();
}
@Override
public int rankCachingTier(Set<ResourceType<?>> resourceTypes, Collection<ServiceConfiguration<?>> serviceConfigs) {
throw new UnsupportedOperationException();
}
@Override
public void start(ServiceProvider<Service> serviceProvider) {
throw new UnsupportedOperationException();
}
@Override
public void stop() {
throw new UnsupportedOperationException();
}
}
public static class FakeAuthoritativeTierProvider implements AuthoritativeTier.Provider {
@Override
@SuppressWarnings("unchecked")
public <K, V> AuthoritativeTier<K, V> createAuthoritativeTier(Store.Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs) {
return mock(AuthoritativeTier.class);
}
@Override
public void releaseAuthoritativeTier(AuthoritativeTier<?, ?> resource) {
throw new UnsupportedOperationException();
}
@Override
public void initAuthoritativeTier(AuthoritativeTier<?, ?> resource) {
throw new UnsupportedOperationException();
}
@Override
public int rankAuthority(ResourceType<?> authorityResource, Collection<ServiceConfiguration<?>> serviceConfigs) {
throw new UnsupportedOperationException();
}
@Override
public void start(ServiceProvider<Service> serviceProvider) {
throw new UnsupportedOperationException();
}
@Override
public void stop() {
throw new UnsupportedOperationException();
}
}
}