org.apache.hadoop.util.ConfTest.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.util.ConfTest.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.hadoop.util;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Map.Entry;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.hadoop.classification.InterfaceAudience;

/**
 * This class validates configuration XML files in ${HADOOP_CONF_DIR} or
 * specified ones.
 */
@InterfaceAudience.Private
public final class ConfTest {

    private static final String USAGE = "Usage: hadoop conftest [-conffile <path>|-h|--help]\n" + "  Options:\n"
            + "  \n" + "  -conffile <path>\n" + "    If not specified, the files in ${HADOOP_CONF_DIR}\n"
            + "    whose name end with .xml will be verified.\n" + "    If specified, that path will be verified.\n"
            + "    You can specify either a file or directory, and\n"
            + "    if a directory specified, the files in that directory\n"
            + "    whose name end with .xml will be verified.\n"
            + "    You can specify this option multiple times.\n" + "  -h, --help       Print this help";

    private static final String HADOOP_CONF_DIR = "HADOOP_CONF_DIR";

    protected ConfTest() {
        super();
    }

    private static List<NodeInfo> parseConf(InputStream in) throws XMLStreamException {
        QName configuration = new QName("configuration");
        QName property = new QName("property");

        List<NodeInfo> nodes = new ArrayList<NodeInfo>();
        Stack<NodeInfo> parsed = new Stack<NodeInfo>();

        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLEventReader reader = factory.createXMLEventReader(in);

        while (reader.hasNext()) {
            XMLEvent event = reader.nextEvent();
            if (event.isStartElement()) {
                StartElement currentElement = event.asStartElement();
                NodeInfo currentNode = new NodeInfo(currentElement);
                if (parsed.isEmpty()) {
                    if (!currentElement.getName().equals(configuration)) {
                        return null;
                    }
                } else {
                    NodeInfo parentNode = parsed.peek();
                    QName parentName = parentNode.getStartElement().getName();
                    if (parentName.equals(configuration)
                            && currentNode.getStartElement().getName().equals(property)) {
                        @SuppressWarnings("unchecked")
                        Iterator<Attribute> it = currentElement.getAttributes();
                        while (it.hasNext()) {
                            currentNode.addAttribute(it.next());
                        }
                    } else if (parentName.equals(property)) {
                        parentNode.addElement(currentElement);
                    }
                }
                parsed.push(currentNode);
            } else if (event.isEndElement()) {
                NodeInfo node = parsed.pop();
                if (parsed.size() == 1) {
                    nodes.add(node);
                }
            } else if (event.isCharacters()) {
                if (2 < parsed.size()) {
                    NodeInfo parentNode = parsed.pop();
                    StartElement parentElement = parentNode.getStartElement();
                    NodeInfo grandparentNode = parsed.peek();
                    if (grandparentNode.getElement(parentElement) == null) {
                        grandparentNode.setElement(parentElement, event.asCharacters());
                    }
                    parsed.push(parentNode);
                }
            }
        }

        return nodes;
    }

    public static List<String> checkConf(InputStream in) {
        List<NodeInfo> nodes = null;
        List<String> errors = new ArrayList<String>();

        try {
            nodes = parseConf(in);
            if (nodes == null) {
                errors.add("bad conf file: top-level element not <configuration>");
            }
        } catch (XMLStreamException e) {
            errors.add("bad conf file: " + e.getMessage());
        }

        if (!errors.isEmpty()) {
            return errors;
        }

        Map<String, List<Integer>> duplicatedProperties = new HashMap<String, List<Integer>>();

        for (NodeInfo node : nodes) {
            StartElement element = node.getStartElement();
            int line = element.getLocation().getLineNumber();

            if (!element.getName().equals(new QName("property"))) {
                errors.add(String.format("Line %d: element not <property>", line));
                continue;
            }

            List<XMLEvent> events = node.getXMLEventsForQName(new QName("name"));
            if (events == null) {
                errors.add(String.format("Line %d: <property> has no <name>", line));
            } else {
                String v = null;
                for (XMLEvent event : events) {
                    if (event.isAttribute()) {
                        v = ((Attribute) event).getValue();
                    } else {
                        Characters c = node.getElement(event.asStartElement());
                        if (c != null) {
                            v = c.getData();
                        }
                    }
                    if (v == null || v.isEmpty()) {
                        errors.add(String.format("Line %d: <property> has an empty <name>", line));
                    }
                }
                if (v != null && !v.isEmpty()) {
                    List<Integer> lines = duplicatedProperties.get(v);
                    if (lines == null) {
                        lines = new ArrayList<Integer>();
                        duplicatedProperties.put(v, lines);
                    }
                    lines.add(node.getStartElement().getLocation().getLineNumber());
                }
            }

            events = node.getXMLEventsForQName(new QName("value"));
            if (events == null) {
                errors.add(String.format("Line %d: <property> has no <value>", line));
            }

            for (QName qName : node.getDuplicatedQNames()) {
                if (!qName.equals(new QName("source"))) {
                    errors.add(String.format("Line %d: <property> has duplicated <%s>s", line, qName));
                }
            }
        }

        for (Entry<String, List<Integer>> e : duplicatedProperties.entrySet()) {
            List<Integer> lines = e.getValue();
            if (1 < lines.size()) {
                errors.add(String.format("Line %s: duplicated <property>s for %s", StringUtils.join(", ", lines),
                        e.getKey()));
            }
        }

        return errors;
    }

