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

Java tutorial

Introduction

Here is the source code for org.openhab.tools.analysis.checkstyle.ServiceComponentManifestCheck.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.BUILD_PROPERTIES_FILE_NAME;
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.MANIFEST_EXTENSION;
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.MANIFEST_FILE_NAME;
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.OSGI_INF_DIRECTORY_NAME;
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.PROPERTIES_EXTENSION;
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.SERVICE_COMPONENT_HEADER_NAME;
import static org.openhab.tools.analysis.checkstyle.api.CheckConstants.XML_EXTENSION;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
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.AbstractStaticCheck;

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

/**
 * Check if all the declarative services are included in the MANIFEST.MF.
 *
 * @author Aleksandar Kovachev - Initial contribution
 * @author Petar Valchev - Changed the verification of the Service-Component
 *         header
 * @author Dimitar Ivanov - Common wildcard adaptions
 * @author Svilen Valkanov - Check the build.properties file
 */
public class ServiceComponentManifestCheck extends AbstractStaticCheck {
    private static final String WILDCARD = "*";

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

    private List<String> manifestServiceComponents = new ArrayList<>();
    private List<String> componentXmlFiles = new ArrayList<>();

    private boolean loggedBestApproachMessage = false;

    private String serviceComponentHeaderValue;
    private int serviceComponentHeaderLineNumber;
    private String manifestPath;
    private String buildPropertiesPath;

    /**
     * Paths relative to the bundle base directory
     **/
    private List<Path> componentXmlRelativePaths = new ArrayList<>();
    private IBuild buildPropertiesFile;

    public ServiceComponentManifestCheck() {
        String message = MessageFormat.format(
                "Executing {0}: Check if all the declarative services are included in the {1}",
                this.getClass().getName(), MANIFEST_FILE_NAME);
        logger.debug(message);
        setFileExtensions(MANIFEST_EXTENSION, XML_EXTENSION, PROPERTIES_EXTENSION);
    }

    @Override
    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {

        Path absolutePath = file.toPath();
        int osgiInfIndex = getIndex(absolutePath, OSGI_INF_DIRECTORY_NAME);
        String fileExtension = FilenameUtils.getExtension(file.getName());

        // The components are .xml files located in OSGI-INF folder
        if (fileExtension.equals(XML_EXTENSION) && osgiInfIndex > -1) {
            // All the defined components are collected to be processed later
            componentXmlFiles.add(file.getName());

            // Get the relative path
            Path relativePath = absolutePath.subpath(osgiInfIndex, absolutePath.getNameCount());
            componentXmlRelativePaths.add(relativePath);
        }

        if (file.getName().equals(MANIFEST_FILE_NAME)) {
            verifyManifest(fileText);
        }

        if (file.getName().equals(BUILD_PROPERTIES_FILE_NAME)) {
            processBuildPropertiesFile(fileText);
        }
    }

    private int getIndex(Path path, String dirName) {
        int result = -1;
        for (int i = 0; i < path.getNameCount(); i++) {
            if (path.getName(i).toString().equals(dirName)) {
                result = i;
                break;
            }
        }
        return result;
    }

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

    @Override
    public void finishProcessing() {
        verifyManifestWildcardDeclaredServiceComponents();
        verifyManifestExplicitlyDeclaredServices();
        verifyBuildPropertiesFile();
    }

    private void verifyBuildPropertiesFile() {
        if (buildPropertiesPath != null) {
            IBuildEntry binIncludes = buildPropertiesFile.getEntry(IBuildEntry.BIN_INCLUDES);
            if (binIncludes != null) {
                String[] includedTokens = binIncludes.getTokens();
                // Exclude the component files that are added to the bin.includes property
                for (String included : includedTokens) {
                    for (Iterator<Path> iterator = componentXmlRelativePaths.iterator(); iterator.hasNext();) {
                        Path componentXmlFile = iterator.next();
                        if (componentXmlFile.startsWith(included)) {
                            iterator.remove();
                        }
                    }
                }
            }

            for (Path path : componentXmlRelativePaths) {
                logMessage(buildPropertiesPath, 0, BUILD_PROPERTIES_FILE_NAME, MessageFormat.format(
                        "The service component {0} isn`t included in the build.properties file."
                                + " Good approach is to include all files by adding `OSGI-INF/` value to the bin.includes property.",
                        path));
            }
        }
    }

