blob: fa1ffd8433727ab779b05b7a2d15e1a9d36318c6 [file] [log] [blame]
/*
* 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.logging.log4j.core.config.xml;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.xml.XMLConstants;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.junit.jupiter.api.Test;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLFilterImpl;
import org.xml.sax.helpers.XMLReaderFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class XmlSchemaTest {
private static final String TARGET_NAMESPACE = "http://logging.apache.org/log4j/2.0/config";
private static final List<String> IGNORE_CONFIGS = Arrays.asList( //
"log4j2-arbiters.xml", // Arbiters violate XML schema as they can appear anywhere
"log4j2-scriptArbiters.xml",
"log4j2-selectArbiters.xml",
"log4j-core-gctests/src/test/resources/gcFreeLogging.xml", // has 2 <Pattern> tags defined
"legacy-plugins.xml", //
"logback", // logback configs
"log4j-xinclude", //
"log4j12", // log4j 1.x configs
"perf-CountingNoOpAppender.xml", // uses test-appender CountingNoOp
"reconfiguration-deadlock.xml", // uses test-appender ReconfigurationDeadlockTestAppender
"XmlConfigurationSecurity.xml" //
);
private static String capitalizeTags(final String xml) {
final StringBuffer sb = new StringBuffer();
final Matcher m = Pattern.compile("([<][/]?[a-z])").matcher(xml);
while (m.find()) {
m.appendReplacement(sb, m.group(1).toUpperCase());
}
return m.appendTail(sb).toString();
}
private static String fixXml(String xml) {
xml = StringUtils.replace(xml, "JSONLayout", "JsonLayout");
xml = StringUtils.replace(xml, "HTMLLayout", "HtmlLayout");
xml = StringUtils.replace(xml, "XMLLayout", "XmlLayout");
xml = StringUtils.replace(xml, "appender-ref", "AppenderRef");
xml = StringUtils.replace(xml, " onmatch=", " onMatch=");
xml = StringUtils.replace(xml, " onMisMatch=", " onMismatch=");
xml = StringUtils.replace(xml, " onmismatch=", " onMismatch=");
xml = StringUtils.replace(xml, "<Marker ", "<MarkerFilter ");
xml = StringUtils.replace(xml, " Value=", " value=");
xml = capitalizeTags(xml);
return xml;
}
@Test
public void testXmlSchemaValidation() throws SAXException, IOException {
final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Schema schema = factory.newSchema(new StreamSource(new File("src/main/resources/Log4j-config.xsd")));
final Validator validator = schema.newValidator();
final XMLFilterImpl namespaceAdder = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
@Override
public void startElement(final String namespace, final String localName, final String qName,
final Attributes atts) throws SAXException {
super.startElement(TARGET_NAMESPACE, localName, qName, atts);
}
};
final MutableInt configs = new MutableInt();
final MutableInt failures = new MutableInt();
try (final Stream<Path> testResources = Files.list(Paths.get("src", "test", "resources"))) {
testResources
.filter(filePath -> {
final String fileName = filePath.getFileName().toString();
if (!fileName.endsWith(".xml"))
return false;
for (final String ignore : IGNORE_CONFIGS) {
if (fileName.contains(ignore))
return false;
}
return true;
}) //
.forEach(filePath -> {
System.out.println("Validating " + configs.incrementAndGet() + ". [" + filePath + "]...");
System.out.flush();
try {
final String xml = fixXml(
FileUtils.readFileToString(filePath.toFile(), Charset.defaultCharset()));
validator.validate(new SAXSource(namespaceAdder,
new InputSource(new ByteArrayInputStream(xml.getBytes()))), null);
} catch (final Exception ex) {
System.err.println(ex);
System.err.flush();
failures.increment();
}
});
}
assertEquals(0, failures.intValue());
}
}