package mil.nga.giat.geowave.core.store; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.ServiceLoader; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import mil.nga.giat.geowave.core.store.adapter.AdapterIndexMappingStore; import mil.nga.giat.geowave.core.store.adapter.AdapterStore; import mil.nga.giat.geowave.core.store.adapter.statistics.DataStatisticsStore; import mil.nga.giat.geowave.core.store.config.ConfigOption; import mil.nga.giat.geowave.core.store.config.ConfigUtils; import mil.nga.giat.geowave.core.store.index.IndexStore; import mil.nga.giat.geowave.core.store.index.SecondaryIndexDataStore; public class GeoWaveStoreFinder { private static final Logger LOGGER = LoggerFactory.getLogger(GeoWaveStoreFinder.class); public static String STORE_HINT_KEY = "store_name"; public static final ConfigOption STORE_HINT_OPTION = new ConfigOption( STORE_HINT_KEY, "Set the GeoWave store, by default it will try to discover based on matching config options. " + getStoreNames(), true, String.class); private static Map<String, StoreFactoryFamilySpi> registeredStoreFactoryFamilies = null; public static DataStatisticsStore createDataStatisticsStore( final Map<String, String> configOptions ) { final StoreFactoryFamilySpi factory = findStoreFamily(configOptions); if (factory == null) { return null; } return factory.getDataStatisticsStoreFactory().createStore( ConfigUtils.populateOptionsFromList( factory.getDataStatisticsStoreFactory().createOptionsInstance(), configOptions)); } public static DataStore createDataStore( final Map<String, String> configOptions ) { final StoreFactoryFamilySpi factory = findStoreFamily(configOptions); if (factory == null) { return null; } return factory.getDataStoreFactory().createStore( ConfigUtils.populateOptionsFromList( factory.getDataStoreFactory().createOptionsInstance(), configOptions)); } public static AdapterStore createAdapterStore( final Map<String, String> configOptions ) { final StoreFactoryFamilySpi factory = findStoreFamily(configOptions); if (factory == null) { return null; } return factory.getAdapterStoreFactory().createStore( ConfigUtils.populateOptionsFromList( factory.getAdapterStoreFactory().createOptionsInstance(), configOptions)); } public static AdapterIndexMappingStore createAdapterIndexMappingStore( final Map<String, String> configOptions ) { final StoreFactoryFamilySpi factory = findStoreFamily(configOptions); if (factory == null) { return null; } return factory.getAdapterIndexMappingStoreFactory().createStore( ConfigUtils.populateOptionsFromList( factory.getAdapterIndexMappingStoreFactory().createOptionsInstance(), configOptions)); } public static IndexStore createIndexStore( final Map<String, String> configOptions ) { final StoreFactoryFamilySpi factory = findStoreFamily(configOptions); if (factory == null) { return null; } return factory.getIndexStoreFactory().createStore( ConfigUtils.populateOptionsFromList( factory.getIndexStoreFactory().createOptionsInstance(), configOptions)); } public static SecondaryIndexDataStore createSecondaryIndexDataStore( final Map<String, String> configOptions ) { final StoreFactoryFamilySpi factory = findStoreFamily(configOptions); if (factory == null) { return null; } return factory.getSecondaryIndexDataStore().createStore( ConfigUtils.populateOptionsFromList( factory.getSecondaryIndexDataStore().createOptionsInstance(), configOptions)); } private static List<String> getMissingRequiredOptions( final StoreFactoryFamilySpi factory, final Map<String, String> configOptions ) { final ConfigOption[] options = ConfigUtils.createConfigOptionsFromJCommander( factory.getDataStoreFactory().createOptionsInstance(), false); final List<String> missing = new ArrayList<String>(); for (final ConfigOption option : options) { if (!option.isOptional() && (!configOptions.containsKey(option.getName()) || (configOptions.get(option.getName()) .equals("null")))) { missing.add(option.getName()); } } return missing; } private static List<String> getMatchingRequiredOptions( final StoreFactoryFamilySpi factory, final Map<String, String> configOptions ) { final ConfigOption[] options = ConfigUtils.createConfigOptionsFromJCommander( factory.getDataStoreFactory().createOptionsInstance(), false); final List<String> matching = new ArrayList<String>(); for (final ConfigOption option : options) { if (!option.isOptional() && (configOptions.containsKey(option.getName()) && (!configOptions.get( option.getName()).equals( "null")))) { matching.add(option.getName()); } } return matching; } public static StoreFactoryFamilySpi findStoreFamily( final Map<String, String> configOptions ) { final Object storeHint = configOptions.get(STORE_HINT_KEY); final Map<String, StoreFactoryFamilySpi> internalStoreFamilies = getRegisteredStoreFactoryFamilies(); if (storeHint != null) { final StoreFactoryFamilySpi factory = internalStoreFamilies.get(storeHint.toString()); if (factory != null) { final List<String> missingOptions = getMissingRequiredOptions( factory, configOptions); if (missingOptions.isEmpty()) { return factory; } LOGGER.error("Unable to find config options for store '" + storeHint.toString() + "'." + ConfigUtils.getOptions(missingOptions)); return null; } else { LOGGER.error("Unable to find store '" + storeHint.toString() + "'"); return null; } } // if the hint is not provided, the factory finder will attempt to find // a factory that has an exact match meaning that all required params // are provided and all provided params are defined as at least optional // params for (final Entry<String, StoreFactoryFamilySpi> entry : internalStoreFamilies.entrySet()) { if (exactMatch( entry.getValue(), configOptions)) { return entry.getValue(); } } // if it cannot find and exact match it will attempt to does not have // any missing options; if multiple/ factories will match, the one with // the most required matching options will be used with // the assumption that it has the most specificity and closest match of // the arguments; if there are multiple factories that match and have // the same number of required matching options, arbitrarily the last // one will be chosen // and a warning message will be logged int matchingFactoryRequiredOptionsCount = -1; StoreFactoryFamilySpi matchingFactory = null; boolean matchingFactoriesHaveSameRequiredOptionsCount = false; LOGGER.debug("Finding Factories (size): " + internalStoreFamilies.size()); for (final Entry<String, StoreFactoryFamilySpi> entry : internalStoreFamilies.entrySet()) { final StoreFactoryFamilySpi factory = entry.getValue(); final List<String> missingOptions = getMissingRequiredOptions( factory, configOptions); final List<String> matchingOptions = getMatchingRequiredOptions( factory, configOptions); if (missingOptions.isEmpty() && ((matchingFactory == null) || (matchingOptions.size() >= matchingFactoryRequiredOptionsCount))) { matchingFactory = factory; matchingFactoriesHaveSameRequiredOptionsCount = (matchingOptions.size() == matchingFactoryRequiredOptionsCount); matchingFactoryRequiredOptionsCount = matchingOptions.size(); } } if (matchingFactory == null) { LOGGER.error("Unable to find any valid store"); } else if (matchingFactoriesHaveSameRequiredOptionsCount) { LOGGER.warn("Multiple valid stores found with equal specificity for store"); LOGGER.warn(matchingFactory.getType() + " will be automatically chosen"); } return matchingFactory; } private static String getStoreNames() { final Set<String> uniqueNames = new HashSet<String>(); uniqueNames.addAll(getRegisteredStoreFactoryFamilies().keySet()); return ConfigUtils.getOptions( uniqueNames).toString(); } public static boolean exactMatch( final StoreFactoryFamilySpi geowaveStoreFactoryFamily, final Map<String, String> params ) { final ConfigOption[] requiredOptions = GeoWaveStoreFinder.getRequiredOptions(geowaveStoreFactoryFamily); // first ensure all required options are fulfilled for (final ConfigOption requiredOption : requiredOptions) { if (!params.containsKey(requiredOption.getName())) { return false; } } // next ensure that all params match an available option final Set<String> availableOptions = new HashSet<String>(); for (final ConfigOption option : GeoWaveStoreFinder.getAllOptions( geowaveStoreFactoryFamily, true)) { availableOptions.add(option.getName()); } for (final String optionName : params.keySet()) { if (!availableOptions.contains(optionName) && !STORE_HINT_KEY.equals(optionName)) { return false; } } // lastly try to create the index store (pick a minimally required // store) try { final StoreFactoryOptions options = ConfigUtils.populateOptionsFromList( geowaveStoreFactoryFamily.getDataStoreFactory().createOptionsInstance(), params); geowaveStoreFactoryFamily.getIndexStoreFactory().createStore( options); } catch (final Exception e) { LOGGER.info( "supplied map is not able to construct index store", e); return false; } return true; } public static synchronized Map<String, StoreFactoryFamilySpi> getRegisteredStoreFactoryFamilies() { registeredStoreFactoryFamilies = getRegisteredFactories( StoreFactoryFamilySpi.class, registeredStoreFactoryFamilies); return registeredStoreFactoryFamilies; } public static synchronized ConfigOption[] getAllOptions( final StoreFactoryFamilySpi storeFactoryFamily, boolean includeHidden ) { final List<ConfigOption> allOptions = new ArrayList<ConfigOption>(); allOptions.addAll(Arrays.asList(ConfigUtils.createConfigOptionsFromJCommander( storeFactoryFamily.getDataStoreFactory().createOptionsInstance(), includeHidden))); // TODO our JCommanderPrefixTranslator's use of reflection does not // follow inheritance, these are commonly inherited classes and options // for all data stores provided as a stop gap until we can investigate // allOptions.addAll( // Arrays.asList( // ConfigUtils.createConfigOptionsFromJCommander( // new BaseDataStoreOptions()))); // allOptions.add( // new ConfigOption( // StoreFactoryOptions.GEOWAVE_NAMESPACE_OPTION, // StoreFactoryOptions.GEOWAVE_NAMESPACE_DESCRIPTION, // true, // String.class)); return allOptions.toArray(new ConfigOption[] {}); } public static synchronized ConfigOption[] getRequiredOptions( final StoreFactoryFamilySpi storeFactoryFamily ) { final List<ConfigOption> requiredOptions = new ArrayList<ConfigOption>(); for (final ConfigOption option : getAllOptions( storeFactoryFamily, false)) { if (!option.isOptional()) { requiredOptions.add(option); } } return requiredOptions.toArray(new ConfigOption[] {}); } private static <T extends GenericFactory> Map<String, T> getRegisteredFactories( final Class<T> cls, Map<String, T> registeredFactories ) { if (registeredFactories == null) { registeredFactories = new HashMap<String, T>(); final Iterator<T> storeFactories = ServiceLoader.load( cls).iterator(); while (storeFactories.hasNext()) { final T storeFactory = storeFactories.next(); if (storeFactory != null) { final String name = storeFactory.getType(); registeredFactories.put( ConfigUtils.cleanOptionName(name), storeFactory); } } } return registeredFactories; } }