/*
* Jopr Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* 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 and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.plugins.jbossas.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.ConfigurationUpdateStatus;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.resource.CreateResourceStatus;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.inventory.CreateResourceReport;
import org.rhq.core.pluginapi.util.SelectiveSkippingEntityResolver;
/**
* Loads and edits Datasources in the datasource XML format.
*
* @author Greg Hinkle
* @author Mark Spritzler
*/
public class DatasourceConfigurationEditor {
public static final String NO_TX_TYPE = "no-tx-datasource";
public static final String LOCAL_TX_TYPE = "local-tx-datasource";
public static final String XA_TX_TYPE = "xa-datasource";
// These lists don't include the one multi-valued prop for each type "connection-property" / "xa-datasource-property"
public static final String[] COMMON_PROPS = { "jndi-name", "connection-url", "user-name", "password",
"min-pool-size", "max-pool-size", "transaction-isolation", "blocking-timeout-millis", "idle-timeout-minutes",
"prepared-statement-cache-size", "valid-connection-checker-class-name", "use-java-context", "security-domain",
"new-connection-sql", "exception-sorter-class-name", "check-valid-connection-sql", "track-statements",
"no-tx-separate-pools", "application-managed-security", "security-domain-and-application" };
public static final String[] NON_XA_PROPS = { "driver-class" };
public static final String[] XA_PROPS = { "xa-datasource-class", "track-connection-by-tx",
"isSameRM-override-value" };
/* All props with customizations
* jndi-name driver-class <!-- xa-datasource-class --> connection-url user-name password min-pool-size
* max-pool-size transaction-isolation blocking-timeout-millis idle-timeout-minutes prepared-statement-cache-size
* valid-connection-checker-class-name use-java-context security-domain new-connection-sql
* exception-sorter-class-name check-valid-connection-sql track-statements connection-property <!--
* xa-datasource-property --> no-tx-separate-pools application-managed-security security-domain-and-application
* track-connection-by-tx" <!-- XA only --> isSameRM-override-value" <!-- XA only -->
*/
private static Log log = LogFactory.getLog(DatasourceConfigurationEditor.class);
private static final String CONNECTION_PROPERTY = "connection-property";
private static final String XA_DATASOURCE_PROPERTY = "xa-datasource-property";
public static Configuration loadDatasource(File file, String name) {
/*
* <local-tx-datasource> <jndi-name>RHQDS</jndi-name>
* <connection-url>${rhq.server.database.connection-url}</connection-url>
* <driver-class>${rhq.server.database.driver-class}</driver-class>
* <user-name>${rhq.server.database.user-name}</user-name>
* <password>${rhq.server.database.password}</password>
*
* <!-- You can include connection properties that will get passed in the
* DriverManager.getConnection(props) call; look at your Driver docs to see what these might be. -->
* <connection-property name="char.encoding">UTF-8</connection-property> <!-- Tells an oracle
* 10.2.0.2 driver to properly implement clobs. --> <connection-property
* name="SetBigStringTryClob">true</connection-property>
*
* <transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
* <min-pool-size>25</min-pool-size> <max-pool-size>100</max-pool-size>
* <blocking-timeout-millis>5000</blocking-timeout-millis> <idle-timeout-minutes>15</idle-timeout-minutes>
* <prepared-statement-cache-size>75</prepared-statement-cache-size>
*
* <type-mapping>${rhq.server.database.type-mapping}</type-mapping>
*
* </local-tx-datasource>
*/
try {
SAXBuilder builder = new SAXBuilder();
SelectiveSkippingEntityResolver entityResolver = SelectiveSkippingEntityResolver.getDtdAndXsdSkippingInstance();
builder.setEntityResolver(entityResolver);
Document doc = builder.build(file);
// Get the root element
Element root = doc.getRootElement();
if (!root.getName().equals("datasources")) {
return null;
}
Element datasourceElement = findDatasourceElement(root, name);
if (datasourceElement == null) {
return null;
}
Configuration config = new Configuration();
String type = datasourceElement.getName();
config.put(new PropertySimple("type", type));
bindElements(datasourceElement, config, COMMON_PROPS);
if (type.equals(XA_TX_TYPE)) {
bindElements(datasourceElement, config, XA_PROPS);
bindMap(datasourceElement, config, CONNECTION_PROPERTY, XA_DATASOURCE_PROPERTY);
// bindXASpecialElements(datasourceElement,config);
} else {
bindElements(datasourceElement, config, NON_XA_PROPS);
bindMap(datasourceElement, config, CONNECTION_PROPERTY, CONNECTION_PROPERTY);
}
return config;
} catch (IOException e) {
log.error("IO error occurred while reading file: " + file, e);
} catch (JDOMException e) {
log.error("Parsing error occurred while reading file: " + file, e);
}
return null;
}
/**
* Writes out datasource changes to the file system. If the file does not exist it will create a new file in the
* requested location.
*
* @param deploymentFile
* @param name
* @param report
*/
public static void updateDatasource(File deploymentFile, String name, ConfigurationUpdateReport report) {
try {
updateDatasource(deploymentFile, name, report.getConfiguration());
report.setStatus(ConfigurationUpdateStatus.SUCCESS);
} catch (IOException e) {
report.setErrorMessageFromThrowable(e);
log.error("IO error occurred while updating datasource at file: " + deploymentFile, e);
} catch (JDOMException e) {
report.setErrorMessageFromThrowable(e);
log.error("Parsing error occurred while updating datasource at file: " + deploymentFile, e);
}
}
/**
* Update or create a Datasource file
* @param deploymentFile
* @param name
* @param report
*/
public static void updateDatasource(File deploymentFile, String name, CreateResourceReport report) {
try {
updateDatasource(deploymentFile, name, report.getResourceConfiguration());
report.setStatus(CreateResourceStatus.SUCCESS);
} catch (IOException e) {
report.setException(e);
log.error("IO error occurred while updating datasource at file: " + deploymentFile, e);
} catch (JDOMException e) {
report.setException(e);
log.error("Parsing error occurred while updating datasource at file: " + deploymentFile, e);
}
}
/**
* Update or create a new datasource
* @param deploymentFile
* @param name
* @param config
* @throws JDOMException
* @throws IOException
*/
private static void updateDatasource(File deploymentFile, String name, Configuration config) throws JDOMException,
IOException {
Document doc;
Element root;
if (deploymentFile.exists() && !deploymentFile.canWrite()) {
throw new RuntimeException("Datasource file " + deploymentFile + " is not writable. Aborting.");
}
if (deploymentFile.exists()) {
SAXBuilder builder = new SAXBuilder();
SelectiveSkippingEntityResolver entityResolver = SelectiveSkippingEntityResolver.getDtdAndXsdSkippingInstance();
builder.setEntityResolver(entityResolver);
doc = builder.build(deploymentFile);
root = doc.getRootElement();
} else {
doc = new Document();
root = new Element("datasources");
doc.setRootElement(root);
}
if (!root.getName().equals("datasources")) {
throw new RuntimeException("Datasource file format exception on [" + deploymentFile
+ "], expected [datasources] element but found [" + root.getName() + "]");
}
Element datasourceElement = findDatasourceElement(root, name);
String type = config.getSimpleValue("type", null);
boolean isNewDatasource = false;
if (datasourceElement == null) {
datasourceElement = new Element(type);
isNewDatasource = true;
} else if (!type.equals(datasourceElement.getName())) {
datasourceElement.setName(type);
}
updateElements(datasourceElement, config, COMMON_PROPS);
if (type.equals(XA_TX_TYPE)) {
updateElements(datasourceElement, config, XA_PROPS);
updateMap(datasourceElement, config, CONNECTION_PROPERTY, XA_DATASOURCE_PROPERTY);
// updateXAElements(datasourceElement, config);
} else {
updateElements(datasourceElement, config, NON_XA_PROPS);
updateMap(datasourceElement, config, CONNECTION_PROPERTY, CONNECTION_PROPERTY);
}
if (isNewDatasource) {
root.addContent(datasourceElement);
}
updateFile(deploymentFile, doc);
}
public static void deleteDataSource(File deploymentFile, String name) {
Document doc;
Element root;
if (deploymentFile==null) {
log.error("DeleteDatasource: passed file is null");
return;
}
if (deploymentFile.exists()) {
try {
SAXBuilder builder = new SAXBuilder();
SelectiveSkippingEntityResolver entityResolver = SelectiveSkippingEntityResolver.getDtdAndXsdSkippingInstance();
builder.setEntityResolver(entityResolver);
doc = builder.build(deploymentFile);
root = doc.getRootElement();
if (root != null) {
if (!root.getName().equals("datasources")) {
throw new RuntimeException("Datasource file format exception on [" + deploymentFile
+ "], expected [datasources] element but found [" + root.getName() + "]");
}
Element datasourceElement = findDatasourceElement(root, name);
root.removeContent(datasourceElement);
}
updateFile(deploymentFile, doc);
} catch (JDOMException e) {
log.error("Parsing error occurred while deleting datasource at file: " + deploymentFile, e);
} catch (IOException e) {
log.error("IO error occurred while deleting datasource at file: " + deploymentFile, e);
}
}
}
/**
* Update the map-elements in the datasource that are below <i>parent</i>.
* @param parent Parent element to add / update
* @param configuration configuration that holds the properties
* @param mapName the name of the map-property in the configuration that holds the elements
* @param propertyElementName the name of the element to write below <i>parent</i>
*/
private static void updateMap(Element parent, Configuration configuration, String mapName,
String propertyElementName) {
PropertyMap map = configuration.getMap(mapName);
// Wrap in ArrayList to avoid ConcurrentModificationException when adding or removing children while iterating.
List<Element> mapElements = new ArrayList<Element>(parent.getChildren(propertyElementName));
if ((map == null) || map.getMap().isEmpty()) {
if (!mapElements.isEmpty()) {
parent.removeChildren(propertyElementName);
}
return;
}
Map<String, Element> elements = new HashMap<String, Element>();
for (Element el : mapElements) {
elements.put(el.getAttributeValue("name"), el);
if (map.get(el.getAttributeValue("name")) == null) {
parent.removeContent(el);
}
}
for (Property prop : map.getMap().values()) {
Element element = elements.get(prop.getName());
if (element == null) {
element = new Element(propertyElementName);
element.setAttribute("name", prop.getName());
parent.addContent(element);
}
element.setText(((PropertySimple) prop).getStringValue());
}
}
private static void updateElements(Element parent, Configuration config, String[] names) {
for (String prop : names) {
updateElement(parent, config, prop);
}
}
private static void updateElement(Element parent, Configuration config, String name) {
String value = config.getSimpleValue(name, null);
Element child = parent.getChild(name);
if (value == null) {
if (child != null) {
parent.removeContent(child);
}
} else {
if (child == null) {
child = new Element(name);
parent.addContent(child);
}
child.setText(value);
}
}
/**
* Look for elements with the name <i>elementName</i> below <i>parent</i> and put
* them in to <i>config</i> as a new map with the name <i>mapName</i>.
* @param parent The parent element to search for children
* @param config The configuration to put the elements found
* @param mapName The name of the map as a key to put into the config
* @param elementName The elements to look for
*/
private static void bindMap(Element parent, Configuration config, String mapName, String elementName) {
PropertyMap map = new PropertyMap(mapName);
for (Object child : parent.getChildren(elementName)) {
Element childElement = (Element) child;
String name = childElement.getAttributeValue("name");
map.put(new PropertySimple(name, childElement.getText()));
}
config.put(map);
}
private static void bindElements(Element parent, Configuration config, String[] names) {
for (String prop : names) {
bindElement(parent, config, prop);
}
}
private static void bindElement(Element parent, Configuration config, String name) {
Element child = parent.getChild(name);
if (child != null) {
config.put(new PropertySimple(name, child.getText()));
}
}
private static Element findDatasourceElement(Element root, String name) {
for (Object child : root.getChildren()) {
Element childElement = (Element) child;
String jndiName = childElement.getChildText("jndi-name");
if (name.equals(jndiName)) {
return childElement;
}
}
return null;
}
private static void updateFile(File deploymentFile, Document doc) throws JDOMException, IOException {
FileOutputStream fos = new FileOutputStream(deploymentFile);
XMLOutputter outp = new XMLOutputter(Format.getPrettyFormat());
outp.output(doc, fos);
fos.flush();
fos.close();
}
}