Package org.sonar.server.qualityprofile

Source Code of org.sonar.server.qualityprofile.RuleActivator

/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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  02110-1301, USA.
*/
package org.sonar.server.qualityprofile;

import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import org.sonar.api.ServerComponent;
import org.sonar.api.server.rule.RuleParamType;
import org.sonar.core.activity.Activity;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.preview.PreviewCache;
import org.sonar.core.qualityprofile.db.ActiveRuleDto;
import org.sonar.core.qualityprofile.db.ActiveRuleKey;
import org.sonar.core.qualityprofile.db.ActiveRuleParamDto;
import org.sonar.core.qualityprofile.db.QualityProfileDto;
import org.sonar.core.rule.RuleDto;
import org.sonar.core.rule.RuleParamDto;
import org.sonar.server.activity.ActivityService;
import org.sonar.server.db.DbClient;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.qualityprofile.db.ActiveRuleDao;
import org.sonar.server.rule.Rule;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleNormalizer;
import org.sonar.server.rule.index.RuleQuery;
import org.sonar.server.search.IndexClient;
import org.sonar.server.search.QueryContext;
import org.sonar.server.search.Result;
import org.sonar.server.util.TypeValidations;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static com.google.common.collect.Lists.newArrayList;

/**
* Activation and deactivation of rules in Quality profiles
*/
public class RuleActivator implements ServerComponent {

  private final DbClient db;
  private final TypeValidations typeValidations;
  private final RuleActivatorContextFactory contextFactory;
  private final PreviewCache previewCache;
  private final IndexClient index;
  private final ActivityService log;

  public RuleActivator(DbClient db, IndexClient index,
                       RuleActivatorContextFactory contextFactory, TypeValidations typeValidations,
                       PreviewCache previewCache, ActivityService log) {
    this.db = db;
    this.index = index;
    this.contextFactory = contextFactory;
    this.typeValidations = typeValidations;
    this.previewCache = previewCache;
    this.log = log;
  }

