Package

Source Code of XConfToolTask

/*
* Copyright 1999-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.
*/

import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;

import javax.xml.transform.TransformerException;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.XMLCatalog;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
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;

/**
* Ant task to patch xmlfiles.
*
*
* replace-properties no|false,anything else
* xpath: xpath expression for context node
* unless-path: xpath expression that must return empty node set
* unless: (deprecated) xpath expression that must return empty node set
* if-prop: use path file only when project property is set
* remove: xpath expression to remove before adding nodes
* add-comments: if specified, overrides the ant task value
* add-attribute: name of attribute to add to context node (requires value)
* add-attribute-<i>name</i>: add attribute <i>name</i> with the specified value
* value: value of attribute to add to context node (requires add-attribute)
* insert-before: xpath expression, add new nodes before
* insert-after: xpath expression, add new nodes after
*
* @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
* @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
* @author <a href="mailto:crafterm@fztig938.bank.dresdner.net">Marcus Crafter</a>
* @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
* @author <a href="mailto:stephan@apache.org">Stephan Michels</a>
* @version CVS $Id: XConfToolTask.java 126532 2005-01-26 19:20:42Z cziegeler $
*/
public final class XConfToolTask extends MatchingTask {

    private static final String NL=System.getProperty("line.separator");
    private static final String FSEP=System.getProperty("file.separator");
   
    private File file;
    //private File directory;
    private File srcdir;
    private boolean addComments;
    /** for resolving entities such as dtds */
    private XMLCatalog xmlCatalog = new XMLCatalog();

    /**
     * Set file, which should be patched.
     *
     * @param file File, which should be patched.
     */
    public void setFile(File file) {
        this.file = file;
    }

    /**
     * Set base directory for the patch files.
     *
     * @param srcdir Base directory for the patch files.
     */
    public void setSrcdir(File srcdir) {
        this.srcdir = srcdir;
    }

    /**
     * Add the catalog to our internal catalog
     *
     * @param xmlCatalog the XMLCatalog instance to use to look up DTDs
     */
    public void addConfiguredXMLCatalog(XMLCatalog newXMLCatalog) {
        this.xmlCatalog.addConfiguredXMLCatalog(newXMLCatalog);
    }

    /**
     * Whether to add a comment indicating where this block of code comes
     * from.
     */
    public void setAddComments(Boolean addComments) {
        this.addComments = addComments.booleanValue();
    }

    /**
     * Initialize internal instance of XMLCatalog
     */
    public void init() throws BuildException {
        super.init();
        xmlCatalog.setProject(this.getProject());
    }

    /**
     * Execute task.
     */
    public void execute() throws BuildException {
        if (this.file == null) {
            throw new BuildException("file attribute is required", this.getLocation());
        }
        try {
            Document document = DocumentCache.getDocument(this.file, this);
           
            if (this.srcdir == null) {
                this.srcdir = this.getProject().resolveFile(".");
            }

            DirectoryScanner scanner = getDirectoryScanner(this.srcdir);
            String[] list = scanner.getIncludedFiles();
            boolean modified = false;
            // process recursive
            File patchfile;
            ArrayList suspended = new ArrayList();
            boolean hasChanged = false;
            for (int i = 0; i < list.length; i++) {
                patchfile = new File(this.srcdir, list[i]);
                try {
                    // Adds configuration snippet from the file to the configuration
                    boolean changed = patch(document, patchfile);
                    hasChanged |= changed;
                    if (!changed) {
                        suspended.add(patchfile);
                    }
                } catch (SAXException e) {
                    log("Ignoring: "+patchfile+"\n(not a valid XML)");
                }
            }
            modified = hasChanged;

            if (hasChanged && !suspended.isEmpty()) {
                log("Try to apply suspended patch files", Project.MSG_DEBUG);
            }

            ArrayList newSuspended = new ArrayList();
            while (hasChanged && !suspended.isEmpty()) {
                hasChanged = false;
                for(Iterator i=suspended.iterator(); i.hasNext();) {
                    patchfile = (File)i.next();
                    try {
                         // Adds configuration snippet from the file to the configuration
                        boolean changed = patch(document, patchfile);
                        hasChanged |= changed;
                        if (!changed) {
                            newSuspended.add(patchfile);
                        }
                    } catch (SAXException e) {
                        log("Ignoring: "+patchfile+"\n(not a valid XML)");
                    }
                }
                suspended = newSuspended;
                newSuspended = new ArrayList();
            }

            if (!suspended.isEmpty()) {
                for(Iterator i=suspended.iterator(); i.hasNext();) {
                    patchfile = (File)i.next();
                    log("Dismiss: "+patchfile.toString(), Project.MSG_DEBUG);
                }
            }

            if (modified) {
                DocumentCache.writeDocument(this.file, document, this);
            } else {
                log("No Changes: " + this.file, Project.MSG_DEBUG);
            }
            DocumentCache.storeDocument(this.file, document, this);
        } catch (TransformerException e) {
            throw new BuildException("TransformerException: "+e);
        } catch (SAXException e) {
            throw new BuildException("SAXException:" +e);          
        } catch (DOMException e) {
            throw new BuildException("DOMException:" +e);          
        } catch (UnknownHostException e) {
            throw new BuildException("UnknownHostException.  Probable cause: The parser is " +
                "trying to resolve a dtd from the internet and no connection exists.\n" +
                "You can either connect to the internet during the build, or patch \n" +
                "XConfToolTask.java to ignore DTD declarations when your parser is in use.");
        } catch (IOException ioe) {
            throw new BuildException("IOException: "+ioe);
        }
    }