    private static File[] listFiles(File dir) {
        return dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return file.isFile() && file.getName().endsWith(".xml");
            }
        });
    }

    @SuppressWarnings("static-access")
    public static void main(String[] args) throws IOException {
        GenericOptionsParser genericParser = new GenericOptionsParser(args);
        String[] remainingArgs = genericParser.getRemainingArgs();

        Option conf = OptionBuilder.hasArg().create("conffile");
        Option help = OptionBuilder.withLongOpt("help").create('h');
        Options opts = new Options().addOption(conf).addOption(help);
        CommandLineParser specificParser = new GnuParser();
        CommandLine cmd = null;
        try {
            cmd = specificParser.parse(opts, remainingArgs);
        } catch (MissingArgumentException e) {
            terminate(1, "No argument specified for -conffile option");
        } catch (ParseException e) {
            terminate(1, USAGE);
        }
        if (cmd == null) {
            terminate(1, "Failed to parse options");
        }

        if (cmd.hasOption('h')) {
            terminate(0, USAGE);
        }

        List<File> files = new ArrayList<File>();
        if (cmd.hasOption("conffile")) {
            String[] values = cmd.getOptionValues("conffile");
            for (String value : values) {
                File confFile = new File(value);
                if (confFile.isFile()) {
                    files.add(confFile);
                } else if (confFile.isDirectory()) {
                    for (File file : listFiles(confFile)) {
                        files.add(file);
                    }
                } else {
                    terminate(1, confFile.getAbsolutePath() + " is neither a file nor directory");
                }
            }
        } else {
            String confDirName = System.getenv(HADOOP_CONF_DIR);
            if (confDirName == null) {
                terminate(1, HADOOP_CONF_DIR + " does not defined");
            }
            File confDir = new File(confDirName);
            if (!confDir.isDirectory()) {
                terminate(1, HADOOP_CONF_DIR + " is not a directory");
            }
            files = Arrays.asList(listFiles(confDir));
        }
        if (files.isEmpty()) {
            terminate(1, "No input file to validate");
        }

        boolean ok = true;
        for (File file : files) {
            String path = file.getAbsolutePath();
            List<String> errors = checkConf(new FileInputStream(file));
            if (errors.isEmpty()) {
                System.out.println(path + ": valid");
            } else {
                ok = false;
                System.err.println(path + ":");
                for (String error : errors) {
                    System.err.println("\t" + error);
                }
            }
        }
        if (ok) {
            System.out.println("OK");
        } else {
            terminate(1, "Invalid file exists");
        }
    }

    private static void terminate(int status, String msg) {
        System.err.println(msg);
        System.exit(status);
    }
}

class NodeInfo {

    private StartElement startElement;
    private List<Attribute> attributes = new ArrayList<Attribute>();
    private Map<StartElement, Characters> elements = new HashMap<StartElement, Characters>();
    private Map<QName, List<XMLEvent>> qNameXMLEventsMap = new HashMap<QName, List<XMLEvent>>();

    public NodeInfo(StartElement startElement) {
        this.startElement = startElement;
    }

    private void addQNameXMLEvent(QName qName, XMLEvent event) {
        List<XMLEvent> events = qNameXMLEventsMap.get(qName);
        if (events == null) {
            events = new ArrayList<XMLEvent>();
            qNameXMLEventsMap.put(qName, events);
        }
        events.add(event);
    }

    public StartElement getStartElement() {
        return startElement;
    }

    public void addAttribute(Attribute attribute) {
        attributes.add(attribute);
        addQNameXMLEvent(attribute.getName(), attribute);
    }

    public Characters getElement(StartElement element) {
        return elements.get(element);
    }

    public void addElement(StartElement element) {
        setElement(element, null);
        addQNameXMLEvent(element.getName(), element);
    }

    public void setElement(StartElement element, Characters text) {
        elements.put(element, text);
    }

    public List<QName> getDuplicatedQNames() {
        List<QName> duplicates = new ArrayList<QName>();
        for (Map.Entry<QName, List<XMLEvent>> e : qNameXMLEventsMap.entrySet()) {
            if (1 < e.getValue().size()) {
                duplicates.add(e.getKey());
            }
        }
        return duplicates;
    }

    public List<XMLEvent> getXMLEventsForQName(QName qName) {
        return qNameXMLEventsMap.get(qName);
    }
}