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