org.kisoonlineapp.XthmlCheckTest.java Source code

Java tutorial

Introduction

Here is the source code for org.kisoonlineapp.XthmlCheckTest.java

Source

/*
 * Copyright 2015 berni.
 *
 * 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
 *
 * 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.kisoonlineapp;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang.StringUtils;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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;

/**
 *
 * @author berni
 */
public class XthmlCheckTest {

    private static final Logger logger = Logger.getLogger("XthmlCheckTest");
    private final String srcmainwebapp = "src/main/webapp";
    private final File defaultBaseDir = new File(srcmainwebapp);

    @BeforeClass
    public static void setUpClass() {
    }

    @AfterClass
    public static void tearDownClass() {
    }

    private DocumentBuilder documentBuilder;
    private XPath xPath;

    @Before
    public void setUp() throws ParserConfigurationException {
        final XMLAndXPathFactory f = new XMLAndXPathFactory();
        this.documentBuilder = f.createDocumentBuilder();
        this.xPath = f.createXPath();
    }

    @After
    public void tearDown() {
    }

    /**
     * Assert that specific elements have id-attribute.
     *
     * @throws Exception
     */
    @Test
    public void testCheckMissingIds() throws Exception {
        final List<String> messageList = new ArrayList<>();
        final List<File> theFilesToCheck = gobbleAllFilesMatching(0, defaultBaseDir);
        assertTrue("#Files " + theFilesToCheck.size(), theFilesToCheck.size() > 0);

        final CheckMissingIds checkMissingIds = new CheckMissingIds();
        for (File theFile : theFilesToCheck) {
            logger.log(Level.FINEST, "Checking file {0}", theFile);
            //---
            final List<String> aMessageList = checkMissingIds.checkFile(theFile);
            messageList.addAll(aMessageList);
        }
        if (!messageList.isEmpty()) {
            fail(messageList.toString());
        }
    }

    /**
     * Assert that elements have unique id-attribute.
     *
     * @throws Exception
     */
    @Test
    public void testChekDuplicateIds() throws Exception {
        final List<String> messageList = new ArrayList<>();
        final List<File> theFilesToCheck = gobbleAllFilesMatching(0, defaultBaseDir);
        assertTrue("#Files " + theFilesToCheck.size(), theFilesToCheck.size() > 0);

        final ChekDuplicateIds chekDuplicateIds = new ChekDuplicateIds();
        for (File theFile : theFilesToCheck) {
            logger.log(Level.FINEST, "Checking file {0}", theFile);
            //---
            List<String> aMessageList = chekDuplicateIds.checkFile(theFile);
            messageList.addAll(aMessageList);
        }
        if (!messageList.isEmpty()) {
            //logger.log(Level.FINEST, warningMessageList.toString());
            fail(messageList.toString());
        }
    }

    /**
     * Assert that specific elements have id-attribute.
     *
     * @throws Exception
     */
    @Test
    public void testCheckMissingAttrs() throws Exception {
        final List<String> messageList = new ArrayList<>();
        final List<File> theFilesToCheck = gobbleAllFilesMatching(0, defaultBaseDir);
        assertTrue("#Files " + theFilesToCheck.size(), theFilesToCheck.size() > 0);

        final List<ICheckFile> checkFileList = Arrays.asList(new CheckMissingRole());

        for (ICheckFile icheckFile : checkFileList) {
            for (File theFile : theFilesToCheck) {
                logger.log(Level.FINEST, "Using {0} checking file {1}",
                        new Object[] { icheckFile.getClass().getName(), theFile });
                //---
                List<String> aMessageList = icheckFile.checkFile(theFile);
                messageList.addAll(aMessageList);
            }
            if (!messageList.isEmpty()) {
                fail(messageList.toString());
            }
        }
    }

    interface ICheckFile {

        List<String> checkFile(File theFile) throws Exception;
    }

    /**
     * Kapselung um fehlende id-Attributes zu finden
     */
    private class CheckMissingRole implements ICheckFile {

        private final List<XPathExpression> xPathExpressionList;

