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