Java tutorial
/* * 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); } }