/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* 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
*******************************************************************************/
package org.ebayopensource.turmeric.runtime.config.validation;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import org.apache.commons.lang.StringUtils;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.jdom.JDOMXPath;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.Text;
import org.jdom.input.JDOMParseException;
import org.jdom.input.SAXBuilder;
public abstract class AbstractVerifier {
private static final Logger LOG = Logger.getLogger(AbstractVerifier.class.getName());
protected Report report;
protected List<String> excludedClasses;
private List<String> excludedConfigs;
private PathRegex pathregex;
protected String asArrayString(List<String> entries) {
StringBuilder str = new StringBuilder();
str.append("[");
boolean delim = false;
for (String entry : entries) {
if (delim) {
str.append(", ");
}
str.append(entry);
delim = true;
}
str.append("]");
return str.toString();
}
protected void assertClassExists(File hit, Document doc, String xpath) {
try {
Namespace ns = doc.getRootElement().getNamespace();
XPath expr = new JDOMXPath(xpath);
expr.addNamespace("c", ns.getURI());
List<?> nodes = expr.selectNodes(doc);
LOG.fine("Searching for: " + xpath);
for (Object node : nodes) {
String classname = null;
if (node instanceof Text) {
Text txt = (Text) node;
classname = txt.getTextNormalize();
assertClassExists(hit, txt.getParentElement(), classname);
continue;
}
if (node instanceof Element) {
Element elem = (Element) node;
classname = elem.getAttributeValue("class");
if (StringUtils.isBlank(classname)) {
classname = elem.getAttributeValue("class-name");
}
assertClassExists(hit, elem, classname);
continue;
}
LOG.fine("Found: " + node);
}
}
catch (JaxenException e) {
LOG.log(Level.WARNING, "Unable to apply xpath: " + xpath, e);
}
}
protected void assertClassExists(File hit, Element elemContext, String classname) {
LOG.fine("Found Text/ClassName: " + classname);
if (isExcludedClasses(classname)) {
return; // skip, excluded
}
if (!doesClassExists(classname)) {
String context = calculateXpathRefish(elemContext);
report.violation(context, "Class not found: %s", classname);
}
}
private boolean isExcludedClasses(String classname) {
if (this.excludedClasses == null) {
return false;
}
return this.excludedClasses.contains(classname);
}
public void setBaseDir(File dir) {
this.pathregex = new PathRegex(dir, getFileRegex());
}
public final void verifyAllConfigMatches() {
List<File> hits = FileFinder.findFileMatches(pathregex);
for (File hit : hits) {
verifyFile(hit);
}
}
public void verifyFile(File file) {
try {
report.fileStart(file);
String relpath = this.pathregex.getRelativePath(file);
if (isExcludedConfigs(relpath)) {
return; // skip. excluded config
}
if (file.length() <= 0) {
return; // skip. empty file
}
verifyHit(file);
}
finally {
report.fileEnd();
}
}
private boolean isExcludedConfigs(String relpath) {
if (this.excludedConfigs == null) {
return false;
}
return this.excludedConfigs.contains(relpath);
}
protected String calculateXpathRefish(Element elem) {
Stack<String> ref = new Stack<String>();
String name;
Element p, e = elem;
while (e != null) {
name = e.getName();
p = e.getParentElement();
if (p != null) {
name = getXmlElementRefName(e, p);
}
ref.push(name);
e = p;
}
StringBuilder refish = new StringBuilder();
refish.append("/");
while (!ref.empty()) {
refish.append("/").append(ref.pop());
}
return refish.toString();
}
private String getXmlElementRefName(Element e, Element p) {
String name = e.getName();
String attrid = e.getAttributeValue("id");
if (StringUtils.isNotBlank(attrid)) {
return name + "[@id='" + attrid + "']";
}
String attrname = e.getAttributeValue("name");
if (StringUtils.isNotBlank(attrname)) {
return name + "[@name='" + attrname + "']";
}
int idx = p.indexOf(e);
if (idx >= 0) {
name += "[" + idx + "]";
}
return name;
}
protected boolean doesClassExists(String classname) {
try {
Class.forName(classname, false, Thread.currentThread().getContextClassLoader());
return true;
}
catch (Exception e) {
return false;
}
}
public List<String> getExcludedClasses() {
return excludedClasses;
}
public List<String> getExcludedConfigs() {
return excludedConfigs;
}
public abstract String getFileRegex();
protected Matcher getHitRegexMatcher(File hit) {
return this.pathregex.getMatcher(hit);
}
public Report getReport() {
return report;
}
public void setReport(Report report) {
this.report = report;
}
public void setExcludedClasses(List<String> excludedClasses) {
this.excludedClasses = excludedClasses;
}
public void setExcludedConfigs(List<String> excludedConfigs) {
this.excludedConfigs = excludedConfigs;
}
public abstract void verifyHit(File hit);
public Document xmlParse(File xmlFile) throws JDOMException, IOException {
try {
SAXBuilder builder = new SAXBuilder(false);
return builder.build(xmlFile);
} catch(JDOMParseException e) {
report.violation(Integer.toString(e.getLineNumber()), "XML Parse Violation: %s",
e.getMessage());
throw e;
}
}
protected boolean validRootElement(File hit, Document doc, String expectedRootName) {
Element root = doc.getRootElement();
if (!expectedRootName.equals(root.getName())) {
report.violation("Root XML element", "Must have root element of <%s/> but found <%s/>",
expectedRootName, root.getName());
return false; // can't work with this XML :-(
}
return true;
}
protected boolean validRootElement(File hit, Document doc, String expectedRootName,
String expectedDefaultNamespace) {
Element root = doc.getRootElement();
if (!expectedRootName.equals(root.getName())) {
report.violation("Root XML element", "Must have root element of <%s/> but found <%s/>",
expectedRootName, root.getName());
return false; // can't work with this XML :-(
}
String actualNamespace = root.getNamespaceURI();
if (StringUtils.isBlank(actualNamespace)) {
report.violation(
"Default Namespace",
"Must have the default namespace declaration of <%s xmlns=\"%s\"/>, but found none",
expectedRootName, expectedDefaultNamespace);
return false; // can't work with this XML :-(
}
else if (!expectedDefaultNamespace.equals(actualNamespace)) {
report.violation(
"Default Namespace",
"Expected default namespace declaration <%s xmlns=\"%s\"> but actually found <%s xmlns=\"%s\"/>",
expectedRootName, expectedDefaultNamespace, root.getName(),
actualNamespace);
// while invalid, this doesn't prevent the rest of the tests from proceeding.
}
return true;
}
}