/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.test.smoke.subsystem.xml;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Date: 23.06.2011
*
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
public class AbstractValidationUnitTest {
private static final String SCHEMAS_LOCATION = "docs/schema";
private static final String JBOSS_DIST_PROP_NAME = "jboss.dist";
private static final String FUTURE_SCHEMA_PROP_NAME = "jboss.test.xml.validation.future.schemas";
private static final Set<String> EXCLUDED_SCHEMA_FILES = new HashSet<String>();
private static final Set<String> FUTURE_SCHEMA_FILES = new HashSet<String>();
private static final Map<String, File> JBOSS_SCHEMAS_MAP = new HashMap<String, File>();
private static final Map<String, File> CURRENT_JBOSS_SCHEMAS_MAP = new HashMap<String, File>();
private static Map<String, String> NAMESPACE_MAP = new HashMap<String, String>();
private static Map<String, String> OUTDATED_NAMESPACES = new HashMap<>();
private static final File JBOSS_DIST_DIR;
static {
// exclude JBoss EJB specific files which redefine the javaee namespace
// triggering the https://issues.apache.org/jira/browse/XERCESJ-1130 bug
EXCLUDED_SCHEMA_FILES.add("jboss-ejb3-2_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb3-spec-2_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-cache_1_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-container-interceptors_1_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-delivery-active_1_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-delivery-active_1_1.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-clustering_1_1.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-iiop_1_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-pool_1_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-resource-adapter-binding_1_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-security_1_0.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-security_1_1.xsd");
EXCLUDED_SCHEMA_FILES.add("jboss-ejb-security-role_1_0.xsd");
String coreVersion = System.getProperty("version.org.wildfly.core");
if (coreVersion != null) {
// We are testing a different version of core than was used in creating our standard configs
// See if we are configured to specially handle newer schema versions in that core
String excluded = System.getProperty(FUTURE_SCHEMA_PROP_NAME);
if (excluded != null) {
excluded = excluded.trim();
String[] pairs = excluded.split(",");
for (String pair : pairs) {
if (pair.length() > 0) {
// The format is <core_version>/<schema_file>
String[] tuple = pair.split("/");
// We only care about the pair if the <core_version> bit matches the
// value of the version.org.wildfly.core system property. This
// way if someone sets -Djboss.test.xml.validation.future.schemas
// in a CI test setup and then forgets to update the setup when
// the relevant core version gets released and integrated, the
// setting will no longer be effective and the no longer "future"
// xsd will get tested normally.
if (tuple.length == 2 && coreVersion.equals(tuple[0])) {
FUTURE_SCHEMA_FILES.add(tuple[1]);
}
}
}
}
}
NAMESPACE_MAP.put("http://java.sun.com/xml/ns/javaee/javaee_6.xsd", "schema/javaee_6.xsd");
NAMESPACE_MAP.put("http://www.w3.org/2001/xml.xsd", "schema/xml.xsd");
NAMESPACE_MAP.put("http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd", "schema/ejb-jar_3_1.xsd");
NAMESPACE_MAP.put("http://www.jboss.org/j2ee/schema/jboss-common_6_0.xsd", "jboss-common_6_0.xsd");
String asDir = System.getProperty(JBOSS_DIST_PROP_NAME);
if (null == asDir) {
JBOSS_DIST_DIR = null;
} else {
JBOSS_DIST_DIR = new File(asDir);
if (!JBOSS_DIST_DIR.exists())
throw new IllegalStateException("Directory set in '" + JBOSS_DIST_PROP_NAME + "' does not exist: " + JBOSS_DIST_DIR.getAbsolutePath());
final File schemaDir = new File(JBOSS_DIST_DIR, SCHEMAS_LOCATION);
final File[] xsds = schemaDir.listFiles(new SchemaFilter(EXCLUDED_SCHEMA_FILES.toArray(new String[EXCLUDED_SCHEMA_FILES.size()])));
for (File xsd : xsds) {
JBOSS_SCHEMAS_MAP.put(xsd.getName(), xsd);
}
Map<String, BigDecimal> mostRecentVersions = new HashMap<>();
Map<String, String> mostRecentNames = new HashMap<>();
Pattern pattern = Pattern.compile("(.*?)_(\\d)_(\\d).xsd");
for(Map.Entry<String, File> entry : JBOSS_SCHEMAS_MAP.entrySet()) {
if (FUTURE_SCHEMA_FILES.contains(entry.getKey())) {
// not "current"; it's future.
continue;
}
final Matcher match = pattern.matcher(entry.getKey());
if(!match.matches()) {
continue;
}
String name = match.group(1);
String major = match.group(2);
String minor = match.group(3);
BigDecimal version = new BigDecimal(major + "." + minor);
BigDecimal current = mostRecentVersions.get(name);
if(current == null || version.compareTo(current) > 0) {
mostRecentVersions.put(name, version);
mostRecentNames.put(name, entry.getKey());
}
}
for (Map.Entry<String, File> entry : JBOSS_SCHEMAS_MAP.entrySet()) {
if (FUTURE_SCHEMA_FILES.contains(entry.getKey())) {
// not "current" or "outdated"; it's future.
continue;
}
final Matcher match = pattern.matcher(entry.getKey());
if (!match.matches()) {
continue;
}
String name = match.group(1);
if (!mostRecentNames.get(name).equals(entry.getKey())) {
OUTDATED_NAMESPACES.put(entry.getKey(), mostRecentNames.get(name));
} else {
CURRENT_JBOSS_SCHEMAS_MAP.put(entry.getKey(), entry.getValue());
}
}
}
}
static final EntityResolver DEFAULT_ENTITY_RESOLVER = new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId == null)
fail("Failed to resolve schema: systemId is null");
int lastSlash = systemId.lastIndexOf('/');
if (lastSlash > 0)
systemId = systemId.substring(lastSlash + 1);
URL xsdUrl = discoverXsd(systemId);
return new InputSource(xsdUrl.openStream());
}
};
static final LSResourceResolver DEFAULT_RESOURCE_RESOLVER = new LSResourceResolver() {
@Override
public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
final DOMImplementationLS impl;
try {
impl = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException("could not create LS input" ,e);
}
LSInput input = impl.createLSInput();
final URL url;
if (NAMESPACE_MAP.containsKey(systemId)) {
url = discoverXsd(NAMESPACE_MAP.get(systemId));
} else {
url = discoverXsd(systemId);
}
try {
input.setByteStream(url.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
return input;
}
};
/**
* The base directory, e.g. {@literal $user.home/../../build/target/jboss-*}.
* <p/>
* Executes {@link org.junit.Assert#fail()} if the base directory is null.
*
* @return the base directory.
*/
protected static File getBaseDir() {
assertNotNull("'" + JBOSS_DIST_PROP_NAME + "' is not set.", JBOSS_DIST_DIR);
assertTrue("Directory set in '" + JBOSS_DIST_PROP_NAME + "' does not exist: " + JBOSS_DIST_DIR.getAbsolutePath(), JBOSS_DIST_DIR.exists());
return JBOSS_DIST_DIR;
}
private static Map<String, File> getSchemas(boolean currentSchemaOnly) {
assertFalse("No schemas found under " + getBaseDir().getAbsolutePath(), JBOSS_SCHEMAS_MAP.isEmpty());
if(currentSchemaOnly) {
return CURRENT_JBOSS_SCHEMAS_MAP;
}
return JBOSS_SCHEMAS_MAP;
}
/**
* A collections of the schema files.
*
* @return a collection of the schema files.
*/
static Collection<File> jbossSchemaFiles(boolean currentSchemasOnly) {
return getSchemas(currentSchemasOnly).values();
}
/**
* Attempts to discover the path to the XSD file.
*
* @param xsdName the xsd file name.
* @return the file.
*/
static URL discoverXsd(final String xsdName) {
if (OUTDATED_NAMESPACES.containsKey(xsdName)) {
throw new RuntimeException("Default configs are not in line with most recent schemas " + xsdName + " has been superseded by " + OUTDATED_NAMESPACES.get(xsdName));
}
final File file = JBOSS_SCHEMAS_MAP.get(xsdName);
URL url = null;
try {
if (file != null) {
url = file.toURI().toURL();
}
} catch (IOException e) {
url = null;
}
String fileName = xsdName;
int index = fileName.lastIndexOf("/");
if(index == -1) {
index = fileName.lastIndexOf("\\");
}
if(index != -1) {
fileName = fileName.substring(index + 1);
}
// Search
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (url == null)
url = classLoader.getResource("docs/schema/" + fileName);
if (url == null)
url = classLoader.getResource("docs/" + fileName);
if (url == null)
url = classLoader.getResource("schema/" + fileName);
if (url == null)
url = classLoader.getResource(fileName);
assertNotNull(xsdName + " not found", url);
return url;
}
/**
* Simple JBoss XSD filter
*/
private static class SchemaFilter implements FilenameFilter {
private static final Pattern PATTERN = Pattern.compile("(jboss|wildfly)-.*\\.xsd$");
private final String[] exclusions;
SchemaFilter() {
this.exclusions = new String[0];
}
SchemaFilter(final String[] exclusions) {
this.exclusions = exclusions;
}
@Override
public boolean accept(final File dir, final String name) {
final boolean accepted = PATTERN.matcher(name).find();
if (accepted) {
// check for explicit excluded files
for (final String excludedFile : exclusions) {
if (excludedFile.equals(name)) {
// file is in exclude list, so we don't accept this file
return false;
}
}
}
return accepted;
}
}
protected static final class ErrorHandlerImpl implements ErrorHandler {
@Override
public void error(SAXParseException e) throws SAXException {
fail(formatMessage(e));
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
fail(formatMessage(e));
}
@Override
public void warning(SAXParseException e) throws SAXException {
System.out.println(formatMessage(e));
}
private String formatMessage(SAXParseException e) {
StringBuffer sb = new StringBuffer();
sb.append(e.getLineNumber()).append(':').append(e.getColumnNumber());
if (e.getPublicId() != null)
sb.append(" publicId='").append(e.getPublicId()).append('\'');
if (e.getSystemId() != null)
sb.append(" systemId='").append(e.getSystemId()).append('\'');
sb.append(' ').append(e.getLocalizedMessage());
sb.append(" a possible cause may be that a subsystem is not using the most up to date schema.");
return sb.toString();
}
}
}