        CheckMissingRole() throws XPathExpressionException {
            XPathCompilerFactory xPathCompilerFactory = new XPathCompilerFactory();
            xPathExpressionList = xPathCompilerFactory.compileExpressions(xPath, new String[] {
                    //
                    "//h:commandButton", "//h:commandLink", "//h:outputLink", "//h:form", });
        }

        @Override
        public List<String> checkFile(File theFile) throws Exception {
            final List<String> failMessageList = new ArrayList<>();
            String attrName = "role";

            List<NodeList> listOfMatchingNodeList = retrieveMatchingElementsFrom(theFile);
            for (NodeList matchingNodeList : listOfMatchingNodeList) {
                for (int i = 0; i < matchingNodeList.getLength(); i++) {
                    Node aNode = matchingNodeList.item(i);
                    logger.log(Level.FINEST, "Checking node {0}", aNode);
                    NamedNodeMap aNamedNodeMap = aNode.getAttributes();
                    Node aIdAttributeNode = aNamedNodeMap.getNamedItem(attrName);
                    if (aIdAttributeNode == null) {
                        String msg = ">>> >>>" + "\n" + "Missing attribute " + attrName + " in " + theFile + ", "
                                + formatNode(aNode) + "\n" + formatNamedNodeMap(aNamedNodeMap) + "<<< <<<" + "\n";
                        failMessageList.add(msg);
                    }
                }
            }
            return failMessageList;
        }

        /**
         * Parse and match xml against some xpath-expressions.
         *
         * @param theFile
         * @return list of matching nodes list.
         *
         * @throws ParserConfigurationException
         * @throws XPathExpressionException
         * @throws FileNotFoundException
         * @throws SAXException
         * @throws IOException
         */
        private List<NodeList> retrieveMatchingElementsFrom(File theFile) throws ParserConfigurationException,
                XPathExpressionException, FileNotFoundException, SAXException, IOException {
            final Document document = documentBuilder.parse(theFile);
            // Quercheck xmlns-prefix muss prefix in NamespaceContextFactory matchen
            final List<NodeList> listOfNodeList = new ArrayList<>();
            for (int i = 0; i < xPathExpressionList.size(); i++) {
                XPathExpression xPathExpression = xPathExpressionList.get(i);
                NodeList nodeList = (NodeList) xPathExpression.evaluate(document, XPathConstants.NODESET);
                logger.log(Level.FINEST, "Evaluated {0}: {1}, {2}", new Object[] {
                        xPathExpression.toString() + ": " + nodeList + ", " + nodeList.getLength() });
                listOfNodeList.add(nodeList);
            }

            return listOfNodeList;
        }
    }

    /**
     * Kapselung um fehlende id-Attributes zu finden
     */
    private class CheckMissingIds implements ICheckFile {

        private final List<XPathExpression> xPathExpressionList;

        CheckMissingIds() throws XPathExpressionException {
            XPathCompilerFactory xPathCompilerFactory = new XPathCompilerFactory();
            xPathExpressionList = xPathCompilerFactory.compileExpressions(xPath, new String[] {
                    //
                    "//h:inputTextarea", "//h:inputText", "//h:inputSecret", "//h:inputHidden", "//h:commandButton",
                    "//h:commandLink", "//h:form", "//h:selectOneListbox", "//h:selectOneMenu",
                    "//h:selectOneRadio", "//h:selectBooleanCheckbox", "//h:selectManyCheckbox",
                    "//h:selectManyListbox", "//h:selectManyMenu", "//input", "//textarea", });
        }