  public List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, String profileKey) {
    RuleActivatorContext context = contextFactory.create(profileKey, activation.getRuleKey(), dbSession);
    return doActivate(dbSession, activation, context);
  }

  public List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, QProfileName profileName) {
    RuleActivatorContext context = contextFactory.create(profileName, activation.getRuleKey(), dbSession);
    return doActivate(dbSession, activation, context);
  }

  List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, QualityProfileDto profileDto) {
    RuleActivatorContext context = contextFactory.create(profileDto, activation.getRuleKey(), dbSession);
    return doActivate(dbSession, activation, context);
  }

  private List<ActiveRuleChange> doActivate(DbSession dbSession, RuleActivation activation, RuleActivatorContext context) {
    context.verifyForActivation();
    List<ActiveRuleChange> changes = Lists.newArrayList();
    ActiveRuleChange change;
    boolean stopPropagation = false;

    ActiveRuleDto activeRule = context.activeRule();
    if (activeRule == null) {
      if (activation.isReset()) {
        // ignore reset when rule is not activated
        return changes;
      }
      // new activation
      change = ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, context.activeRuleKey());
      applySeverityAndParamToChange(activation, context, change);
      if (activation.isCascade() || context.isSameAsParent(change)) {
        change.setInheritance(ActiveRule.Inheritance.INHERITED);
      }
    } else {
      // already activated
      if (activation.isCascade() && activeRule.doesOverride()) {
        // propagating to descendants, but child profile already overrides rule -> stop propagation
        return changes;
      }
      change = ActiveRuleChange.createFor(ActiveRuleChange.Type.UPDATED, context.activeRuleKey());
      if (activation.isCascade() && activeRule.getInheritance() == null) {
        // activate on child, then on parent -> mark child as overriding parent
        change.setInheritance(ActiveRule.Inheritance.OVERRIDES);
        change.setSeverity(context.currentSeverity());
        change.setParameters(context.activeRuleParamsAsStringMap());
        stopPropagation = true;
      } else {
        applySeverityAndParamToChange(activation, context, change);
        if (!activation.isCascade() && context.parentActiveRule() != null) {
          // override rule which is already declared on parents
          change.setInheritance(context.isSameAsParent(change) ? ActiveRule.Inheritance.INHERITED : ActiveRule.Inheritance.OVERRIDES);
        }
      }
      if (context.isSame(change)) {
        change = null;
      }
    }

    if (change != null) {
      changes.add(change);
      persist(change, context, dbSession);
    }

    if (!stopPropagation) {
      changes.addAll(cascadeActivation(dbSession, activation, context.profile().getKey()));
    }

    if (!changes.isEmpty()) {
      updateProfileDate(dbSession, context);
      previewCache.reportGlobalModification(dbSession);
    }
    return changes;
  }

  private void updateProfileDate(DbSession dbSession, RuleActivatorContext context) {
    context.profile().setRulesUpdatedAtAsDate(context.getInitDate());
    db.qualityProfileDao().update(dbSession, context.profile());
  }

  /**
   * Severity and parameter values are :
   * 1. defined by end-user
   * 2. else inherited from parent profile
   * 3. else defined by rule defaults
   * <p/>
   * On custom rules, it's always rule parameters that are used
   */
  private void applySeverityAndParamToChange(RuleActivation request, RuleActivatorContext context, ActiveRuleChange change) {
    if (request.isReset()) {
      // load severity and params from parent profile, else from default values
      change.setSeverity(firstNonNull(
        context.parentSeverity(), context.defaultSeverity()));
      for (RuleParamDto ruleParamDto : context.ruleParams()) {
        String paramKey = ruleParamDto.getName();
        change.setParameter(paramKey, validateParam(ruleParamDto, firstNonNull(
          context.parentParamValue(paramKey), context.defaultParamValue(paramKey))));
      }

    } else if (context.activeRule() != null) {
      // already activated -> load severity and parameters from request, else keep existing ones, else from parent,
      // else from default
      change.setSeverity(firstNonNull(
        request.getSeverity(),
        context.currentSeverity(),
        context.parentSeverity(),
        context.defaultSeverity()));
      for (RuleParamDto ruleParamDto : context.ruleParams()) {
        String paramKey = ruleParamDto.getName();
        change.setParameter(paramKey, validateParam(ruleParamDto, firstNonNull(
          context.requestParamValue(request, paramKey),
          context.currentParamValue(paramKey),
          context.parentParamValue(paramKey),
          context.defaultParamValue(paramKey))));
      }

    } else if (context.activeRule() == null) {
      // not activated -> load severity and parameters from request, else from parent, else from defaults
      change.setSeverity(firstNonNull(
        request.getSeverity(),
        context.parentSeverity(),
        context.defaultSeverity()));
      for (RuleParamDto ruleParamDto : context.ruleParams()) {
        String paramKey = ruleParamDto.getName();
        change.setParameter(paramKey, validateParam(ruleParamDto,
          firstNonNull(
            context.requestParamValue(request, paramKey),
            context.parentParamValue(paramKey),
            context.defaultParamValue(paramKey))));
      }
    }
  }

  @CheckForNull
  String firstNonNull(String... strings) {
    for (String s : strings) {
      if (s != null) {
        return s;
      }
    }
    return null;
  }

  private List<ActiveRuleChange> cascadeActivation(DbSession session, RuleActivation activation, String profileKey) {
    List<ActiveRuleChange> changes = Lists.newArrayList();

    // get all inherited profiles
    List<QualityProfileDto> children = db.qualityProfileDao().findChildren(session, profileKey);
    for (QualityProfileDto child : children) {
      RuleActivation childActivation = new RuleActivation(activation).setCascade(true);
      changes.addAll(activate(session, childActivation, child.getKey()));
    }
    return changes;
  }

  private ActiveRuleDto persist(ActiveRuleChange change, RuleActivatorContext context, DbSession dbSession) {
    ActiveRuleDto activeRule = null;
    if (change.getType() == ActiveRuleChange.Type.ACTIVATED) {
      activeRule = doInsert(change, context, dbSession);

    } else if (change.getType() == ActiveRuleChange.Type.DEACTIVATED) {
      ActiveRuleDao dao = db.activeRuleDao();
      dao.deleteByKey(dbSession, change.getKey());

    } else if (change.getType() == ActiveRuleChange.Type.UPDATED) {
      activeRule = doUpdate(change, context, dbSession);
    }
    log.write(dbSession, Activity.Type.QPROFILE, change);
    return activeRule;
  }

  private ActiveRuleDto doInsert(ActiveRuleChange change, RuleActivatorContext context, DbSession dbSession) {
    ActiveRuleDto activeRule;
    ActiveRuleDao dao = db.activeRuleDao();
    activeRule = ActiveRuleDto.createFor(context.profile(), context.rule());
    String severity = change.getSeverity();
    if (severity != null) {
      activeRule.setSeverity(severity);
    }
    ActiveRule.Inheritance inheritance = change.getInheritance();
    if (inheritance != null) {
      activeRule.setInheritance(inheritance.name());
    }
    dao.insert(dbSession, activeRule);
    for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
      if (param.getValue() != null) {
        ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(context.ruleParamsByKeys().get(param.getKey()));
        paramDto.setValue(param.getValue());
        dao.addParam(dbSession, activeRule, paramDto);
      }
    }
    return activeRule;
  }

  private ActiveRuleDto doUpdate(ActiveRuleChange change, RuleActivatorContext context, DbSession dbSession) {
    ActiveRuleDao dao = db.activeRuleDao();
    ActiveRuleDto activeRule = context.activeRule();
    String severity = change.getSeverity();
    if (severity != null) {
      activeRule.setSeverity(severity);
    }
    ActiveRule.Inheritance inheritance = change.getInheritance();
    if (inheritance != null) {
      activeRule.setInheritance(inheritance.name());
    }
    dao.update(dbSession, activeRule);

    for (Map.Entry<String, String> param : change.getParameters().entrySet()) {
      ActiveRuleParamDto activeRuleParamDto = context.activeRuleParamsAsMap().get(param.getKey());
      if (activeRuleParamDto == null) {
        // did not exist
        if (param.getValue() != null) {
          activeRuleParamDto = ActiveRuleParamDto.createFor(context.ruleParamsByKeys().get(param.getKey()));
          activeRuleParamDto.setValue(param.getValue());
          dao.addParam(dbSession, activeRule, activeRuleParamDto);
        }
      } else {
        if (param.getValue() != null) {
          activeRuleParamDto.setValue(param.getValue());
          dao.updateParam(dbSession, activeRule, activeRuleParamDto);
        } else {
          dao.deleteParam(dbSession, activeRule, activeRuleParamDto);
        }
      }
    }
    return activeRule;
  }

  /**
   * Deactivate a rule on a Quality profile. Does nothing if the rule is not activated, but
   * fails (fast) if the rule or the profile does not exist.
   */
  List<ActiveRuleChange> deactivate(ActiveRuleKey key) {
    DbSession dbSession = db.openSession(false);
    try {
      List<ActiveRuleChange> changes = deactivate(dbSession, key);
      dbSession.commit();
      return changes;
    } finally {
      dbSession.close();
    }
  }

  /**
   * Deactivate a rule on a Quality profile WITHOUT committing db session and WITHOUT checking permissions
   */
  List<ActiveRuleChange> deactivate(DbSession dbSession, ActiveRuleKey key) {
    return deactivate(dbSession, key, false);
  }

  /**
   * Deactivate a rule on a Quality profile WITHOUT committing db session, WITHOUT checking permissions, and forcing removal of inherited rules
   */
  public List<ActiveRuleChange> deactivate(DbSession dbSession, RuleDto ruleDto) {
    List<ActiveRuleChange> changes = Lists.newArrayList();
    List<ActiveRuleDto> activeRules = db.activeRuleDao().findByRule(dbSession, ruleDto);
    for (ActiveRuleDto activeRule : activeRules) {
      changes.addAll(deactivate(dbSession, activeRule.getKey(), true));
    }
    return changes;
  }

  /**
   * @param force if true then inherited rules are deactivated
   */
  public List<ActiveRuleChange> deactivate(DbSession dbSession, ActiveRuleKey key, boolean force) {
    return cascadeDeactivation(key, dbSession, false, force);
  }

  private List<ActiveRuleChange> cascadeDeactivation(ActiveRuleKey key, DbSession dbSession, boolean isCascade, boolean force) {
    List<ActiveRuleChange> changes = Lists.newArrayList();
    RuleActivatorContext context = contextFactory.create(key.qProfile(), key.ruleKey(), dbSession);
    ActiveRuleChange change;
    if (context.activeRule() == null) {
      return changes;
    }
    if (!force && !isCascade && context.activeRule().getInheritance() != null) {
      throw new BadRequestException("Cannot deactivate inherited rule '" + key.ruleKey() + "'");
    }
    change = ActiveRuleChange.createFor(ActiveRuleChange.Type.DEACTIVATED, key);
    changes.add(change);
    persist(change, context, dbSession);

    // get all inherited profiles
    List<QualityProfileDto> profiles = db.qualityProfileDao().findChildren(dbSession, key.qProfile());

    for (QualityProfileDto profile : profiles) {
      ActiveRuleKey activeRuleKey = ActiveRuleKey.of(profile.getKey(), key.ruleKey());
      changes.addAll(cascadeDeactivation(activeRuleKey, dbSession, true, force));
    }

    if (!changes.isEmpty()) {
      updateProfileDate(dbSession, context);
      previewCache.reportGlobalModification(dbSession);
    }

    return changes;
  }

  @CheckForNull
  private String validateParam(RuleParamDto ruleParam, @Nullable String value) {
    if (value != null) {
      RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType());
      if (ruleParamType.multiple()) {
        List<String> values = newArrayList(Splitter.on(",").split(value));
        typeValidations.validate(values, ruleParamType.type(), ruleParamType.values());
      } else {
        typeValidations.validate(value, ruleParamType.type(), ruleParamType.values());
      }
    }
    return value;
  }

  BulkChangeResult bulkActivate(RuleQuery ruleQuery, String profileKey, @Nullable String severity) {
    BulkChangeResult result = new BulkChangeResult();
    RuleIndex ruleIndex = index.get(RuleIndex.class);
    DbSession dbSession = db.openSession(false);
    try {
      Result<Rule> ruleSearchResult = ruleIndex.search(ruleQuery, new QueryContext().setScroll(true)
        .setFieldsToReturn(Arrays.asList(RuleNormalizer.RuleField.KEY.field())));
      Iterator<Rule> rules = ruleSearchResult.scroll();
      while (rules.hasNext()) {
        Rule rule = rules.next();
        try {
          RuleActivation activation = new RuleActivation(rule.key());
          activation.setSeverity(severity);
          List<ActiveRuleChange> changes = activate(dbSession, activation, profileKey);
          result.addChanges(changes);
          if (!changes.isEmpty()) {
            result.incrementSucceeded();
          }

        } catch (BadRequestException e) {
          // other exceptions stop the bulk activation
          result.incrementFailed();
          result.getErrors().add(e.errors());
        }
      }
      dbSession.commit();
    } finally {
      dbSession.close();
    }
    return result;
  }

  BulkChangeResult bulkDeactivate(RuleQuery ruleQuery, String profile) {
    DbSession dbSession = db.openSession(false);
    try {
      RuleIndex ruleIndex = index.get(RuleIndex.class);
      BulkChangeResult result = new BulkChangeResult();
      Result<Rule> ruleSearchResult = ruleIndex.search(ruleQuery, new QueryContext().setScroll(true)
        .setFieldsToReturn(Arrays.asList(RuleNormalizer.RuleField.KEY.field())));
      Iterator<Rule> rules = ruleSearchResult.scroll();
      while (rules.hasNext()) {
        try {
          Rule rule = rules.next();
          ActiveRuleKey key = ActiveRuleKey.of(profile, rule.key());
          List<ActiveRuleChange> changes = deactivate(dbSession, key);
          result.addChanges(changes);
          if (!changes.isEmpty()) {
            result.incrementSucceeded();
          }
        } catch (BadRequestException e) {
          // other exceptions stop the bulk activation
          result.incrementFailed();
          result.getErrors().add(e.errors());
        }
      }
      dbSession.commit();
      return result;
    } finally {
      dbSession.close();
    }
  }

  void setParent(String key, @Nullable String parentKey) {
    DbSession dbSession = db.openSession(false);
    try {
      setParent(dbSession, key, parentKey);
      dbSession.commit();

    } finally {
      dbSession.close();
    }
  }

  void setParent(DbSession dbSession, String profileKey, @Nullable String parentKey) {
    QualityProfileDto profile = db.qualityProfileDao().getNonNullByKey(dbSession, profileKey);
    if (parentKey == null) {
      // unset if parent is defined, else nothing to do
      removeParent(dbSession, profile);

    } else if (profile.getParentKee() == null || !parentKey.equals(profile.getParentKee())) {
      QualityProfileDto parentProfile = db.qualityProfileDao().getNonNullByKey(dbSession, parentKey);
      if (isDescendant(dbSession, profile, parentProfile)) {
        throw new BadRequestException(String.format("Descendant profile '%s' can not be selected as parent of '%s'", parentKey, profileKey));
      }
      removeParent(dbSession, profile);

      // set new parent
      profile.setParentKee(parentKey);
      db.qualityProfileDao().update(dbSession, profile);
      for (ActiveRuleDto parentActiveRule : db.activeRuleDao().findByProfileKey(dbSession, parentKey)) {
        try {
          RuleActivation activation = new RuleActivation(parentActiveRule.getKey().ruleKey());
          activate(dbSession, activation, profileKey);
        } catch (BadRequestException e) {
          // for example because rule status is REMOVED
          // TODO return errors
        }
      }
    }
  }

  /**
   * Does not commit
   */
  private void removeParent(DbSession dbSession, QualityProfileDto profileDto) {
    if (profileDto.getParentKee() != null) {
      profileDto.setParentKee(null);
      db.qualityProfileDao().update(dbSession, profileDto);
      for (ActiveRuleDto activeRule : db.activeRuleDao().findByProfileKey(dbSession, profileDto.getKey())) {
        if (ActiveRuleDto.INHERITED.equals(activeRule.getInheritance())) {
          deactivate(dbSession, activeRule.getKey(), true);
        } else if (ActiveRuleDto.OVERRIDES.equals(activeRule.getInheritance())) {
          activeRule.setInheritance(null);
          db.activeRuleDao().update(dbSession, activeRule);
        }
      }
    }
  }

  boolean isDescendant(DbSession dbSession, QualityProfileDto childProfile, @Nullable QualityProfileDto parentProfile) {
    QualityProfileDto currentParent = parentProfile;
    while (currentParent != null) {
      if (childProfile.getName().equals(currentParent.getName())) {
        return true;
      }
      String parentKey = currentParent.getParentKee();
      if (parentKey != null) {
        currentParent = db.qualityProfileDao().getByKey(dbSession, parentKey);
      } else {
        currentParent = null;
      }
    }
    return false;
  }
}
TOP

Related Classes of org.sonar.server.qualityprofile.RuleActivator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.