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