org.sonar.plugins.qualityprofileprogression.batch.ProfileProgressionDecorator.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.plugins.qualityprofileprogression.batch.ProfileProgressionDecorator.java

Source

/*
 * Quality Profile Progression
 * Copyright (C) 2012 David T S Maitland
 * david.ts.maitland@gmail.com
 *
 * This program 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 3 of the License, or (at your option) any later version.
 *
 * This program 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 program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonar.plugins.qualityprofileprogression.batch;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Decorator;
import org.sonar.api.batch.DecoratorContext;
import org.sonar.api.config.Settings;
import org.sonar.api.database.DatabaseSession;
import org.sonar.api.database.model.ResourceModel;
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationManager;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.Violation;
import org.sonar.jpa.dao.ProfilesDao;
import org.sonar.jpa.dao.RulesDao;
import org.sonar.plugins.qualityprofileprogression.ProfileProgressionException;
import org.sonar.plugins.qualityprofileprogression.ProfileProgressionPlugin;
import org.sonar.plugins.qualityprofileprogression.ProjectProfileProgressionStatus;
import org.sonar.plugins.qualityprofileprogression.api.ProfilesManager;

public class ProfileProgressionDecorator implements Decorator {
    private DatabaseSession session;
    private RulesProfile profile;
    private Settings settings;
    private NotificationManager notificationManager;
    private ProfilesDao profilesDao;
    private RulesDao rulesDao;
    private ProfilesManager profilesManager;
    private QualityProfileProjectDao qualityProfileProjectDao;
    Logger logger = LoggerFactory.getLogger(this.getClass());

    public ProfileProgressionDecorator(DatabaseSession session, RulesProfile profile, Settings settings,
            NotificationManager notificationManager) {
        super();
        this.session = session;
        this.profile = profile;
        this.settings = settings;
        this.notificationManager = notificationManager;
        this.rulesDao = new RulesDao(session);
        this.profilesManager = new ProfilesManager(session, rulesDao);
        this.profilesDao = new ProfilesDao(session);
        this.qualityProfileProjectDao = new QualityProfileProjectDao(session);
    }

    public boolean shouldExecuteOnProject(Project project) {
        return isPluginEnabled();
    }

    protected boolean isPluginEnabled() {
        // first check if its been disabled globally
        boolean isEnabled = settings.getBoolean(ProfileProgressionPlugin.GLOBAL_QUALITY_PROFILE_CHANGE_ENABLED_KEY);

        logger.debug("{} is{} enabled at global level.", this.getClass().getSimpleName(),
                (isEnabled ? "" : " not"));

        if (isEnabled)
        // also check project
        {
            isEnabled = settings.getBoolean(ProfileProgressionPlugin.PROJECT_QUALITY_PROFILE_CHANGE_ENABLED_KEY);

            logger.debug("{} is{} enabled at project level.", this.getClass().getSimpleName(),
                    (isEnabled ? "" : " not"));
        }

        return isEnabled;
    }

    public void decorate(Resource resource, DecoratorContext context) {
        if (Scopes.isProject(resource)) {
            try {
                logger.debug("Analysed project: {}", resource.getKey());

                Project project = (Project) resource;

                int violationThreshold = getViolationThreshold();

                String[] profileNameArray = getProfileNameArray(resource.getLanguage().getKey());

                int projectViolationPercentage = getProjectsViolationPercentage(context);

                // if measure lower than threshold progress quality profile
                if (projectViolationPercentage < violationThreshold) {
                    logger.info("{} project's % violations ({}) lower than threshold ({}).", new Object[] {
                            resource.getEffectiveKey(), projectViolationPercentage, violationThreshold });

                    progressQualityProfile(project, profileNameArray, projectViolationPercentage,
                            violationThreshold);
                } else {
                    logger.debug("Project's % violations is not lower than threshold.");
                }

            } catch (ProfileProgressionException pie) {
                logger.warn(pie.getMessage());
            }
        }
    }

    protected void progressQualityProfile(Project project, String[] profileNameArray,
            int projectViolationPercentage, int violationThreshold) throws ProfileProgressionException {
        // get current quality profile - profile class
        // variable
        String profileName = profile.getName();

        logger.debug("Project's current profile: {}", profileName);

        // get current profile index
        int currentProfileIndex = getMatchingIndex(profileNameArray, profileName);

        // loop variables
        RulesProfile profileToUpdate = null;
        String parentProfileName = null;

        // if direct profile not a managed one
        check_profile_hierarchy: while (currentProfileIndex < 0) {
            if (profileToUpdate == null) {
                logger.debug("{} project's profile ({}) is not in incremetor's set {}.",
                        new Object[] { project.getEffectiveKey(), profileName, profileNameArray });
            }

            logger.debug("Getting {} profile's parent.", profileName);

            // get profile object
            profileToUpdate = profilesDao.getProfile(project.getLanguage().getKey(), profileName);

            if (profileToUpdate == null) {
                throw new ProfileProgressionException("Unable to find profile '" + profileName + "' for language '"
                        + project.getLanguage().getName() + "'.");
            } else {
                // get parent profile's name
                parentProfileName = profileToUpdate.getParentName();

                if (parentProfileName != null) {
                    logger.debug("{} profile's parent is {}.", profileName, parentProfileName);

                    // check if parent profile is managed
                    currentProfileIndex = getMatchingIndex(profileNameArray, parentProfileName);
                } else {
                    logger.debug("Profile has null parent.");
                    profileToUpdate = null;
                    break check_profile_hierarchy;
                }

                // make parent current profile so we get that profile object in
                // next loop iteration
                profileName = parentProfileName;
            }
        }

        // if managed profile not found anywhere update the project to first
        // profile
        // or use next profile in list
        int nextProfileIndex = currentProfileIndex + 1;

        if (nextProfileIndex >= profileNameArray.length) {
            logger.debug("Project is associated with last profile ({}) in set ({}).", profile.getName(),
                    profileNameArray);
        } else {
            String nextProfileName = profileNameArray[nextProfileIndex];
            logger.debug("Next profile name is {}.", nextProfileName);

            // build project resource model
            ResourceModel projectModel = session.getEntityManager().find(ResourceModel.class, project.getId());

            if (projectModel == null) {
                throw new ProfileProgressionException("Unable to find project '" + project.getKey()
                        + "' in database with id: " + project.getId());
            }

            // create status class
            ProjectProfileProgressionStatus profileStatus = new ProjectProfileProgressionStatus();
            profileStatus.setAnalysisVersion(project.getAnalysisVersion());
            profileStatus.setNextProfileName(nextProfileName);
            profileStatus.setProfileToUpdate(profileToUpdate);
            profileStatus.setProject(projectModel);
            profileStatus.setProjectViolationPercentage(projectViolationPercentage);

            if (profileToUpdate == null) {
                updateProjectsProfile(profileStatus);
            } else {
                updateProfilesParent(profileStatus);
            }

            sendNotification(profileStatus);
        }
    }

    protected void sendNotification(ProjectProfileProgressionStatus profileStatus) {
        Notification notification = new Notification(ProfileProgressionPlugin.NOTIFICATION_TYPE_KEY);
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_PROJECT_NAME_KEY,
                profileStatus.getProject().getName());
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_PROJECT_KEY_KEY,
                profileStatus.getProject().getKey());
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_PROJECT_ID_KEY,
                String.valueOf(profileStatus.getProject().getId()));
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_ANALYSIS_VERSION_KEY,
                profileStatus.getAnalysisVersion());
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_MESSAGE_KEY,
                profileStatus.getNotificationMessage());
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_PROJECT_NOTIFICATION_USERS_KEY,
                settings.getString(ProfileProgressionPlugin.PROJECT_QUALITY_PROFILE_CHANGE_NOTIFICATION_USER_KEY));
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_PROJECT_PROGRESSED_KEY,
                String.valueOf(profileStatus.profileShouldBeProgressed()));
        notification.setFieldValue(ProfileProgressionPlugin.NOTIFICATION_PROJECT_VIOLATIONS_KEY,
                String.valueOf(profileStatus.getProjectViolationPercentage()));

        logger.info("Sending profile progression notification for project {}",
                profileStatus.getProject().getName());

        notificationManager.scheduleForSending(notification);
    }

    protected void updateProfilesParent(ProjectProfileProgressionStatus profileStatus)
            throws ProfileProgressionException {
        checkForMultipleProjectUsage(profileStatus);

        if (profileStatus.profileShouldBeProgressed()) {
            logger.info("{} quality profile's parent is managed by {}",
                    profileStatus.getProfileToUpdate().getName(), this.getClass().getSimpleName());

            profilesManager.changeParentProfile(profileStatus.getProfileToUpdate().getId(),
                    profileStatus.getNextProfileName(), "admin");

            String message = String.format("Progressed %1$s quality profile's parent to %2$s",
                    profileStatus.getProfileToUpdate().getName(), profileStatus.getNextProfileName());
            logger.info(message);

            profileStatus.setNotificationMessage(message);
        }
    }

    protected void checkForMultipleProjectUsage(ProjectProfileProgressionStatus profileStatus)
            throws ProfileProgressionException {
        Set<ResourceModel> otherProjects = qualityProfileProjectDao
                .getAllProjectsUsingQualityProfile(profileStatus.getProfileToUpdate());

        if (otherProjects.size() > 1) {
            // prevent profile progression
            profileStatus.setProfileShouldBeProgressed(false);

            // set notification message
            StringBuilder sb = new StringBuilder();
            sb.append(profileStatus.getProfileToUpdate().getName());
            sb.append(" quality profile, &/or its children, are used by projects other than ");
            sb.append(profileStatus.getProject().getKey());
            sb.append(".\nQuality profile for ");
            sb.append(profileStatus.getProject().getKey());
            sb.append(" will not be progressed.");
            sb.append("\nProjects also using the ");
            sb.append(profileStatus.getProfileToUpdate().getName());
            sb.append(" quality profile, &/or its children: ");

            for (Iterator<ResourceModel> iterator = otherProjects.iterator(); iterator.hasNext();) {
                ResourceModel otherProject = iterator.next();
                if (profileStatus.getProject().equals(otherProject) == false) {
                    sb.append(otherProject.getKey());
                    sb.append("\n ");
                }
            }

            logger.debug(sb.toString());

            profileStatus.setNotificationMessage(sb.toString());
        }
    }

    protected void updateProjectsProfile(ProjectProfileProgressionStatus profileStatus)
            throws ProfileProgressionException {

        ResourceModel project = profileStatus.getProject();

        // set project's quality profile to next
        // one
        RulesProfile nextProfile = profilesDao.getProfile(project.getLanguageKey(),
                profileStatus.getNextProfileName());

        if (nextProfile == null) {
            throw new ProfileProgressionException("Unable to find profile '" + profileStatus.getNextProfileName()
                    + "' for language " + project.getLanguageKey() + "'.");
        } else {
            project.setRulesProfile(nextProfile);

            // save project change
            session.save(project);
            session.commit();
        }

        String message = String.format("Progressed %1$s project's profile to %2$s", project.getKey(),
                profileStatus.getNextProfileName());
        logger.info(message);
        profileStatus.setNotificationMessage(message);
    }

    protected int getProjectsViolationPercentage(DecoratorContext context) {
        List<Violation> violations = context.getViolations();
        logger.debug("Found {} violations.", violations.size());

        List<ActiveRule> rules = profile.getActiveRules();
        logger.debug("Found {} rules.", rules.size());

        // get base measures
        BigDecimal numberOfViolations = new BigDecimal(violations.size());
        BigDecimal numberOfRules = new BigDecimal(rules.size());

        // get violation ratio
        BigDecimal projectViolationDecimal = null;
        if (numberOfRules.equals(new BigDecimal(0)))
        // no rules so 0% violations
        {
            projectViolationDecimal = new BigDecimal(0);
        } else {
            projectViolationDecimal = numberOfViolations.divide(numberOfRules);
        }

        // convert to percentage
        int projectViolationPercentage = projectViolationDecimal.multiply(new BigDecimal(100)).intValue();

        logger.debug("Project's % violations: {}", projectViolationPercentage);

        return projectViolationPercentage;
    }

    protected String[] getProfileNameArray(String languageKey) throws ProfileProgressionException {
        // check for project setting
        String lastProfileName = getLanguagesTargetProfileName(languageKey,
                ProfileProgressionPlugin.PROJECT_TARGET_LANGUAGE_QUALITY_PROFILE_KEY);

        if (lastProfileName == null || lastProfileName.length() < 1) {
            // get global setting
            lastProfileName = getLanguagesTargetProfileName(languageKey,
                    ProfileProgressionPlugin.GLOBAL_TARGET_LANGUAGE_QUALITY_PROFILE_KEY);
        }

        if (lastProfileName == null) {
            throw new ProfileProgressionException("No target quality profile found.");
        }

        return getProfileHierchy(languageKey, lastProfileName);
    }

    protected String[] getProfileHierchy(String languageKey, String lastProfileName)
            throws ProfileProgressionException {
        // collect profile hierarchy
        List<String> profiles = new ArrayList<String>();
        profiles.add(lastProfileName);

        // get last profile in hierarchy
        RulesProfile profile = profilesDao.getProfile(languageKey, lastProfileName);

        // check we can find the specified profile for the current project's
        // language
        if (profile == null) {
            throw new ProfileProgressionException(
                    "Unable to find \"" + lastProfileName + "\" quality profile for language: " + languageKey);
        }

        // navigate up profile hierarchy
        while (profile != null && profile.getParentName() != null) {
            profiles.add(profile.getParentName());
            profile = profilesDao.getProfile(languageKey, profile.getParentName());
        }

        // convert List to String[]
        String[] profileHierarchy = profiles.toArray(new String[profiles.size()]);

        // make lastProfileName last
        ArrayUtils.reverse(profileHierarchy);

        return profileHierarchy;
    }

    protected String getLanguagesTargetProfileName(String languageKey, String settingName) {
        String targetProfileName = null;

        String targetProfileSetting = settings.getString(settingName);

        if (targetProfileSetting != null) {

            StringTokenizer tokenizer = new StringTokenizer(targetProfileSetting,
                    ProfileProgressionPlugin.TARGET_LANGUAGE_QUALITY_PROFILE_DELIM);

            // if more than one language extract quality profile assigned to
            // current project's language
            if (tokenizer.countTokens() > 1 && targetProfileSetting.contains(languageKey)) {
                String langProfilePair = null;
                while (tokenizer.hasMoreTokens()) {
                    langProfilePair = tokenizer.nextToken();
                    if (langProfilePair != null && langProfilePair.startsWith(languageKey)) {
                        targetProfileName = langProfilePair.substring(languageKey.length()
                                + ProfileProgressionPlugin.TARGET_LANGUAGE_QUALITY_PROFILE_ASSIGN.length());
                    }
                }
            } else if (targetProfileSetting
                    .contains(ProfileProgressionPlugin.TARGET_LANGUAGE_QUALITY_PROFILE_ASSIGN))
            // pull out single language target
            {
                targetProfileName = targetProfileSetting
                        .substring(targetProfileSetting
                                .indexOf(ProfileProgressionPlugin.TARGET_LANGUAGE_QUALITY_PROFILE_ASSIGN)
                                + ProfileProgressionPlugin.TARGET_LANGUAGE_QUALITY_PROFILE_ASSIGN.length())
                        .replaceAll(ProfileProgressionPlugin.TARGET_LANGUAGE_QUALITY_PROFILE_DELIM, "").trim();
            } else
            // just take the value specified
            {
                targetProfileName = targetProfileSetting
                        .replaceAll(ProfileProgressionPlugin.TARGET_LANGUAGE_QUALITY_PROFILE_DELIM, "").trim();
            }
        }

        return targetProfileName;
    }

    protected Integer getViolationThreshold(String thresholdKey) throws ProfileProgressionException {
        Integer violationThreshold = null;

        // get threshold setting
        try {
            violationThreshold = settings.getInt(thresholdKey);
        } catch (NumberFormatException nfe) {
            logger.warn("Error parsing {} setting value \"{}\" to an integer.", thresholdKey,
                    settings.getString(thresholdKey));
            violationThreshold = 0;
        }

        logger.debug("Found {} setting: {}", thresholdKey, violationThreshold);

        // validate threshold value
        if (violationThreshold < 0 || violationThreshold >= 100) {
            throw new ProfileProgressionException(thresholdKey + " setting value " + violationThreshold
                    + " out of range; should be in the range 0-99.");
        }

        return violationThreshold;
    }

    protected int getViolationThreshold() throws ProfileProgressionException {
        Integer violationThreshold = getViolationThreshold(
                ProfileProgressionPlugin.QUALITY_PROFILE_CHANGE_THRESHOLD_KEY);

        if (violationThreshold == null || violationThreshold == 0) {
            throw new ProfileProgressionException("Violation threshold setting canot be found (or is zero).");
        }

        return violationThreshold;
    }

    protected int getMatchingIndex(String[] profileNameArray, String profileName) {
        int index = -1;
        for (int i = 0; i < profileNameArray.length; i++) {
            if (profileName.equals(profileNameArray[i])) {
                index = i;
                break;
            }
        }

        logger.debug("{} profile's index of array: {}", profileName, index);

        return index;
    }

    public void setProfilesDao(ProfilesDao profilesDao) {
        this.profilesDao = profilesDao;
    }

}