001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2013 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.profiles;
021
022import com.google.common.base.Predicate;
023import com.google.common.collect.Iterables;
024import com.google.common.collect.Lists;
025import org.apache.commons.collections.CollectionUtils;
026import org.apache.commons.collections.Transformer;
027import org.apache.commons.lang.StringUtils;
028import org.apache.commons.lang.builder.EqualsBuilder;
029import org.apache.commons.lang.builder.HashCodeBuilder;
030import org.sonar.api.rules.ActiveRule;
031import org.sonar.api.rules.Rule;
032import org.sonar.api.rules.RulePriority;
033import org.sonar.api.utils.MessageException;
034
035import javax.annotation.CheckForNull;
036import javax.persistence.*;
037
038import java.util.ArrayList;
039import java.util.List;
040
041/**
042 * This class is badly named. It should be "QualityProfile". Indeed it does not relate only to rules but to metric thresholds too.
043 */
044@Entity
045@Table(name = "rules_profiles")
046public class RulesProfile implements Cloneable {
047
048  /**
049   * Name of the default profile "Sonar Way"
050   * @deprecated in 4.2. Use your own constant.
051   */
052  @Deprecated
053  public static final String SONAR_WAY_NAME = "Sonar way";
054
055  /**
056   * Name of the default java profile "Sonar way with Findbugs"
057   * @deprecated in 4.2. Use your own constant.
058   */
059  @Deprecated
060  public static final String SONAR_WAY_FINDBUGS_NAME = "Sonar way with Findbugs";
061
062  /**
063   * Name of the default java profile "Sun checks"
064   * @deprecated in 4.2. Use your own constant.
065   */
066  @Deprecated
067  public static final String SUN_CONVENTIONS_NAME = "Sun checks";
068
069  @Id
070  @Column(name = "id")
071  @GeneratedValue
072  private Integer id;
073
074  @Column(name = "name", updatable = true, nullable = false)
075  private String name;
076
077  @Column(name = "version", updatable = true, nullable = false)
078  private int version = 1;
079
080  @Transient
081  private Boolean defaultProfile = Boolean.FALSE;
082
083  @Column(name = "used_profile", updatable = true, nullable = false)
084  private Boolean used = Boolean.FALSE;
085
086  @Column(name = "language", updatable = true, nullable = false, length = 20)
087  private String language;
088
089  @Column(name = "parent_name", updatable = true, nullable = true)
090  private String parentName;
091
092  @OneToMany(mappedBy = "rulesProfile", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
093  private List<ActiveRule> activeRules = Lists.newArrayList();
094
095  @OneToMany(mappedBy = "rulesProfile", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
096  private List<Alert> alerts = Lists.newArrayList();
097
098  /**
099   * @deprecated use the factory method create()
100   */
101  @Deprecated
102  public RulesProfile() {
103  }
104
105  /**
106   * @deprecated since 2.3. Use the factory method create()
107   */
108  @Deprecated
109  public RulesProfile(String name, String language) {
110    this.name = name;
111    this.language = language;
112    this.activeRules = Lists.newArrayList();
113    this.alerts = Lists.newArrayList();
114  }
115
116  /**
117   * @deprecated since 2.3. Use the factory method create()
118   */
119  @Deprecated
120  public RulesProfile(String name, String language, boolean defaultProfile, /* kept for backward-compatibility */boolean provided) {
121    this(name, language);
122    this.defaultProfile = defaultProfile;
123  }
124
125  public Integer getId() {
126    return id;
127  }
128
129  /**
130   * @return the profile name, unique by language.
131   */
132  public String getName() {
133    return name;
134  }
135
136  /**
137   * Set the profile name.
138   */
139  public RulesProfile setName(String s) {
140    this.name = s;
141    return this;
142  }
143
144  public int getVersion() {
145    return version;
146  }
147
148  public RulesProfile setVersion(int version) {
149    this.version = version;
150    return this;
151  }
152
153  public Boolean getUsed() {
154    return used;
155  }
156
157  public RulesProfile setUsed(Boolean used) {
158    this.used = used;
159    return this;
160  }
161
162  /**
163   * @return the list of active rules
164   */
165  public List<ActiveRule> getActiveRules() {
166    return getActiveRules(false);
167  }
168
169  /**
170   * @return the list of active rules
171   */
172  public List<ActiveRule> getActiveRules(boolean acceptDisabledRules) {
173    if (acceptDisabledRules) {
174      return activeRules;
175    }
176    List<ActiveRule> result = Lists.newArrayList();
177    for (ActiveRule activeRule : activeRules) {
178      if (activeRule.isEnabled()) {
179        result.add(activeRule);
180      }
181    }
182    return result;
183  }
184
185  public RulesProfile removeActiveRule(ActiveRule activeRule) {
186    activeRules.remove(activeRule);
187    return this;
188  }
189
190  public RulesProfile addActiveRule(ActiveRule activeRule) {
191    activeRules.add(activeRule);
192    return this;
193  }
194
195  /**
196   * Set the list of active rules
197   */
198  public void setActiveRules(List<ActiveRule> activeRules) {
199    this.activeRules = activeRules;
200  }
201
202  /**
203   * @return whether this is the default profile for the language
204   */
205  public Boolean getDefaultProfile() {
206    return defaultProfile;
207  }
208
209  /**
210   * Set whether this is the default profile for the language. The default profile is used when none is explicitly defined when auditing a
211   * project.
212   */
213  public void setDefaultProfile(Boolean b) {
214    this.defaultProfile = b;
215  }
216
217  /**
218   * @deprecated since 3.3 not replaced
219   */
220  @Deprecated
221  public Boolean getProvided() {
222    return false;
223  }
224
225  /**
226   * @deprecated since 3.3 not replaced
227   */
228  @Deprecated
229  public void setProvided(Boolean b) {
230  }
231
232  /**
233   * @deprecated since 3.3. Always return true.
234   */
235  @Deprecated
236  public Boolean getEnabled() {
237    return Boolean.TRUE;
238  }
239
240  /**
241   * @deprecated since 3.3. Always return true.
242   */
243  @Deprecated
244  public boolean isEnabled() {
245    return true;
246  }
247
248  /**
249   * @deprecated since 3.3.
250   */
251  @Deprecated
252  public RulesProfile setEnabled(Boolean b) {
253    throw new UnsupportedOperationException("The field RulesProfile#enabled is not supported since 3.3.");
254  }
255
256  /**
257   * @return the profile language
258   */
259  public String getLanguage() {
260    return language;
261  }
262
263  /**
264   * Set the profile language
265   */
266  public RulesProfile setLanguage(String s) {
267    this.language = s;
268    return this;
269  }
270
271  /**
272   * For internal use only.
273   *
274   * @since 2.5
275   */
276  @CheckForNull
277  public String getParentName() {
278    return parentName;
279  }
280
281  /**
282   * For internal use only.
283   *
284   * @since 2.5
285   */
286  public void setParentName(String parentName) {
287    this.parentName = parentName;
288  }
289
290  /**
291   * @return the list of alerts defined in the profile
292   */
293  public List<Alert> getAlerts() {
294    return alerts;
295  }
296
297  /**
298   * Sets the list of alerts for the profile
299   */
300  public void setAlerts(List<Alert> alerts) {
301    this.alerts = alerts;
302  }
303
304  /**
305   * Note: disabled rules are excluded.
306   *
307   * @return the list of active rules for a given severity
308   */
309  public List<ActiveRule> getActiveRules(RulePriority severity) {
310    List<ActiveRule> result = Lists.newArrayList();
311    for (ActiveRule activeRule : activeRules) {
312      if (activeRule.getSeverity().equals(severity) && activeRule.isEnabled()) {
313        result.add(activeRule);
314      }
315    }
316    return result;
317  }
318
319  /**
320   * @deprecated since 2.3 use {@link #getActiveRulesByRepository(String)} instead.
321   */
322  @Deprecated
323  public List<ActiveRule> getActiveRulesByPlugin(String repositoryKey) {
324    return getActiveRulesByRepository(repositoryKey);
325  }
326
327  /**
328   * Get the active rules of a specific repository.
329   * Only enabled rules are selected. Disabled rules are excluded.
330   */
331  public List<ActiveRule> getActiveRulesByRepository(String repositoryKey) {
332    List<ActiveRule> result = Lists.newArrayList();
333    for (ActiveRule activeRule : activeRules) {
334      if (repositoryKey.equals(activeRule.getRepositoryKey()) && activeRule.isEnabled()) {
335        result.add(activeRule);
336      }
337    }
338    return result;
339  }
340
341  /**
342   * Note: disabled rules are excluded.
343   *
344   * @return an active rule from a plugin key and a rule key if the rule is activated, null otherwise
345   */
346  @CheckForNull
347  public ActiveRule getActiveRule(String repositoryKey, String ruleKey) {
348    for (ActiveRule activeRule : activeRules) {
349      if (StringUtils.equals(activeRule.getRepositoryKey(), repositoryKey) && StringUtils.equals(activeRule.getRuleKey(), ruleKey) && activeRule.isEnabled()) {
350        return activeRule;
351      }
352    }
353    return null;
354  }
355
356  /**
357   * Note: disabled rules are excluded.
358   */
359  @CheckForNull
360  public ActiveRule getActiveRuleByConfigKey(String repositoryKey, String configKey) {
361    for (ActiveRule activeRule : activeRules) {
362      if (StringUtils.equals(activeRule.getRepositoryKey(), repositoryKey) && StringUtils.equals(activeRule.getConfigKey(), configKey) && activeRule.isEnabled()) {
363        return activeRule;
364      }
365    }
366    return null;
367  }
368
369  /**
370   * Note: disabled rules are excluded.
371   */
372  @CheckForNull
373  public ActiveRule getActiveRule(Rule rule) {
374    return getActiveRule(rule.getRepositoryKey(), rule.getKey());
375  }
376
377  /**
378   * @param optionalSeverity if null, then the default rule severity is used
379   */
380  public ActiveRule activateRule(final Rule rule, RulePriority optionalSeverity) {
381    if (Iterables.any(activeRules, new Predicate<ActiveRule>() {
382      @Override
383      public boolean apply(ActiveRule input) {
384        return input.getRule().equals(rule);
385      }
386    })) {
387      throw MessageException.of(String.format(
388        "The definition of the profile '%s' (language '%s') contains multiple occurrences of the '%s:%s' rule. The plugin which declares this profile should fix this.",
389        getName(), getLanguage(), rule.getRepositoryKey(), rule.getKey()));
390    }
391    ActiveRule activeRule = new ActiveRule();
392    activeRule.setRule(rule);
393    activeRule.setRulesProfile(this);
394    activeRule.setSeverity(optionalSeverity == null ? rule.getSeverity() : optionalSeverity);
395    activeRules.add(activeRule);
396    return activeRule;
397  }
398
399  @Override
400  public boolean equals(Object obj) {
401    if (!(obj instanceof RulesProfile)) {
402      return false;
403    }
404    if (this == obj) {
405      return true;
406    }
407    RulesProfile other = (RulesProfile) obj;
408    return new EqualsBuilder().append(language, other.getLanguage()).append(name, other.getName()).isEquals();
409  }
410
411  @Override
412  public int hashCode() {
413    return new HashCodeBuilder(17, 37).append(language).append(name).toHashCode();
414  }
415
416  @Override
417  public Object clone() {
418    RulesProfile clone = RulesProfile.create(getName(), getLanguage());
419    clone.setDefaultProfile(getDefaultProfile());
420    clone.setParentName(getParentName());
421    if (activeRules != null && !activeRules.isEmpty()) {
422      clone.setActiveRules(new ArrayList<ActiveRule>(CollectionUtils.collect(activeRules, new Transformer() {
423        public Object transform(Object input) {
424          return ((ActiveRule) input).clone();
425        }
426      })));
427    }
428    if (CollectionUtils.isNotEmpty(getAlerts())) {
429      clone.setAlerts(new ArrayList<Alert>(CollectionUtils.collect(getAlerts(), new Transformer() {
430        public Object transform(Object input) {
431          return ((Alert) input).clone();
432        }
433      })));
434    }
435    return clone;
436  }
437
438  @Override
439  public String toString() {
440    return new StringBuilder().append("[name=").append(name).append(",language=").append(language).append("]").toString();
441  }
442
443  public static RulesProfile create(String name, String language) {
444    return new RulesProfile().setName(name).setLanguage(language);
445  }
446
447  public static RulesProfile create() {
448    return new RulesProfile();
449  }
450}