        @Override
        public List<String> checkFile(File theFile) throws Exception {
            final List<String> failMessageList = new ArrayList<>();
            final Set<String> idValueSet = new HashSet<>();
            List<NodeList> listOfMatchingNodeList = retrieveMatchingElementsFrom(theFile);
            for (NodeList matchingNodeList : listOfMatchingNodeList) {
                for (int i = 0; i < matchingNodeList.getLength(); i++) {
                    Node aNode = matchingNodeList.item(i);
                    logger.log(Level.FINEST, "Checking node {0}", aNode);
                    NamedNodeMap aNamedNodeMap = aNode.getAttributes();
                    Node aIdAttributeNode = aNamedNodeMap.getNamedItem("id");
                    if (aIdAttributeNode == null) {
                        String msg = ">>> >>>" + "\n" + "Missing attribute id in " + theFile + ", "
                                + formatNode(aNode) + "\n" + formatNamedNodeMap(aNamedNodeMap) + "<<< <<<" + "\n";
                        failMessageList.add(msg);
                    } else {
                        String idValue = aIdAttributeNode.getNodeValue();
                        if (idValueSet.contains(idValue)) {
                            String msg = ">>> >>>" + "\n" + "Duplicate attribute id value in " + theFile + ", "
                                    + formatNode(aNode) + "\n" + formatNamedNodeMap(aNamedNodeMap) + "<<< <<<"
                                    + "\n";
                            failMessageList.add(msg);
                        } else {
                            idValueSet.add(idValue);
                        }
                    }
                }
            }
            return failMessageList;
        }

        /**
         * Parse and match xml against some xpath-expressions.
         *
         * @param theFile
         * @return list of matching nodes list.
         *
         * @throws ParserConfigurationException
         * @throws XPathExpressionException
         * @throws FileNotFoundException
         * @throws SAXException
         * @throws IOException
         */
        private List<NodeList> retrieveMatchingElementsFrom(File theFile) throws ParserConfigurationException,
                XPathExpressionException, FileNotFoundException, SAXException, IOException {
            final Document document = documentBuilder.parse(theFile);
            // Quercheck xmlns-prefix muss prefix in NamespaceContextFactory matchen
            final List<NodeList> listOfNodeList = new ArrayList<>();
            for (int i = 0; i < xPathExpressionList.size(); i++) {
                XPathExpression xPathExpression = xPathExpressionList.get(i);
                NodeList nodeList = (NodeList) xPathExpression.evaluate(document, XPathConstants.NODESET);
                logger.log(Level.FINEST, "Evaluated {0}: {1}, {2}",
                        new Object[] { xPathExpression.toString(), nodeList, nodeList.getLength() });
                listOfNodeList.add(nodeList);
            }

            return listOfNodeList;
        }
    }

    /**
     * Kapselung um duplicate-ids zu finden.
     *
     */
    private class ChekDuplicateIds implements ICheckFile {

        private final List<XPathExpression> xPathExpressionList;

        ChekDuplicateIds() throws XPathExpressionException {
            XPathCompilerFactory xPathCompilerFactory = new XPathCompilerFactory();
            xPathExpressionList = xPathCompilerFactory.compileExpressions(xPath, new String[] { "//@id", });
        }

        @Override
        public List<String> checkFile(File theFile) throws Exception {
            final List<String> failMessageList = new ArrayList<>();
            final Set<String> idValueSet = new HashSet<>();
            List<NodeList> listOfMatchingNodeList = retrieveMatchingIdAttributeFrom(theFile);
            for (NodeList matchingNodeList : listOfMatchingNodeList) {
                for (int i = 0; i < matchingNodeList.getLength(); i++) {
                    Node aNode = matchingNodeList.item(i);
                    logger.log(Level.FINEST, "Checking node ", aNode);
                    String idValue = aNode.getNodeValue();
                    if (idValueSet.contains(idValue)) {
                        String msg = ">>> >>>" + "\n" + "Duplicate attribute id value in " + theFile + ", "
                                + formatNode(aNode) + "\n" + "<<< <<<" + "\n";
                        failMessageList.add(msg);
                    } else {
                        idValueSet.add(idValue);
                    }

                }
            }
            return failMessageList;
        }

        private List<NodeList> retrieveMatchingIdAttributeFrom(File theFile) throws ParserConfigurationException,
                XPathExpressionException, FileNotFoundException, SAXException, IOException {
            final Document document = documentBuilder.parse(theFile);
            // Quercheck xmlns-prefix muss prefix in NamespaceContextFactory matchen
            final List<NodeList> listOfNodeList = new ArrayList<>();
            for (int i = 0; i < xPathExpressionList.size(); i++) {
                final XPathExpression xPathExpression = xPathExpressionList.get(i);
                NodeList nodeList = (NodeList) xPathExpression.evaluate(document, XPathConstants.NODESET);
                logger.log(Level.FINEST, "Evaluated {0}: {1}, {2}",
                        new Object[] { xPathExpression.toString(), nodeList, nodeList.getLength() });
                listOfNodeList.add(nodeList);
            }

            return listOfNodeList;
        }
    }

