package io.lumify.core.config; import io.lumify.core.bootstrap.InjectHelper; import io.lumify.core.exception.LumifyException; import io.lumify.core.model.ontology.Concept; import io.lumify.core.model.ontology.OntologyProperty; import io.lumify.core.model.ontology.OntologyRepository; import io.lumify.core.model.ontology.Relationship; import io.lumify.core.util.ClassUtil; import io.lumify.core.util.LumifyLogger; import io.lumify.core.util.LumifyLoggerFactory; import org.apache.commons.beanutils.ConvertUtilsBean; import org.json.JSONObject; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; /** * Responsible for parsing application configuration file and providing * configuration values to the application */ public class Configuration { private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(Configuration.class); private static final String LINE_SEPARATOR = System.getProperty("line.separator"); public static final String BASE_URL = "base.url"; public static final String HADOOP_URL = "hadoop.url"; public static final String HDFS_LIB_CACHE_SOURCE_DIRECTORY = "hdfsLibcache.sourceDirectory"; public static final String HDFS_LIB_CACHE_TEMP_DIRECTORY = "hdfsLibcache.tempDirectory"; public static final String HDFS_LIB_CACHE_HDFS_USER = "hdfsLibcache.user"; public static final String LIB_DIRECTORY = "lib-directory"; public static final String ZK_SERVERS = "zookeeper.serverNames"; public static final String MODEL_PROVIDER = "model.provider"; public static final String USER_REPOSITORY = "repository.user"; public static final String WORKSPACE_REPOSITORY = "repository.workspace"; public static final String AUTHORIZATION_REPOSITORY = "repository.authorization"; public static final String ONTOLOGY_REPOSITORY = "repository.ontology"; public static final String AUDIT_REPOSITORY = "repository.audit"; public static final String ARTIFACT_THUMBNAIL_REPOSITORY = "repository.artifactThumbnail"; public static final String WORK_QUEUE_REPOSITORY = "repository.workQueue"; public static final String LONG_RUNNING_PROCESS_REPOSITORY = "repository.longRunningProcess"; public static final String SYSTEM_NOTIFICATION_REPOSITORY = "repository.systemNotification"; public static final String WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_ENABLED = "webAppEmbedded.longRunningProcessRunner.enabled"; public static final boolean WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_ENABLED_DEFAULT = true; public static final String WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_THREAD_COUNT = "webAppEmbedded.longRunningProcessRunner.threadCount"; public static final int WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_THREAD_COUNT_DEFAULT = 1; public static final String WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_ENABLED = "webAppEmbedded.graphPropertyWorkerRunner.enabled"; public static final boolean WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_ENABLED_DEFAULT = true; public static final String WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_THREAD_COUNT = "webAppEmbedded.graphPropertyWorkerRunner.threadCount"; public static final int WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_THREAD_COUNT_DEFAULT = 1; public static final String USER_NOTIFICATION_REPOSITORY = "repository.userNotification"; public static final String ONTOLOGY_REPOSITORY_OWL = "repository.ontology.owl"; public static final String GRAPH_PROVIDER = "graph"; public static final String VISIBILITY_TRANSLATOR = "security.visibilityTranslator"; public static final String AUDIT_VISIBILITY_LABEL = "audit.visibilityLabel"; public static final String DEFAULT_PRIVILEGES = "newuser.privileges"; public static final String WEB_PROPERTIES_PREFIX = "web.ui."; public static final String WEB_GEOCODER_ENABLED = WEB_PROPERTIES_PREFIX + "geocoder.enabled"; public static final String DEV_MODE = "devMode"; public static final String DEFAULT_SEARCH_RESULT_COUNT = "search.defaultSearchCount"; public static final String LOCK_REPOSITORY_PATH_PREFIX = "lockRepository.pathPrefix"; public static final String USER_SESSION_COUNTER_PATH_PREFIX = "userSessionCounter.pathPrefix"; public static final String DEFAULT_TIME_ZONE = "default.timeZone"; public static final String RABBITMQ_PREFETCH_COUNT = "rabbitmq.prefetch.count"; public static final String QUEUE_PREFIX = "queue.prefix"; private final ConfigurationLoader configurationLoader; private final LumifyResourceBundleManager lumifyResourceBundleManager; private Map<String, String> config = new HashMap<>(); public Configuration(final ConfigurationLoader configurationLoader, final Map<?, ?> config) { this.configurationLoader = configurationLoader; this.lumifyResourceBundleManager = new LumifyResourceBundleManager(); for (Map.Entry entry : config.entrySet()) { if (entry.getValue() != null) { set(entry.getKey().toString(), entry.getValue()); } } } public String get(String propertyKey, String defaultValue) { return config.containsKey(propertyKey) ? config.get(propertyKey) : defaultValue; } public boolean getBoolean(String propertyKey, boolean defaultValue) { return Boolean.parseBoolean(get(propertyKey, Boolean.toString(defaultValue))); } public Integer getInt(String propertyKey, Integer defaultValue) { return Integer.parseInt(get(propertyKey, defaultValue == null ? null : defaultValue.toString())); } public Integer getInt(String propertyKey) { return getInt(propertyKey, null); } public <T> Class<? extends T> getClass(String propertyKey) { String className = get(propertyKey, null); if (className == null) { throw new LumifyException("Could not find required property " + propertyKey); } try { LOGGER.debug("found class \"%s\" for configuration \"%s\"", className, propertyKey); return ClassUtil.forName(className); } catch (LumifyException e) { throw new LumifyException("Could not load class " + className + " for property " + propertyKey, e); } } public Map<String, String> getSubset(String keyPrefix) { Map<String, String> subset = new HashMap<>(); for (Map.Entry<String, String> entry : this.config.entrySet()) { if (!entry.getKey().startsWith(keyPrefix + ".") && !entry.getKey().equals(keyPrefix)) { continue; } String newKey = entry.getKey().substring(keyPrefix.length()); if (newKey.startsWith(".")) { newKey = newKey.substring(1); } subset.put(newKey, entry.getValue()); } return subset; } public void setConfigurables(Object o, String keyPrefix) { Map<String, String> subset = getSubset(keyPrefix); setConfigurables(o, subset); } public void setConfigurables(Object o, Map<String, String> config) { ConvertUtilsBean convertUtilsBean = new ConvertUtilsBean(); Map<Method, PostConfigurationValidator> validatorMap = new HashMap<>(); for (Method m : o.getClass().getMethods()) { Configurable configurableAnnotation = m.getAnnotation(Configurable.class); if (configurableAnnotation != null) { if (m.getParameterTypes().length != 1) { throw new LumifyException("Invalid method to be configurable. Expected 1 argument. Found " + m.getParameterTypes().length + " arguments"); } String propName = m.getName().substring("set".length()); if (propName.length() > 1) { propName = propName.substring(0, 1).toLowerCase() + propName.substring(1); } String name; String defaultValue; if (configurableAnnotation.name() != null) { name = configurableAnnotation.name(); defaultValue = configurableAnnotation.defaultValue(); } else { name = propName; defaultValue = null; } String val; if (config.containsKey(name)) { val = config.get(name); } else { if (Configurable.DEFAULT_VALUE.equals(defaultValue)) { if (configurableAnnotation.required()) { throw new LumifyException("Could not find property " + name + " for " + o.getClass().getName() + " and no default value was specified."); } else { continue; } } val = defaultValue; } try { Object convertedValue = convertUtilsBean.convert(val, m.getParameterTypes()[0]); m.invoke(o, convertedValue); } catch (Exception ex) { throw new LumifyException("Could not set property " + m.getName() + " on " + o.getClass().getName()); } } PostConfigurationValidator validatorAnnotation = m.getAnnotation(PostConfigurationValidator.class); if (validatorAnnotation != null) { if (m.getParameterTypes().length != 0) { throw new LumifyException("Invalid validator method " + o.getClass().getName() + "." + m.getName() + "(). Expected 0 arguments. Found " + m.getParameterTypes().length + " arguments"); } if (m.getReturnType() != Boolean.TYPE) { throw new LumifyException("Invalid validator method " + o.getClass().getName() + "." + m.getName() + "(). Expected Boolean return type. Found " + m.getReturnType()); } validatorMap.put(m, validatorAnnotation); } } for (Method method : validatorMap.keySet()) { try { if (!(Boolean) method.invoke(o)) { String description = validatorMap.get(method).description(); description = description.equals("") ? "()" : "(" + description + ")"; throw new LumifyException(o.getClass().getName() + "." + method.getName() + description + " returned false"); } } catch (InvocationTargetException e) { throw new LumifyException("InvocationTargetException invoking validator " + o.getClass().getName() + "." + method.getName(), e); } catch (IllegalAccessException e) { throw new LumifyException("IllegalAccessException invoking validator " + o.getClass().getName() + "." + method.getName(), e); } } } public Map toMap() { return this.config; } public Iterable<String> getKeys() { return this.config.keySet(); } public Iterable<String> getKeys(String keyPrefix) { getSubset(keyPrefix).keySet(); Set<String> keys = new TreeSet<>(); for (String key : getKeys()) { if (key.startsWith(keyPrefix)) { keys.add(key); } } return keys; } public void set(String propertyKey, Object value) { if (value == null) { config.remove(propertyKey); } else { config.put(propertyKey, value.toString()); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); SortedSet<String> keys = new TreeSet<>(this.config.keySet()); boolean first = true; for (String key : keys) { if (first) { first = false; } else { sb.append(LINE_SEPARATOR); } if (key.toLowerCase().contains("password")) { sb.append(key).append(": ********"); } else { sb.append(key).append(": ").append(get(key, null)); } } return sb.toString(); } public org.apache.hadoop.conf.Configuration toHadoopConfiguration() { org.apache.hadoop.conf.Configuration conf = new org.apache.hadoop.conf.Configuration(); for (Object entryObj : this.toMap().entrySet()) { Map.Entry entry = (Map.Entry) entryObj; conf.set(entry.getKey().toString(), entry.getValue().toString()); } return conf; } public org.apache.hadoop.conf.Configuration toHadoopConfiguration(org.apache.hadoop.conf.Configuration additionalConfiguration) { org.apache.hadoop.conf.Configuration hadoopConfig = toHadoopConfiguration(); hadoopConfig.setBoolean("mapred.used.genericoptionsparser", true); // eliminates warning on our version of hadoop for (Map.Entry<String, String> toolConfItem : additionalConfiguration) { hadoopConfig.set(toolConfItem.getKey(), toolConfItem.getValue()); } return hadoopConfig; } public File resolveFileName(String fileName) { return this.configurationLoader.resolveFileName(fileName); } public JSONObject toJSON(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } return toJSON(lumifyResourceBundleManager.getBundle(locale)); } public JSONObject toJSON(ResourceBundle resourceBundle) { JSONObject properties = new JSONObject(); OntologyRepository ontologyRepository = InjectHelper.getInstance(OntologyRepository.class); for (Concept concept : ontologyRepository.getConceptsWithProperties()) { for (String intent : concept.getIntents()) { properties.put(OntologyRepository.CONFIG_INTENT_CONCEPT_PREFIX + intent, concept.getIRI()); } } for (OntologyProperty property : ontologyRepository.getProperties()) { for (String intent : property.getIntents()) { properties.put(OntologyRepository.CONFIG_INTENT_PROPERTY_PREFIX + intent, property.getTitle()); } } for (Relationship relationship : ontologyRepository.getRelationships()) { for (String intent : relationship.getIntents()) { properties.put(OntologyRepository.CONFIG_INTENT_RELATIONSHIP_PREFIX + intent, relationship.getIRI()); } } for (String key : getKeys()) { if (key.startsWith(io.lumify.core.config.Configuration.WEB_PROPERTIES_PREFIX)) { properties.put(key.replaceFirst(io.lumify.core.config.Configuration.WEB_PROPERTIES_PREFIX, ""), get(key, "")); } else if (key.startsWith("ontology.intent")) { properties.put(key, get(key, "")); } } JSONObject messages = new JSONObject(); if (resourceBundle != null) { for (String key : resourceBundle.keySet()) { messages.put(key, resourceBundle.getString(key)); } } JSONObject configuration = new JSONObject(); configuration.put("properties", properties); configuration.put("messages", messages); return configuration; } public Map<String, Map<String, String>> getMultiValue(String prefix) { return getMultiValue(this.config.entrySet(), prefix); } /** * Processing configuration items that looks like this: * <p/> * repository.ontology.owl.dev.iri=http://lumify.io/dev * repository.ontology.owl.dev.dir=examples/ontology-dev/ * <p/> * repository.ontology.owl.csv.iri=http://lumify.io/csv * repository.ontology.owl.csv.dir=storm/plugins/csv/ontology/ * <p/> * Into a hash like this: * <p/> * - dev * - iri: http://lumify.io/dev * - dir: examples/ontology-dev/ * - csv * - iri: http://lumify.io/csv * - dir: storm/plugins/csv/ontology/ */ public static Map<String, Map<String, String>> getMultiValue(Iterable<Map.Entry<String, String>> config, String prefix) { if (!prefix.endsWith(".")) { prefix = prefix + "."; } Map<String, Map<String, String>> results = new HashMap<>(); for (Map.Entry<String, String> item : config) { if (item.getKey().startsWith(prefix)) { String rest = item.getKey().substring(prefix.length()); int ch = rest.indexOf('.'); String key; String subkey; if (ch > 0) { key = rest.substring(0, ch); subkey = rest.substring(ch + 1); } else { key = rest; subkey = ""; } Map<String, String> values = results.get(key); if (values == null) { values = new HashMap<>(); results.put(key, values); } values.put(subkey, item.getValue()); } } return results; } }