/* * gvNIX is an open source tool for rapid application development (RAD). * Copyright (C) 2010 Generalitat Valenciana * * 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 org.gvnix.dynamic.configuration.roo.addon.config; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import org.apache.felix.scr.annotations.Component; import org.gvnix.dynamic.configuration.roo.addon.entity.DynProperty; import org.gvnix.dynamic.configuration.roo.addon.entity.DynPropertyList; import org.springframework.roo.process.manager.MutableFile; import org.springframework.roo.support.logging.HandlerUtils; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * Abstract dynamic configuration component of XML files. * <p> * Extends this class to manage new XML file values. * </p> * * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a * href="http://www.dgti.gva.es">General Directorate for Information * Technologies (DGTI)</a> */ @Component(componentAbstract = true) public abstract class XmlDynamicConfiguration extends FileDynamicConfiguration { protected static final String REF_ATTRIBUTE_NAME = "ref"; protected static final String XPATH_ELEMENT_SEPARATOR = "/"; protected static final String XPATH_NAMESPACE_SUFIX = ":"; protected static final String XPATH_ATTRIBUTE_PREFIX = "@"; protected static final String XPATH_ARRAY_SUFIX = "]"; protected static final String XPATH_ARRAY_PREFIX = "["; protected static final String XPATH_EQUALS_SYMBOL = "="; protected static final String XPATH_STRING_DELIMITER = "'"; private static final Logger logger = HandlerUtils .getLogger(XmlDynamicConfiguration.class); /** * {@inheritDoc} */ public DynPropertyList read() { DynPropertyList dynProps = new DynPropertyList(); // Get the XML file path MutableFile file = getFile(); // If managed file not exists, nothing to do if (file != null) { // Obtain the XML file on DOM document format Document doc = getXmlDocument(file); // Create the dynamic properties list from XML document file dynProps.addAll(getProperties("", doc.getChildNodes())); } return dynProps; } /** * {@inheritDoc} */ public void write(DynPropertyList dynProps) { // Get the XML file path MutableFile file = getFile(); if (file != null) { // Obtain the root element of the XML file Document doc = getXmlDocument(file); Element root = doc.getDocumentElement(); // Update the root element property values with dynamic properties setProperties(root, dynProps); // Update the XML file XmlUtils.writeXml(file.getOutputStream(), doc); } else if (!dynProps.isEmpty()) { logger.log(Level.WARNING, "File " + getFilePath() + " not exists and there are dynamic properties to set it"); } } /** * Get a document from a file. * * @param file Mutable file * @return File document */ protected Document getXmlDocument(MutableFile file) { Document doc = null; try { // Get the XML file and parse it to document DocumentBuilder build = XmlUtils.getDocumentBuilder(); doc = build.parse(fileManager.getInputStream(file .getCanonicalPath())); } catch (SAXException se) { throw new IllegalStateException("Cant parse the XML file", se); } catch (IOException ioe) { throw new IllegalStateException("Cant read the XML file", ioe); } return doc; } /** * Generate a dynamic property list from a list of XML nodes. * <p> * Only TEXT_NODE, TEXT_NODE and ATTRIBUTE_NODE nodes are considered. On * ATTRIBUTE_NODE nodes references neither are considered. * </p> * * @param baseName Parent node name of node list * @param nodes XML node list to convert * @return Dynamic property list */ protected DynPropertyList getProperties(String baseName, NodeList nodes) { DynPropertyList dynProps = new DynPropertyList(); // Iterate all nodes on list for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); // Only consider element, text or attribute nodes if (isValidNode(node)) { // Generate the xpath expression that points to property String xpath = getPropertyXpath(baseName, nodes, i); // Add dynamic properties related to node attributes dynProps.addAll(getPropertyAttributes(node, xpath)); // Add dynamic properties related to childs nodes and attrs dynProps.addAll(getProperties(xpath, node.getChildNodes())); // Add dynamic property related to this node String content = node.getTextContent(); if (node.getNodeType() == Node.TEXT_NODE && content.trim().length() > 0) { dynProps.add(new DynProperty(baseName, content)); } } } return dynProps; } /** * The node is a valid type to be generated as dynamic property ? * <p> * TEXT_NODE, TEXT_NODE and ATTRIBUTE_NODE nodes are valid. * </p> * * @param node Node to check * @return Has valid format */ protected boolean isValidNode(Node node) { short type = node.getNodeType(); if (type == Node.ELEMENT_NODE || type == Node.TEXT_NODE || type == Node.ATTRIBUTE_NODE) { return true; } return false; } /** * Create the dynamic properties related to this node attributes. * * @param node Node to add to list * @param xpath Xpath expression of this node * @return Dynamic property list */ protected DynPropertyList getPropertyAttributes(Node node, String xpath) { DynPropertyList dynProps = new DynPropertyList(); // Iterate all node attributes, if exists NamedNodeMap attrs = node.getAttributes(); if (attrs != null) { for (int j = 0; j < attrs.getLength(); j++) { // Get attribute and it name Node attr = attrs.item(j); String attrName = attr.getNodeName(); // Create dynamic property, except attribute references if (!attrName.equals(REF_ATTRIBUTE_NAME)) { dynProps.add(new DynProperty(xpath + XPATH_ARRAY_PREFIX + XPATH_ATTRIBUTE_PREFIX + attrName + XPATH_ARRAY_SUFIX, attr.getNodeValue())); } } } return dynProps; } /** * Generate the xpath expression that points to property. * <p> * The xpath expression could be an array or not. * </p> * * @param baseName Base name of current node * @param nodes List of brother nodes * @param i Index of current node * @return Xpath expression */ protected String getPropertyXpath(String baseName, NodeList nodes, int i) { // Get current node name String name = nodes.item(i).getNodeName(); // Iterate brother nodes searching other nodes with same name int index = 0; int temp = 0; for (int j = 0; j < nodes.getLength(); j++) { // If exists a brother node with same name and is valid Node node = nodes.item(j); if (name.equals(node.getNodeName()) && isValidNode(node)) { // Calculate node index related to brother nodes with same name if (j > i) { index = temp; break; } else { temp++; } } } // If temp greater than 1, node is part of an array on index position if (temp > 1) { index = temp; } if (index == 0) { // Xpath expression of an element return baseName + XPATH_ELEMENT_SEPARATOR + name; } else { // Xpath expression of an element array return baseName + XPATH_ELEMENT_SEPARATOR + name + XPATH_ARRAY_PREFIX + index + XPATH_ARRAY_SUFIX; } } /** * Remove possible namespaces from a xpath expression. * * @param xpath Xpath expressión with optional namespaces * @return Xpath expresion without namespaces, if exists */ protected String removeNamespaces(String xpath) { // Find all namespace separators sufix int end; while ((end = xpath.indexOf(XPATH_NAMESPACE_SUFIX)) != -1) { // Namespace starts on element separator if attr prefix not before String substr = xpath.substring(0, end + 1); int ini = substr.lastIndexOf(XPATH_ELEMENT_SEPARATOR); int ini2 = substr.lastIndexOf(XPATH_ATTRIBUTE_PREFIX); if (ini > ini2) { // Remove namespace substring from xpath xpath = xpath.replace(xpath.substring(ini + 1, end + 1), ""); } else { // If attribute prefix before, is an attribute not a namespace break; } } return xpath; } /** * Update the root element property values with dynamic properties. * * @param root Parent element * @param dynProps Dynamic property list */ protected void setProperties(Element root, DynPropertyList dynProps) { // Iterate all dynamic properties to update for (DynProperty dynProp : dynProps) { // Remove possible namespaces String xpath = removeNamespaces(dynProp.getKey()); // If attr prefix present, there is an attribute else an element int index; if ((index = xpath.indexOf(XPATH_ARRAY_PREFIX + XPATH_ATTRIBUTE_PREFIX)) != -1) { // Set the new attribute value through container element Element elem = XmlUtils.findFirstElement( xpath.substring(0, index), root); if (elem == null) { logger.log(Level.WARNING, "Element " + xpath + " to set attribute value not exists on file"); } else { String name = xpath .substring(index + 2, xpath.length() - 1); Attr attr = elem.getAttributeNode(name); if (attr == null) { logger.log(Level.WARNING, "Element attribute " + xpath + " to set value not exists on file"); } else { attr.setValue(dynProp.getValue()); } } } else { // Set the new element content Element elem = XmlUtils.findFirstElement(xpath, root); if (elem == null) { logger.log(Level.WARNING, "Element " + xpath + " to set text content not exists on file"); } else { elem.setTextContent(dynProp.getValue()); } } } } }