org.openhab.tools.analysis.checkstyle.EshInfXmlValidationCheck.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.tools.analysis.checkstyle.EshInfXmlValidationCheck.java

Source

/**
 * Copyright (c) 2010-2018 by the respective copyright holders.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.openhab.tools.analysis.checkstyle;

import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.*;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.XMLConstants;
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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.pde.core.build.IBuild;
import org.eclipse.pde.core.build.IBuildEntry;
import org.openhab.tools.analysis.checkstyle.api.AbstractEshInfXmlCheck;
import org.openhab.tools.analysis.utils.CachingHttpClient;
import org.openhab.tools.analysis.utils.ContentReceviedCallback;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.FileText;

/**
 * Validate the thing-types, binding and config xml-s against their xsd schemas.<br>
 * Check if all files from ESH-INF are included in the build.properties file.
 *
 * @author Aleksandar Kovachev - Initial implementation
 * @author Svlien Valkanov - Some code refactoring and cleanup,
 *         added check for the build.properties file,
 *         download schema files only once
 *
 */
public class EshInfXmlValidationCheck extends AbstractEshInfXmlCheck {
    private static final String MESSAGE_NOT_INCLUDED_XML_FILE = "The file {0} isn't included in the build.properties file. Good approach is to include all files by adding `ESH-INF/` value to the bin.includes property.";

    private final Log logger = LogFactory.getLog(this.getClass());

    private Map<Path, File> eshInfFiles = new HashMap<>();
    private IBuild buildPropertiesFile;

    private String thingSchema;
    private String bindingSchema;
    private String configSchema;

    private static Schema thingSchemaFile;
    private static Schema bindingSchemaFile;
    private static Schema configSchemaFile;

    /**
     * Sets the configuration property for the thing schema file.
     *
     * @param thingSchema URL of the thing schema file
     */
    public void setThingSchema(String thingSchema) {
        this.thingSchema = thingSchema;
    }

    /**
     * Sets the configuration property for the binding schema file.
     *
     * @param bindingSchema URL of the binding schema file
     */
    public void setBindingSchema(String bindingSchema) {
        this.bindingSchema = bindingSchema;
    }

    /**
     * Sets the configuration property for the config schema file.
     *
     * @param configSchema URL of the config schema file
     */
    public void setConfigSchema(String configSchema) {
        this.configSchema = configSchema;
    }

    public EshInfXmlValidationCheck() {
        setFileExtensions(XML_EXTENSION, PROPERTIES_EXTENSION);
    }

    @Override
    public void beginProcessing(String charset) {
        ContentReceviedCallback<Schema> callback = new ContentReceviedCallback<Schema>() {
            SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

            @Override
            public Schema transform(byte[] content) {
                try {
                    InputStream is = new ByteArrayInputStream(content);
                    return schemaFactory.newSchema(new StreamSource(is));
                } catch (SAXException e) {
                    logger.error("Unable to parse schema ", e);
                    return null;
                }
            }
        };

        CachingHttpClient<Schema> cachingClient = new CachingHttpClient<>(callback);

        bindingSchemaFile = getXSD(bindingSchema, cachingClient);
        thingSchemaFile = getXSD(thingSchema, cachingClient);
        configSchemaFile = getXSD(configSchema, cachingClient);

        super.beginProcessing(charset);
    }

    @Override
    public void finishProcessing() {
        // Check for missing ESH-INF files in the build.properties
        checkBuildProperties();
    }

    @Override
    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
        logger.debug("Processing the " + file.getName());

        if (file.getName().equals(BUILD_PROPERTIES_FILE_NAME)) {
            processBuildProperties(fileText);
        } else {
            super.processFiltered(file, fileText);
        }
    }

    private void checkBuildProperties() {
        if (buildPropertiesFile != null) {
            IBuildEntry binIncludes = buildPropertiesFile.getEntry(IBuildEntry.BIN_INCLUDES);
            if (binIncludes != null) {
                String[] includedTokens = binIncludes.getTokens();
                // Exclude processed files that are included
                for (String included : includedTokens) {
                    // Iterator is used, as the collection will be modified
                    for (Iterator<Entry<Path, File>> it = eshInfFiles.entrySet().iterator(); it.hasNext();) {
                        Map.Entry<Path, File> entry = it.next();
                        if (entry.getKey().startsWith(included)) {
                            it.remove();
                        }
                    }
                }
            }
            logMissingEntries(eshInfFiles, MESSAGE_NOT_INCLUDED_XML_FILE);
        }
    }

    @Override
    protected void checkConfigFile(FileText xmlFileText) throws CheckstyleException {
        File xmlFile = xmlFileText.getFile();
        addToEshFiles(xmlFile);
        validateXmlAgainstSchema(xmlFile, configSchemaFile);
    }

    @Override
    protected void checkBindingFile(FileText xmlFileText) throws CheckstyleException {
        File xmlFile = xmlFileText.getFile();
        addToEshFiles(xmlFile);
        validateXmlAgainstSchema(xmlFile, bindingSchemaFile);
    }

    @Override
    protected void checkThingTypeFile(FileText xmlFileText) throws CheckstyleException {
        File xmlFile = xmlFileText.getFile();
        addToEshFiles(xmlFile);
        validateXmlAgainstSchema(xmlFile, thingSchemaFile);
    }

    private void processBuildProperties(FileText fileText) throws CheckstyleException {
        try {
            buildPropertiesFile = parseBuildProperties(fileText);
        } catch (CheckstyleException e) {
            logger.error("Problem occurred while parsing the file " + fileText.getFile().getPath(), e);
        }
    }

    private void validateXmlAgainstSchema(File xmlFile, Schema schema) {
        if (schema != null) {
            try {
                Validator validator = schema.newValidator();
                validator.validate(new StreamSource(xmlFile));
            } catch (SAXParseException exception) {
                String message = exception.getMessage();
                // Removing the type of the logged message (For example - "cvc-complex-type.2.4.b: ...").
                message = message.substring(message.indexOf(":") + 2);
                int lineNumber = exception.getLineNumber();
                log(lineNumber, message, xmlFile.getPath());
            } catch (IOException | SAXException e) {
                logger.error("Problem occurred while parsing the file " + xmlFile.getName(), e);
            }
        } else {
            logger.warn("XML validation will be skipped as the schema file download failed.");
        }
    }

    private <K> void logMissingEntries(Map<K, File> collection, String message) {
        for (K element : collection.keySet()) {
            File xmlFile = collection.get(element);
            logMessage(xmlFile.getPath(), 0, xmlFile.getName(), MessageFormat.format(message, element));
        }
    }

    private void addToEshFiles(File xmlFile) {
        Path filePath = xmlFile.toPath();
        Path bundlePath = filePath.getParent().getParent().getParent();
        Path relativePath = bundlePath.relativize(filePath);
        eshInfFiles.put(relativePath, xmlFile);
    }

    private Schema getXSD(String schemaUrlString, CachingHttpClient<Schema> client) {
        try {
            URL schemaUrl = new URL(schemaUrlString);
            return client.get(schemaUrl);
        } catch (IOException e) {
            String message = MessageFormat.format("Unable to get XSD file {0} : {1}", schemaUrlString,
                    e.getMessage());
            logger.error(message, e);
            return null;
        }

    }
}