    /**
     * Patch XML document with a given patch file.
     *
     * @param configuration Orginal document
     * @param component Patch document
     * @param patchFile Patch file
     *
     * @return True, if the document was successfully patched
     */
    private boolean patch(final Document configuration,
                           final File patchFile)
                           throws TransformerException, IOException, DOMException, SAXException {

        Document component = DocumentCache.getDocument(patchFile, this);
        String filename = patchFile.toString();
                           
        // Check to see if Document is an xconf-tool document
        Element elem = component.getDocumentElement();

        String extension = filename.lastIndexOf(".")>0?filename.substring(filename.lastIndexOf(".")+1):"";
        String basename = basename(filename);

        if (!elem.getTagName().equals(extension)) {
      throw new BuildException("Not a valid xpatch file: "+filename);
        }

        String replacePropertiesStr = elem.getAttribute("replace-properties");

        boolean replaceProperties = !("no".equalsIgnoreCase(replacePropertiesStr) ||
                                      "false".equalsIgnoreCase(replacePropertiesStr));

        // Get 'root' node were 'component' will be inserted into
        String xpath = getAttribute(elem, "xpath", replaceProperties);
        if ( xpath == null ) {
            throw new IOException("Attribute 'xpath' is required.");   
        }
        NodeList nodes = XPathAPI.selectNodeList(configuration, xpath);

        // Suspend, because the xpath returned not one node
        if (nodes.getLength() !=1 ) {
            log("Suspending: "+filename, Project.MSG_DEBUG);
            return false;
        }
        Node root = nodes.item(0);

        // Test that 'root' node satisfies 'component' insertion criteria
        String testPath = getAttribute(elem, "unless-path", replaceProperties);
        if (testPath == null || testPath.length() == 0) {
            // only look for old "unless" attr if unless-path is not present
            testPath = getAttribute(elem, "unless", replaceProperties);
        }
        // Is if-path needed?
        String ifProp = getAttribute(elem, "if-prop", replaceProperties);
        boolean ifValue = false;
        if (ifProp != null && !ifProp.equals("")) {
            ifValue = Boolean.valueOf(this.getProject().getProperty(ifProp)).booleanValue();
        }

        if (ifProp != null && ifProp.length() > 0 && !ifValue ) {
            log("Skipping: " + filename, Project.MSG_DEBUG);
            return false;
        } else if (testPath != null && testPath.length() > 0 &&
            XPathAPI.selectNodeList(root, testPath).getLength() != 0) {
            log("Skipping: " + filename, Project.MSG_DEBUG);
            return false;
        } else {
            // Test if component wants us to remove a list of nodes first
            xpath = getAttribute(elem, "remove", replaceProperties);

            if (xpath != null && xpath.length() > 0) {
                nodes = XPathAPI.selectNodeList(configuration, xpath);

                for (int i = 0, length = nodes.getLength(); i<length; i++) {
                    Node node = nodes.item(i);
                    Node parent = node.getParentNode();

                    parent.removeChild(node);
                }
            }

            // Test for an attribute that needs to be added to an element
            String name = getAttribute(elem, "add-attribute", replaceProperties);
            String value = getAttribute(elem, "value", replaceProperties);

            if (name != null && name.length() > 0) {
                if (value == null) {
                    throw new IOException("No attribute value specified for 'add-attribute' "+
                                          xpath);
                }
                if (root instanceof Element) {
                    ((Element) root).setAttribute(name, value);
                }
            }
            // Override addComments from ant task if specified as an attribute
            String addCommentsAttr = getAttribute(elem, "add-comments", replaceProperties);
            if ((addCommentsAttr!=null) && (addCommentsAttr.length()>0)) {
                setAddComments(new Boolean(addCommentsAttr));
            }

            // Allow multiple attributes to be added or modified
            if (root instanceof Element) {
                NamedNodeMap attrMap = elem.getAttributes();
                for (int i=0; i<attrMap.getLength(); ++i){
                    Attr attr = (Attr)attrMap.item(i);
                    final String addAttr = "add-attribute-";
                    if (attr.getName().startsWith(addAttr)) {
                        String key = attr.getName().substring(addAttr.length());
                        ((Element) root).setAttribute(key, attr.getValue());
                    }
                }
            }

            // Test if 'component' provides desired insertion point
            xpath = getAttribute(elem, "insert-before", replaceProperties);
            Node before = null;

            if (xpath != null && xpath.length() > 0) {
                nodes = XPathAPI.selectNodeList(root, xpath);
                if (nodes.getLength() == 0) {
                    log("Error in: "+filename);
                    throw new IOException("XPath ("+xpath+") returned zero nodes");
                }
                before = nodes.item(0);
            } else {
                xpath = getAttribute(elem, "insert-after", replaceProperties);
                if (xpath != null && xpath.length() > 0) {
                    nodes = XPathAPI.selectNodeList(root, xpath);
                    if (nodes.getLength() == 0) {
                        log("Error in: "+filename);
                        throw new IOException("XPath ("+xpath+") zero nodes.");
                    }
                    before = nodes.item(nodes.getLength()-1).getNextSibling();
                }
            }

            // Add 'component' data into 'root' node
            log("Processing: "+filename);
            NodeList componentNodes = component.getDocumentElement().getChildNodes();

            if (this.addComments) {
                root.appendChild(configuration.createComment("..... Start configuration from '"+basename+"' "));
                root.appendChild(configuration.createTextNode(NL));
            }
            for (int i = 0; i<componentNodes.getLength(); i++) {
                Node node = configuration.importNode(componentNodes.item(i),
                                                     true);

                if (replaceProperties) {
                    replaceProperties(node);
                }
                if (before==null) {
                    root.appendChild(node);
                } else {
                    root.insertBefore(node, before);
                }
            }
            if (this.addComments) {
                root.appendChild(configuration.createComment("..... End configuration from '"+basename+"' "));
                root.appendChild(configuration.createTextNode(NL));
            }
            return true;
        }
    }

