/**
* Copyright 2016 Hortonworks.
*
* 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.hortonworks.registries.cache.view;
import com.hortonworks.registries.cache.Cache;
import com.hortonworks.registries.cache.LoadableCache;
import com.hortonworks.registries.cache.exception.CacheException;
import com.hortonworks.registries.cache.stats.CacheStats;
import com.hortonworks.registries.cache.view.datastore.DataStoreReader;
import com.hortonworks.registries.cache.view.datastore.DataStoreWriter;
import com.hortonworks.registries.cache.view.io.loader.CacheLoaderCallback;
import com.hortonworks.registries.cache.view.io.writer.CacheWriter;
import com.hortonworks.registries.cache.AbstractCache;
import com.hortonworks.registries.cache.view.io.loader.CacheLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class DataStoreBackedCache<K,V> extends AbstractCache<K,V> implements LoadableCache<K,V> {
private static final Logger LOG = LoggerFactory.getLogger(DataStoreBackedCache.class);
private final Cache<K, V> cache;
private final CacheLoader<K, V> cacheLoader;
private final CacheWriter<K, V> cacheWriter;
private final DataStoreReader<K, V> dataStoreReader;
public DataStoreBackedCache(Cache<K, V> cache, CacheLoader<K, V> cacheLoader, DataStoreReader<K, V> dataStoreReader,
CacheWriter<K, V> cacheWriter) {
validateArguments(cache, dataStoreReader, cacheLoader, cacheWriter);
this.cache = cache;
this.dataStoreReader = dataStoreReader;
this.cacheLoader = cacheLoader;
this.cacheWriter = cacheWriter;
}
public void loadAll(Collection<? extends K> keys, CacheLoaderCallback<K,V> callback) {
if (cacheLoader != null) {
cacheLoader.loadAll(keys, callback);
}
}
@Override
public V get(K key) throws CacheException {
V val = cache.get(key);
if (val == null && dataStoreReader != null) { // in sync read through
val = dataStoreReader.read(key);
}
return val;
}
@Override
public void put(K key, V val) {
cache.put(key, val);
if (cacheWriter != null) { // in sync write through
cacheWriter.write(key, val);
}
}
@Override
public Map<K, V> getAll(Collection<? extends K> keys) { // TODO what if trying to load more keys than max number of keys that be kept in the cache ?
Map<K, V> present = cache.getAll(keys);
if (dataStoreReader != null) {
if (present == null || present.isEmpty()) {
present = dataStoreReader.readAll(keys); // in sync read through
} else if (present.size() < keys.size()) {
Set<K> notPresent = new HashSet<>(keys);
notPresent.removeAll(present.keySet());
Map<K, V> loaded = dataStoreReader.readAll(notPresent); // in sync read through
present.putAll(loaded);
}
}
// LOG.debug("Entries existing in cache [{}]. Keys non existing in cache: [{}]", present, notPresent.removeAll(loaded));
return present;
}
@Override
public void putAll(Map<? extends K, ? extends V> entries) {
cache.putAll(entries);
if (cacheWriter != null) { // sync or async write, depending on the writing strategy chosen
cacheWriter.writeAll(entries);
}
}
@Override
public void remove(K key) {
cache.remove(key);
if (cacheWriter != null) { // sync or async delete, depending on the writing strategy chosen
cacheWriter.delete(key);
}
}
@Override
public void removeAll(Collection<? extends K> keys) {
cache.removeAll(keys);
if (cacheWriter != null) { // sync or async delete, depending on the writing strategy chosen
cacheWriter.deleteAll(keys);
}
}
@Override
public void clear() {
cache.clear();
LOG.info("Cache cleared. Entries only removed from cache but not from backing data store"); //TODO: Do we want to remove from DB as well ?
}
@Override
public long size() {
return cache.size();
}
@Override
public CacheStats stats() {
return cache.stats();
}
@Override
public String toString() {
return "DataStoreBackedCache{" +
"cache=" + cache +
", cacheLoader=" + cacheLoader +
", cacheWriter=" + cacheWriter +
", dataStore=" + dataStoreReader +
"} " + super.toString();
}
// =========== Private helper methods ===========
private void validateArguments(Cache<K, V> cache, DataStoreReader<K, V> dataStore,
CacheLoader<K, V> cacheLoader, DataStoreWriter<K, V> dataStoreWriter) {
if (cache == null) {
throw new IllegalArgumentException("Cache reference cannot be null");
}
if (dataStore == null && cacheLoader == null && dataStoreWriter == null) {
throw new IllegalArgumentException(String.format("At least one non null implementation of %s, %s, or %s " +
"is required. If no backing data store is required consider using a non backed implementation of %s",
getSimpleName(DataStoreReader.class), getSimpleName(DataStoreWriter.class),
getSimpleName(CacheLoader.class), getSimpleName(Cache.class)));
}
LOG.info("Created {}", this);
}
private String getSimpleName(Class<?> clazz) {
return clazz.getSimpleName();
}
}