001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.configuration; 029 030import org.opencms.cache.CmsVfsMemoryObjectCache; 031import org.opencms.file.CmsFile; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.main.OpenCms; 037import org.opencms.module.CmsModule; 038import org.opencms.xml.CmsXmlEntityResolver; 039import org.opencms.xml.CmsXmlException; 040import org.opencms.xml.CmsXmlUtils; 041 042import java.util.ArrayList; 043import java.util.HashMap; 044import java.util.List; 045import java.util.Map; 046 047import org.apache.commons.logging.Log; 048 049import org.dom4j.Document; 050import org.dom4j.Element; 051import org.dom4j.Node; 052 053import com.google.common.primitives.Doubles; 054 055/** 056 * Class for accessing global 'weighted' configuration parameters defined in parameter files in the VFS. Used as a singleton. 057 * 058 * <p> 059 * Parameter files are XML configuration files that contain a list of named, string-valued configuration parameters, optionally with a numeric weight. The weight can be set 060 * individually for each parameter, or globally for a whole parameter file, but individual weights override parameter file weights. The schema for these is defined in org/opencm/configuration/paramfile.dtd. 061 * <p> 062 * To register a parameter file in OpenCms, its path must be listed as a value of the 'paramfile' module parameter for an installed module. The module parameter 063 * can be set on multiple modules, and may also contain multiple paths separated by commas. 064 * <p> 065 * When retrieving a value that is defined in multiple parameter files, the one with the highest weight wins. If there are multiple instances with the same weight, which one of them wins is implementation dependent. 066 */ 067public class CmsParameterStore { 068 069 /** 070 * An individual weighted parameter value, with a 'source' attribute for better debuggability. 071 */ 072 public static class WeightedValue { 073 074 /** The source where the value comes from. */ 075 private String m_source; 076 077 /** The actual value. */ 078 private String m_value; 079 080 /** The weight of the value. */ 081 private double m_weight; 082 083 /** 084 * Creates a new weighted value. 085 * 086 * @param value the actual value 087 * @param weight the weight 088 * @param source the source 089 */ 090 public WeightedValue(String value, double weight, String source) { 091 092 m_value = value; 093 m_source = source; 094 m_weight = weight; 095 } 096 097 /** 098 * Gets the source of the value (for debugging). 099 * 100 * @return the source of the value 101 */ 102 public String getSource() { 103 104 return m_source; 105 } 106 107 /** 108 * Gets the value. 109 * 110 * @return value 111 */ 112 public String getValue() { 113 114 return m_value; 115 } 116 117 /** 118 * Gets the weight of the value. 119 * 120 * @return the weight of the value 121 */ 122 public double getWeight() { 123 124 return m_weight; 125 } 126 } 127 128 /** XML attribute name. */ 129 public static final String A_NAME = "name"; 130 131 /** XML attribute name. */ 132 public static final String A_WEIGHT = "weight"; 133 134 /** Default weight, if not defined in parameter file. */ 135 public static final double DEFAULT_WEIGHT = 100.0; 136 137 /** XML node name. */ 138 public static final String N_PARAM = "param"; 139 140 /** Module parameter for registering parameter files. */ 141 public static final String PARAM_PARAMFILE = "paramfile"; 142 143 /** The global parameter store instance. */ 144 private static final CmsParameterStore INSTANCE = new CmsParameterStore(); 145 146 /** Logger instance for this class. */ 147 private static final Log LOG = CmsLog.getLog(CmsParameterStore.class); 148 149 /** 150 * Gets the global instance. 151 * 152 * @return the global instance 153 */ 154 public static CmsParameterStore getInstance() { 155 156 return INSTANCE; 157 } 158 159 /** 160 * Helper method for parsing a parameter file from a byte array. 161 * 162 * @param data the binary data for the parameter file 163 * @param source the source identifier 164 * @return the of parameters 165 * @throws CmsXmlException if something goes wrong 166 */ 167 public static Map<String, WeightedValue> parse(byte[] data, String source) throws CmsXmlException { 168 169 CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(null); 170 Document doc; 171 // don't want missing DTD reference to cause a hard error, so we just warn on validation 172 // errors and reparse without validation 173 try { 174 doc = CmsXmlUtils.unmarshalHelper(data, resolver, /*validate=*/true); 175 } catch (CmsXmlException e) { 176 LOG.warn(e.getLocalizedMessage(), e); 177 doc = CmsXmlUtils.unmarshalHelper(data, resolver, false); 178 } 179 return parse(doc.getRootElement(), source); 180 } 181 182 /** 183 * Helper method for parsing a parameter file from a VFS resource. 184 * 185 * @param cms the CmsObject 186 * @param path the path of the resource 187 * @return the map of parameters 188 * 189 * @throws CmsException if something goes wrong 190 */ 191 public static Map<String, WeightedValue> parse(CmsObject cms, String path) throws CmsException { 192 193 CmsFile file = cms.readFile(path, CmsResourceFilter.IGNORE_EXPIRATION); 194 return parse(file.getContents(), file.getRootPath()); 195 196 } 197 198 /** 199 * Parses a parameter file from an XML element. 200 * 201 * @param rootElem the root element of the XML 202 * @param source the source identifier 203 * 204 * @return the parameter map 205 */ 206 public static Map<String, WeightedValue> parse(Element rootElem, String source) { 207 208 double defaultWeight = DEFAULT_WEIGHT; 209 String defaultWeightStr = rootElem.attributeValue(A_WEIGHT); 210 Map<String, WeightedValue> result = new HashMap<>(); 211 212 if (defaultWeightStr != null) { 213 try { 214 defaultWeight = Double.parseDouble(defaultWeightStr); 215 } catch (NumberFormatException e) { 216 LOG.error(source + ":" + e.getLocalizedMessage(), e); 217 } 218 } 219 for (Node node : rootElem.selectNodes(N_PARAM)) { 220 Element paramElem = (Element)node; 221 String nameStr = paramElem.attributeValue(A_NAME); 222 String weightStr = paramElem.attributeValue(A_WEIGHT); 223 String content = paramElem.getText(); 224 if (nameStr == null) { 225 LOG.error("Missing name attribute in " + source); 226 continue; 227 } 228 double weight = defaultWeight; 229 if (weightStr != null) { 230 Double weightObj = Doubles.tryParse(weightStr); 231 if (weightObj != null) { 232 weight = weightObj.doubleValue(); 233 } 234 } 235 WeightedValue val = new WeightedValue(content, weight, source); 236 result.put(nameStr, val); 237 } 238 return result; 239 } 240 241 /** 242 * Gets the string value with the maximal weight for the parameter with the given key. 243 * 244 * @param cms the CMS context 245 * @param key the key 246 * 247 * @return the string value with maximal weight 248 */ 249 public String getValue(CmsObject cms, String key) { 250 251 WeightedValue val = getWeightedValue(cms, key); 252 return val == null ? null : val.getValue(); 253 } 254 255 /** 256 * Finds the value with the maximal weight for the given key. 257 * 258 * @param cms the CMS context 259 * @param key the parameter key 260 * 261 * @return the value with the maximal weight 262 */ 263 public WeightedValue getWeightedValue(CmsObject cms, String key) { 264 265 return getConfigurations(cms).stream().map(m -> m.get(key)).filter(val -> val != null).max( 266 (v1, v2) -> Double.compare(v1.getWeight(), v2.getWeight())).orElse(null); 267 } 268 269 /** 270 * Retrieves data for all registered parameter files. 271 * 272 * @param cms the CMS context 273 * @return the list of parameter maps from all registered files 274 */ 275 private List<Map<String, WeightedValue>> getConfigurations(CmsObject cms) { 276 277 List<CmsModule> modules = OpenCms.getModuleManager().getAllInstalledModules(); 278 List<Map<String, WeightedValue>> result = new ArrayList<>(); 279 for (CmsModule module : modules) { 280 String paramConfigsStr = module.getParameter(PARAM_PARAMFILE); 281 if (paramConfigsStr != null) { 282 String[] paths = paramConfigsStr.trim().split(" *, *"); 283 for (String path : paths) { 284 result.add(getConfigurationWithCache(cms, path)); 285 } 286 } 287 } 288 return result; 289 } 290 291 /** 292 * Gets the configuration for a VFS path, and uses a cache for the results. 293 * 294 * @param cms the CMS context 295 * @param path the path 296 * @return the parameter map 297 */ 298 @SuppressWarnings("unchecked") 299 private Map<String, WeightedValue> getConfigurationWithCache(CmsObject cms, String path) { 300 301 String rootPath = cms.getRequestContext().addSiteRoot(path); 302 Map<String, WeightedValue> result; 303 result = (Map<String, WeightedValue>)CmsVfsMemoryObjectCache.getVfsMemoryObjectCache().getCachedObject( 304 cms, 305 rootPath); 306 if (result == null) { 307 try { 308 result = parse(cms, path); 309 } catch (CmsException e) { 310 LOG.info(path + ": " + e.getLocalizedMessage(), e); 311 result = new HashMap<>(); 312 } 313 CmsVfsMemoryObjectCache.getVfsMemoryObjectCache().putCachedObject(cms, rootPath, result); 314 } 315 return result; 316 317 } 318 319}