    private void verifyManifestWildcardDeclaredServiceComponents() {
        // We use iterator, because we will modify the list while iterating
        // through it
        Iterator<String> manifestServiceComponentsIterator = manifestServiceComponents.iterator();

        while (manifestServiceComponentsIterator.hasNext()) {
            String manifestServiceComponent = manifestServiceComponentsIterator.next();

            if (manifestServiceComponent.equals(WILDCARD)) {
                logBestApproachMessage();

                // The service component is declared as OSGI-INF/* and all the services are included. There is
                // no need of further comparison of the two lists
                manifestServiceComponents.clear();
                componentXmlFiles.clear();
                break;
            }

            // Now check all the .xml service component definitions
            String manifestServiceComponentName = StringUtils.substringBefore(manifestServiceComponent,
                    "." + XML_EXTENSION);

            if (manifestServiceComponentName.contains(WILDCARD)) {
                // *.xml is used in the service component declaration
                if (manifestServiceComponentName.equals(WILDCARD)) {
                    if (manifestServiceComponents.size() > 1) {
                        // if there is any explicit service declaration in
                        // addition to *.xml
                        logMessage(serviceComponentHeaderLineNumber,
                                "If you are using OSGI-INF/*.xml, do not include any of the services explicitly. "
                                        + "Otherwise they will be included more than once.");
                    }

                    // The service component is declared as *.xml and all the services are included. There is no need of
                    // further comparison of the two lists
                    manifestServiceComponents.clear();
                    componentXmlFiles.clear();
                    break;
                } else {
                    // Wildcard other than *.xml is used
                    logBestApproachMessage();
                    Pattern pattern = Pattern.compile(manifestServiceComponentName);
                    boolean matchedPattern = false;

                    // we use iterator, because we will modify the list while
                    // iterating through it
                    Iterator<String> componentXmlFilesIterator = componentXmlFiles.iterator();
                    while (componentXmlFilesIterator.hasNext()) {
                        String componentXml = componentXmlFilesIterator.next();
                        Matcher matcher = pattern.matcher(componentXml);
                        if (matcher.find()) {
                            // if any of the services matches the manifest
                            // service component regex,
                            // remove them from the list, so that we can verify
                            // only the service components,
                            // that are declared with their full name later
                            componentXmlFilesIterator.remove();
                            matchedPattern = true;
                        }
                    }

                    if (!matchedPattern) {
                        logMessage(serviceComponentHeaderLineNumber,
                                String.format(
                                        "The service component %s does not match any of the exisitng services.",
                                        manifestServiceComponent));
                    }

                    // remove the regex service component definition,
                    // so that we can verify only the service components,
                    // that are declared with their full name later
                    manifestServiceComponentsIterator.remove();
                }
            } else {
                // if no wildcard is used and the service is declared explicitly
                logBestApproachMessage();
            }
        }
    }

    private void verifyManifestExplicitlyDeclaredServices() {
        // list in which we will store all the common elements of
        // manifestServiceComponents and componentXmlFiles
        List<String> intersection = new ArrayList<>(manifestServiceComponents);
        intersection.retainAll(componentXmlFiles);

        // log a message for every not included service in the manifest
        componentXmlFiles.removeAll(intersection);
        for (String service : componentXmlFiles) {
            logMessage(serviceComponentHeaderLineNumber,
                    String.format("The service %s is not included in the MANIFEST.MF file. "
                            + "Are you sure that there is no need to be included?", service));
        }

        // log a message for every service component definition,
        // that does not have a corresponding service
        manifestServiceComponents.removeAll(intersection);
        for (String service : manifestServiceComponents) {
            logMessage(serviceComponentHeaderLineNumber,
                    String.format("The service %s does not exist in the OSGI-INF folder.", service));
        }
    }

    private void verifyManifest(FileText fileText) {
        File file = fileText.getFile();

        manifestPath = file.getPath();
        try {
            Manifest manifest = new Manifest(new FileInputStream(file));
            Attributes attributes = manifest.getMainAttributes();

            serviceComponentHeaderValue = attributes.getValue(SERVICE_COMPONENT_HEADER_NAME);

            if (serviceComponentHeaderValue != null) {
                serviceComponentHeaderLineNumber = findLineNumberSafe(fileText, SERVICE_COMPONENT_HEADER_NAME, 0,
                        "Service component header line number not found.");
                List<String> serviceComponentsList = Arrays.asList(serviceComponentHeaderValue.trim().split(","));
                for (String serviceComponent : serviceComponentsList) {
                    // We assume that the defined service component refers to existing file
                    File serviceComponentFile = new File(serviceComponent);
                    String serviceComponentParentDirectoryName = serviceComponentFile.getParentFile().getName();

                    if (!serviceComponentParentDirectoryName.equals(OSGI_INF_DIRECTORY_NAME)) {
                        // if the parent directory of the service is not
                        // OSGI-INF
                        logMessage(serviceComponentHeaderLineNumber,
                                String.format("Incorrect directory for services - %s. "
                                        + "The best practice is services metadata files to be placed directly in OSGI-INF directory.",
                                        serviceComponentParentDirectoryName));
                    }

                    String serviceComponentName = serviceComponentFile.getName();

                    // We will process either .xml or OSGi-INF/* service components
                    if (serviceComponentName.endsWith(XML_EXTENSION) || serviceComponentName.endsWith(WILDCARD)) {
                        manifestServiceComponents.add(serviceComponentName);
                    } else {
                        logMessage(serviceComponentHeaderLineNumber,
                                String.format("The service %s is with invalid extension."
                                        + "Only XML metadata files for services description are expected in the OSGI-INF directory.",
                                        serviceComponentName));
                    }
                }
            }
        } catch (IOException e) {
            logger.error("Problem occurred while parsing the file " + file.getPath(), e);
        }
    }

    private void logBestApproachMessage() {
        if (!loggedBestApproachMessage) {
            logMessage(serviceComponentHeaderLineNumber, "A good approach is to use OSGI-INF/*.xml "
                    + "instead of including the services metadata files separately or using common wildcard.");
            loggedBestApproachMessage = true;
        }
    }

    private void logMessage(int line, String message) {
        logMessage(manifestPath, line, MANIFEST_FILE_NAME, message);
    }
}