Java tutorial
/********************************************************************************** * $URL$ * $Id$ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.component.impl; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Comparator; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.Arrays; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener.BlockingConfigItem; import org.sakaiproject.component.locales.SakaiLocales; import org.sakaiproject.thread_local.api.ThreadLocalManager; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.util.SakaiProperties; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.io.Resource; import au.com.bytecode.opencsv.CSVParser; /** * <p> * BasicConfigurationService is a basic implementation of the ServerConfigurationService. * </p> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class BasicConfigurationService implements ServerConfigurationService, ApplicationContextAware { private static final String SOURCE_GET_STRINGS = "getStrings"; /** Our log (commons). */ private static Log M_log = LogFactory.getLog(BasicConfigurationService.class); /** * The delegate that handles all the configuration for tools. */ private final ToolConfigurationService toolConfigurationService = new ToolConfigurationService(); /** The instance id for this app server. */ private String instanceId = null; /** This is computed, joining the configured serverId and the set instanceId. */ private String serverIdInstance = null; /** The map of values from the loaded properties wherein property placeholders have * <em>not</em> been dereferenced */ private Properties rawProperties; private static final String SAKAI_LOCALES_KEY = "locales"; private static final String SAKAI_LOCALES_MORE = "locales.more"; // default is blank/null private static final String SAKAI_SYSTEM_PROPERTY_SUFFIX = "@SystemProperty"; /********************************************************************************************************************************************************************************************************************************************************** * Dependencies *********************************************************************************************************************************************************************************************************************************************************/ /** * the ThreadLocalManager collaborator. */ private ThreadLocalManager threadLocalManager; /** * the SessionManager collaborator. */ private SessionManager sessionManager; private SakaiProperties sakaiProperties; /********************************************************************************************************************************************************************************************************************************************************** * Configuration *********************************************************************************************************************************************************************************************************************************************************/ /** * Configuration: set the file name for tool order file. * * @param string * The file name for tool order file. */ public void setToolOrderFile(String string) { toolConfigurationService.setToolOrderFile(string); } /** * Configuration: set the resource to load in which the default tool order is defined. * * @param defaultToolOrderResource * The resource containing a tool order. */ public void setDefaultToolOrderResource(Resource defaultToolOrderResource) { toolConfigurationService.setDefaultToolOrderResource(defaultToolOrderResource); } /********************************************************************************************************************************************************************************************************************************************************** * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ private ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * Final initialization, once all dependencies are set. */ public void init() { // can enable the output of the complete set of configuration items using: config.dump.to.log this.rawProperties = sakaiProperties.getRawProperties(); // populate the security keys set String securedKeys = getRawProperty("config.secured.key.names"); if (securedKeys != null) { String[] keys = securedKeys.split(","); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < keys.length; i++) { String key = StringUtils.trimToNull(keys[i]); if (key != null) { this.secureConfigurationKeys.add(key); } } } M_log.info("Configured " + this.secureConfigurationKeys.size() + " secured key names: " + this.secureConfigurationKeys); // always add "password@javax.sql.BaseDataSource" this.secureConfigurationKeys.add("password@javax.sql.BaseDataSource"); // load up some things that are not part of the config but are used by it this.addConfigItem(new ConfigItemImpl("sakai.home", this.getSakaiHomePath()), "SCS"); this.addConfigItem(new ConfigItemImpl("sakai.gatewaySiteId", this.getGatewaySiteId()), "SCS"); this.addConfigItem(new ConfigItemImpl("portal.loggedOutURL", this.getLoggedOutUrl()), "SCS"); // put all the properties into the configuration map Map<String, Properties> allSakaiProps = sakaiProperties.getSeparateProperties(); for (Entry<String, Properties> entry : allSakaiProps.entrySet()) { // all properties from security.properties should be secured if ("security.properties".equalsIgnoreCase(entry.getKey())) { for (String securedKey : entry.getValue().stringPropertyNames()) { this.secureConfigurationKeys.add(securedKey); } } this.addProperties(entry.getValue(), entry.getKey()); } M_log.info("Configured " + this.secureConfigurationKeys.size() + " secured keys from all sources"); M_log.info("Loaded " + configurationItems.size() + " config items from all initial sources"); if (this.getBoolean("config.dereference.on.load.initial", true)) { int changed = dereferenceConfig(); M_log.info("Dereference Initial: Changed (dereferenced) " + changed + " item values out of " + configurationItems.size() + " initial config items"); } // load all the providers which are known (the rest have to manually register their configs), must be singleton without lazy init Map<String, ConfigurationProvider> providerBeans = this.applicationContext .getBeansOfType(ConfigurationProvider.class, false, false); if (providerBeans != null) { int configCounter = 0; for (ConfigurationProvider provider : providerBeans.values()) { List<ConfigItem> items; try { items = provider.registerConfigItems(this.getConfigData()); configCounter += items.size(); this.addConfigList(items); } catch (Exception e) { M_log.warn( "Unable to load the config values from provider (" + provider.getClass() + "): " + e); } } M_log.info("Found and loaded " + configCounter + " config values from " + providerBeans.size() + " configuration providers"); } if (this.getBoolean("config.dereference.on.load.all", false)) { int changed = dereferenceConfig(); M_log.info("Dereference All: Changed (dereferenced) " + changed + " item values out of all " + configurationItems.size() + " config items"); } // OTHER STUFF try { // set a unique instance id for this server run // Note: to reduce startup dependency, just use the current time, NOT the id service. instanceId = Long.toString(System.currentTimeMillis()); serverIdInstance = getServerId() + "-" + instanceId; } catch (Exception t) { M_log.warn("init(): ", t); } M_log.info("init()"); // Initialise the tool configuration service toolConfigurationService.setUseToolGroup(getConfig("config.sitemanage.useToolGroup", false)); toolConfigurationService.init(); } /** * Final cleanup. */ public void destroy() { this.applicationContext = null; this.listeners.clear(); M_log.info("destroy()"); } /********************************************************************************************************************************************************************************************************************************************************** * ServerConfigurationService implementation *********************************************************************************************************************************************************************************************************************************************************/ /** * {@inheritDoc} */ public String getServerId() { return getConfig("serverId", "localhost"); //return (String) properties.get("serverId"); } /** * {@inheritDoc} */ public String getServerInstance() { return instanceId; } /** * {@inheritDoc} */ public String getServerIdInstance() { return serverIdInstance; } /** * {@inheritDoc} */ public String getServerUrl() { // try to get the value pre-computed for this request, to better match the request server naming conventions String rv = (String) threadLocalManager.get(CURRENT_SERVER_URL); if (rv == null) { rv = getConfig("serverUrl", "http://localhost:8080"); //rv = (String) properties.get("serverUrl"); } return rv; } /** * {@inheritDoc} */ public String getServerName() { return getConfig("serverName", "localhost"); //(String) properties.get("serverName"); } /** * {@inheritDoc} */ public String getAccessUrl() { return getServerUrl() + getAccessPath(); //(String) properties.get("accessPath"); } /** * {@inheritDoc} */ public String getAccessPath() { return getConfig("accessPath", "/access"); //(String) properties.get("accessPath"); } /** * {@inheritDoc} */ public String getHelpUrl(String helpContext) { String rv = getPortalUrl() + getConfig("helpPath", "/help") + "/main"; //(String) properties.get("helpPath") + "/main"; if (helpContext != null) { rv += "?help=" + helpContext; } return rv; } /** * {@inheritDoc} */ public String getPortalUrl() { /* String rv = (String) threadLocalManager.get(CURRENT_PORTAL_PATH); if (rv == null) { rv = (String) properties.get("portalPath"); } */ //KNL-758, SAK-20431 - don't use the portal path that the RequestFilter gives us, //as that is based on the current context. Instead use the actual value we have //set in sakai.properties String rv = getConfig("portalPath", "/portal"); //(String) properties.get("portalPath"); @SuppressWarnings("UnnecessaryLocalVariable") String portalUrl = getServerUrl() + rv; return portalUrl; } /** * {@inheritDoc} */ public String getToolUrl() { return getServerUrl() + getConfig("toolPath", "/portal/tool"); //(String) properties.get("toolPath"); } /** * {@inheritDoc} */ public String getUserHomeUrl() { // get the configured URL (the text "#UID#" will be repalced with the current logged in user id // NOTE: this is relative to the server root String rv = getConfig("userHomeUrl", null); //(String) properties.get("userHomeUrl"); // form a site based portal id if not configured if (rv == null) { rv = getConfig("portalPath", "/portal") + "/site/~#UID#"; // (String) properties.get("portalPath") + "/site/~#UID#" } // check for a logged in user String user = sessionManager.getCurrentSessionUserId(); boolean loggedIn = (user != null); // if logged in, replace the UID in the pattern if (loggedIn) { rv = rv.replaceAll("#UID#", user); } // make it full, adding the server root rv = getServerUrl() + rv; return rv; } /** * {@inheritDoc} */ public String getGatewaySiteId() { String rv = getConfig("gatewaySiteId", "!gateway"); //(String) properties.get("gatewaySiteId"); if (rv == null) { rv = "~anon"; } return rv; } /** * {@inheritDoc} */ public String getLoggedOutUrl() { String rv = getConfig("loggedOutUrl", "/portal"); //(String) properties.get("loggedOutUrl"); if (rv != null) { // if not a full URL, add the server to the front if (rv.startsWith("/")) { rv = getServerUrl() + rv; } } // use the portal URL if there's no logout defined else { rv = getPortalUrl(); } return rv; } /** * {@inheritDoc} */ public String getSakaiHomePath() { return System.getProperty("sakai.home"); } /** * {@inheritDoc} */ public String getRawProperty(String name) { String rv = null; if (this.rawProperties.containsKey(name)) { // NOTE: raw properties ONLY contains the data read in from the properties files rv = StringUtils.trimToNull((String) this.rawProperties.get(name)); } else { // check the config storage since it is not in the raw props ConfigItem ci = getConfigItem(name); if (ci != null && ci.getValue() != null) { rv = ci.getValue().toString(); } } if (rv == null) rv = ""; return rv; } /** * {@inheritDoc} */ public String getString(String name) { return getString(name, ""); //properties); } /** * {@inheritDoc} */ public String getString(String name, String dflt) { /** * NOTE: everything calls this in order to resolve a configuration setting, * we take advantage of that by doing the heavy lifting in this method * (which includes variable replacement) */ //return getString(name, dflt, properties); String value = dflt; // retrieve a registered config item for this name ConfigItemImpl ci = findConfigItem(name, dflt); if (ci != null) { if (ci.getValue() != null) { value = StringUtils.trimToNull(ci.getValue().toString()); } else { // if the default value is set then we will return that instead //noinspection StatementWithEmptyBody if (ci.getDefaultValue() != null) { value = StringUtils.trimToNull(ci.getDefaultValue().toString()); } else { // the stored value and default value are null so we will allow the dflt to override } } } if (StringUtils.isNotEmpty(value)) { // check if we need to do any variable replacement value = dereferenceValue(value); } return value; } /** * Pattern to find "${var}" in strings (should match "key" from ${key}) */ static Pattern referencePattern = Pattern.compile("\\$\\{(.+?)\\}"); /** * This will search for any values in the string which need to be replaced * with actual values from the current known set of properties which are * available to the config service * * @param value any string (might have a reference like: "thing-${suffix}") * @return the value with all matched ${vars} replaced (unmatched ones are left as is), only returns null if the input is null */ protected String dereferenceValue(String value) { if (M_log.isDebugEnabled()) M_log.debug("dereferenceValue(" + value + ")"); /* * NOTE: if the performance of this becomes an issue then the right way to handle it is * to place a flag on the ConfigItem to indicate if there is replaceable refs in it * (probably when this runs the first time) and if there are none then skip this * until the value is changed and then reset the flag so it will be checked again, * if there are still issues then adding a "lastChecked" timestamp and * a cache of the processed value to the ConfigItem which is used as long as the timestamp * has not expired (maybe 15 mins or something), with automatic expiration when the value changes */ String drValue = value; if (value != null && value.length() >= 4) { // min length of a replaceable value - "${a}" Matcher matcher = referencePattern.matcher(value); if (matcher.find()) { if (M_log.isDebugEnabled()) M_log.debug("dereferenceValue(" + value + "), found refs to replace"); matcher.reset(); StringBuilder sb = new StringBuilder(); // loop through and find the vars to replace and write out the new string int pointer = 0; while (matcher.find()) { String name = matcher.group(1); if (name != null && StringUtils.isNotBlank(name)) { // look up the value String replacementValue = null; ConfigItemImpl ci = findConfigItem(name, null); if (ci != null) { // found the config name so we will at least replace with empty string replacementValue = ""; if (ci.getValue() != null) { replacementValue = StringUtils.trimToEmpty(ci.getValue().toString()); } } sb.append(value.substring(pointer, matcher.start())); if (replacementValue == null) { replacementValue = matcher.group(); // just put the ${name} back in } else { // need to recurse in the case of nested refs replacementValue = dereferenceValue(replacementValue); } sb.append(replacementValue); pointer = matcher.end(); } } sb.append(value.substring(pointer, value.length())); // get the remainder of the value drValue = sb.toString(); } } if (M_log.isDebugEnabled()) M_log.debug("dereferenceValue(" + value + "): return=" + drValue); return drValue; } /** * Goes through the entire config and resolves all references and updates the actual * stored values in the config. * NOTE: this is destructive and probably not a good idea to run generally * * @return the number of config items which were changed */ protected int dereferenceConfig() { int counter = 0; for (Entry<String, ConfigItemImpl> entry : configurationItems.entrySet()) { ConfigItemImpl configItem = entry.getValue(); if (configItem.getValue() != null) { String currentValue = configItem.getValue().toString(); String newValue = dereferenceValue(currentValue); if (!currentValue.equals(newValue)) { configItem.setValue(newValue); counter++; } } } return counter; } /** * {@inheritDoc} */ public String[] getStrings(String name) { String[] rv = null; // get the count int count = getInt(name + ".count", -1); if (count == 0) { // zero count means empty array rv = new String[0]; } else if (count > 0) { rv = new String[count]; for (int i = 1; i <= count; i++) { rv[i - 1] = getString(name + "." + i, ""); } // store the array in the properties this.addConfigItem(new ConfigItemImpl(name, rv, TYPE_ARRAY, SOURCE_GET_STRINGS), SOURCE_GET_STRINGS); } else { if (findConfigItem(name, null) != null) { // the config name exists String value = getString(name); if (StringUtils.isBlank(value)) { // empty value is an empty array rv = new String[0]; this.addConfigItem(new ConfigItemImpl(name, rv, TYPE_ARRAY, SOURCE_GET_STRINGS), SOURCE_GET_STRINGS); } else { CSVParser csvParser = new CSVParser(',', '"', '\\', false, true); // should configure this for default CSV parsing try { rv = csvParser.parseLine(value); this.addConfigItem(new ConfigItemImpl(name, rv, TYPE_ARRAY, SOURCE_GET_STRINGS), SOURCE_GET_STRINGS); } catch (IOException e) { M_log.warn( "Config property (" + name + ") read as multi-valued string, but failure occurred while parsing: " + e, e); } } } } return rv; } /** * {@inheritDoc} */ public int getInt(String name, int dflt) { String value = getString(name); if (StringUtils.isEmpty(value)) return dflt; return Integer.parseInt(value); } /** * {@inheritDoc} */ public boolean getBoolean(String name, boolean dflt) { String value = getString(name); if (StringUtils.isEmpty(value)) return dflt; return Boolean.valueOf(value); } /** * {@inheritDoc} */ public List<String> getToolGroup(String groupName) { return toolConfigurationService.getToolGroup(groupName); } /** * {@inheritDoc} */ public List<String> getCategoryGroups(String category) { return toolConfigurationService.getCategoryGroups(category); } /** * {@inheritDoc} */ public List<String> getToolOrder(String category) { return toolConfigurationService.getToolOrder(category); } /** * {@inheritDoc} */ public List<String> getToolsRequired(String category) { return toolConfigurationService.getToolsRequired(category); } /** * {@inheritDoc} */ public List<String> getDefaultTools(String category) { return toolConfigurationService.getDefaultTools(category); } /** * {@inheritDoc} */ public List<String> getToolCategories(String category) { return toolConfigurationService.getToolCategories(category); } /** * {@inheritDoc} */ public Map<String, List<String>> getToolCategoriesAsMap(String category) { return toolConfigurationService.getToolCategoriesAsMap(category); } /** * {@inheritDoc} */ public Map<String, String> getToolToCategoryMap(String category) { return toolConfigurationService.getToolToCategoryMap(category); } /* * Returns true if selected tool is contained in pre-initialized list of selected items * @params toolId id of the selected tool */ public boolean toolGroupIsSelected(String groupName, String toolId) { return toolConfigurationService.toolGroupIsSelected(groupName, toolId); } /* * Returns true if selected tool is contained in pre-initialized list of required items * @parms toolId id of the selected tool */ public boolean toolGroupIsRequired(String groupName, String toolId) { return toolConfigurationService.toolGroupIsRequired(groupName, toolId); } /** * Get the list of allowed locales as controlled by config params for {@value #SAKAI_LOCALES_KEY} and {@value #SAKAI_LOCALES_MORE} * @return an array of all allowed Locales for this installation */ public Locale[] getSakaiLocales() { String localesStr = getString(SAKAI_LOCALES_KEY, SakaiLocales.SAKAI_LOCALES_DEFAULT); if (localesStr == null) { // means locales= is set localesStr = ""; // empty to get default locale only } else if (StringUtils.isBlank(localesStr)) { // missing or not set localesStr = SakaiLocales.SAKAI_LOCALES_DEFAULT; } String[] locales = StringUtils.split(localesStr, ','); // NOTE: these need to be trimmed (which getLocaleFromString will do) String[] localesMore = getStrings(SAKAI_LOCALES_MORE); locales = (String[]) ArrayUtils.addAll(locales, localesMore); HashSet<Locale> localesSet = new HashSet<Locale>(); // always include the default locale localesSet.add(Locale.getDefault()); if (!ArrayUtils.isEmpty(locales)) { // convert from strings to Locales //noinspection ForLoopReplaceableByForEach for (int i = 0; i < locales.length; i++) { localesSet.add(getLocaleFromString(locales[i])); } } // Sort Locales and remove duplicates Locale[] localesArray = localesSet.toArray(new Locale[localesSet.size()]); Arrays.sort(localesArray, new LocaleComparator()); return localesArray; } /** * Comparator for sorting locale by DisplayName */ static final class LocaleComparator implements Comparator<Locale> { /** * Compares Locale objects by comparing the DisplayName * * @param localeOne * 1st Locale Object for comparison * @param localeTwo * 2nd Locale Object for comparison * @return negative, zero, or positive integer * (obj1 charge is less than, equal to, or greater than the obj2 charge) */ public int compare(Locale localeOne, Locale localeTwo) { String displayNameOne = localeOne.getDisplayName(localeOne).toLowerCase(); String displayNameTwo = localeTwo.getDisplayName(localeTwo).toLowerCase(); return displayNameOne.compareTo(displayNameTwo); } @Override public boolean equals(Object obj) { //noinspection SimplifiableIfStatement if (obj instanceof LocaleComparator) { return super.equals(obj); } else { return false; } } } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService#getLocaleFromString(java.lang.String) */ public Locale getLocaleFromString(String localeString) { // should this just use LocalUtils.toLocale()? - can't - it thinks en_GB is invalid for example if (localeString != null) { // force en-US (dash separated) values into underscore style localeString = StringUtils.replaceChars(localeString, '-', '_'); } else { return null; } String[] locValues = localeString.trim().split("_"); if (locValues.length >= 3 && StringUtils.isNotBlank(locValues[2])) { return new Locale(locValues[0], locValues[1], locValues[2]); // language, country, variant } else if (locValues.length == 2 && StringUtils.isNotBlank(locValues[1])) { return new Locale(locValues[0], locValues[1]); // language, country } else if (locValues.length == 1 && StringUtils.isNotBlank(locValues[0])) { return new Locale(locValues[0]); // language } else { return Locale.getDefault(); } } public void setSakaiProperties(SakaiProperties sakaiProperties) { this.sakaiProperties = sakaiProperties; } public void setThreadLocalManager(ThreadLocalManager threadLocalManager) { this.threadLocalManager = threadLocalManager; } public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } /** * @deprecated do not use this anymore, use {@link #getConfigData()} to get all properties */ public Properties getProperties() { return sakaiProperties.getProperties(); } // new config handling code - 08 Sept 2011 private ConcurrentHashMap<String, ConfigItemImpl> configurationItems = new ConcurrentHashMap<String, ConfigItemImpl>(); private HashSet<String> secureConfigurationKeys = new HashSet<String>(); protected ConcurrentHashMap<String, WeakReference<ConfigurationListener>> listeners = new ConcurrentHashMap<String, WeakReference<ConfigurationListener>>(); /** * INTERNAL * Adds a set of config items using the data from a set of properties * @param p the properties * @param source the source name */ protected void addProperties(Properties p, String source) { if (p != null) { if (source == null || "".equals(source)) { source = UNKNOWN; } M_log.info("Adding " + p.size() + " properties from " + source); for (Enumeration<Object> e = p.keys(); e.hasMoreElements(); /**/) { String name = (String) e.nextElement(); String value = p.getProperty(name); // KNL-1361 - Add support for system-scoped properties if (name != null && name.endsWith(SAKAI_SYSTEM_PROPERTY_SUFFIX) && name.length() > SAKAI_SYSTEM_PROPERTY_SUFFIX.length()) { name = name.substring(0, name.length() - SAKAI_SYSTEM_PROPERTY_SUFFIX.length()); System.setProperty(name, value); M_log.info("Promoted to system property: " + name); continue; } ConfigItemImpl ci = new ConfigItemImpl(name, value, source); this.addConfigItem(ci, source); } } } /** * INTERNAL * Adds a set of config items from a list * @param list the list */ protected void addConfigList(List<ConfigItem> list) { if (list != null && !list.isEmpty()) { M_log.info("Adding " + list.size() + " config items from a list"); for (ConfigItem configItem : list) { this.registerConfigItem(configItem); } } } /** * INTERNAL * Adds the config item if it does not exist OR updates it if it does * * @param configItem the config item * @param source the source of the update * @return the config item if it was add or null if not (only case would be if the input was null) */ protected ConfigItemImpl addConfigItem(ConfigItemImpl configItem, String source) { ConfigItemImpl ci = null; if (configItem != null) { ConfigItemImpl currentCI = null; if (configurationItems.containsKey(configItem.getName())) { // item exists currentCI = configurationItems.get(configItem.getName()); } // notify the before listeners boolean haltProcessing = false; if (this.listeners != null && !this.listeners.isEmpty()) { for (Entry<String, WeakReference<ConfigurationListener>> entry : this.listeners.entrySet()) { // check if any listener refs are no longer valid ConfigurationListener listener = entry.getValue().get(); if (listener != null) { try { ConfigItem rvci = listener.changing(currentCI, configItem); //noinspection StatementWithEmptyBody if (rvci == null) { // continue } else if (rvci instanceof BlockingConfigItem) { haltProcessing = true; M_log.info("add configItem (" + configItem + ") processing halted by " + listener); break; // HALT processing } else { // merge in the safe changes to the config item configItem.merge(rvci); } } catch (Exception e) { M_log.warn("Exception when calling listener (" + listener + "): " + e); } } else { // cleanup bad listener ref this.listeners.remove(entry.getKey()); } } } if (!haltProcessing) { // update the config item boolean changed = false; if (currentCI != null) { // update it if (!SOURCE_GET_STRINGS.equals(source)) { // only update if the source is not the getStrings() method currentCI.changed(configItem.getValue(), source); changed = true; if (!currentCI.isRegistered() && configItem.isRegistered()) { // need to force items which are not yet registered to be registered currentCI.registered = true; } } ci = currentCI; } else { // add the new one configItem.setSource(source); if (secureConfigurationKeys.contains(configItem.getName())) { configItem.secured = true; } configurationItems.put(configItem.getName(), configItem); ci = configItem; changed = true; } // notify the after listeners (only if something changed) if (changed) { if (this.listeners != null && !this.listeners.isEmpty()) { for (Entry<String, WeakReference<ConfigurationListener>> entry : this.listeners .entrySet()) { // check if any listener refs are no longer valid ConfigurationListener listener = entry.getValue().get(); if (listener != null) { try { listener.changed(ci, currentCI); } catch (Exception e) { M_log.warn("Exception when calling listener (" + listener + "): " + e); } } else { // cleanup bad listener ref this.listeners.remove(entry.getKey()); } } } } // DONE with notifying listeners } } return ci; } /** * INTERNAL * Finds a config item by name, use this whenever retrieving the item for lookup * * @param name the key name for the config value * @return the config item OR null if none exists */ protected ConfigItemImpl findConfigItem(String name, Object defaultValue) { ConfigItemImpl ci = null; if (name != null && !"".equals(name)) { ci = configurationItems.get(name); if (ci == null) { // add unregistered when not found for tracking later ConfigItemImpl configItemImpl = new ConfigItemImpl(name); configItemImpl.setDefaultValue(defaultValue); configItemImpl.setSource("get"); this.addConfigItem(configItemImpl, "get"); } else { // update the access log ci.requested(); // https://jira.sakaiproject.org/browse/KNL-1130 - assume string has no default in cases where it is "" or null if (ServerConfigurationService.TYPE_STRING.equals(ci.type)) { ci.defaulted = !(defaultValue == null || "".equals(defaultValue)); } else if (defaultValue != null) { ci.defaulted = true; } if (!ci.isRegistered()) { // we do not return unregistered config values ci = null; } } } return ci; } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService#getConfig(java.lang.String, java.lang.Object) */ public <T> T getConfig(String name, T defaultValue) { T returnValue; Object value = null; ConfigItem ci = configurationItems.get(name); if (ci != null && ci.getValue() != null) { value = ci.getValue(); } if (defaultValue == null) { returnValue = (T) this.getString(name); if ("".equals(returnValue)) { returnValue = null; } } else { if (defaultValue instanceof Number) { int num = ((Number) defaultValue).intValue(); int intValue = this.getInt(name, num); returnValue = (T) Integer.valueOf(intValue); } else if (defaultValue instanceof Boolean) { boolean bool = (Boolean) defaultValue; boolean boolValue = this.getBoolean(name, bool); returnValue = (T) Boolean.valueOf(boolValue); } else if (defaultValue instanceof String) { returnValue = (T) this.getString(name, (String) defaultValue); } else if (defaultValue.getClass().isArray()) { returnValue = (T) value; } else { returnValue = (T) this.getRawProperty(name); } } return returnValue; } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService#getConfigItem(java.lang.String) */ public ConfigItem getConfigItem(String name) { ConfigItem ci = configurationItems.get(name); if (ci != null) { ci = ci.copy(); } return ci; } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService#getConfigData() */ public ConfigData getConfigData() { ArrayList<ConfigItem> configItems = new ArrayList<ConfigItem>(configurationItems.values()); return new ConfigDataImpl(configItems); } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService#registerConfigItem(org.sakaiproject.component.api.ServerConfigurationService.ConfigItem) */ public ConfigItem registerConfigItem(ConfigItem configItem) { if (configItem == null) { throw new IllegalArgumentException("configItem must be set"); } ConfigItemImpl ci = null; if (StringUtils.isNotBlank(configItem.getName())) { ci = new ConfigItemImpl(configItem.getName(), configItem.getValue(), configItem.getSource()); if (configItem.getValue() != null) { ci.setValue(configItem.getValue()); } if (configItem.getDefaultValue() != null) { ci.setDefaultValue(configItem.getDefaultValue()); } ci = this.addConfigItem(ci, ci.getSource()); } else { M_log.warn("Skipping registering invalid config item (name not set): " + configItem); } return ci; } /* (non-Javadoc) * @see org.sakaiproject.component.api.ServerConfigurationService#registerListener(org.sakaiproject.component.api.ServerConfigurationService.ConfigurationListener) */ public void registerListener(ConfigurationListener configurationListener) { if (configurationListener != null) { String name = configurationListener.getClass().getName() + "@" + configurationListener.hashCode(); WeakReference<ConfigurationListener> ref = new WeakReference<ConfigurationListener>( configurationListener); this.listeners.put(name, ref); } } }