/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/component/impl/BasicConfigurationService.java $
* $Id: BasicConfigurationService.java 130787 2013-10-23 11:47:59Z azeckoski@unicon.net $
***********************************************************************************
*
* 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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
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.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.Iterator;
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.sakaiproject.util.Xml;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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 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;
/** File name within sakai.home for the tool order file. */
private String toolOrderFile = null;
private Resource defaultToolOrderResource;
/** loaded tool orders - map keyed by category of List of tool id strings. */
private Map m_toolOrders = new HashMap();
private Map m_toolGroups = new HashMap<String,List>(); // Map = [group1,{tool1,tool2,tool3}],[group2,{tool2,tool4}],[group3,{tool1,tool5}]
private Map m_toolGroupCategories = new HashMap<String,List>(); // Map = [course,{group1, group2,group3}],[project,{group1, group3, group4}],[portfolio,{group4}]
private Map m_toolGroupRequired = new HashMap<String,List>();
private Map m_toolGroupSelected = new HashMap<String,List>();
/** required tools - map keyed by category of List of tool id strings. */
private Map m_toolsRequired = new HashMap();
/** default tools - map keyed by category of List of tool id strings. */
private Map m_defaultTools = new HashMap();
/** default tool categories in order mapped by site type */
private Map<String, List<String>> m_toolCategoriesList = new HashMap<String, List<String>>();
/** default tool categories to tool id maps mapped by site type */
private Map<String, Map<String, List<String>>> m_toolCategoriesMap = new HashMap<String, Map<String, List<String>>>();
/** default tool id to tool category maps mapped by site type */
private Map<String, Map<String, String>> m_toolToToolCategoriesMap = new HashMap<String, Map<String, String>>();
private static final String SAKAI_LOCALES_KEY = "locales";
private static final String SAKAI_LOCALES_MORE = "locales.more"; // default is blank/null
/**********************************************************************************************************************************************************************************************************************************************************
* 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)
{
toolOrderFile = string;
}
/**********************************************************************************************************************************************************************************************************************************************************
* 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
this.secureConfigurationKeys.add("password@javax.sql.BaseDataSource");
String securedKeys = getRawProperty("config.secured.key.names");
if (securedKeys != null) {
String[] keys = securedKeys.split(",");
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);
// 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()) {
this.addProperties(entry.getValue(), entry.getKey());
}
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);
}
// load in the tool order, if specified, from the sakai home area
if (toolOrderFile != null)
{
boolean useToolGroups = getConfig("config.sitemanage.useToolGroup", false);
File f = new File(toolOrderFile);
if (f.exists())
{
FileInputStream fis = null;
try
{
fis = new FileInputStream(f);
if ( ! useToolGroups ) // default, legacy toolOrder.xml format
loadToolOrder(fis);
else // optional format with tool groups
loadToolGroups(fis);
}
catch (Exception t)
{
M_log.warn("init(): trouble loading tool order from : " + toolOrderFile, t);
}
finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
else
{
// start with the distributed defaults from the classpath
try
{
if ( ! useToolGroups ) // default, legacy toolOrder.xml format
loadToolOrder(defaultToolOrderResource.getInputStream());
else // optional format with tool groups
loadToolGroups(defaultToolOrderResource.getInputStream());
}
catch (Exception t)
{
M_log.warn("init(): trouble loading tool order from default toolOrder.xml", t);
}
}
}
M_log.info("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");
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
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;
}
/*
private String getString(String name, Properties fromProperties) {
return getString(name, "", fromProperties);
}
private String getString(String name, String dflt, Properties fromProperties) {
String rv = StringUtils.trimToNull((String) fromProperties.get(name));
if (rv == null) rv = dflt;
return rv;
}
*/
/**
* {@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).booleanValue();
}
/**
* {@inheritDoc}
*/
public List getToolGroup(String groupName)
{
if (groupName != null)
{
List groups = (List) m_toolGroups.get(groupName);
if (groups != null)
{
M_log.debug("getToolGroup: " + groups.toString());
return groups;
}
}
return new Vector();
}
/**
* {@inheritDoc}
*/
public List getCategoryGroups(String category)
{
if (category != null)
{
List groups = (List) m_toolGroupCategories.get(category);
if (groups != null)
{
M_log.debug("getCategoryGroups: " + groups.toString());
return groups;
}
}
return new Vector();
}
/**
* {@inheritDoc}
*/
public List getToolOrder(String category)
{
if (category != null)
{
List order = (List) m_toolOrders.get(category);
if (order != null)
{
return order;
}
}
return new Vector();
}
/**
* {@inheritDoc}
*/
public List getToolsRequired(String category)
{
if (category != null)
{
List order = (List) m_toolsRequired.get(category);
if (order != null)
{
return order;
}
}
return new Vector();
}
/**
* {@inheritDoc}
*/
public List getDefaultTools(String category)
{
if (category != null)
{
List order = (List) m_defaultTools.get(category);
if (order != null)
{
return order;
}
}
return new Vector();
}
/**
* {@inheritDoc}
*/
public List<String> getToolCategories(String category)
{
if (category != null)
{
List<String> categories = m_toolCategoriesList.get(category);
if (categories != null)
{
return categories;
}
}
return new Vector();
}
/**
* {@inheritDoc}
*/
public Map<String, List<String>> getToolCategoriesAsMap(String category)
{
if (category != null)
{
Map<String, List<String>> categories = m_toolCategoriesMap.get(category);
if (categories != null)
{
return categories;
}
}
return new HashMap();
}
/**
* {@inheritDoc}
*/
public Map<String, String> getToolToCategoryMap(String category)
{
if (category != null)
{
Map<String, String> categories = m_toolToToolCategoriesMap.get(category);
if (categories != null)
{
return categories;
}
}
return new HashMap();
}
/*
* Load tools by group, from toolOrder.xml file with optional groups defined
*/
private void loadToolGroups(InputStream in)
{
Document doc = Xml.readDocumentFromStream(in);
Element root = doc.getDocumentElement();
if (!root.getTagName().equals("toolGroups"))
{
M_log.info("loadToolGroups: invalid root element (expecting \"toolGroups\"): " + root.getTagName());
return;
}
NodeList groupNodes = root.getElementsByTagName("group");
if (groupNodes != null) {
for (int k = 0; k < groupNodes.getLength(); k++) {
Node g_node = groupNodes.item(k);
if (g_node.getNodeType() != Node.ELEMENT_NODE) continue;
Element g_element = (Element) g_node;
String groupName = StringUtils.trimToNull(g_element.getAttribute("name"));
//
if ((groupName != null) ) {
// group of this name already in map?
List groupList = (List) m_toolGroups.get(groupName);
if (groupList == null) {
groupList = new Vector();
m_toolGroups.put(groupName, groupList);
}
// add tools
NodeList tools = g_element.getElementsByTagName("tool");
final int toolCount = tools.getLength();
for (int j = 0; j < toolCount; j++) {
Element toolElement = (Element) tools.item(j);
// add this tool
String toolId = toolElement.getAttribute("id");
groupList.add(toolId);
String req = StringUtils.trimToNull(toolElement.getAttribute("required"));
if ((req != null) && (Boolean.TRUE.toString().equalsIgnoreCase(req)))
{
List reqList = (List) m_toolGroupRequired.get(groupName);
if (reqList==null) {
reqList = new ArrayList<String>();
m_toolGroupRequired.put(groupName,reqList);
}
reqList.add(toolId);
}
String sel = StringUtils.trimToNull(toolElement.getAttribute("selected"));
if ((sel != null) && (Boolean.TRUE.toString().equalsIgnoreCase(sel)))
{
List selList = (List) m_toolGroupSelected.get(groupName);
if (selList==null) {
selList = new ArrayList<String>();
m_toolGroupSelected.put(groupName,selList);
}
selList.add(toolId);
}
}
// add group to category(s)
String groupCategories = StringUtils.trimToNull(g_element.getAttribute("category"));
if (groupCategories != null) {
List<String> list = new ArrayList<String>(Arrays.asList(groupCategories.split(",")));
for(Iterator<String> itr = list.iterator(); itr.hasNext();) {
String catName = itr.next();
List groupCategoryList = (List) m_toolGroupCategories.get(catName);
if (groupCategoryList==null) {
groupCategoryList = new ArrayList();
m_toolGroupCategories.put(catName,groupCategoryList);
}
groupCategoryList.add(groupName);
}
}
}
}
}
}
/*
* Returns true if selected tool is contained in pre-initialized list of selected items
* @parms toolId id of the selected tool
*/
public boolean toolGroupIsSelected(String groupName, String toolId)
{
List selList = (List) m_toolGroupRequired.get(groupName);
if (selList == null) {
return false;
}
else {
int result = selList.indexOf(toolId);
return result >= 0;
}
}
/*
* 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)
{
List reqList = (List) m_toolGroupRequired.get(groupName);
if (reqList == null) {
return false;
}
else {
int result = reqList.indexOf(toolId);
return result >= 0;
}
}
/**
* Load this single file as a registration file, loading tools and locks.
*
* @param in
* The Stream to load
*/
private void loadToolOrder(InputStream in)
{
Document doc = Xml.readDocumentFromStream(in);
Element root = doc.getDocumentElement();
if (!root.getTagName().equals("toolOrder"))
{
M_log.info("loadToolOrder: invalid root element (expecting \"toolOrder\"): " + root.getTagName());
return;
}
// read the children nodes
NodeList rootNodes = root.getChildNodes();
final int rootNodesLength = rootNodes.getLength();
for (int i = 0; i < rootNodesLength; i++)
{
Node rootNode = rootNodes.item(i);
if (rootNode.getNodeType() != Node.ELEMENT_NODE) continue;
Element rootElement = (Element) rootNode;
// look for "category" elements
if (rootElement.getTagName().equals("category"))
{
String name = StringUtils.trimToNull(rootElement.getAttribute("name"));
if (name != null)
{
// form a list for this category
List order = (List) m_toolOrders.get(name);
if (order == null)
{
order = new Vector();
m_toolOrders.put(name, order);
List required = new Vector();
m_toolsRequired.put(name, required);
List defaultTools = new Vector();
m_defaultTools.put(name, defaultTools);
List<String> toolCategories = new Vector();
m_toolCategoriesList.put(name, toolCategories);
Map<String, List<String>> toolCategoryMappings = new HashMap();
m_toolCategoriesMap.put(name, toolCategoryMappings);
Map<String, String> toolToCategoryMap = new HashMap();
m_toolToToolCategoriesMap.put(name, toolToCategoryMap);
// get the kids
NodeList nodes = rootElement.getChildNodes();
final int nodesLength = nodes.getLength();
for (int c = 0; c < nodesLength; c++)
{
Node node = nodes.item(c);
if (node.getNodeType() != Node.ELEMENT_NODE) continue;
Element element = (Element) node;
if (element.getTagName().equals("tool"))
{
processTool(element, order, required, defaultTools);
}
else if (element.getTagName().equals("toolCategory")) {
processCategory(element, order, required, defaultTools,
toolCategories, toolCategoryMappings, toolToCategoryMap);
}
}
}
}
}
}
}
private void processCategory(Element element, List order, List required,
List defaultTools, List<String> toolCategories,
Map<String, List<String>> toolCategoryMappings,
Map<String, String> toolToCategoryMap) {
String name = element.getAttribute("id");
NodeList nameList = element.getElementsByTagName("name");
if (nameList.getLength() > 0) {
Element nameElement = (Element) nameList.item(0);
name = nameElement.getTextContent();
}
toolCategories.add(name);
List<String> toolCategoryTools = new Vector();
toolCategoryMappings.put(name, toolCategoryTools);
NodeList nodes = element.getChildNodes();
final int nodesLength = nodes.getLength();
for (int c = 0; c < nodesLength; c++)
{
Node node = nodes.item(c);
if (node.getNodeType() != Node.ELEMENT_NODE) continue;
Element toolElement = (Element) node;
if (toolElement.getTagName().equals("tool"))
{
String id = processTool(toolElement, order, required, defaultTools);
toolCategoryTools.add(id);
toolToCategoryMap.put(id, name);
}
}
}
private String processTool(Element element, List order, List required, List defaultTools) {
String id = StringUtils.trimToNull(element.getAttribute("id"));
if (id != null)
{
order.add(id);
}
String req = StringUtils.trimToNull(element.getAttribute("required"));
if ((req != null) && (Boolean.TRUE.toString().equalsIgnoreCase(req)))
{
required.add(id);
}
String sel = StringUtils.trimToNull(element.getAttribute("selected"));
if ((sel != null) && (Boolean.TRUE.toString().equalsIgnoreCase(sel)))
{
defaultTools.add(id);
}
return id;
}
/**
* 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
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) {
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;
}
public void setDefaultToolOrderResource(Resource defaultToolOrderResource) {
this.defaultToolOrderResource = defaultToolOrderResource;
}
/**
* @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);
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);
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 = defaultValue;
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).booleanValue();
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);
}
}
}