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      .toString();
499  }
500
501  private String removeNewLineCharacters(String text) {
502    String removedCRLF = StringUtils.remove(text, "\n");
503    removedCRLF = StringUtils.remove(removedCRLF, "\r");
504    removedCRLF = StringUtils.remove(removedCRLF, "\n\r");
505    removedCRLF = StringUtils.remove(removedCRLF, "\r\n");
506    return removedCRLF;
507  }
508
509  public static Rule create() {
510    return new Rule();
511  }
512
513  /**
514   * Create with all required fields
515   */
516  public static Rule create(String repositoryKey, String key, String name) {
517    return new Rule().setUniqueKey(repositoryKey, key).setName(name);
518  }
519
520  /**
521   * Create with all required fields
522   *
523   * @since 2.10
524   */
525  public static Rule create(String repositoryKey, String key) {
526    return new Rule().setUniqueKey(repositoryKey, key);
527  }
528
529  /**
530   * @since 3.6
531   */
532  public static Set<String> getStatusList() {
533    return ImmutableSet.of(STATUS_READY, STATUS_BETA, STATUS_DEPRECATED, STATUS_REMOVED);
534  }
535
536  /**
537   * @since 3.6
538   */
539  public RuleKey ruleKey() {
540    return RuleKey.of(getRepositoryKey(), getKey());
541  }
542}