    /**
     * Huebsches String-Format fuer einen Node
     *
     * @param n
     * @return human readable repr. of a Node
     */
    private String formatNode(Node n) {
        final String SEP = "\n";
        StringBuilder sb = new StringBuilder();
        sb.append(n.getNodeName()).append(SEP).append(n.getNodeValue()).append(SEP).append(n.getNodeType())
                .append(SEP).append(n.getBaseURI()).append(SEP).append(n.getLocalName()).append(SEP)
                .append(n.getNamespaceURI()).append(SEP).append(n.getPrefix()).append(SEP);
        String formatNode = sb.toString();
        return formatNode;
    }

    /**
     * Huebsches String-Format fuer einen NamedNodeMap (ie. attributes)
     *
     * @param nnm
     * @return human readable repr. of a Node
     */
    private String formatNamedNodeMap(NamedNodeMap nnm) {
        final String SEP = "\n";
        StringBuilder sb = new StringBuilder();
        sb.append(nnm.getLength());
        for (int i = 0; i < nnm.getLength(); i++) {
            Node n = nnm.item(i);
            sb.append("#").append(i).append(n.getNodeName()).append(": ").append(n.getNodeValue()).append(SEP);
        }
        String formatNamedNodeMap = sb.toString();
        return formatNamedNodeMap;
    }

    /**
     * Find all xhtml files recurivly
     *
     * @param depth
     * @param baseDir
     * @return
     */
    private List<File> gobbleAllFilesMatching(int depth, File baseDir) {
        final int MAX_DEPTH = 50;
        final List<File> theFiles = new ArrayList<>();
        if (depth > MAX_DEPTH) {
            return theFiles;
        }
        final XhtmlFileFilter xhtmlFileFilter = new XhtmlFileFilter();
        File[] files = baseDir.listFiles(xhtmlFileFilter);
        if (files != null && files.length > 0) {
            for (File aFile : Arrays.asList(files)) {
                if (aFile.isDirectory()) {
                    List<File> filesToAdd = gobbleAllFilesMatching(depth + 1, aFile);
                    theFiles.addAll(filesToAdd);
                } else {
                    theFiles.add(aFile);
                }
            }
        }
        return theFiles;
    }

    static class XMLAndXPathFactory {

        public XPath createXPath() {
            XPath xpath = XPathFactory.newInstance().newXPath();
            final NamespaceContextFactory namespaceContextFactory = new NamespaceContextFactory();
            xpath.setNamespaceContext(namespaceContextFactory.createNamespaceContext());
            return xpath;
        }

        /**
         * Factory for setting up xpath namespace-context
         */
        private static class NamespaceContextFactory {

            private final Map<String, String> mapPrefixNamespaceURI = new HashMap<>();

            NamespaceContextFactory() {
                final String[] prefixNamespaceURI = { "", "http://www.w3.org/1999/xhtml", "xhtml",
                        "http://www.w3.org/1999/xhtml", "ui", "http://java.sun.com/jsf/facelets", "f",
                        "http://java.sun.com/jsf/core", "h", "http://java.sun.com/jsf/html", "kiso",
                        "http://kisoonlineapp/facelets", "forge", "http://jboss.org/forge/view", };
                for (int i = 0; i < prefixNamespaceURI.length; i += 2) {
                    String prefix = prefixNamespaceURI[i];
                    String uri = prefixNamespaceURI[i + 1];
                    mapPrefixNamespaceURI.put(prefix, uri);
                }
            }

            NamespaceContext createNamespaceContext() {
                NamespaceContext namespaceContext = new NamespaceContext() {

                    @Override
                    public String getNamespaceURI(String prefix) {
                        String uri = mapPrefixNamespaceURI.get(prefix);
                        if (uri == null) {
                            uri = XMLConstants.NULL_NS_URI;
                        }
                        return uri;
                    }

                    @Override
                    public Iterator getPrefixes(String val) {
                        return null;
                    }

                    @Override
                    public String getPrefix(String uri) {
                        return null;
                    }
                };
                return namespaceContext;
            }
        }

