001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (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 GmbH, 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.i18n.CmsEncoder; 031import org.opencms.main.CmsLog; 032import org.opencms.util.CmsFileUtil; 033import org.opencms.xml.CmsXmlEntityResolver; 034import org.opencms.xml.CmsXmlErrorHandler; 035 036import java.io.File; 037import java.io.FileOutputStream; 038import java.io.IOException; 039import java.io.OutputStream; 040import java.net.URL; 041import java.text.SimpleDateFormat; 042import java.util.ArrayList; 043import java.util.Date; 044import java.util.Iterator; 045import java.util.List; 046 047import org.apache.commons.digester.Digester; 048import org.apache.commons.logging.Log; 049 050import org.dom4j.Document; 051import org.dom4j.DocumentHelper; 052import org.dom4j.Element; 053import org.dom4j.dom.DOMDocumentType; 054import org.dom4j.io.OutputFormat; 055import org.dom4j.io.XMLWriter; 056import org.xml.sax.SAXException; 057 058/** 059 * Configuration manager for digesting the OpenCms XML configuration.<p> 060 * 061 * Reads the individual configuration class nodes first and creaes new 062 * instances of the "base" configuration classes.<p> 063 * 064 * @since 6.0.0 065 */ 066public class CmsConfigurationManager implements I_CmsXmlConfiguration { 067 068 /** The location of the OpenCms configuration DTD if the default prefix is the system ID. */ 069 public static final String DEFAULT_DTD_LOCATION = "org/opencms/configuration/"; 070 071 /** The default prefix for the OpenCms configuration DTD. */ 072 public static final String DEFAULT_DTD_PREFIX = "http://www.opencms.org/dtd/6.0/"; 073 074 /** The name of the default XML file for this configuration. */ 075 public static final String DEFAULT_XML_FILE_NAME = "opencms.xml"; 076 077 /** The name of the DTD file for this configuration. */ 078 public static final String DTD_FILE_NAME = "opencms-configuration.dtd"; 079 080 /** The "opencms" root node of the XML configuration. */ 081 public static final String N_ROOT = "opencms"; 082 083 /** Postfix for original configuration files. */ 084 public static final String POSTFIX_ORI = ".ori"; 085 086 /** The config node. */ 087 protected static final String N_CONFIG = "config"; 088 089 /** The configurations node. */ 090 protected static final String N_CONFIGURATION = "configuration"; 091 092 /** Date format for the backup file time prefix. */ 093 private static final SimpleDateFormat BACKUP_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_"); 094 095 /** The log object for this class. */ 096 private static final Log LOG = CmsLog.getLog(CmsConfigurationManager.class); 097 098 /** The number of days to keep old backups for. */ 099 private static final long MAX_BACKUP_DAYS = 15; 100 101 /** The folder where to store the backup files of the configuration. */ 102 private File m_backupFolder; 103 104 /** The base folder where the configuration files are located. */ 105 private File m_baseFolder; 106 107 /** The initialized configuration classes. */ 108 private List<I_CmsXmlConfiguration> m_configurations; 109 110 /** The digester for reading the XML configuration. */ 111 private Digester m_digester; 112 113 /** The configuration based on <code>opencms.properties</code>. */ 114 private CmsParameterConfiguration m_propertyConfiguration; 115 116 /** 117 * Creates a new OpenCms configuration manager.<p> 118 * 119 * @param baseFolder base folder where XML configurations to load are located 120 */ 121 public CmsConfigurationManager(String baseFolder) { 122 123 m_baseFolder = new File(baseFolder); 124 if (!m_baseFolder.exists()) { 125 if (LOG.isErrorEnabled()) { 126 LOG.error(Messages.get().getBundle().key( 127 Messages.LOG_INVALID_CONFIG_BASE_FOLDER_1, 128 m_baseFolder.getAbsolutePath())); 129 } 130 } 131 m_backupFolder = new File(m_baseFolder.getAbsolutePath() + File.separatorChar + "backup"); 132 if (!m_backupFolder.exists()) { 133 if (LOG.isDebugEnabled()) { 134 LOG.debug(Messages.get().getBundle().key( 135 Messages.LOG_CREATE_CONFIG_BKP_FOLDER_1, 136 m_backupFolder.getAbsolutePath())); 137 } 138 m_backupFolder.mkdirs(); 139 } 140 if (LOG.isDebugEnabled()) { 141 LOG.debug(Messages.get().getBundle().key(Messages.LOG_CONFIG_BASE_FOLDER_1, m_baseFolder.getAbsolutePath())); 142 LOG.debug(Messages.get().getBundle().key(Messages.LOG_CONFIG_BKP_FOLDER_1, m_backupFolder.getAbsolutePath())); 143 } 144 cacheDtdSystemId(this); 145 m_configurations = new ArrayList<I_CmsXmlConfiguration>(); 146 } 147 148 /** 149 * Adds a configuration object to the configuration manager.<p> 150 * 151 * @param configuration the configuration to add 152 */ 153 public void addConfiguration(I_CmsXmlConfiguration configuration) { 154 155 if (LOG.isDebugEnabled()) { 156 LOG.debug(Messages.get().getBundle().key(Messages.LOG_ADD_CONFIG_1, configuration)); 157 } 158 m_configurations.add(configuration); 159 cacheDtdSystemId(configuration); 160 } 161 162 /** 163 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String) 164 */ 165 public void addConfigurationParameter(String paramName, String paramValue) { 166 167 // noop, this configuration has no additional parameters 168 } 169 170 /** 171 * @see org.opencms.configuration.I_CmsXmlConfiguration#addXmlDigesterRules(org.apache.commons.digester.Digester) 172 */ 173 public void addXmlDigesterRules(Digester digester) { 174 175 // add rule for <configuration> node 176 digester.addObjectCreate( 177 "*/" + N_CONFIGURATION + "/" + N_CONFIG, 178 I_CmsXmlConfiguration.A_CLASS, 179 CmsConfigurationException.class); 180 digester.addSetNext("*/" + N_CONFIGURATION + "/" + N_CONFIG, "addConfiguration"); 181 } 182 183 /** 184 * @see org.opencms.configuration.I_CmsXmlConfiguration#generateXml(org.dom4j.Element) 185 */ 186 public Element generateXml(Element parent) { 187 188 // add the <configuration> node 189 Element configurationElement = parent.addElement(N_CONFIGURATION); 190 for (int i = 0; i < m_configurations.size(); i++) { 191 // append the individual configuration 192 I_CmsXmlConfiguration configuration = m_configurations.get(i); 193 configurationElement.addElement(N_CONFIG).addAttribute( 194 I_CmsXmlConfiguration.A_CLASS, 195 configuration.getClass().getName()); 196 } 197 return parent; 198 } 199 200 /** 201 * Creates the XML document build from the provided configuration.<p> 202 * 203 * @param configuration the configuration to build the XML for 204 * @return the XML document build from the provided configuration 205 */ 206 public Document generateXml(I_CmsXmlConfiguration configuration) { 207 208 // create a new document 209 Document result = DocumentHelper.createDocument(); 210 211 // set the document type 212 DOMDocumentType docType = new DOMDocumentType(); 213 docType.setElementName(N_ROOT); 214 docType.setSystemID(configuration.getDtdUrlPrefix() + configuration.getDtdFilename()); 215 result.setDocType(docType); 216 217 Element root = result.addElement(N_ROOT); 218 // start the XML generation 219 configuration.generateXml(root); 220 221 // return the resulting document 222 return result; 223 } 224 225 /** 226 * Returns the backup folder.<p> 227 * 228 * @return the backup folder 229 */ 230 public File getBackupFolder() { 231 232 return m_backupFolder; 233 } 234 235 /** 236 * Returns the properties read from <code>opencms.properties</code>.<p> 237 * 238 * @see #setConfiguration(CmsParameterConfiguration) 239 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration() 240 */ 241 public CmsParameterConfiguration getConfiguration() { 242 243 return m_propertyConfiguration; 244 } 245 246 /** 247 * Returns a specific configuration from the list of initialized configurations.<p> 248 * 249 * @param clazz the configuration class that should be returned 250 * @return the initialized configuration class instance, or <code>null</code> if this is not found 251 */ 252 public I_CmsXmlConfiguration getConfiguration(Class<?> clazz) { 253 254 for (int i = 0; i < m_configurations.size(); i++) { 255 I_CmsXmlConfiguration configuration = m_configurations.get(i); 256 if (clazz.equals(configuration.getClass())) { 257 return configuration; 258 } 259 } 260 return null; 261 } 262 263 /** 264 * Returns the list of all initialized configurations.<p> 265 * 266 * @return the list of all initialized configurations 267 */ 268 public List<I_CmsXmlConfiguration> getConfigurations() { 269 270 return m_configurations; 271 } 272 273 /** 274 * @see org.opencms.configuration.I_CmsXmlConfiguration#getDtdFilename() 275 */ 276 public String getDtdFilename() { 277 278 return DTD_FILE_NAME; 279 } 280 281 /** 282 * @see org.opencms.configuration.I_CmsXmlConfiguration#getDtdSystemLocation() 283 */ 284 public String getDtdSystemLocation() { 285 286 return DEFAULT_DTD_LOCATION; 287 } 288 289 /** 290 * @see org.opencms.configuration.I_CmsXmlConfiguration#getDtdUrlPrefix() 291 */ 292 public String getDtdUrlPrefix() { 293 294 return DEFAULT_DTD_PREFIX; 295 } 296 297 /** 298 * @see org.opencms.configuration.I_CmsXmlConfiguration#getXmlFileName() 299 */ 300 public String getXmlFileName() { 301 302 return DEFAULT_XML_FILE_NAME; 303 } 304 305 /** 306 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration() 307 */ 308 public void initConfiguration() { 309 310 // does not need to be initialized 311 if (LOG.isDebugEnabled()) { 312 LOG.debug(Messages.get().getBundle().key(Messages.LOG_INIT_CONFIGURATION_1, this)); 313 } 314 } 315 316 /** 317 * Loads the OpenCms configuration from the given XML file.<p> 318 * 319 * @throws SAXException in case of XML parse errors 320 * @throws IOException in case of file IO errors 321 */ 322 public void loadXmlConfiguration() throws SAXException, IOException { 323 324 URL baseUrl = m_baseFolder.toURI().toURL(); 325 if (LOG.isDebugEnabled()) { 326 LOG.debug(Messages.get().getBundle().key(Messages.LOG_BASE_URL_1, baseUrl)); 327 } 328 329 // first load the base configuration 330 loadXmlConfiguration(baseUrl, this); 331 332 // now iterate all sub-configurations 333 Iterator<I_CmsXmlConfiguration> i = m_configurations.iterator(); 334 while (i.hasNext()) { 335 loadXmlConfiguration(baseUrl, i.next()); 336 } 337 338 // remove the old backups 339 removeOldBackups(MAX_BACKUP_DAYS); 340 } 341 342 /** 343 * Sets the configuration read from the <code>opencms.properties</code>.<p> 344 * 345 * @param propertyConfiguration the configuration read from the <code>opencms.properties</code> 346 * 347 * @see #getConfiguration() 348 */ 349 public void setConfiguration(CmsParameterConfiguration propertyConfiguration) { 350 351 m_propertyConfiguration = propertyConfiguration; 352 } 353 354 /** 355 * Writes the XML configuration for the provided configuration instance.<p> 356 * 357 * @param clazz the configuration class to write the XML for 358 * @throws IOException in case of I/O errors while writing 359 * @throws CmsConfigurationException if the given class is not a valid configuration class 360 */ 361 public void writeConfiguration(Class<?> clazz) throws IOException, CmsConfigurationException { 362 363 I_CmsXmlConfiguration configuration = getConfiguration(clazz); 364 if (configuration == null) { 365 throw new CmsConfigurationException(Messages.get().container( 366 Messages.ERR_CONFIG_WITH_UNKNOWN_CLASS_1, 367 clazz.getName())); 368 } 369 370 // generate the file URL for the XML input 371 File file = new File(m_baseFolder, configuration.getXmlFileName()); 372 if (LOG.isDebugEnabled()) { 373 LOG.debug(Messages.get().getBundle().key(Messages.LOG_WRITE_CONFIG_XMLFILE_1, file.getAbsolutePath())); 374 } 375 376 // generate the XML document 377 Document config = generateXml(configuration); 378 379 // output the document 380 XMLWriter writer = null; 381 OutputFormat format = OutputFormat.createPrettyPrint(); 382 format.setIndentSize(4); 383 format.setTrimText(false); 384 format.setEncoding(CmsEncoder.ENCODING_UTF_8); 385 386 try { 387 OutputStream out = new FileOutputStream(file); 388 writer = new XMLWriter(out, format); 389 writer.write(config); 390 writer.flush(); 391 } finally { 392 if (writer != null) { 393 writer.close(); 394 } 395 } 396 397 if (LOG.isInfoEnabled()) { 398 LOG.info(Messages.get().getBundle().key( 399 Messages.LOG_WRITE_CONFIG_SUCCESS_2, 400 file.getAbsolutePath(), 401 configuration.getClass().getName())); 402 } 403 } 404 405 /** 406 * Creates a backup of the given XML configurations input file.<p> 407 * 408 * @param configuration the configuration for which the input file should be backed up 409 */ 410 private void backupXmlConfiguration(I_CmsXmlConfiguration configuration) { 411 412 String fromName = m_baseFolder.getAbsolutePath() + File.separatorChar + configuration.getXmlFileName(); 413 String toDatePrefix = BACKUP_DATE_FORMAT.format(new Date()); 414 String toName = m_backupFolder.getAbsolutePath() 415 + File.separatorChar 416 + toDatePrefix 417 + configuration.getXmlFileName(); 418 419 if (LOG.isDebugEnabled()) { 420 LOG.debug(Messages.get().getBundle().key(Messages.LOG_CREATE_CONFIG_BKP_2, fromName, toName)); 421 } 422 423 try { 424 CmsFileUtil.copy(fromName, toName); 425 } catch (IOException e) { 426 LOG.error(Messages.get().getBundle().key(Messages.LOG_CREATE_CONFIG_BKP_FAILURE_1, toName), e); 427 } 428 } 429 430 /** 431 * Adds a new DTD system id prefix mapping for internal resolution of external URLs.<p> 432 * 433 * @param configuration the configuration to add the mapping from 434 */ 435 private void cacheDtdSystemId(I_CmsXmlConfiguration configuration) { 436 437 if (configuration.getDtdSystemLocation() != null) { 438 try { 439 String file = CmsFileUtil.readFile( 440 configuration.getDtdSystemLocation() + configuration.getDtdFilename(), 441 CmsEncoder.ENCODING_UTF_8); 442 CmsXmlEntityResolver.cacheSystemId( 443 configuration.getDtdUrlPrefix() + configuration.getDtdFilename(), 444 file.getBytes(CmsEncoder.ENCODING_UTF_8)); 445 if (LOG.isDebugEnabled()) { 446 LOG.debug(Messages.get().getBundle().key( 447 Messages.LOG_CACHE_DTD_SYSTEM_ID_1, 448 configuration.getDtdUrlPrefix() 449 + configuration.getDtdFilename() 450 + " --> " 451 + configuration.getDtdSystemLocation() 452 + configuration.getDtdFilename())); 453 } 454 } catch (IOException e) { 455 LOG.error( 456 Messages.get().getBundle().key( 457 Messages.LOG_CACHE_DTD_SYSTEM_ID_FAILURE_1, 458 configuration.getDtdSystemLocation() + configuration.getDtdFilename()), 459 e); 460 } 461 } 462 } 463 464 /** 465 * Loads the OpenCms configuration from the given XML URL.<p> 466 * 467 * @param url the base URL of the XML configuration to load 468 * @param configuration the configuration to load 469 * @throws SAXException in case of XML parse errors 470 * @throws IOException in case of file IO errors 471 */ 472 private void loadXmlConfiguration(URL url, I_CmsXmlConfiguration configuration) throws SAXException, IOException { 473 474 // generate the file URL for the XML input 475 URL fileUrl = new URL(url, configuration.getXmlFileName()); 476 if (LOG.isDebugEnabled()) { 477 LOG.debug(Messages.get().getBundle().key(Messages.LOG_LOAD_CONFIG_XMLFILE_1, fileUrl)); 478 } 479 480 // create a backup of the configuration 481 backupXmlConfiguration(configuration); 482 483 // instantiate Digester and enable XML validation 484 m_digester = new Digester(); 485 m_digester.setUseContextClassLoader(true); 486 m_digester.setValidating(true); 487 m_digester.setEntityResolver(new CmsXmlEntityResolver(null)); 488 m_digester.setRuleNamespaceURI(null); 489 m_digester.setErrorHandler(new CmsXmlErrorHandler(fileUrl.getFile())); 490 491 // add this class to the Digester 492 m_digester.push(configuration); 493 494 configuration.addXmlDigesterRules(m_digester); 495 496 // start the parsing process 497 m_digester.parse(fileUrl.openStream()); 498 } 499 500 /** 501 * Removes all backups that are older then the given number of days.<p> 502 * 503 * @param daysToKeep the days to keep the backups for 504 */ 505 private void removeOldBackups(long daysToKeep) { 506 507 long maxAge = (System.currentTimeMillis() - (daysToKeep * 24 * 60 * 60 * 1000)); 508 File[] files = m_backupFolder.listFiles(); 509 for (int i = 0; i < files.length; i++) { 510 File file = files[i]; 511 long lastMod = file.lastModified(); 512 if ((lastMod < maxAge) & (!file.getAbsolutePath().endsWith(CmsConfigurationManager.POSTFIX_ORI))) { 513 file.delete(); 514 if (LOG.isDebugEnabled()) { 515 LOG.debug(Messages.get().getBundle().key(Messages.LOG_REMOVE_CONFIG_FILE_1, file.getAbsolutePath())); 516 } 517 } 518 } 519 } 520}