org.apache.tools.ant.taskdefs.XmlProperty Maven / Gradle / Ivy
Show all versions of testatoo-container-jetty-full Show documentation
/*
* Copyright 2002-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.tools.ant.taskdefs;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.XMLCatalog;
import org.apache.tools.ant.util.FileUtils;
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;
import org.xml.sax.EntityResolver;
/**
* Loads property values from a valid XML file, generating the
* property names from the file's element and attribute names.
*
* Example:
*
* <root-tag myattr="true">
* <inner-tag someattr="val">Text</inner-tag>
* <a2><a3><a4>false</a4></a3></a2>
* <x>x1</x>
* <x>x2</x>
* </root-tag>
*
*
* this generates the following properties:
*
*
* root-tag(myattr)=true
* root-tag.inner-tag=Text
* root-tag.inner-tag(someattr)=val
* root-tag.a2.a3.a4=false
* root-tag.x=x1,x2
*
*
* The collapseAttributes property of this task can be set
* to true (the default is false) which will instead result in the
* following properties (note the difference in names of properties
* corresponding to XML attributes):
*
*
* root-tag.myattr=true
* root-tag.inner-tag=Text
* root-tag.inner-tag.someattr=val
* root-tag.a2.a3.a4=false
* root-tag.x=x1,x2
*
*
* Optionally, to more closely mirror the abilities of the Property
* task, a selected set of attributes can be treated specially. To
* enable this behavior, the "semanticAttributes" property of this task
* must be set to true (it defaults to false). If this attribute is
* specified, the following attributes take on special meaning
* (setting this to true implicitly sets collapseAttributes to true as
* well):
*
*
* - value: Identifies a text value for a property.
* - location: Identifies a file location for a property.
* - id: Sets an id for a property
* - refid: Sets a property to the value of another property
* based upon the provided id
* - pathid: Defines a path rather than a property with
* the given id.
*
*
* For example, with keepRoot = false, the following properties file:
*
*
* <root-tag>
* <build>
* <build folder="build">
* <classes id="build.classes" location="${build.folder}/classes"/>
* <reference refid="build.classes"/>
* </build>
* <compile>
* <classpath pathid="compile.classpath">
* <pathelement location="${build.classes}"/>
* </classpath>
* </compile>
* <run-time>
* <jars>*.jar</jars>
* <classpath pathid="run-time.classpath">
* <path refid="compile.classpath"/>
* <pathelement path="${run-time.jars}"/>
* </classpath>
* </run-time>
* </root-tag>
*
*
* is equivalent to the following entries in a build file:
*
*
* <property name="build" location="build"/>
* <property name="build.classes" location="${build.location}/classes"/>
* <property name="build.reference" refid="build.classes"/>
*
* <property name="run-time.jars" value="*.jar/>
*
* <classpath id="compile.classpath">
* <pathelement location="${build.classes}"/>
* </classpath>
*
* <classpath id="run-time.classpath">
* <path refid="compile.classpath"/>
* <pathelement path="${run-time.jars}"/>
* </classpath>
*
*
* This task requires the following attributes:
*
*
* - file: The name of the file to load.
*
*
* This task supports the following attributes:
*
*
* - prefix: Optionally specify a prefix applied to
* all properties loaded. Defaults to an empty string.
* - keepRoot: Indicate whether the root xml element
* is kept as part of property name. Defaults to true.
* - validate: Indicate whether the xml file is validated.
* Defaults to false.
* - collapseAttributes: Indicate whether attributes are
* stored in property names with parens or with period
* delimiters. Defaults to false, meaning properties
* are stored with parens (i.e., foo(attr)).
* - semanticAttributes: Indicate whether attributes
* named "location", "value", "refid" and "path"
* are interpreted as ant properties. Defaults
* to false.
* - rootDirectory: Indicate the directory to use
* as the root directory for resolving location
* properties. Defaults to the directory
* of the project using the task.
* - includeSemanticAttribute: Indicate whether to include
* the semantic attribute ("location" or "value") as
* part of the property name. Defaults to false.
*
*
* @ant.task name="xmlproperty" category="xml"
*/
public class XmlProperty extends org.apache.tools.ant.Task {
private File src;
private String prefix = "";
private boolean keepRoot = true;
private boolean validate = false;
private boolean collapseAttributes = false;
private boolean semanticAttributes = false;
private boolean includeSemanticAttribute = false;
private File rootDirectory = null;
private FileUtils fileUtils = FileUtils.newFileUtils();
private Hashtable addedAttributes = new Hashtable();
private XMLCatalog xmlCatalog = new XMLCatalog();
private static final String ID = "id";
private static final String REF_ID = "refid";
private static final String LOCATION = "location";
private static final String VALUE = "value";
private static final String PATH = "path";
private static final String PATHID = "pathid";
private static final String[] ATTRIBUTES = new String[] {
ID, REF_ID, LOCATION, VALUE, PATH, PATHID
};
/**
* Constructor.
*/
public XmlProperty() {
super();
}
/**
* Initializes the task.
*/
public void init() {
super.init();
xmlCatalog.setProject(getProject());
}
/**
* @return the xmlCatalog as the entityresolver.
*/
protected EntityResolver getEntityResolver() {
return xmlCatalog;
}
/**
* Run the task.
* @throws BuildException The exception raised during task execution.
* @todo validate the source file is valid before opening, print a better error message
* @todo add a verbose level log message listing the name of the file being loaded
*/
public void execute()
throws BuildException {
if (getFile() == null) {
String msg = "XmlProperty task requires a file attribute";
throw new BuildException(msg);
}
try {
log("Loading " + src.getAbsolutePath(), Project.MSG_VERBOSE);
if (src.exists()) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validate);
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(getEntityResolver());
Document document = builder.parse(src);
Element topElement = document.getDocumentElement();
// Keep a hashtable of attributes added by this task.
// This task is allow to override its own properties
// but not other properties. So we need to keep track
// of which properties we've added.
addedAttributes = new Hashtable();
if (keepRoot) {
addNodeRecursively(topElement, prefix, null);
} else {
NodeList topChildren = topElement.getChildNodes();
int numChildren = topChildren.getLength();
for (int i = 0; i < numChildren; i++) {
addNodeRecursively(topChildren.item(i), prefix, null);
}
}
} else {
log("Unable to find property file: " + src.getAbsolutePath(),
Project.MSG_VERBOSE);
}
} catch (SAXException sxe) {
// Error generated during parsing
Exception x = sxe;
if (sxe.getException() != null) {
x = sxe.getException();
}
throw new BuildException(x);
} catch (ParserConfigurationException pce) {
// Parser with specified options can't be built
throw new BuildException(pce);
} catch (IOException ioe) {
// I/O error
throw new BuildException(ioe);
}
}
/** Iterate through all nodes in the tree. */
private void addNodeRecursively(Node node, String prefix,
Object container) {
// Set the prefix for this node to include its tag name.
String nodePrefix = prefix;
if (node.getNodeType() != Node.TEXT_NODE) {
if (prefix.trim().length() > 0) {
nodePrefix += ".";
}
nodePrefix += node.getNodeName();
}
// Pass the container to the processing of this node,
Object nodeObject = processNode(node, nodePrefix, container);
// now, iterate through children.
if (node.hasChildNodes()) {
NodeList nodeChildren = node.getChildNodes();
int numChildren = nodeChildren.getLength();
for (int i = 0; i < numChildren; i++) {
// For each child, pass the object added by
// processNode to its children -- in other word, each
// object can pass information along to its children.
addNodeRecursively(nodeChildren.item(i), nodePrefix,
nodeObject);
}
}
}
void addNodeRecursively(org.w3c.dom.Node node, String prefix) {
addNodeRecursively(node, prefix, null);
}
/**
* Process the given node, adding any required attributes from
* this child node alone -- but not processing any
* children.
*
* @param node the XML Node to parse
* @param prefix A string to prepend to any properties that get
* added by this node.
* @param container Optionally, an object that a parent node
* generated that this node might belong to. For example, this
* node could be within a node that generated a Path.
* @return the Object created by this node. Generally, this is
* either a String if this node resulted in setting an attribute,
* or a Path.
*/
public Object processNode (Node node, String prefix, Object container) {
// Parse the attribute(s) and text of this node, adding
// properties for each.
// if the "path" attribute is specified, then return the created path
// which will be passed to the children of this node.
Object addedPath = null;
// The value of an id attribute of this node.
String id = null;
if (node.hasAttributes()) {
NamedNodeMap nodeAttributes = node.getAttributes();
// Is there an id attribute?
Node idNode = nodeAttributes.getNamedItem(ID);
id = (semanticAttributes && idNode != null
? idNode.getNodeValue() : null);
// Now, iterate through the attributes adding them.
for (int i = 0; i < nodeAttributes.getLength(); i++) {
Node attributeNode = nodeAttributes.item(i);
if (!semanticAttributes) {
String attributeName = getAttributeName(attributeNode);
String attributeValue = getAttributeValue(attributeNode);
addProperty(prefix + attributeName, attributeValue, null);
} else {
String nodeName = attributeNode.getNodeName();
String attributeValue = getAttributeValue(attributeNode);
Path containingPath = (container != null
&& container instanceof Path ? (Path) container : null);
/*
* The main conditional logic -- if the attribute
* is somehow "special" (i.e., it has known
* semantic meaning) then deal with it
* appropriately.
*/
if (nodeName.equals(ID)) {
// ID has already been found above.
continue;
} else if (containingPath != null
&& nodeName.equals(PATH)) {
// A "path" attribute for a node within a Path object.
containingPath.setPath(attributeValue);
} else if (container instanceof Path
&& nodeName.equals(REF_ID)) {
// A "refid" attribute for a node within a Path object.
containingPath.setPath(attributeValue);
} else if (container instanceof Path
&& nodeName.equals(LOCATION)) {
// A "location" attribute for a node within a
// Path object.
containingPath.setLocation(resolveFile(attributeValue));
} else if (nodeName.equals(PATHID)) {
// A node identifying a new path
if (container != null) {
throw new BuildException("XmlProperty does not "
+ "support nested paths");
}
addedPath = new Path(getProject());
getProject().addReference(attributeValue, addedPath);
} else {
// An arbitrary attribute.
String attributeName = getAttributeName(attributeNode);
addProperty(prefix + attributeName, attributeValue, id);
}
}
}
}
String nodeText = null;
if (node.getNodeType() == Node.TEXT_NODE) {
// For the text node, add a property.
nodeText = getAttributeValue(node);
} else if ((node.getNodeType() == Node.ELEMENT_NODE)
&& (node.getChildNodes().getLength() == 1)
&& (node.getFirstChild().getNodeType() == Node.CDATA_SECTION_NODE)) {
nodeText = node.getFirstChild().getNodeValue();
}
if (nodeText != null) {
// If the containing object was a String, then use it as the ID.
if (semanticAttributes && id == null
&& container instanceof String) {
id = (String) container;
}
if (nodeText.trim().length() != 0) {
addProperty(prefix, nodeText, id);
}
}
// Return the Path we added or the ID of this node for
// children to reference if needed. Path objects are
// definitely used by child path elements, and ID may be used
// for a child text node.
return (addedPath != null ? addedPath : id);
}
/**
* Actually add the given property/value to the project
* after writing a log message.
*/
private void addProperty (String name, String value, String id) {
String msg = name + ":" + value;
if (id != null) {
msg += ("(id=" + id + ")");
}
log(msg, Project.MSG_DEBUG);
if (addedAttributes.containsKey(name)) {
// If this attribute was added by this task, then
// we append this value to the existing value.
// We use the setProperty method which will
// forcibly override the property if it already exists.
// We need to put these properties into the project
// when we read them, though (instead of keeping them
// outside of the project and batch adding them at the end)
// to allow other properties to reference them.
value = (String) addedAttributes.get(name) + "," + value;
getProject().setProperty(name, value);
} else {
getProject().setNewProperty(name, value);
}
addedAttributes.put(name, value);
if (id != null) {
getProject().addReference(id, value);
}
}
/**
* Return a reasonable attribute name for the given node.
* If we are using semantic attributes or collapsing
* attributes, the returned name is ".nodename".
* Otherwise, we return "(nodename)". This is long-standing
* (and default) <xmlproperty> behavior.
*/
private String getAttributeName (Node attributeNode) {
String attributeName = attributeNode.getNodeName();
if (semanticAttributes) {
// Never include the "refid" attribute as part of the
// attribute name.
if (attributeName.equals(REF_ID)) {
return "";
// Otherwise, return it appended unless property to hide it is set.
} else if (!isSemanticAttribute(attributeName)
|| includeSemanticAttribute) {
return "." + attributeName;
} else {
return "";
}
} else if (collapseAttributes) {
return "." + attributeName;
} else {
return "(" + attributeName + ")";
}
}
/**
* Return whether the provided attribute name is recognized or not.
*/
private static boolean isSemanticAttribute (String attributeName) {
for (int i = 0; i < ATTRIBUTES.length; i++) {
if (attributeName.equals(ATTRIBUTES[i])) {
return true;
}
}
return false;
}
/**
* Return the value for the given attribute.
* If we are not using semantic attributes, its just the
* literal string value of the attribute.
*
* If we are using semantic attributes, then first
* dependent properties are resolved (i.e., ${foo} is resolved
* based on the foo property value), and then an appropriate data
* type is used. In particular, location-based properties are
* resolved to absolute file names. Also for refid values, look
* up the referenced object from the project.
*/
private String getAttributeValue (Node attributeNode) {
String nodeValue = attributeNode.getNodeValue().trim();
if (semanticAttributes) {
String attributeName = attributeNode.getNodeName();
nodeValue = getProject().replaceProperties(nodeValue);
if (attributeName.equals(LOCATION)) {
File f = resolveFile(nodeValue);
return f.getPath();
} else if (attributeName.equals(REF_ID)) {
Object ref = getProject().getReference(nodeValue);
if (ref != null) {
return ref.toString();
}
}
}
return nodeValue;
}
/**
* The XML file to parse; required.
* @param src the file to parse
*/
public void setFile(File src) {
this.src = src;
}
/**
* the prefix to prepend to each property
* @param prefix the prefix to prepend to each property
*/
public void setPrefix(String prefix) {
this.prefix = prefix.trim();
}
/**
* flag to include the xml root tag as a
* first value in the property name; optional,
* default is true
* @param keepRoot if true (default), include the xml root tag
*/
public void setKeeproot(boolean keepRoot) {
this.keepRoot = keepRoot;
}
/**
* flag to validate the XML file; optional, default false
* @param validate if true validate the XML file, default false
*/
public void setValidate(boolean validate) {
this.validate = validate;
}
/**
* flag to treat attributes as nested elements;
* optional, default false
* @param collapseAttributes if true treat attributes as nested elements
*/
public void setCollapseAttributes(boolean collapseAttributes) {
this.collapseAttributes = collapseAttributes;
}
/**
* Attribute to enable special handling of attributes - see ant manual.
* @param semanticAttributes if true enable the special handling.
*/
public void setSemanticAttributes(boolean semanticAttributes) {
this.semanticAttributes = semanticAttributes;
}
/**
* The directory to use for resolving file references.
* Ignored if semanticAttributes is not set to true.
* @param rootDirectory the directory.
*/
public void setRootDirectory(File rootDirectory) {
this.rootDirectory = rootDirectory;
}
/**
* Include the semantic attribute name as part of the property name.
* Ignored if semanticAttributes is not set to true.
* @param includeSemanticAttribute if true include the sematic attribute
* name.
*/
public void setIncludeSemanticAttribute(boolean includeSemanticAttribute) {
this.includeSemanticAttribute = includeSemanticAttribute;
}
/**
* add an XMLCatalog as a nested element; optional.
* @param catalog the XMLCatalog to use
*/
public void addConfiguredXMLCatalog(XMLCatalog catalog) {
xmlCatalog.addConfiguredXMLCatalog(catalog);
}
/* Expose members for extensibility */
/**
* @return the file attribute.
*/
protected File getFile () {
return this.src;
}
/**
* @return the prefix attribute.
*/
protected String getPrefix () {
return this.prefix;
}
/**
* @return the keeproot attribute.
*/
protected boolean getKeeproot () {
return this.keepRoot;
}
/**
* @return the validate attribute.
*/
protected boolean getValidate () {
return this.validate;
}
/**
* @return the collapse attributes attribute.
*/
protected boolean getCollapseAttributes () {
return this.collapseAttributes;
}
/**
* @return the semantic attributes attribute.
*/
protected boolean getSemanticAttributes () {
return this.semanticAttributes;
}
/**
* @return the root directory attribute.
*/
protected File getRootDirectory () {
return this.rootDirectory;
}
/**
* @return the include semantic attribute.
*/
protected boolean getIncludeSementicAttribute () {
return this.includeSemanticAttribute;
}
/**
* Let project resolve the file - or do it ourselves if
* rootDirectory has been set.
*/
private File resolveFile(String fileName) {
if (rootDirectory == null) {
return getProject().resolveFile(fileName);
}
return fileUtils.resolveFile(rootDirectory, fileName);
}
}