Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.marmotta.platform.core.services.config; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import org.apache.commons.configuration.*; import org.apache.commons.configuration.interpol.ConfigurationInterpolator; import org.apache.commons.lang.text.StrLookup; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.marmotta.platform.core.api.config.ConfigurationService; import org.apache.marmotta.platform.core.events.ConfigurationChangedEvent; import org.apache.marmotta.platform.core.events.ConfigurationServiceInitEvent; import org.apache.marmotta.platform.core.events.LoggingStartEvent; import org.apache.marmotta.platform.core.model.config.CoreOptions; import org.apache.marmotta.platform.core.util.FallbackConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.PreDestroy; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Event; import javax.enterprise.inject.Any; import javax.inject.Inject; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.ObjectName; import javax.servlet.ServletContext; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.net.URL; import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.*; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This service offers access to the system configuration of the LMF and takes care of initialising the system * properly on startup. * * @author Sebastian Schaffert * @author Sergio Fernndez * */ @ApplicationScoped public class ConfigurationServiceImpl implements ConfigurationService { private String home; private static Logger log = LoggerFactory.getLogger(ConfigurationService.class); private String configFileName, metaFileName; /** * The configuration wrapper, loads first the stored configuration and then all stored configuration options */ private CompositeConfiguration config; /** * The backend for storing configuration values, usually ${marmotta.home}/system-config.properties */ private Configuration saveConfiguration; /** * The backend for storing metadata about configuration values, usually ${marmotta.home}/system-meta.properties */ private Configuration saveMetadata; /** * The metadata about the configuration options */ private CompositeConfiguration configDescriptions; private HashMap<String, Boolean> runtimeFlags; @Inject @Any private Event<ConfigurationChangedEvent> configurationEvent; @Inject @Any private Event<ConfigurationServiceInitEvent> configurationInitEvent; @Inject @Any private Event<LoggingStartEvent> loggingStartEvent; private int serverPort = 0; //private String serverContext; private String serverName = null; private boolean initialising; private boolean initialised = false; private ServletContext servletContext; /** * A lock to ensure proper concurrent access to the configuration. The system requests a write lock in case a * setXXX() method is called and a read lock in case a getXXX() method is called. */ private ReadWriteLock lock; /** * Backlog for delayed event collection; only fires a configuration changed event if there has not been a further * update in a specified amount of time (default 250ms); */ private Set<String> eventBacklog; private long EVENT_DELAY = 250L; /* * Timer and task for delayed execution of configuration changed events */ private Timer eventTimer; private ReentrantLock eventLock; public ConfigurationServiceImpl() { runtimeFlags = new HashMap<String, Boolean>(); lock = new ReentrantReadWriteLock(); eventTimer = new Timer("Configuration Event Timer", true); eventLock = new ReentrantLock(); } /** * Initialise the ConfigurationService. Takes the marmotta.home system property from the servlet * init parameters * (web.xml) as bootstrap home. In case a system-config.properties file is found in this * directory, the * configuration is then loaded from this file. In case a system-config.properties file does not * yet exist, * the method loads bootstrap settings from the resource default-config.properties in the * classpath (in the * standard bundling, the file is in the kiwi-core-XXX.jar archive). After loading the * configuration, the * service initialises the plugin subsystem and the SOLR home directory by unpacking the default * configuration * if the SOLR configuration does not yet exist. * @param override */ @Override public void initialize(String home, Configuration override) { lock.writeLock().lock(); try { initialising = true; log.info("Apache Marmotta Configuration Service starting up ..."); if (isTomcat7()) { log.info("Apache Marmotta running on Apache Tomcat 7.x"); } else if (isTomcat6()) { log.info("Apache Marmotta running on Apache Tomcat <= 6.x"); } else if (isJetty7()) { log.info("Apache Marmotta running on Jetty 7.x"); } else if (isJetty6()) { log.info("Apache Marmotta running on Jetty <= 6.x"); } else { log.info("Apache Marmotta running on an unknown servlet container"); } setHome(home); if (getHome() != null) { File f1 = new File(getHome()); if (!f1.exists()) { f1.mkdirs(); } // ensure directory for user configuration files File f2 = new File(getHome() + File.separator + DIR_CONFIG); if (!f2.exists()) { f2.mkdirs(); } // ensure directory for logging messages File f3 = new File(getHome() + File.separator + DIR_LOG); if (!f3.exists()) { f3.mkdirs(); } // ensure directory for importing data File f4 = new File(getHome() + File.separator + DIR_IMPORT); if (!f4.exists()) { f4.mkdirs(); } } // the save configuration will be in the home directory try { if (getHome() != null) { configFileName = getHome() + File.separator + "system-config.properties"; metaFileName = getHome() + File.separator + "system-meta.properties"; File configFile = new File(configFileName); if (!configFile.exists()) { log.info("creating system configuration in configuration file {}", configFile.getAbsolutePath()); } else { log.info("reading system configuration from existing configuration file {}", configFile.getAbsolutePath()); } saveConfiguration = new PropertiesConfiguration(configFile); File metaFile = new File(metaFileName); if (!metaFile.exists()) { log.info("creating system configuration metadata in configuration file {}", metaFile.getAbsolutePath()); } else { log.info("reading system configuration metadata from existing configuration file {}", metaFile.getAbsolutePath()); } saveMetadata = new PropertiesConfiguration(metaFile); } else { log.error( "error while initialising configuration: no marmotta.home property given; creating memory-only configuration"); saveConfiguration = new MapConfiguration(new HashMap<String, Object>()); saveMetadata = new MapConfiguration(new HashMap<String, Object>()); } } catch (Exception e) { log.error("error while initialising configuration file {}: {}; creating memory-only configuration", configFileName, e.getMessage()); saveConfiguration = new MapConfiguration(new HashMap<String, Object>()); saveMetadata = new MapConfiguration(new HashMap<String, Object>()); } config = new FallbackConfiguration(); final ConfigurationInterpolator _int = config.getInterpolator(); _int.registerLookup("pattern.quote", new StrLookup() { @Override public String lookup(String key) { return Pattern.quote(_int.getDefaultLookup().lookup(key)); } }); _int.registerLookup("urlencode", new StrLookup() { @Override public String lookup(String key) { try { return URLEncoder.encode(_int.getDefaultLookup().lookup(key), "utf8"); } catch (UnsupportedEncodingException e) { return _int.getDefaultLookup().lookup(key); } } }); config.addConfiguration(saveConfiguration, true); // load all default-config.properties try { Enumeration<URL> configs = this.getClass().getClassLoader() .getResources("config-defaults.properties"); while (configs.hasMoreElements()) { URL url = configs.nextElement(); config.addConfiguration(new PropertiesConfiguration(url)); } } catch (IOException e) { log.error("I/O error while loading default configurations", e); } catch (ConfigurationException e) { log.error("configuration error while loading default configurations", e); } // legacy support (to be removed) try { Enumeration<URL> configs = this.getClass().getClassLoader() .getResources("default-config.properties"); while (configs.hasMoreElements()) { URL url = configs.nextElement(); config.addConfiguration(new PropertiesConfiguration(url)); log.warn( "found legacy configuration file {}; should be replaced with per-module configuration!", url); } } catch (IOException e) { log.error("I/O error while loading default configurations", e); } catch (ConfigurationException e) { log.error("configuration error while loading default configurations", e); } // create the configuration that is responsible for getting metadata about configuration keys in the main // configuration; since the keys will be different, we store changes also to the main save configuration configDescriptions = new FallbackConfiguration(); configDescriptions.addConfiguration(saveMetadata, true); try { Enumeration<URL> configs = this.getClass().getClassLoader() .getResources("config-descriptions.properties"); while (configs.hasMoreElements()) { URL url = configs.nextElement(); configDescriptions.addConfiguration(new PropertiesConfiguration(url)); } } catch (IOException e) { log.error("I/O error while loading configuration descriptions", e); } catch (ConfigurationException e) { log.error("configuration error while loading configuration descriptions", e); } // setup home if it is given as system property, // the bootstrap configuration is overwritten if (getHome() != null) { config.setProperty("marmotta.home", getHome()); } // in case override configuration is given, change all settings in the configuration accordingly if (override != null) { for (Iterator<String> it = override.getKeys(); it.hasNext();) { String key = it.next(); config.setProperty(key, override.getProperty(key)); } } save(); // configuration service is now ready to use initialised = true; loggingStartEvent.fire(new LoggingStartEvent()); // this should maybe move to the KiWiPreStartupFilter ... initDatabaseConfiguration(); save(); log.info("Apache Marmotta Configuration Service: initialisation completed"); configurationInitEvent.fire(new ConfigurationServiceInitEvent()); } finally { lock.writeLock().unlock(); } } @Override public boolean isInitialising() { return initialising; } /** * Signal that initialisation of the system has completed and configuration events are now enabled. * * @param initialising */ @Override public void setInitialising(boolean initialising) { this.initialising = initialising; log.info("Initialisation completed, enabling configuration events"); } private void initDatabaseConfiguration() { if (!config.getBoolean("kiwi.setup.database")) { log.info("SETUP: Setting up initial Apache Marmotta database configuration ..."); String db_type = config.getString("database.type", "h2"); config.setProperty("database.h2.url", "jdbc:h2:" + getHome() + "/db/marmotta;MVCC=true;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=10"); if (db_type.equals("h2")) { config.setProperty("database.url", "jdbc:h2:" + getHome() + "/db/marmotta;MVCC=true;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=10"); config.setProperty("database.user", "sa"); config.setProperty("database.password", "sa"); config.setProperty("database.mode", "create"); } config.setProperty("kiwi.setup.database", true); } } @PreDestroy public void shutdown() { log.info("shutting down configuration service"); } /** * Pass the servlet context over to the configuration service to provide runtime information about * the environment to the rest of the system. * * @param context */ @Override public void setServletContext(ServletContext context) { this.servletContext = context; } /** * Get the servlet context used when initialising the system * * @return */ @Override public ServletContext getServletContext() { return servletContext; } /** * Get the base URI out of the current request. The base URI * is used e.g. to generate URIs of internal content items * * @see org.apache.marmotta.platform.core.api.config.ConfigurationService#getBaseUri() */ @Override public String getBaseUri() { return getStringConfiguration(CoreOptions.BASE_URI); } /** * Get the base path of the system, which is the relative path from the server host. * For example, in the case of http://localhost:8080/LMF, /LMF would be returned as the path. * * @return a String representing the path */ @Override public String getPath() { return getStringConfiguration("kiwi.path"); } /** * Get the server uri of the system, i.e. a uri that when entered in the browser accesses the * server that runs the KiWi (and SOLR) applications. Can be used to compute the paths of * other applications relative to the current application. Computed like the base uri. * * @see ConfigurationService#getServerUri() */ @Override public String getServerUri() { String serverUrl = getStringConfiguration(CoreOptions.SERVER_URI); if (serverUrl.endsWith("/")) return serverUrl; else return serverUrl + "/"; } /** * List all configuration keys defined for the system configuration of KiWi. * * @return */ @Override public List<String> listConfigurationKeys() { lock.readLock().lock(); try { List<String> keys = new LinkedList<String>(); for (Iterator<String> it = config.getKeys(); it.hasNext();) { keys.add(it.next()); } return keys; } finally { lock.readLock().unlock(); } } /** * List all configuration keys defined for the system configuration of KiWi having prefix. * * @param prefix the prefix of keys that should be returned * @return */ @Override public List<String> listConfigurationKeys(String prefix) { lock.readLock().lock(); try { List<String> keys = new LinkedList<String>(); for (Iterator<String> it = config.getKeys(prefix); it.hasNext();) { keys.add(it.next()); } return keys; } finally { lock.readLock().unlock(); } } /** * List all configuration keys matching a certain regular expression pattern. Returns a matcher object for all * matching keys that can be used to access capturing groups * * @param pattern * @return */ @Override public List<Matcher> listConfigurationKeys(Pattern pattern) { lock.readLock().lock(); try { List<Matcher> keys = new LinkedList<Matcher>(); for (Iterator<String> it = config.getKeys(); it.hasNext();) { Matcher m = pattern.matcher(it.next()); if (m.matches()) { keys.add(m); } } return keys; } finally { lock.readLock().unlock(); } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#isConfigurationSet(java.lang.String) */ @Override public boolean isConfigurationSet(String key) { lock.readLock().lock(); try { return config.containsKey(key); } finally { lock.readLock().unlock(); } } /** * Get the configuration for the given key. If there is no such configuration, a new one is * created with empty value (returns null). * * @param key unique configuration key for lookup * @return a configuration object with either the configured value or null as value */ @Override public Object getConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getProperty(key); } finally { lock.readLock().unlock(); } } /** * Return the comment for the configuration with the given key as string. If there is no such * configuration, null is returned * * @param key unique configuration key for lookup * @return a string describing the configuration option or null if no comment was given */ @Override public String getComment(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return configDescriptions.getString(key + ".description"); } finally { lock.readLock().unlock(); } } /** * Return the type for the configuration with the given key as string. If there is no such * configuration, null is returned * @param key unique configuration key for lookup * @return a string describing the type for key or DEFAULT_TYPE (String) if no type was given */ @Override public String getType(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { String s = configDescriptions.getString(key + ".type"); return s != null ? s : String.class.getName(); } finally { lock.readLock().unlock(); } } /** * Set the configuration "key" to the string value "value". * * @param key * @param value */ @Override public void setConfiguration(String key, Object value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); if (!config.containsKey(key) || !ObjectUtils.equals(value, config.getProperty(key))) { lock.writeLock().lock(); try { config.setProperty(key, value); save(); } finally { lock.writeLock().unlock(); } if (!initialising) { raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } /** * Set type for a configuration key * * @param key key for configuration fields * @param type type for configuratino field */ @Override public void setType(String key, String type) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.writeLock().lock(); try { configDescriptions.setProperty(key + ".type", type); save(); } finally { lock.writeLock().unlock(); } } /** * Set type for a configuration key * * @param key key for configuration fields * @param description type for configuratino field */ @Override public void setComment(String key, String description) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.writeLock().lock(); try { configDescriptions.setProperty(key + ".description", description); save(); } finally { lock.writeLock().unlock(); } } /** * * */ @Override public String getStringConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(true); } String result = config.getString(key); if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(false); } return result; } finally { lock.readLock().unlock(); } } @Override public String getStringConfiguration(String key, String defaultValue) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(true); } String result = config.getString(key, defaultValue); if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(false); } return result; } finally { lock.readLock().unlock(); } } @Override public double getDoubleConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getDouble(key, 0.0); } finally { lock.readLock().unlock(); } } @Override public double getDoubleConfiguration(String key, double defaultValue) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getDouble(key, defaultValue); } finally { lock.readLock().unlock(); } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#setDoubleConfiguration(java.lang.String, double) */ @Override public void setDoubleConfiguration(String key, double value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); if (!config.containsKey(key) || !ObjectUtils.equals(value, config.getDouble(key))) { lock.writeLock().lock(); try { config.setProperty(key, value); save(); } finally { lock.writeLock().unlock(); } if (!initialising) { raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } @Override public int getIntConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getInt(key, 0); } finally { lock.readLock().unlock(); } } @Override public int getIntConfiguration(String key, int defaultValue) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getInt(key, defaultValue); } finally { lock.readLock().unlock(); } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#setIntConfiguration(java.lang.String, int) */ @Override public void setIntConfiguration(String key, int value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); if (!config.containsKey(key) || !ObjectUtils.equals(value, config.getInt(key))) { lock.writeLock().lock(); try { config.setProperty(key, value); save(); } finally { lock.writeLock().unlock(); } if (!initialising) { raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } /** * Get the configuration for the given key. If there is no such configuration, 0 is returned * * @param key unique configuration key for lookup * @return a int value with either the configured value or 0 */ @Override public long getLongConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getLong(key, 0); } finally { lock.readLock().unlock(); } } /** * Get the configuration for the given key. If there is no such configuration, a new one is * created using the provided defaultValue as double value. * * @param key unique configuration key for lookup * @param defaultValue default value if configuration not found * @return a configuration object with either the configured value or defaultValue */ @Override public long getLongConfiguration(String key, long defaultValue) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getLong(key, defaultValue); } finally { lock.readLock().unlock(); } } /** * Set the system configuration with the given key to the given int value. * * @param key * @param value */ @Override public void setLongConfiguration(String key, long value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); if (!config.containsKey(key) || !ObjectUtils.equals(value, config.getLong(key))) { lock.writeLock().lock(); try { config.setProperty(key, value); save(); } finally { lock.writeLock().unlock(); } if (!initialising) { raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } @Override public boolean getBooleanConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getBoolean(key, false); } finally { lock.readLock().unlock(); } } @Override public boolean getBooleanConfiguration(String key, boolean defaultValue) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getBoolean(key, defaultValue); } finally { lock.readLock().unlock(); } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#setIntConfiguration(java.lang.String, int) */ @Override public void setBooleanConfiguration(String key, boolean value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); if (!config.containsKey(key) || value != config.getBoolean(key)) { lock.writeLock().lock(); try { config.setProperty(key, value); save(); } finally { lock.writeLock().unlock(); } if (!initialising) { raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#getListConfiguration(java.lang.String) */ @Override public Properties getPropertiesConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { return config.getProperties(key); } finally { lock.readLock().unlock(); } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#getListConfiguration(java.lang.String) */ @Override public List<String> getListConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { String[] result = config.getStringArray(key); if (result.length == 1 && "".equals(result[0].trim())) return Collections.emptyList(); return Lists.newArrayList(result); } finally { lock.readLock().unlock(); } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#getListConfiguration(java.lang.String, * java.util.List) */ @Override public List<String> getListConfiguration(String key, List<String> defaultValue) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.readLock().lock(); try { if (config.containsKey(key)) { String[] values = config.getStringArray(key); if (values.length > 0) return Lists.newArrayList(values); else return Lists.newArrayList(); } else return defaultValue; } finally { lock.readLock().unlock(); } } /* * (non-Javadoc) * * @see kiwi.api.config.ConfigurationService#setListConfiguration(java.lang.String, * java.util.List) */ @Override public void setListConfiguration(String key, List<String> value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); if (!config.containsKey(key) || !ObjectUtils.equals(value, config.getList(key))) { lock.writeLock().lock(); try { config.setProperty(key, value); save(); } finally { lock.writeLock().unlock(); } if (!initialising) { log.debug("firing configuration changed event for key {}", key); raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } @Override public void removeConfiguration(String key) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.writeLock().lock(); try { config.clearProperty(key); configDescriptions.clearProperty(key + ".type"); configDescriptions.clearProperty(key + ".description"); save(); } finally { lock.writeLock().unlock(); } } @Override public void setConfiguration(String key, String value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); log.debug("setting configuration {} = {}", key, value); if (!config.containsKey(key) || !ObjectUtils.equals(value, config.getString(key))) { lock.writeLock().lock(); try { if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(true); } config.setProperty(key, value); if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(false); } save(); } finally { lock.writeLock().unlock(); } if (!initialising) { log.debug("firing configuration changed event for key {}", key); raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } /** * Set a configuration value without firing an event. This is in rare cases needed to avoid * propagation of events. * * @param key * @param value */ @Override public void setConfigurationWithoutEvent(String key, Object value) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.writeLock().lock(); try { if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(true); } config.setProperty(key, value); if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(false); } save(); } finally { lock.writeLock().unlock(); } } @Override public void setConfigurations(Map<String, ?> values) { Preconditions.checkNotNull(values); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); lock.writeLock().lock(); try { if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(true); } for (Map.Entry<String, ?> entry : values.entrySet()) { config.setProperty(entry.getKey(), entry.getValue()); } if (config instanceof AbstractConfiguration) { ((AbstractConfiguration) config).setDelimiterParsingDisabled(false); } save(); } finally { lock.writeLock().unlock(); } if (!initialising) { raiseDelayedConfigurationEvent((values.keySet())); } } @Override public void setConfiguration(String key, List<String> values) { Preconditions.checkNotNull(key); Preconditions.checkState(initialised, "ConfigurationService not yet initialised; call initialise() manually"); if (!config.containsKey(key) || !ObjectUtils.equals(values, config.getList(key))) { lock.writeLock().lock(); try { config.setProperty(key, values); save(); } finally { lock.writeLock().unlock(); } if (!initialising) { raiseDelayedConfigurationEvent(Collections.singleton(key)); } } } /** * The work directory for marmotta, where all applications will create their own subdirectories * @deprecated This name is misleading, use {@link #getHome()} instead. */ @Override @Deprecated public String getWorkDir() { return getHome(); } protected void save() { if (saveConfiguration instanceof PropertiesConfiguration) { try { log.debug("Saving configuration values"); final PropertiesConfiguration conf = (PropertiesConfiguration) saveConfiguration; saveSecure(conf); } catch (ConfigurationException e) { log.error("could not save system configuration: {}", e.getMessage()); } } if (saveMetadata instanceof PropertiesConfiguration) { try { log.debug("Saving configuration description"); final PropertiesConfiguration conf = (PropertiesConfiguration) saveMetadata; saveSecure(conf); } catch (ConfigurationException e) { log.error("could not save system metadata: {}", e.getMessage()); } } } protected void saveSecure(final PropertiesConfiguration conf) throws ConfigurationException { final File file = conf.getFile(); try { if (file == null) { throw new ConfigurationException("No file name has been set!"); } else if ((file.createNewFile() || true) && !file.canWrite()) { throw new IOException("Cannot write to file " + file.getAbsolutePath() + ". Is it read-only?"); } } catch (IOException e) { throw new ConfigurationException(e.getMessage(), e); } log.debug("Saving {}", file.getAbsolutePath()); final String fName = file.getName(); try { int lastDot = fName.lastIndexOf('.'); lastDot = lastDot > 0 ? lastDot : fName.length(); final Path configPath = file.toPath(); // Create a tmp file next to the original final Path tmp = Files.createTempFile(configPath.getParent(), fName.substring(0, lastDot) + ".", fName.substring(lastDot)); try { Files.copy(configPath, tmp, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); } catch (IOException iox) { log.error("Could not create temp-file {}: {}", tmp, iox.getMessage()); throw iox; } log.trace("using temporary file: {}", tmp); // Save the config to the tmp file conf.save(tmp.toFile()); log.trace("tmp saved, now replacing the original file: {}", configPath); // Replace the original with the tmp file try { try { Files.move(tmp, configPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } catch (AtomicMoveNotSupportedException amnx) { log.trace("atomic move not available: {}, trying without", amnx.getMessage()); Files.move(tmp, configPath, StandardCopyOption.REPLACE_EXISTING); } } catch (IOException iox) { log.error("Could not write to {}, a backup was created in {}", configPath, tmp); throw iox; } log.info("configuration successfully saved to {}", configPath); } catch (final Throwable t) { throw new ConfigurationException("Unable to save the configuration to the file " + fName, t); } } /** * Return the value of the runtime flag passed as argument. * * @param flag * @return */ @Override public boolean getRuntimeFlag(String flag) { if (runtimeFlags.get(flag) != null) return runtimeFlags.get(flag); else return false; } /** * Set a flag at runtime that is discarded on system shutdown; used e.g. to indicate that * certain * processes have already been carried out. * * @param value */ @Override public void setRuntimeFlag(String flag, boolean value) { runtimeFlags.put(flag, value); } /** * Set the LMF_HOME value to the correct path. Used during the initialization process. * @deprecated LMF_HOME is deprecated, use {@link #setHome(String)} instead! */ @Override @Deprecated public void setLMFHome(String home) { log.warn( "ConfigurationService.setLMFHome() is deprecated, consider call directly ConfigurationService.setHome()"); this.setHome(home); } /** * Set the home value to the correct path. Used during the initialization process. * * @param home */ @Override public void setHome(String home) { this.home = home; } /** * Return the value of the LMF_HOME setting. Used during the initialization process. * @deprecated use {@link #getHome()} instead */ @Override @Deprecated public String getLMFHome() { log.warn( "ConfigurationService.getLMFHome() is deprecated, consider call directly ConfigurationService.getHome()"); return getHome(); } /** * Return the value of the home setting. Used during the initialization process. * * @return */ @Override public String getHome() { return home; } /** * Get the base context URI * * @return base context */ @Override public String getBaseContext() { return getBaseUri() + CONTEXT_PATH + "/"; } /** * Return the context used for storing system information. * * @return a KiWiUriResource representing the system knowledge space */ @Override public String getSystemContext() { return getBaseUri() + CONTEXT_PATH + CONTEXT_SYSTEM; } /** * Get the uri of the inferred context * * @return uri of this inferred context */ @Override public String getInferredContext() { if (StringUtils.isBlank(config.getString("contexts.inferred", null))) { return null; } else { return config.getString("contexts.inferred", null); } } /** * Get the uri of the default context * * @return */ @Override public String getDefaultContext() { if (StringUtils.isBlank(config.getString("contexts.default", null))) { return null; } else { return config.getString("contexts.default", null); } } /** * Get the uri of the context used for caching linked data * * @return */ @Override public String getCacheContext() { return getBaseUri() + CONTEXT_PATH + "/" + CONTEXT_CACHE; } @Override public String getEnhancerContex() { return getBaseUri() + CONTEXT_PATH + "/" + CONTEXT_ENHANCEMENT; } /** * Get a string describing the type and version of the application server running the LMF. * * @return */ @Override public String getServerInfo() { if (isTomcat7()) return "Apache Tomcat 7.x"; else if (isTomcat6()) return "Apache Tomcat <= 6.x"; else if (isJetty7()) return "Jetty 7.x"; else if (isJetty6()) return "Jetty 6.x"; else return "Unknown Servlet Container"; } /** * Try figuring out on which port the server is running ... */ @Override public int getServerPort() { if (serverPort == 0) { if (isTomcat6()) { // tomcat <= 6.x try { Object server = Class.forName("org.apache.catalina.ServerFactory").getMethod("getServer") .invoke(null); Object service = Array.get(server.getClass().getMethod("findServices").invoke(server), 0); Object connector = Array.get(service.getClass().getMethod("findConnectors").invoke(service), 0); int port = (Integer) connector.getClass().getMethod("getPort").invoke(connector); log.info("Tomcat <= 6.x detected, server port: {}", port); serverPort = port; } catch (Exception e) { } } else if (isTomcat7()) { // tomcat 7.x try { MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0); ObjectName name = new ObjectName("Catalina", "type", "Server"); Object server = mBeanServer.getAttribute(name, "managedResource"); Object service = Array.get(server.getClass().getMethod("findServices").invoke(server), 0); Object connector = Array.get(service.getClass().getMethod("findConnectors").invoke(service), 0); int port = (Integer) connector.getClass().getMethod("getPort").invoke(connector); log.info("Tomcat 7.x detected, server port: {}", port); serverPort = port; } catch (Exception e) { } } else { log.warn("not running on Tomcat, could not determine server port, returning default of 8080"); serverPort = 8080; } } return serverPort; } /** * Try figuring out the local name of the server * @return */ @Override public String getServerName() { if (serverName == null) { try { serverName = java.net.InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { serverName = "localhost"; } } return serverName; } /** * Return the context path of this application * @return */ @Override public String getServerContext() { return servletContext.getContextPath(); } /** * Shutdown the application server running this web application; tries to determine the kind of server we are * running under and send the proper shutdown signal before exiting with System.exit */ @Override public void performServerShutdown() { try { MBeanServer server = (MBeanServer) Class.forName("org.apache.catalina.mbeans.MBeanUtils") .getMethod("createServer").invoke(null); ObjectName name; if (isTomcat6()) { // Tomcat 6.x name = new ObjectName("Catalina:type=Service,serviceName=Catalina"); server.invoke(name, "stop", new Object[0], new String[0]); log.warn("shutting down Apache Tomcat server on user request"); } else if (isTomcat7()) { // Tomcat 7.x name = new ObjectName("Catalina", "type", "Service"); server.invoke(name, "stop", new Object[0], new String[0]); log.warn("shutting down Apache Tomcat server on user request"); } } catch (Exception ex) { log.error("shutting down other servers than Apache Tomcat is not supported", ex); } // ensure complete shutdown System.exit(0); } /** * Return true if Jetty 6.x is detected; tests for presence of class org.mortbay.jetty.Server * @return */ @Override public boolean isJetty6() { try { Class.forName("org.mortbay.jetty.Server"); return true; } catch (ClassNotFoundException e) { return false; } } /** * Return true if Jetty 7.x is detected; tests for presence of class org.eclipse.jetty.server.Server * @return */ @Override public boolean isJetty7() { try { Class.forName("org.eclipse.jetty.server.Server"); return true; } catch (ClassNotFoundException e) { return false; } } /** * Return true if Tomcat 6.x is detected; tests for presence of class org.apache.catalina.ServerFactory * @return */ @Override public boolean isTomcat6() { try { Class.forName("org.apache.catalina.ServerFactory"); return true; } catch (ClassNotFoundException e) { return false; } } /** * Return true if Tomcat 7.x is detected; tests for presence of class org.apache.catalina.CatalinaFactory * @return */ @Override public boolean isTomcat7() { try { Class.forName("org.apache.catalina.CatalinaFactory"); return true; } catch (ClassNotFoundException e) { return false; } } /** * Return the context parameter, servlet init parameter, or system property with the given key. This method * provides unified access to properties configured in either the web.xml, the context.xml or via a system property * passed on startup. It can be used by services that cannot read their configuration from the main * system-config.properties. * * @param key * @return */ @Override public String getContextParam(String key) { String value = System.getProperty(key); if (value == null) { if (servletContext != null) { value = servletContext.getInitParameter(key); if (value == null) { value = servletContext.getAttribute(key) != null ? servletContext.getAttribute(key).toString() : null; } } } return value; } /** * Start a delayed execution of raising an event * @param keys */ private void raiseDelayedConfigurationEvent(Set<String> keys) { eventLock.lock(); try { if (eventBacklog == null) { eventBacklog = new HashSet<>(); } eventBacklog.addAll(keys); if (eventTimer != null) { eventTimer.cancel(); } eventTimer = new Timer("Configuration Event Timer", true); eventTimer.schedule(new EventTimerTask(), EVENT_DELAY); } finally { eventLock.unlock(); } if (log.isDebugEnabled()) { log.debug("updated configuration keys [{}]", StringUtils.join(keys, ", ")); } } /** * Delayed event firing task */ private class EventTimerTask extends TimerTask { @Override public void run() { eventLock.lock(); try { Set<String> keys = eventBacklog; eventBacklog = null; if (log.isDebugEnabled()) { log.debug("firing delayed ({}ms) configuration changed event with keys [{}]", EVENT_DELAY, StringUtils.join(keys, ", ")); } configurationEvent.fire(new ConfigurationChangedEvent(keys)); } finally { eventLock.unlock(); } } } }