/**
* 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.io.loader;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.hortonworks.registries.cache.Cache;
import com.hortonworks.registries.cache.view.datastore.DataStoreReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CacheLoaderAsync<K,V> extends CacheLoader<K,V> {
private static final int DEFAULT_NUM_THREADS = 5;
private static final Logger LOG = LoggerFactory.getLogger(CacheLoaderAsync.class);
private final ListeningExecutorService executorService;
public CacheLoaderAsync(Cache<K, V> cache, DataStoreReader<K,V> dataStoreReader) {
this(cache, dataStoreReader, Executors.newFixedThreadPool(DEFAULT_NUM_THREADS));
}
public CacheLoaderAsync(Cache<K, V> cache, DataStoreReader<K,V> dataStoreReader, ExecutorService executorService) {
super(cache, dataStoreReader);
this.executorService = MoreExecutors.listeningDecorator(executorService);
}
public void loadAll(final Collection<? extends K> keys, CacheLoaderCallback<K,V> callback) {
try {
ListenableFuture myCall = executorService.submit(new DataStoreCallable(keys));
Futures.addCallback(myCall, new CacheLoaderAsyncFutureCallback(keys, callback));
} catch (Exception e) {
handleException(keys, callback, e, LOG);
}
}
private class DataStoreCallable implements Callable<Map<K,V>> {
private final Collection<? extends K> keys;
public DataStoreCallable(Collection<? extends K> keys) {
this.keys = keys;
}
@Override
public Map<K, V> call() throws Exception {
final Map<K, V> result = dataStoreReader.readAll(keys);
LOG.debug("Call to data store for keys [{}] returned [{}]", keys, result);
return result;
}
}
public class CacheLoaderAsyncFutureCallback implements FutureCallback<Map<K,V>> {
private final Collection<? extends K> keys;
private final CacheLoaderCallback<K,V> callback;
public CacheLoaderAsyncFutureCallback(Collection<? extends K> keys, CacheLoaderCallback<K, V> callback) {
this.keys = keys;
this.callback = callback;
}
@Override
public void onSuccess(Map<K, V> read) {
LOG.debug("Raw result of call to data store for keys [{}] returned [{}]", keys, read);
Map<K,V> loaded = new HashMap<>();
if (read != null) {
for (Map.Entry<K, V> re : read.entrySet()) {
if (re.getKey() != null) {
loaded.put(re.getKey(), re.getValue());
} else {
LOG.trace("Not loading into cache entry with null value [{}]", re);
}
}
}
cache.putAll(loaded);
LOG.debug("Loaded cache [{}]", loaded);
callback.onCacheLoaded(loaded);
}
@Override
public void onFailure(Throwable t) {
callback.onCacheLoadingFailure(t);
}
}
}