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 */
020
021package org.sonar.api.rules;
022
023import com.google.common.base.Joiner;
024import com.google.common.collect.ImmutableSet;
025import org.apache.commons.lang.StringUtils;
026import org.apache.commons.lang.builder.EqualsBuilder;
027import org.apache.commons.lang.builder.HashCodeBuilder;
028import org.apache.commons.lang.builder.ToStringBuilder;
029import org.apache.commons.lang.builder.ToStringStyle;
030import org.sonar.api.database.DatabaseProperties;
031import org.sonar.api.rule.RuleKey;
032import org.sonar.api.utils.SonarException;
033import org.sonar.check.Cardinality;
034
035import javax.annotation.CheckForNull;
036import javax.annotation.Nullable;
037import javax.persistence.*;
038
039import java.util.*;
040
041@Entity
042@Table(name = "rules")
043public class Rule {
044
045  /**
046   * @since 3.6
047   */
048  public static final String STATUS_BETA = "BETA";
049  /**
050   * @since 3.6
051   */
052  public static final String STATUS_DEPRECATED = "DEPRECATED";
053  /**
054   * @since 3.6
055   */
056  public static final String STATUS_READY = "READY";
057
058  /**
059   * For internal use only.
060   *
061   * @since 3.6
062   */
063  public static final String STATUS_REMOVED = "REMOVED";
064
065  /**
066   * List of available status
067   *
068   * @since 3.6
069   */
070  private static final Set<String> STATUS_LIST = ImmutableSet.of(STATUS_READY, STATUS_BETA, STATUS_DEPRECATED, STATUS_REMOVED);
071
072  /**
073   * @since 4.2
074   */
075  private static final String[] DEFAULT_TAGS = new String[0];
076
077  @Id
078  @Column(name = "id")
079  @GeneratedValue
080  private Integer id;
081
082  /**
083   * The default priority given to a rule if not explicitly set
084   */
085  public static final RulePriority DEFAULT_PRIORITY = RulePriority.MAJOR;
086
087  @Column(name = "name", updatable = true, nullable = true, length = 200)
088  private String name;
089
090  @Column(name = "plugin_rule_key", updatable = false, nullable = true, length = 200)
091  private String key;
092
093  @Column(name = "plugin_config_key", updatable = true, nullable = true, length = 500)
094  private String configKey;
095
096  @Column(name = "priority", updatable = true, nullable = true)
097  @Enumerated(EnumType.ORDINAL)
098  private RulePriority priority = DEFAULT_PRIORITY;
099
100  @Column(name = "description", updatable = true, nullable = true, length = DatabaseProperties.MAX_TEXT_SIZE)
101  private String description;
102
103  @Column(name = "plugin_name", updatable = true, nullable = false)
104  private String pluginName;
105
106  @Enumerated(EnumType.STRING)
107  @Column(name = "cardinality", updatable = true, nullable = false)
108  private Cardinality cardinality = Cardinality.SINGLE;
109
110  @Column(name = "status", updatable = true, nullable = true)
111  private String status = STATUS_READY;
112
113  @Column(name = "language", updatable = true, nullable = true)
114  private String language;
115
116  @ManyToOne(fetch = FetchType.EAGER)
117  @JoinColumn(name = "parent_id", updatable = true, nullable = true)
118  private Rule parent = null;
119
120  @org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
121  @OneToMany(mappedBy = "rule")
122  private List<RuleParam> params = new ArrayList<RuleParam>();
123
124  @Temporal(TemporalType.TIMESTAMP)
125  @Column(name = "created_at", updatable = true, nullable = true)
126  private Date createdAt;
127
128  @Temporal(TemporalType.TIMESTAMP)
129  @Column(name = "updated_at", updatable = true, nullable = true)
130  private Date updatedAt;
131
132  private transient String[] tags = DEFAULT_TAGS;
133
134  /**
135   * @deprecated since 2.3. Use the factory method {@link #create()}
136   */
137  @Deprecated
138  public Rule() {
139  }
140
141  /**
142   * Creates rule with minimum set of info
143   *
144   * @param pluginName the plugin name indicates which plugin the rule belongs to
145   * @param key        the key should be unique within a plugin, but it is even more careful for the time being that it is unique across the
146   *                   application
147   * @deprecated since 2.3. Use the factory method {@link #create()}
148   */
149  @Deprecated
150  public Rule(String pluginName, String key) {
151    this.pluginName = pluginName;
152    this.key = key;
153    this.configKey = key;
154  }
155
156  public Integer getId() {
157    return id;
158  }
159
160  /**
161   * @deprecated since 2.3. visibility should be decreased to protected or package
162   */
163  @Deprecated
164  public void setId(Integer id) {
165    this.id = id;
166  }
167
168  @CheckForNull
169  public String getName() {
170    return name;
171  }
172
173  /**
174   * Sets the rule name
175   */
176  public Rule setName(@Nullable String name) {
177    this.name = removeNewLineCharacters(name);
178    return this;
179  }
180
181  public String getKey() {
182    return key;
183  }
184
185  /**
186   * Sets the rule key
187   */
188  public Rule setKey(String key) {
189    this.key = key;
190    return this;
191  }
192
193  /**
194   * @deprecated since 2.5 use {@link #getRepositoryKey()} instead
195   */
196  @Deprecated
197  public String getPluginName() {
198    return pluginName;
199  }
200
201  /**
202   * @deprecated since 2.5 use {@link #setRepositoryKey(String)} instead
203   */
204  @Deprecated
205  public Rule setPluginName(String pluginName) {
206    this.pluginName = pluginName;
207    return this;
208  }
209
210  public String getConfigKey() {
211    return configKey;
212  }
213
214  /**
215   * Sets the configuration key
216   */
217  public Rule setConfigKey(String configKey) {
218    this.configKey = configKey;
219    return this;
220  }
221
222  public String getDescription() {
223    return description;
224  }
225
226  /**
227   * Sets the rule description
228   */
229  public Rule setDescription(String description) {
230    this.description = StringUtils.strip(description);
231    return this;
232  }
233
234  /**
235   * @deprecated in 3.6. Replaced by {@link #setStatus(String status)}.
236   */
237  @Deprecated
238  public Rule setEnabled(Boolean enabled) {
239    throw new UnsupportedOperationException("No more supported since version 3.6.");
240  }
241
242  public Boolean isEnabled() {
243    return !STATUS_REMOVED.equals(status);
244  }
245
246  public List<RuleParam> getParams() {
247    return params;
248  }
249
250  public RuleParam getParam(String key) {
251    for (RuleParam param : params) {
252      if (StringUtils.equals(key, param.getKey())) {
253        return param;
254      }
255    }
256    return null;
257  }
258
259  /**
260   * Sets the rule parameters
261   */
262  public Rule setParams(List<RuleParam> params) {
263    this.params.clear();
264    for (RuleParam param : params) {
265      param.setRule(this);
266      this.params.add(param);
267    }
268    return this;
269  }
270
271  public RuleParam createParameter() {
272    RuleParam parameter = new RuleParam()
273        .setRule(this);
274    params.add(parameter);
275    return parameter;
276  }
277
278  public RuleParam createParameter(String key) {
279    RuleParam parameter = new RuleParam()
280        .setKey(key)
281        .setRule(this);
282    params.add(parameter);
283    return parameter;
284  }
285
286  /**
287   * @deprecated since 2.5. See http://jira.codehaus.org/browse/SONAR-2007
288   */
289  @Deprecated
290  public Integer getCategoryId() {
291    return null;
292  }
293
294  /**
295   * @since 2.5
296   */
297  public RulePriority getSeverity() {
298    return priority;
299  }
300
301  /**
302   * @param severity severity to set, if null, uses the default priority.
303   * @since 2.5
304   */
305  public Rule setSeverity(RulePriority severity) {
306    if (severity == null) {
307      this.priority = DEFAULT_PRIORITY;
308    } else {
309      this.priority = severity;
310    }
311    return this;
312  }
313
314  /**
315   * @deprecated since 2.5 use {@link #getSeverity()} instead. See http://jira.codehaus.org/browse/SONAR-1829
316   */
317  @Deprecated
318  public RulePriority getPriority() {
319    return priority;
320  }
321
322  /**
323   * Sets the rule priority. If null, uses the default priority
324   *
325   * @deprecated since 2.5 use {@link #setSeverity(RulePriority)} instead. See http://jira.codehaus.org/browse/SONAR-1829
326   */
327  @Deprecated
328  public Rule setPriority(RulePriority priority) {
329    return setSeverity(priority);
330  }
331
332  public String getRepositoryKey() {
333    return pluginName;
334  }
335
336  public Rule setRepositoryKey(String s) {
337    this.pluginName = s;
338    return this;
339  }
340
341  public Rule setUniqueKey(String repositoryKey, String key) {
342    return setRepositoryKey(repositoryKey).setKey(key).setConfigKey(key);
343  }
344
345  public Cardinality getCardinality() {
346    return cardinality;
347  }
348
349  public Rule setCardinality(Cardinality c) {
350    this.cardinality = c;
351    return this;
352  }
353
354  public Rule getParent() {
355    return parent;
356  }
357
358  public Rule setParent(Rule parent) {
359    this.parent = parent;
360    return this;
361  }
362
363  /**
364   * @since 3.6
365   */
366  public String getStatus() {
367    return status;
368  }
369
370  /**
371   * @since 3.6
372   */
373  public Rule setStatus(String status) {
374    if (!STATUS_LIST.contains(status)) {
375      throw new SonarException("The status of a rule can only contain : " + Joiner.on(", ").join(STATUS_LIST));
376    }
377    this.status = status;
378    return this;
379  }
380
381  /**
382   * @since 3.6
383   */
384  public Date getCreatedAt() {
385    return createdAt;
386  }
387
388  /**
389   * @since 3.6
390   */
391  public Rule setCreatedAt(Date d) {
392    this.createdAt = d;
393    return this;
394  }
395
396  /**
397   * @since 3.6
398   */
399  public Date getUpdatedAt() {
400    return updatedAt;
401  }
402
403  /**
404   * @since 3.6
405   */
406  public Rule setUpdatedAt(Date updatedAt) {
407    this.updatedAt = updatedAt;
408    return this;
409  }
410
411  /**
412   * @since 3.6
413   */
414  public String getLanguage() {
415    return language;
416  }
417
418  /**
419   * For internal use only.
420   *
421   * @since 3.6
422   */
423  public Rule setLanguage(String language) {
424    this.language = language;
425    return this;
426  }
427
428  /**
429   * For definition of rule only
430   */
431  public String[] getTags() {
432    return tags;
433  }
434
435  /**
436   * For definition of rule only
437   */
438  public void setTags(String[] tags) {
439    this.tags = tags;
440  }
441
442  @Override
443  public boolean equals(Object obj) {
444    if (!(obj instanceof Rule)) {
445      return false;
446    }
447    if (this == obj) {
448      return true;
449    }
450    Rule other = (Rule) obj;
451    return new EqualsBuilder()
452        .append(pluginName, other.getRepositoryKey())
453        .append(key, other.getKey())
454        .isEquals();
455  }
456
457  @Override
458  public int hashCode() {
459    return new HashCodeBuilder(17, 37)
460        .append(pluginName)
461        .append(key)
462        .toHashCode();
463  }
464
465  @Override
466  public String toString() {
467    // Note that ReflectionToStringBuilder will not work here - see SONAR-3077
468    return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
469        .append("id", id)
470        .append("name", name)
471        .append("key", key)
472        .append("configKey", configKey)
473        .append("plugin", pluginName)
474        .append("severity", priority)
475        .append("cardinality", cardinality)
476        .append("status", status)
477        .append("language", language)
478        .append("parent", parent)
479        .toString();
480  }
481
482  @CheckForNull
483  private String removeNewLineCharacters(@Nullable String text) {
484    String removedCRLF = StringUtils.remove(text, "\n");
485    removedCRLF = StringUtils.remove(removedCRLF, "\r");
486    removedCRLF = StringUtils.remove(removedCRLF, "\n\r");
487    removedCRLF = StringUtils.remove(removedCRLF, "\r\n");
488    return removedCRLF;
489  }
490
491  public static Rule create() {
492    return new Rule();
493  }
494
495  /**
496   * Create with all required fields
497   */
498  public static Rule create(String repositoryKey, String key, String name) {
499    return new Rule().setUniqueKey(repositoryKey, key).setName(name);
500  }
501
502  /**
503   * Create with all required fields
504   *
505   * @since 2.10
506   */
507  public static Rule create(String repositoryKey, String key) {
508    return new Rule().setUniqueKey(repositoryKey, key);
509  }
510
511  /**
512   * @since 3.6
513   */
514  public RuleKey ruleKey() {
515    return RuleKey.of(getRepositoryKey(), getKey());
516  }
517}