/*
* Config
*
* Copyright (C) 2010 Jaroslav Merxbauer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package notwa.common;
import java.io.File;
import java.io.FileWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
/**
* Config is the utility class for acquiring the data from configuration file.
* Config is written and suposed to be used as a singleton to provide sole
* instance operating over the single XML file stored in the hardcoded path in
* the system.
* <p>Even thought there is no motivation to have something written to the config
* file, so there is no actual motivation to have this class as singleton,
* it is discouraged to change this behavior as the need to persist any kind
* of information here could arise in future. This would then may cause the
* trouble.</p>
*
* @author Jaroslav Merxbauer
* @version %I% %G%
*/
public class Config {
private static Config instance;
private Document dom;
private ApplicationSettings as = new ApplicationSettings();
private Set<NotwaConnectionInfo> connections = new TreeSet<NotwaConnectionInfo>();
private static String configFilePath = "./user.config";
private final XPath xpath = XPathFactory.newInstance().newXPath();
private final Logger log;
/**
* Hidden constructor to prevent instancing the class from outside world.
*/
protected Config() {
log = Logger.getLogger(this.getClass());
File configFile = new File(configFilePath);
try {
this.parse(configFile);
log.info("Successfully parsed the config file " + configFilePath);
} catch (Exception ex) {
log.error("Error occured while parsing the config file!", ex);
}
}
/**
* Intended to use only during the application startup to specify the config
* file path.
*
* @param path - The actual path to the config file.
*/
public static void setConfigFilePath(String path) {
configFilePath = path;
}
/**
* Gets the sole instance of the Config class which is maintained as the
* static singleton.
*
* @return The singleton instance
*/
public static Config getInstance() {
if (instance == null)
instance = new Config();
return instance;
}
/**
* Gets all connection information parsed from the configuration file.
* Every connection information is wrapped into the <code>ConnectionInfo</code>
* instance and all these instances are kept inside single <code>Collection</code>.
*
* @return <code>Collection</code> of all <code>ConnectionInfo</code>.
*/
public Collection<NotwaConnectionInfo> getConnecionStrings() {
return connections;
}
/**
* Gets the <code>ApplicationSettings</code> parsed from the configuration file.
*
* <note>If you change them and want you to save them to the physical file
* during next {@link #save()} call, use
* {@link #setApplicationsSettings(notwa.common.ApplicationSettings) </note>
* to update them.
*
* @return The parsed <code>ApplicationSettings</code>.
*/
public ApplicationSettings getApplicationSettings() {
return as;
}
public void setApplicationsSettings(ApplicationSettings as) {
this.as = as;
}
public void setConnectionInfo(NotwaConnectionInfo nci) {
connections.add(nci);
}
/**
* Parse the XML configuration document utilizing the DOM {@link Document}.
*
* @param configFile The file claimed to be the configuration XML file.
* @throws Exception If the config file does not exist.
*/
private void parse(File configFile) throws Exception {
if (configFile.exists()) {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
this.dom = db.parse(configFile);
for (Node node : getChildNodesByPath(dom.getDocumentElement(), "./*")) {
if (node.getNodeName().equals("ApplicationSettings")) {
as.parseFromConfig(node);
}
else if (node.getNodeName().equals("AvailableDatabases")) {
for (int i=0; i<node.getChildNodes().getLength(); i++) {
Node subNode = node.getChildNodes().item(i);
if (subNode.getNodeName().equals("Database")) {
NotwaConnectionInfo nci = new NotwaConnectionInfo();
nci.parseFromConfig(subNode);
connections.add(nci);
}
}
}
}
} else {
throw new Exception("Config file does not exists!");
}
}
/**
* Saves the current state of <code>Config</code> to the physical file.
*/
public void save() {
try {
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
Document doc = docBuilder.newDocument();
/*
* NotwaConfiguration
*/
Element notwaConfigurationElement = doc.createElement("NotwaConfiguration");
doc.appendChild(notwaConfigurationElement);
/*
* ApplicationSettings
*/
Element appSettings = doc.createElement("ApplicationSettings");
notwaConfigurationElement.appendChild(appSettings);
/*
* ApplicationSettings - childs
*/
Element skin = doc.createElement("Skin");
skin.setAttribute("name", as.getSkin());
Element rememberLogin = doc.createElement("RememberLogin");
rememberLogin.setAttribute("remember", as.isRememberNotwaLogin() ? "1" : "0");
appSettings.appendChild(skin);
appSettings.appendChild(rememberLogin);
/*
* AvailableDatabases
*/
Element availableDatabases = doc.createElement("AvailableDatabases");
notwaConfigurationElement.appendChild(availableDatabases);
/*
* AvailableDatabases - childs
*/
for (NotwaConnectionInfo nci : connections) {
Element database = doc.createElement("Database");
database.setAttribute("label", nci.getLabel());
database.setAttribute("dbname", nci.getDbname());
database.setAttribute("host", nci.getHost());
database.setAttribute("port", nci.getPort());
database.setAttribute("user", nci.getUser());
database.setAttribute("password", nci.getPassword());
database.setAttribute("notwaLogin", nci.getNotwaUserName());
availableDatabases.appendChild(database);
}
//set up a transformer
TransformerFactory transfac = TransformerFactory.newInstance();
Transformer trans = transfac.newTransformer();
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty(OutputKeys.METHOD, "xml");
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","3");
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
DOMSource source = new DOMSource(doc);
trans.transform(source, result);
File configFile = new File(configFilePath);
if (configFile.delete() || !configFile.exists()) {
FileWriter fw = new FileWriter(configFile, true);
fw.append(sw.toString());
fw.close();
}
else {
throw new Exception("Config file cannot be deleted!");
}
} catch (Exception ex) {
log.error("Error occured while saving the config file!", ex);
}
}
/**
* Finds all the child elements of given parent matching the xpath provided.
*
* @param parent The Node where to start.
* @param path The path to be evaluated.
* @return The <code>List</code> of all the child nodes matching the given xpath.
*/
private List<Node> getChildNodesByPath(Node parent, String path) {
List<Node> childs = new ArrayList<Node>();
try {
NodeList rawChilds = (NodeList) xpath.evaluate(path, parent, XPathConstants.NODESET);
for (int i = 0; i < rawChilds.getLength(); i++) {
childs.add(rawChilds.item(i));
}
} catch (XPathExpressionException xpeex) {
log.error("Error occured while executing an XPath expression!", xpeex);
}
return childs;
}
/**
* This functions is needed when one is unsure if config has been modified
*/
public void reloadConfig() {
connections.clear();
File configFile = new File(configFilePath);
try {
this.parse(configFile);
} catch (Exception ex) {
log.error("Error occured while reloading the config!", ex);
}
}
}