/** * (c) Copyright 2012 WibiData, Inc. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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.kiji.mapreduce.kvstore; import java.io.Closeable; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.google.common.collect.Maps; import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.util.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.kiji.annotations.ApiAudience; import org.kiji.annotations.ApiStability; import org.kiji.mapreduce.kvstore.framework.KeyValueStoreConfiguration; import org.kiji.mapreduce.kvstore.impl.KeyValueStoreConfigSerializer; /** * Class that manages the creation of KeyValueStoreReaders associated * with a set of bound KeyValueStore name--instance pairs. * * <p>This also manages a cache of opened readers, which will be returned * if available, rather than creating a new store reader for a given named * store.</p> * * <p>The {@link #close()} method of this object will close all KeyValueStoreReaders * associated with it. You should call this when you are done with the * readers, or close them all individually.</p> */ @ApiAudience.Public @ApiStability.Evolving public final class KeyValueStoreReaderFactory implements Closeable { private static final Logger LOG = LoggerFactory.getLogger( KeyValueStoreReaderFactory.class.getName()); /** The set of Key-value stores to provide to the KeyValueStoreClient. */ private final Map<String, KeyValueStore<?, ?>> mKeyValueStores; /** A cache of opened key-value stores we may use and modify. */ private final Map<String, KeyValueStoreReader<?, ?>> mKVStoreReaderCache; /** Creates an empty KeyValueStoreReaderFactory. */ private KeyValueStoreReaderFactory() { this(Collections.<String, KeyValueStore<?, ?>>emptyMap()); } /** * Creates a KeyValueStoreReaderFactory backed by a map of specific store bindings. * * @param storeBindings defines the set of KeyValueStores available, and the * names by which they are registered. */ private KeyValueStoreReaderFactory(Map<String, KeyValueStore<?, ?>> storeBindings) { mKeyValueStores = Collections.unmodifiableMap( new HashMap<String, KeyValueStore<?, ?>>(storeBindings)); mKVStoreReaderCache = Maps.newConcurrentMap(); } /** * Creates a KeyValueStoreReaderFactory backed by store bindings specified in a Configuration. * * @param conf the Configuration from which a set of KeyValueStore bindings should * be deserialized and initialized. * @throws IOException if there is an error deserializing or initializing a * KeyValueStore instance. */ private KeyValueStoreReaderFactory(Configuration conf) throws IOException { Map<String, KeyValueStore<?, ?>> keyValueStores = new HashMap<String, KeyValueStore<?, ?>>(); int numKvStores = conf.getInt(KeyValueStoreConfigSerializer.CONF_KEY_VALUE_STORE_COUNT, KeyValueStoreConfigSerializer.DEFAULT_KEY_VALUE_STORE_COUNT); for (int i = 0; i < numKvStores; i++) { KeyValueStoreConfiguration kvStoreConf = KeyValueStoreConfiguration.createInConfiguration(conf, i); Class<? extends KeyValueStore> kvStoreClass = kvStoreConf .<KeyValueStore>getClass(KeyValueStoreConfigSerializer.CONF_CLASS, null, KeyValueStore.class); String kvStoreName = kvStoreConf.get(KeyValueStoreConfigSerializer.CONF_NAME, ""); if (null != kvStoreClass) { KeyValueStore<?, ?> kvStore = ReflectionUtils.newInstance(kvStoreClass, conf); if (null != kvStore) { kvStore.initFromConf(kvStoreConf); if (kvStoreName.isEmpty()) { LOG.warn("Deserialized KeyValueStore not bound to a name; ignoring."); continue; } keyValueStores.put(kvStoreName, kvStore); } } } mKeyValueStores = Collections.unmodifiableMap(keyValueStores); mKVStoreReaderCache = Maps.newConcurrentMap(); } /** * Creates an empty KeyValueStoreReaderFactory. * * @return a new, empty KeyValueStoreReaderFactory. */ public static KeyValueStoreReaderFactory createEmpty() { return new KeyValueStoreReaderFactory(); } /** * Creates a KeyValueStoreReaderFactory backed by a map of specific store bindings. * * @param storeBindings defines the set of KeyValueStores available, and the * names by which they are registered. * @return a new KeyValueStoreReaderFactory backed by a copy of the specified storeBindings. */ public static KeyValueStoreReaderFactory create(Map<String, KeyValueStore<?, ?>> storeBindings) { return new KeyValueStoreReaderFactory(storeBindings); } /** * Creates a KeyValueStoreReaderFactory backed by store bindings specified in a Configuration. * * @param conf the Configuration from which a set of KeyValueStore bindings should * be deserialized and initialized. * @throws IOException if there is an error deserializing or initializing a * KeyValueStore instance. * @return a new KeyValueStoreReaderFactory backed by the storeBindings specified in conf. */ public static KeyValueStoreReaderFactory create(Configuration conf) throws IOException { return new KeyValueStoreReaderFactory(conf); } /** * Closes all KeyValueStoreReaders opened by this factory. */ @Override public void close() { for (KeyValueStoreReader<?, ?> reader : mKVStoreReaderCache.values()) { if (null != reader && reader.isOpen()) { IOUtils.closeQuietly(reader); } } } /** * Opens a KeyValueStore associated with storeName for read-access. * * <p>Calling openStore() multiple times on the same name will reuse the same * reader unless it is closed.</p> * * <p>Note: this method is thread safe as long as readers are only being created * and opened - two threads requesting the same reader will receive the same one. * In a multi-threaded environment, a closed reader may still be returned. You * can prevent this by avoiding calling close() on individual readers returned * from this method. Instead, call close() on the KeyValueStoreReaderFactory when * you are done with all stores, and all readers will be closed.</p> * * @param <K> The key type for the KeyValueStore. * @param <V> The value type for the KeyValueStore. * @param storeName the name of the KeyValueStore to open. * @return A KeyValueStoreReader associated with this storeName, or null * if there is no such KeyValueStore available. * @throws IOException if there is an error opening the underlying storage resource. */ @SuppressWarnings("unchecked") public <K, V> KeyValueStoreReader<K, V> openStore(String storeName) throws IOException { KeyValueStore<K, V> store = (KeyValueStore<K, V>) mKeyValueStores.get(storeName); KeyValueStoreReader<?, ?> reader = mKVStoreReaderCache.get(storeName); if (null != store && (null == reader || !reader.isOpen())) { synchronized (store) { reader = mKVStoreReaderCache.get(storeName); if (null == reader || !reader.isOpen()) { reader = store.open(); mKVStoreReaderCache.put(storeName, reader); } } } return (KeyValueStoreReader<K, V>) reader; } }