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