All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.configuration.AbstractFileConfiguration Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.apache.commons.configuration;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
import org.apache.commons.configuration.reloading.ReloadingStrategy;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;

/**
 * 

Partial implementation of the {@code FileConfiguration} interface. * Developers of file based configuration may want to extend this class, * the two methods left to implement are {@link FileConfiguration#load(Reader)} * and {@link FileConfiguration#save(Writer)}.

*

This base class already implements a couple of ways to specify the location * of the file this configuration is based on. The following possibilities * exist: *

  • URLs: With the method {@code setURL()} a full URL to the * configuration source can be specified. This is the most flexible way. Note * that the {@code save()} methods support only file: URLs.
  • *
  • Files: The {@code setFile()} method allows to specify the * configuration source as a file. This can be either a relative or an * absolute file. In the former case the file is resolved based on the current * directory.
  • *
  • As file paths in string form: With the {@code setPath()} method a * full path to a configuration file can be provided as a string.
  • *
  • Separated as base path and file name: This is the native form in which * the location is stored. The base path is a string defining either a local * directory or a URL. It can be set using the {@code setBasePath()} * method. The file name, non surprisingly, defines the name of the configuration * file.

*

The configuration source to be loaded can be specified using one of the * methods described above. Then the parameterless {@code load()} method can be * called. Alternatively, one of the {@code load()} methods can be used which is * passed the source directly. These methods typically do not change the * internally stored file; however, if the configuration is not yet associated * with a configuration source, the first call to one of the {@code load()} * methods sets the base path and the source URL. This fact has to be taken * into account when calling {@code load()} multiple times with different file * paths.

*

Note that the {@code load()} methods do not wipe out the configuration's * content before the new configuration file is loaded. Thus it is very easy to * construct a union configuration by simply loading multiple configuration * files, e.g.

*

 * config.load(configFile1);
 * config.load(configFile2);
 * 

*

After executing this code fragment, the resulting configuration will * contain both the properties of configFile1 and configFile2. On the other * hand, if the current configuration file is to be reloaded, {@code clear()} * should be called first. Otherwise the properties are doubled. This behavior * is analogous to the behavior of the {@code load(InputStream)} method * in {@code java.util.Properties}.

* * @author Emmanuel Bourg * @version $Id: AbstractFileConfiguration.java 1234118 2012-01-20 20:36:04Z oheger $ * @since 1.0-rc2 */ public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration, FileSystemBased { /** Constant for the configuration reload event.*/ public static final int EVENT_RELOAD = 20; /** Constant fro the configuration changed event. */ public static final int EVENT_CONFIG_CHANGED = 21; /** The root of the file scheme */ private static final String FILE_SCHEME = "file:"; /** Stores the file name.*/ protected String fileName; /** Stores the base path.*/ protected String basePath; /** The auto save flag.*/ protected boolean autoSave; /** Holds a reference to the reloading strategy.*/ protected ReloadingStrategy strategy; /** A lock object for protecting reload operations.*/ protected Object reloadLock = new Lock("AbstractFileConfiguration"); /** Stores the encoding of the configuration file.*/ private String encoding; /** Stores the URL from which the configuration file was loaded.*/ private URL sourceURL; /** A counter that prohibits reloading.*/ private int noReload; /** The FileSystem being used for this Configuration */ private FileSystem fileSystem = FileSystem.getDefaultFileSystem(); /** * Default constructor * * @since 1.1 */ public AbstractFileConfiguration() { initReloadingStrategy(); setLogger(LogFactory.getLog(getClass())); addErrorLogListener(); } /** * Creates and loads the configuration from the specified file. The passed * in string must be a valid file name, either absolute or relativ. * * @param fileName The name of the file to load. * * @throws ConfigurationException Error while loading the file * @since 1.1 */ public AbstractFileConfiguration(String fileName) throws ConfigurationException { this(); // store the file name setFileName(fileName); // load the file load(); } /** * Creates and loads the configuration from the specified file. * * @param file The file to load. * @throws ConfigurationException Error while loading the file * @since 1.1 */ public AbstractFileConfiguration(File file) throws ConfigurationException { this(); // set the file and update the url, the base path and the file name setFile(file); // load the file if (file.exists()) { load(); } } /** * Creates and loads the configuration from the specified URL. * * @param url The location of the file to load. * @throws ConfigurationException Error while loading the file * @since 1.1 */ public AbstractFileConfiguration(URL url) throws ConfigurationException { this(); // set the URL and update the base path and the file name setURL(url); // load the file load(); } public void setFileSystem(FileSystem fileSystem) { if (fileSystem == null) { throw new NullPointerException("A valid FileSystem must be specified"); } this.fileSystem = fileSystem; } public void resetFileSystem() { this.fileSystem = FileSystem.getDefaultFileSystem(); } public FileSystem getFileSystem() { return this.fileSystem; } public Object getReloadLock() { return reloadLock; } /** * Load the configuration from the underlying location. * * @throws ConfigurationException if loading of the configuration fails */ public void load() throws ConfigurationException { if (sourceURL != null) { load(sourceURL); } else { load(getFileName()); } } /** * Locate the specified file and load the configuration. If the configuration is * already associated with a source, the current source is not changed. * Otherwise (i.e. this is the first load operation), the source URL and * the base path are set now based on the source to be loaded. * * @param fileName the name of the file to be loaded * @throws ConfigurationException if an error occurs */ public void load(String fileName) throws ConfigurationException { try { URL url = ConfigurationUtils.locate(this.fileSystem, basePath, fileName); if (url == null) { throw new ConfigurationException("Cannot locate configuration source " + fileName); } load(url); } catch (ConfigurationException e) { throw e; } catch (Exception e) { throw new ConfigurationException("Unable to load the configuration file " + fileName, e); } } /** * Load the configuration from the specified file. If the configuration is * already associated with a source, the current source is not changed. * Otherwise (i.e. this is the first load operation), the source URL and * the base path are set now based on the source to be loaded. * * @param file the file to load * @throws ConfigurationException if an error occurs */ public void load(File file) throws ConfigurationException { try { load(ConfigurationUtils.toURL(file)); } catch (ConfigurationException e) { throw e; } catch (Exception e) { throw new ConfigurationException("Unable to load the configuration file " + file, e); } } /** * Load the configuration from the specified URL. If the configuration is * already associated with a source, the current source is not changed. * Otherwise (i.e. this is the first load operation), the source URL and * the base path are set now based on the source to be loaded. * * @param url the URL of the file to be loaded * @throws ConfigurationException if an error occurs */ public void load(URL url) throws ConfigurationException { if (sourceURL == null) { if (StringUtils.isEmpty(getBasePath())) { // ensure that we have a valid base path setBasePath(url.toString()); } sourceURL = url; } InputStream in = null; try { in = fileSystem.getInputStream(url); load(in); } catch (ConfigurationException e) { throw e; } catch (Exception e) { throw new ConfigurationException("Unable to load the configuration from the URL " + url, e); } finally { // close the input stream try { if (in != null) { in.close(); } } catch (IOException e) { getLogger().warn("Could not close input stream", e); } } } /** * Load the configuration from the specified stream, using the encoding * returned by {@link #getEncoding()}. * * @param in the input stream * * @throws ConfigurationException if an error occurs during the load operation */ public void load(InputStream in) throws ConfigurationException { load(in, getEncoding()); } /** * Load the configuration from the specified stream, using the specified * encoding. If the encoding is null the default encoding is used. * * @param in the input stream * @param encoding the encoding used. {@code null} to use the default encoding * * @throws ConfigurationException if an error occurs during the load operation */ public void load(InputStream in, String encoding) throws ConfigurationException { Reader reader = null; if (encoding != null) { try { reader = new InputStreamReader(in, encoding); } catch (UnsupportedEncodingException e) { throw new ConfigurationException( "The requested encoding is not supported, try the default encoding.", e); } } if (reader == null) { reader = new InputStreamReader(in); } load(reader); } /** * Save the configuration. Before this method can be called a valid file * name must have been set. * * @throws ConfigurationException if an error occurs or no file name has * been set yet */ public void save() throws ConfigurationException { if (getFileName() == null) { throw new ConfigurationException("No file name has been set!"); } if (sourceURL != null) { save(sourceURL); } else { save(fileName); } strategy.init(); } /** * Save the configuration to the specified file. This doesn't change the * source of the configuration, use setFileName() if you need it. * * @param fileName the file name * * @throws ConfigurationException if an error occurs during the save operation */ public void save(String fileName) throws ConfigurationException { try { URL url = this.fileSystem.getURL(basePath, fileName); if (url == null) { throw new ConfigurationException("Cannot locate configuration source " + fileName); } save(url); /*File file = ConfigurationUtils.getFile(basePath, fileName); if (file == null) { throw new ConfigurationException("Invalid file name for save: " + fileName); } save(file); */ } catch (ConfigurationException e) { throw e; } catch (Exception e) { throw new ConfigurationException("Unable to save the configuration to the file " + fileName, e); } } /** * Save the configuration to the specified URL. * This doesn't change the source of the configuration, use setURL() * if you need it. * * @param url the URL * * @throws ConfigurationException if an error occurs during the save operation */ public void save(URL url) throws ConfigurationException { OutputStream out = null; try { out = fileSystem.getOutputStream(url); save(out); if (out instanceof VerifiableOutputStream) { ((VerifiableOutputStream) out).verify(); } } catch (IOException e) { throw new ConfigurationException("Could not save to URL " + url, e); } finally { closeSilent(out); } } /** * Save the configuration to the specified file. The file is created * automatically if it doesn't exist. This doesn't change the source * of the configuration, use {@link #setFile} if you need it. * * @param file the target file * * @throws ConfigurationException if an error occurs during the save operation */ public void save(File file) throws ConfigurationException { OutputStream out = null; try { out = fileSystem.getOutputStream(file); save(out); } finally { closeSilent(out); } } /** * Save the configuration to the specified stream, using the encoding * returned by {@link #getEncoding()}. * * @param out the output stream * * @throws ConfigurationException if an error occurs during the save operation */ public void save(OutputStream out) throws ConfigurationException { save(out, getEncoding()); } /** * Save the configuration to the specified stream, using the specified * encoding. If the encoding is null the default encoding is used. * * @param out the output stream * @param encoding the encoding to use * @throws ConfigurationException if an error occurs during the save operation */ public void save(OutputStream out, String encoding) throws ConfigurationException { Writer writer = null; if (encoding != null) { try { writer = new OutputStreamWriter(out, encoding); } catch (UnsupportedEncodingException e) { throw new ConfigurationException( "The requested encoding is not supported, try the default encoding.", e); } } if (writer == null) { writer = new OutputStreamWriter(out); } save(writer); } /** * Return the name of the file. * * @return the file name */ public String getFileName() { return fileName; } /** * Set the name of the file. The passed in file name can contain a * relative path. * It must be used when referring files with relative paths from classpath. * Use {@link AbstractFileConfiguration#setPath(String) * setPath()} to set a full qualified file name. * * @param fileName the name of the file */ public void setFileName(String fileName) { if (fileName != null && fileName.startsWith(FILE_SCHEME) && !fileName.startsWith("file://")) { fileName = "file://" + fileName.substring(FILE_SCHEME.length()); } sourceURL = null; this.fileName = fileName; getLogger().debug("FileName set to " + fileName); } /** * Return the base path. * * @return the base path * @see FileConfiguration#getBasePath() */ public String getBasePath() { return basePath; } /** * Sets the base path. The base path is typically either a path to a * directory or a URL. Together with the value passed to the * {@code setFileName()} method it defines the location of the * configuration file to be loaded. The strategies for locating the file are * quite tolerant. For instance if the file name is already an absolute path * or a fully defined URL, the base path will be ignored. The base path can * also be a URL, in which case the file name is interpreted in this URL's * context. Because the base path is used by some of the derived classes for * resolving relative file names it should contain a meaningful value. If * other methods are used for determining the location of the configuration * file (e.g. {@code setFile()} or {@code setURL()}), the * base path is automatically set. * * @param basePath the base path. */ public void setBasePath(String basePath) { if (basePath != null && basePath.startsWith(FILE_SCHEME) && !basePath.startsWith("file://")) { basePath = "file://" + basePath.substring(FILE_SCHEME.length()); } sourceURL = null; this.basePath = basePath; getLogger().debug("Base path set to " + basePath); } /** * Return the file where the configuration is stored. If the base path is a * URL with a protocol different than "file", or the configuration * file is within a compressed archive, the return value * will not point to a valid file object. * * @return the file where the configuration is stored; this can be null */ public File getFile() { if (getFileName() == null && sourceURL == null) { return null; } else if (sourceURL != null) { return ConfigurationUtils.fileFromURL(sourceURL); } else { return ConfigurationUtils.getFile(getBasePath(), getFileName()); } } /** * Set the file where the configuration is stored. The passed in file is * made absolute if it is not yet. Then the file's path component becomes * the base path and its name component becomes the file name. * * @param file the file where the configuration is stored */ public void setFile(File file) { sourceURL = null; setFileName(file.getName()); setBasePath((file.getParentFile() != null) ? file.getParentFile() .getAbsolutePath() : null); } /** * Returns the full path to the file this configuration is based on. The * return value is a valid File path only if this configuration is based on * a file on the local disk. * If the configuration was loaded from a packed archive the returned value * is the string form of the URL from which the configuration was loaded. * * @return the full path to the configuration file */ public String getPath() { return fileSystem.getPath(getFile(), sourceURL, getBasePath(), getFileName()); } /** * Sets the location of this configuration as a full or relative path name. * The passed in path should represent a valid file name on the file system. * It must not be used to specify relative paths for files that exist * in classpath, either plain file system or compressed archive, * because this method expands any relative path to an absolute one which * may end in an invalid absolute path for classpath references. * * @param path the full path name of the configuration file */ public void setPath(String path) { setFile(new File(path)); } URL getSourceURL() { return sourceURL; } /** * Return the URL where the configuration is stored. * * @return the configuration's location as URL */ public URL getURL() { return (sourceURL != null) ? sourceURL : ConfigurationUtils.locate(this.fileSystem, getBasePath(), getFileName()); } /** * Set the location of this configuration as a URL. For loading this can be * an arbitrary URL with a supported protocol. If the configuration is to * be saved, too, a URL with the "file" protocol should be * provided. * * @param url the location of this configuration as URL */ public void setURL(URL url) { setBasePath(ConfigurationUtils.getBasePath(url)); setFileName(ConfigurationUtils.getFileName(url)); sourceURL = url; getLogger().debug("URL set to " + url); } public void setAutoSave(boolean autoSave) { this.autoSave = autoSave; } public boolean isAutoSave() { return autoSave; } /** * Save the configuration if the automatic persistence is enabled * and if a file is specified. */ protected void possiblySave() { if (autoSave && fileName != null) { try { save(); } catch (ConfigurationException e) { throw new ConfigurationRuntimeException("Failed to auto-save", e); } } } /** * Adds a new property to this configuration. This implementation checks if * the auto save mode is enabled and saves the configuration if necessary. * * @param key the key of the new property * @param value the value */ @Override public void addProperty(String key, Object value) { synchronized (reloadLock) { super.addProperty(key, value); possiblySave(); } } /** * Sets a new value for the specified property. This implementation checks * if the auto save mode is enabled and saves the configuration if * necessary. * * @param key the key of the affected property * @param value the value */ @Override public void setProperty(String key, Object value) { synchronized (reloadLock) { super.setProperty(key, value); possiblySave(); } } @Override public void clearProperty(String key) { synchronized (reloadLock) { super.clearProperty(key); possiblySave(); } } public ReloadingStrategy getReloadingStrategy() { return strategy; } public void setReloadingStrategy(ReloadingStrategy strategy) { this.strategy = strategy; strategy.setConfiguration(this); strategy.init(); } /** * Performs a reload operation if necessary. This method is called on each * access of this configuration. It asks the associated reloading strategy * whether a reload should be performed. If this is the case, the * configuration is cleared and loaded again from its source. If this * operation causes an exception, the registered error listeners will be * notified. The error event passed to the listeners is of type * {@code EVENT_RELOAD} and contains the exception that caused the * event. */ public void reload() { reload(false); } public boolean reload(boolean checkReload) { synchronized (reloadLock) { if (noReload == 0) { try { enterNoReload(); // avoid reentrant calls if (strategy.reloadingRequired()) { if (getLogger().isInfoEnabled()) { getLogger().info("Reloading configuration. URL is " + getURL()); } refresh(); // notify the strategy strategy.reloadingPerformed(); } } catch (Exception e) { fireError(EVENT_RELOAD, null, null, e); // todo rollback the changes if the file can't be reloaded if (checkReload) { return false; } } finally { exitNoReload(); } } } return true; } /** * Reloads the associated configuration file. This method first clears the * content of this configuration, then the associated configuration file is * loaded again. Updates on this configuration which have not yet been saved * are lost. Calling this method is like invoking {@code reload()} * without checking the reloading strategy. * * @throws ConfigurationException if an error occurs * @since 1.7 */ public void refresh() throws ConfigurationException { fireEvent(EVENT_RELOAD, null, getURL(), true); setDetailEvents(false); boolean autoSaveBak = this.isAutoSave(); // save the current state this.setAutoSave(false); // deactivate autoSave to prevent information loss try { clear(); load(); } finally { this.setAutoSave(autoSaveBak); // set autoSave to previous value setDetailEvents(true); } fireEvent(EVENT_RELOAD, null, getURL(), false); } /** * Send notification that the configuration has changed. */ public void configurationChanged() { fireEvent(EVENT_CONFIG_CHANGED, null, getURL(), true); } /** * Enters the "No reloading mode". As long as this mode is active * no reloading will be performed. This is necessary for some * implementations of {@code save()} in derived classes, which may * cause a reload while accessing the properties to save. This may cause the * whole configuration to be erased. To avoid this, this method can be * called first. After a call to this method there always must be a * corresponding call of {@link #exitNoReload()} later! (If * necessary, {@code finally} blocks must be used to ensure this. */ protected void enterNoReload() { synchronized (reloadLock) { noReload++; } } /** * Leaves the "No reloading mode". * * @see #enterNoReload() */ protected void exitNoReload() { synchronized (reloadLock) { if (noReload > 0) // paranoia check { noReload--; } } } /** * Sends an event to all registered listeners. This implementation ensures * that no reloads are performed while the listeners are invoked. So * infinite loops can be avoided that can be caused by event listeners * accessing the configuration's properties when they are invoked. * * @param type the event type * @param propName the name of the property * @param propValue the value of the property * @param before the before update flag */ @Override protected void fireEvent(int type, String propName, Object propValue, boolean before) { enterNoReload(); try { super.fireEvent(type, propName, propValue, before); } finally { exitNoReload(); } } @Override public Object getProperty(String key) { synchronized (reloadLock) { reload(); return super.getProperty(key); } } @Override public boolean isEmpty() { reload(); synchronized (reloadLock) { return super.isEmpty(); } } @Override public boolean containsKey(String key) { reload(); synchronized (reloadLock) { return super.containsKey(key); } } /** * Returns an {@code Iterator} with the keys contained in this * configuration. This implementation performs a reload if necessary before * obtaining the keys. The {@code Iterator} returned by this method * points to a snapshot taken when this method was called. Later changes at * the set of keys (including those caused by a reload) won't be visible. * This is because a reload can happen at any time during iteration, and it * is impossible to determine how this reload affects the current iteration. * When using the iterator a client has to be aware that changes of the * configuration are possible at any time. For instance, if after a reload * operation some keys are no longer present, the iterator will still return * those keys because they were found when it was created. * * @return an {@code Iterator} with the keys of this configuration */ @Override public Iterator getKeys() { reload(); List keyList = new LinkedList(); enterNoReload(); try { for (Iterator it = super.getKeys(); it.hasNext();) { keyList.add(it.next()); } return keyList.iterator(); } finally { exitNoReload(); } } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { this.encoding = encoding; } /** * Creates a copy of this configuration. The new configuration object will * contain the same properties as the original, but it will lose any * connection to a source file (if one exists); this includes setting the * source URL, base path, and file name to null. This is done to * avoid race conditions if both the original and the copy are modified and * then saved. * * @return the copy * @since 1.3 */ @Override public Object clone() { AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone(); copy.setBasePath(null); copy.setFileName(null); copy.initReloadingStrategy(); return copy; } /** * Helper method for initializing the reloading strategy. */ private void initReloadingStrategy() { setReloadingStrategy(new InvariantReloadingStrategy()); } /** * A helper method for closing an output stream. Occurring exceptions will * be ignored. * * @param out the output stream to be closed (may be null) * @since 1.5 */ protected void closeSilent(OutputStream out) { try { if (out != null) { out.close(); } } catch (IOException e) { getLogger().warn("Could not close output stream", e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy