/* * 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(); } }