    private String getAttribute(Element elem, String attrName, boolean replaceProperties) {
        String attr = elem.getAttribute(attrName);
        if (attr == null) {
            return null;
        } else if (replaceProperties) {
            return getProject().replaceProperties(attr);
        } else {
            return attr;
        }
    }

    private void replaceProperties(Node n) throws DOMException {
        NamedNodeMap attrs = n.getAttributes();
        if (attrs != null) {
            for (int i = 0; i < attrs.getLength(); i++) {
                Node attr = attrs.item(i);
                attr.setNodeValue(getProject().replaceProperties(attr.getNodeValue()));    
            }
        }
        switch (n.getNodeType()) {
            case Node.ATTRIBUTE_NODE:
            case Node.CDATA_SECTION_NODE:
            case Node.TEXT_NODE: {
                n.setNodeValue(getProject().replaceProperties(n.getNodeValue()));
                break;
            }
            case Node.DOCUMENT_NODE:
            case Node.DOCUMENT_FRAGMENT_NODE:
            case Node.ELEMENT_NODE: {
                Node child = n.getFirstChild();
                while (child != null) {
                    replaceProperties(child);
                    child = child.getNextSibling();
                }
                break;
            }
            default: {
                // ignore all other node types
            }
        }
    }

    /** Returns the file name (excluding directories and extension). */
    private String basename(String fileName) {
        int start = fileName.lastIndexOf(FSEP)+1; // last '/'
        int end = fileName.lastIndexOf(".")// last '.'

        if (end == 0) {
            end = fileName.length();
        }
        return fileName.substring(start, end);
    }
}
TOP

Related Classes of XConfToolTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.