/*
* 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;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynComponent;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynConfiguration;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynProperty;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynPropertyList;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
/**
* Manage configurations.
*
* @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
@Service
public class ConfigurationsImpl implements Configurations {
private static final String TAMPLATE_NAME = "dynamic-configuration-template.xml";
private static final String FILE_NAME = "dynamic-configuration.xml";
private static final String ELEMENT_NAME = "dynamic-configuration";
private static final String CONFIGURATION_ELEMENT_NAME = "configuration";
private static final String COMPONENT_ELEMENT_NAME = "component";
private static final String PROPERTY_ELEMENT_NAME = "property";
private static final String KEY_ELEMENT_NAME = "key";
private static final String VALUE_ELEMENT_NAME = "value";
private static final String ACTIVE_ELEMENT_NAME = "active";
private static final String BASE_ELEMENT_NAME = "base";
private static final String ID_ATTRIBUTE_NAME = "id";
private static final String NAME_ATTRIBUTE_NAME = "name";
private static final String CONFIGURATION_XPATH = "/" + ELEMENT_NAME + "/"
+ CONFIGURATION_ELEMENT_NAME;
private static final String ACTIVE_CONFIGURATION_XPATH = "/" + ELEMENT_NAME
+ "/" + ACTIVE_ELEMENT_NAME;
private static final String BASE_CONFIGURATION_XPATH = "/" + ELEMENT_NAME
+ "/" + BASE_ELEMENT_NAME;
@Reference
private PathResolver pathResolver;
@Reference
private FileManager fileManager;
/**
* {@inheritDoc}
*/
public void addConfiguration(DynConfiguration dynConf) {
// Obtain the configuration document
Document doc = getConfigurationDocument();
// Add new configuration element
Element conf = doc.createElement(CONFIGURATION_ELEMENT_NAME);
conf.setAttribute(NAME_ATTRIBUTE_NAME, dynConf.getName());
doc.getDocumentElement().appendChild(conf);
// Iterate all child dynamic components of the dynamic configuration
for (DynComponent dynComps : dynConf.getComponents()) {
Element comp = addComponent(conf, dynComps.getId(),
dynComps.getName());
// Iterate all child dynamic properties of the dynamic component
for (DynProperty dynProp : dynComps.getProperties()) {
// Add new property on component
addProperty(comp, dynProp.getKey(), dynProp.getValue());
}
}
// Update the configuration file
saveConfiguration(conf);
}
/**
* {@inheritDoc}
*/
public void deleteConfiguration(Element conf) {
// Remove configuration element and their child component elements
List<Element> comps = XmlUtils.findElements(COMPONENT_ELEMENT_NAME,
conf);
for (Element comp : comps) {
conf.removeChild(comp);
}
conf.getParentNode().removeChild(conf);
// If active configuration, remove the reference on configuration file
Element activeConf = isActiveConfiguration(conf);
if (activeConf != null) {
activeConf.setTextContent("");
}
// Update the configuration file
saveConfiguration(conf);
}
/**
* {@inheritDoc}
*/
public DynConfiguration parseConfiguration(Element conf, String property) {
DynConfiguration dynConf = new DynConfiguration();
// Iterate all child component elements from the configuration element
List<Element> comps = XmlUtils.findElements(COMPONENT_ELEMENT_NAME,
conf);
for (Element comp : comps) {
DynComponent dynComp = parseComponent(comp, property);
if (dynComp != null) {
dynConf.addComponent(dynComp);
}
}
// Set name property of dynamic configuration
dynConf.setName(conf.getAttribute(NAME_ATTRIBUTE_NAME));
// If configuration is active, mark it
if (isActiveConfiguration(conf) != null) {
dynConf.setActive(true);
}
return dynConf;
}
/**
* {@inheritDoc}
*/
public DynComponent parseComponent(Element comp, String name) {
// If property name specified, only it be considered
List<Element> props;
if (name == null) {
props = XmlUtils.findElements("*", comp);
}
else {
props = XmlUtils.findElements(PROPERTY_ELEMENT_NAME + "/"
+ KEY_ELEMENT_NAME + "[text()='" + name + "']/..", comp);
}
// Iterate all child property elements from the component element
DynPropertyList dynProps = new DynPropertyList();
for (Element prop : props) {
dynProps.add(parseProperty(prop));
}
if (dynProps.size() == 0) {
return null;
}
// Add new dynamic component
NamedNodeMap attributes = comp.getAttributes();
return new DynComponent(attributes.getNamedItem(ID_ATTRIBUTE_NAME)
.getNodeValue(), attributes.getNamedItem(NAME_ATTRIBUTE_NAME)
.getNodeValue(), dynProps);
}
/**
* {@inheritDoc}
*/
public DynProperty parseProperty(Element prop) {
// Create a dynamic property from property element
Node key = getKeyElement(prop);
Node value = getValueElement(prop);
return new DynProperty(key.getTextContent(), value == null ? null
: value.getTextContent());
}
/**
* {@inheritDoc}
*/
public Node getKeyElement(Element prop) {
return XmlUtils.findFirstElement(KEY_ELEMENT_NAME, prop);
}
/**
* {@inheritDoc}
*/
public Node getValueElement(Element prop) {
return XmlUtils.findFirstElement(VALUE_ELEMENT_NAME, prop);
}
/**
* {@inheritDoc}
*/
public Element findConfiguration(String name) {
return XmlUtils.findFirstElement(CONFIGURATION_XPATH + "[@"
+ NAME_ATTRIBUTE_NAME + "='" + name + "']",
getConfigurationDocument().getDocumentElement());
}
/**
* {@inheritDoc}
*/
public Element getBaseConfiguration() {
return XmlUtils.findFirstElement(BASE_CONFIGURATION_XPATH,
getConfigurationDocument().getDocumentElement());
}
/**
* {@inheritDoc}
*/
public List<Element> getAllConfigurations() {
return XmlUtils.findElements(CONFIGURATION_XPATH,
getConfigurationDocument().getDocumentElement());
}
/**
* {@inheritDoc}
*/
public Element getProperty(String configuration, String property) {
// TODO Properties with same name can exist at different components
return XmlUtils.findFirstElement(CONFIGURATION_XPATH + "[@"
+ NAME_ATTRIBUTE_NAME + "='" + configuration + "']" + "/"
+ COMPONENT_ELEMENT_NAME + "/" + PROPERTY_ELEMENT_NAME + "/"
+ KEY_ELEMENT_NAME + "[text()='" + property + "']/..",
getConfigurationDocument().getDocumentElement());
}
/**
* {@inheritDoc}
*/
public void saveConfiguration(Element elem) {
String path = getConfigurationFilePath();
MutableFile file = fileManager.updateFile(path);
XmlUtils.writeXml(file.getOutputStream(), elem.getOwnerDocument());
}
/**
* {@inheritDoc}
*/
public DynConfiguration getActiveConfiguration() {
DynConfiguration dynConf = null;
Element elem = XmlUtils.findFirstElement(ACTIVE_CONFIGURATION_XPATH,
getConfigurationDocument().getDocumentElement());
if (elem != null) {
Element active = findConfiguration(elem.getTextContent());
if (active != null) {
dynConf = parseConfiguration(active, null);
}
}
return dynConf;
}
/**
* {@inheritDoc}
*/
public void setActiveConfiguration(String name) {
// Get active configuration element
Element elem = XmlUtils.findFirstElement(ACTIVE_CONFIGURATION_XPATH,
getConfigurationDocument().getDocumentElement());
elem.setTextContent(name);
saveConfiguration(elem);
}
/**
* {@inheritDoc}
*/
public void addProperties(String name, String value, String compId,
String compName) {
// Add property in all stored configurations
List<Element> confs = getAllConfigurations();
for (Element conf : confs) {
addProperty(name, value, compId, compName, conf);
}
// Add property in base configuration
addProperty(name, value, compId, compName, getBaseConfiguration());
}
/**
* {@inheritDoc}
*/
public DynProperty updateProperty(String configuration, String property,
String value) {
// Get the required property element to update
Element prop = getProperty(configuration, property);
if (prop == null) {
return null;
}
// Get the property value element
Node valueElem = getValueElement(prop);
if (value == null) {
if (valueElem != null) {
// Undefined property value: remove property value element
prop.removeChild(valueElem);
}
}
else {
if (valueElem == null) {
// Undo undefined property value: Create property value element
valueElem = prop.getOwnerDocument().createElement(
VALUE_ELEMENT_NAME);
prop.appendChild(valueElem);
}
// Set property value
valueElem.setTextContent(value);
}
saveConfiguration(prop);
return parseProperty(prop);
}
/**
* Add a component property with some name and value on a configuration.
*
* @param name Property name
* @param value Property value
* @param compId Component id
* @param compId Component name
* @param conf Configuration element
*/
private void addProperty(String name, String value, String compId,
String compName, Element conf) {
// Get component or create it if not exists
Element comp = getComponent(compId, conf);
if (comp == null) {
comp = addComponent(conf, compId, compName);
}
// Add property on component and save configuration
addProperty(comp, name, value);
saveConfiguration(conf);
}
/**
* Get a component element with some name from a configuration element.
*
* @param id Component identificador
* @param conf Configuration element
* @return Component element
*/
private Element getComponent(String id, Element conf) {
// Find required component of configuration or create if not exists
return XmlUtils.findFirstElement(COMPONENT_ELEMENT_NAME + "[@"
+ ID_ATTRIBUTE_NAME + "='" + id + "']", conf);
}
/**
* Add new property element containing key and value elements on component.
*
* @param comp Component where add property
* @param key Property key
* @param value Property value
*/
private void addProperty(Element comp, String key, String value) {
// Get document and create property element
Document doc = comp.getOwnerDocument();
Element propElem = doc.createElement(PROPERTY_ELEMENT_NAME);
// Create key
Element keyElem = doc.createElement(KEY_ELEMENT_NAME);
keyElem.setTextContent(key);
propElem.appendChild(keyElem);
// Create value element
Element valueElem = doc.createElement(VALUE_ELEMENT_NAME);
valueElem.setTextContent(value);
propElem.appendChild(valueElem);
// Add property on component
comp.appendChild(propElem);
}
/**
* Add new component element containing id and name elements on
* configuration.
*
* @param conf Configuration where add component
* @param id Component identificator
* @param name Component name
*/
private Element addComponent(Element conf, String id, String name) {
// Add new component element
Document doc = conf.getOwnerDocument();
Element comp = doc.createElement(COMPONENT_ELEMENT_NAME);
comp.setAttribute(ID_ATTRIBUTE_NAME, id);
comp.setAttribute(NAME_ATTRIBUTE_NAME, name);
conf.appendChild(comp);
return comp;
}
/**
* Check if a configuration is the active.
*
* @param conf Configuration element
* @return Active element or null if is not the active
*/
private Element isActiveConfiguration(Element conf) {
// Get active configuration element
Element activeConf = XmlUtils.findFirstElement(
ACTIVE_CONFIGURATION_XPATH, conf.getOwnerDocument()
.getDocumentElement());
// Mark the configuration as active if same than this
String name = conf.getAttribute(NAME_ATTRIBUTE_NAME);
if (activeConf != null && activeConf.getTextContent().equals(name)) {
return activeConf;
}
return null;
}
/**
* Get the configuration file from disk in a dom document format.
*
* @return Dom document instance of the XML configuration
*/
private Document getConfigurationDocument() {
Document doc;
try {
String path = getConfigurationFilePath();
DocumentBuilder build = XmlUtils.getDocumentBuilder();
doc = build.parse(fileManager.getInputStream(path));
}
catch (SAXException se) {
throw new IllegalStateException(
"Cant parse the configuration file", se);
}
catch (IOException ioe) {
throw new IllegalStateException("Cant read the configuration file",
ioe);
}
return doc;
}
/**
* Get the configuration file path.
* <p>
* If XML configuration file not exists, will be created with a template.
* </p>
*
* @return Configuration file path
*/
private String getConfigurationFilePath() {
String path = pathResolver
.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_RESOURCES, ""),
FILE_NAME);
if (!fileManager.exists(path)) {
OutputStream outputStream = null;
try {
outputStream = fileManager.createFile(path).getOutputStream();
IOUtils.copy(
FileUtils.getInputStream(getClass(), TAMPLATE_NAME),
outputStream);
}
catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
finally {
IOUtils.closeQuietly(outputStream);
}
}
return path;
}
}