        /**
         * Create DocumentBuilder which is namespace-aware, having fast
         * entity-resolver, and throwing exceptions in case of parsing
         * exceptions.
         *
         * @return
         * @throws ParserConfigurationException
         */
        public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
            final DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            builderFactory.setNamespaceAware(true);
            builderFactory.setValidating(false);
            assertEquals(false, builderFactory.isCoalescing());
            assertEquals(true, builderFactory.isExpandEntityReferences());
            assertEquals(false, builderFactory.isIgnoringComments());
            assertEquals(false, builderFactory.isIgnoringElementContentWhitespace());
            assertEquals(true, builderFactory.isNamespaceAware());
            assertEquals(false, builderFactory.isValidating());
            assertEquals(false, builderFactory.isXIncludeAware());

            final DocumentBuilder builder = builderFactory.newDocumentBuilder();
            assertEquals(true, builder.isNamespaceAware());
            assertEquals(false, builder.isValidating());

            // Use dummy entity resolver, skipping reading the systemId
            // takes to much time parsing systemIds like "http://www.w3c.org/....
            builder.setEntityResolver(createEntityResolver());

            builder.setErrorHandler(createErrorHandler());

            return builder;
        }

        protected EntityResolver createEntityResolver() {
            EntityResolver entityResolver = new EntityResolver() {

                @Override
                public InputSource resolveEntity(String publicId, String systemId)
                        throws SAXException, IOException {
                    logger.log(Level.FINEST, "resolveEntity {0} {1}", new Object[] { publicId, systemId });
                    byte[] buf = new byte[0];
                    ByteArrayInputStream bais = new ByteArrayInputStream(buf);
                    // return an empty input source....
                    InputSource inputSource = new InputSource(bais);
                    //String minimalDTD = "<!--- minimal dtd -->";
                    //InputSource inputSource = new InputSource(new StringReader(minimalDTD));
                    return inputSource;
                }
            };
            return entityResolver;
        }

        protected ErrorHandler createErrorHandler() {
            ErrorHandler errorHandler = new ErrorHandler() {

                @Override
                public void warning(SAXParseException exception) throws SAXException {
                    throw new RuntimeException("Parsing warning ", exception);
                }

                @Override
                public void error(SAXParseException exception) throws SAXException {
                    throw new RuntimeException("Parsing error ", exception);
                }

                @Override
                public void fatalError(SAXParseException exception) throws SAXException {
                    throw new RuntimeException("Parsing fatalError ", exception);
                }
            };
            return errorHandler;
        }
    }

    static class XPathCompilerFactory {

        List<XPathExpression> compileExpressions(XPath xPath, String[] expressions)
                throws XPathExpressionException {
            final List<XPathExpression> xPathExpressionList = new ArrayList<>();

            for (int i = 0; i < expressions.length; i++) {
                final String expression = expressions[i];
                final XPathExpression xPathExpression = xPath.compile(expression);
                xPathExpressionList.add(xPathExpression);
            }
            return xPathExpressionList;
        }
    }

    /**
     * File filter accepting only directory and files having ext ".xhtml"
     */
    private static class XhtmlFileFilter implements FileFilter {

        @Override
        public boolean accept(File pathname) {
            final boolean acceptDir;
            acceptDir = pathname.isDirectory()
                    // excluding dir names containing
                    && !StringUtils.contains(pathname.getAbsolutePath(), ".svn")
                    && !StringUtils.contains(pathname.getAbsolutePath(), ".git")
                    && !StringUtils.contains(pathname.getAbsolutePath(), "tmp")
                    && !StringUtils.contains(pathname.getAbsolutePath(), "temp");
            final boolean acceptFile;
            acceptFile = pathname.canRead() && pathname.isFile()
            // accepting filename
                    && StringUtils.endsWith(pathname.getName(), "xhtml");
            final boolean accept = acceptDir || acceptFile;
            logger.log(Level.FINEST, "Filtering {0}: {1}", new Object[] { pathname, accept });

            return accept;
        }
    }
}