001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 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;
050import javax.persistence.Transient;
051
052import java.util.ArrayList;
053import java.util.Date;
054import java.util.List;
055import java.util.Set;
056
057@Entity
058@Table(name = "rules")
059public class Rule {
060
061  /**
062   * @since 3.6
063   */
064  public static final String STATUS_BETA = "BETA";
065  /**
066   * @since 3.6
067   */
068  public static final String STATUS_DEPRECATED = "DEPRECATED";
069  /**
070   * @since 3.6
071   */
072  public static final String STATUS_READY = "READY";
073
074  /**
075   * For internal use only.
076   *
077   * @since 3.6
078   */
079  public static final String STATUS_REMOVED = "REMOVED";
080
081  /**
082   * List of available status
083   *
084   * @since 3.6
085   */
086  private static final Set<String> STATUS_LIST = ImmutableSet.of(STATUS_READY, STATUS_BETA, STATUS_DEPRECATED, STATUS_REMOVED);
087
088  /**
089   * @since 4.2
090   */
091  private static final String[] DEFAULT_TAGS = new String[0];
092
093  @Id
094  @Column(name = "id")
095  @GeneratedValue
096  private Integer id;
097
098  /**
099   * The default priority given to a rule if not explicitly set
100   */
101  public static final RulePriority DEFAULT_PRIORITY = RulePriority.MAJOR;
102
103  @Column(name = "name", updatable = true, nullable = true, length = 200)
104  private String name;
105
106  @Column(name = "plugin_rule_key", updatable = false, nullable = true, length = 200)
107  private String key;
108
109  @Column(name = "plugin_config_key", updatable = true, nullable = true, length = 500)
110  private String configKey;
111
112  @Column(name = "priority", updatable = true, nullable = true)
113  @Enumerated(EnumType.ORDINAL)
114  private RulePriority priority = DEFAULT_PRIORITY;
115
116  @Column(name = "description", updatable = true, nullable = true, length = DatabaseProperties.MAX_TEXT_SIZE)
117  private String description;
118
119  @Column(name = "plugin_name", updatable = true, nullable = false)
120  private String pluginName;
121
122  @Enumerated(EnumType.STRING)
123  @Column(name = "is_template", updatable = true, nullable = false)
124  private boolean isTemplate = false;
125
126  @Column(name = "status", updatable = true, nullable = true)
127  private String status = STATUS_READY;
128
129  @Column(name = "language", updatable = true, nullable = true)
130  private String language;
131
132  @ManyToOne(fetch = FetchType.EAGER)
133  @JoinColumn(name = "template_id", updatable = true, nullable = true)
134  private Rule template = null;
135
136  @Column(name = "characteristic_id", updatable = true, nullable = true)
137  private Integer characteristicId;
138
139  @Column(name = "default_characteristic_id", updatable = true, nullable = true)
140  private Integer defaultCharacteristicId;
141
142  @org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
143  @OneToMany(mappedBy = "rule")
144  private List<RuleParam> params = new ArrayList<RuleParam>();
145
146  @Temporal(TemporalType.TIMESTAMP)
147  @Column(name = "created_at", updatable = true, nullable = true)
148  private Date createdAt;
149
150  @Temporal(TemporalType.TIMESTAMP)
151  @Column(name = "updated_at", updatable = true, nullable = true)
152  private Date updatedAt;
153
154  @Transient
155  private String defaultCharacteristicKey;
156  @Transient
157  private String defaultSubCharacteristicKey;
158  @Transient
159  private String characteristicKey;
160  @Transient
161  private String subCharacteristicKey;
162
163  private transient String[] tags = DEFAULT_TAGS;
164
165  /**
166   * @deprecated since 2.3. Use the factory method {@link #create()}
167   */
168  @Deprecated
169  public Rule() {
170  }
171
172  /**
173   * Creates rule with minimum set of info
174   *
175   * @param pluginName the plugin name indicates which plugin the rule belongs to
176   * @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
177   *                   application
178   * @deprecated since 2.3. Use the factory method {@link #create()}
179   */
180  @Deprecated
181  public Rule(String pluginName, String key) {
182    this.pluginName = pluginName;
183    this.key = key;
184    this.configKey = key;
185  }
186
187  public Integer getId() {
188    return id;
189  }
190
191  /**
192   * @deprecated since 2.3. visibility should be decreased to protected or package
193   */
194  @Deprecated
195  public void setId(Integer id) {
196    this.id = id;
197  }
198
199  public String getName() {
200    return name;
201  }
202
203  /**
204   * Sets the rule name
205   */
206  public Rule setName(String name) {
207    this.name = removeNewLineCharacters(name);
208    return this;
209  }
210
211  public String getKey() {
212    return key;
213  }
214
215  /**
216   * Sets the rule key
217   */
218  public Rule setKey(String key) {
219    this.key = key;
220    return this;
221  }
222
223  /**
224   * @deprecated since 2.5 use {@link #getRepositoryKey()} instead
225   */
226  @Deprecated
227  public String getPluginName() {
228    return pluginName;
229  }
230
231  /**
232   * @deprecated since 2.5 use {@link #setRepositoryKey(String)} instead
233   */
234  @Deprecated
235  public Rule setPluginName(String pluginName) {
236    this.pluginName = pluginName;
237    return this;
238  }
239
240  public String getConfigKey() {
241    return configKey;
242  }
243
244  /**
245   * Sets the configuration key
246   */
247  public Rule setConfigKey(String configKey) {
248    this.configKey = configKey;
249    return this;
250  }
251
252  public String getDescription() {
253    return description;
254  }
255
256  /**
257   * Sets the rule description
258   */
259  public Rule setDescription(String description) {
260    this.description = StringUtils.strip(description);
261    return this;
262  }
263
264  /**
265   * @deprecated in 3.6. Replaced by {@link #setStatus(String status)}.
266   */
267  @Deprecated
268  public Rule setEnabled(Boolean enabled) {
269    throw new UnsupportedOperationException("No more supported since version 3.6.");
270  }
271
272  public Boolean isEnabled() {
273    return !STATUS_REMOVED.equals(status);
274  }
275
276  public List<RuleParam> getParams() {
277    return params;
278  }
279
280  public RuleParam getParam(String key) {
281    for (RuleParam param : params) {
282      if (StringUtils.equals(key, param.getKey())) {
283        return param;
284      }
285    }
286    return null;
287  }
288
289  /**
290   * Sets the rule parameters
291   */
292  public Rule setParams(List<RuleParam> params) {
293    this.params.clear();
294    for (RuleParam param : params) {
295      param.setRule(this);
296      this.params.add(param);
297    }
298    return this;
299  }
300
301  public RuleParam createParameter() {
302    RuleParam parameter = new RuleParam()
303      .setRule(this);
304    params.add(parameter);
305    return parameter;
306  }
307
308  public RuleParam createParameter(String key) {
309    RuleParam parameter = new RuleParam()
310      .setKey(key)
311      .setRule(this);
312    params.add(parameter);
313    return parameter;
314  }
315
316  /**
317   * @deprecated since 2.5. See http://jira.codehaus.org/browse/SONAR-2007
318   */
319  @Deprecated
320  public Integer getCategoryId() {
321    return null;
322  }
323
324  /**
325   * @since 2.5
326   */
327  public RulePriority getSeverity() {
328    return priority;
329  }
330
331  /**
332   * @param severity severity to set, if null, uses the default priority.
333   * @since 2.5
334   */
335  public Rule setSeverity(RulePriority severity) {
336    if (severity == null) {
337      this.priority = DEFAULT_PRIORITY;
338    } else {
339      this.priority = severity;
340    }
341    return this;
342  }
343
344  /**
345   * @deprecated since 2.5 use {@link #getSeverity()} instead. See http://jira.codehaus.org/browse/SONAR-1829
346   */
347  @Deprecated
348  public RulePriority getPriority() {
349    return priority;
350  }
351
352  /**
353   * Sets the rule priority. If null, uses the default priority
354   *
355   * @deprecated since 2.5 use {@link #setSeverity(RulePriority)} instead. See http://jira.codehaus.org/browse/SONAR-1829
356   */
357  @Deprecated
358  public Rule setPriority(RulePriority priority) {
359    return setSeverity(priority);
360  }
361
362  public String getRepositoryKey() {
363    return pluginName;
364  }
365
366  public Rule setRepositoryKey(String s) {
367    this.pluginName = s;
368    return this;
369  }
370
371  public Rule setUniqueKey(String repositoryKey, String key) {
372    return setRepositoryKey(repositoryKey).setKey(key).setConfigKey(key);
373  }
374
375  /**
376   * @since 4.4
377   */
378  public boolean isTemplate() {
379    return isTemplate;
380  }
381
382  /**
383   * @since 4.4
384   */
385  public Rule setIsTemplate(boolean isTemplate) {
386    this.isTemplate = isTemplate;
387    return this;
388  }
389
390  /**
391   * @deprecated since 4.4, use {@link #isTemplate()}
392   */
393  @Deprecated
394  public Cardinality getCardinality() {
395    return isTemplate ? Cardinality.MULTIPLE : Cardinality.SINGLE;
396  }
397
398  /**
399   * @deprecated since 4.4, use {@link #setIsTemplate(boolean)}
400   */
401  @Deprecated
402  public Rule setCardinality(Cardinality c) {
403    this.isTemplate = Cardinality.MULTIPLE.equals(c);
404    return this;
405  }
406
407  /**
408   * @deprecated since 4.4, use {@link #getTemplate()}
409   */
410  @Deprecated
411  public Rule getParent() {
412    return template;
413  }
414
415  /**
416   * @deprecated since 4.4, use {@link #setTemplate(Rule)}}
417   */
418  @Deprecated
419  public Rule setParent(Rule parent) {
420    this.template = parent;
421    return this;
422  }
423
424  /**
425   * @since 4.4
426   */
427  public Rule getTemplate() {
428    return template;
429  }
430
431  /**
432   * @since 4.4
433   */
434  public Rule setTemplate(Rule template) {
435    this.template = template;
436    return this;
437  }
438
439  /**
440   * @since 3.6
441   */
442  public String getStatus() {
443    return status;
444  }
445
446  /**
447   * @since 3.6
448   */
449  public Rule setStatus(String status) {
450    if (!STATUS_LIST.contains(status)) {
451      throw new SonarException("The status of a rule can only contain : " + Joiner.on(", ").join(STATUS_LIST));
452    }
453    this.status = status;
454    return this;
455  }
456
457  /**
458   * @since 3.6
459   */
460  public Date getCreatedAt() {
461    return createdAt;
462  }
463
464  /**
465   * @since 3.6
466   */
467  public Rule setCreatedAt(Date d) {
468    this.createdAt = d;
469    return this;
470  }
471
472  /**
473   * @since 3.6
474   */
475  public Date getUpdatedAt() {
476    return updatedAt;
477  }
478
479  /**
480   * @since 3.6
481   */
482  public Rule setUpdatedAt(Date updatedAt) {
483    this.updatedAt = updatedAt;
484    return this;
485  }
486
487  /**
488   * @since 3.6
489   */
490  public String getLanguage() {
491    return language;
492  }
493
494  /**
495   * For internal use only.
496   *
497   * @since 3.6
498   */
499  public Rule setLanguage(String language) {
500    this.language = language;
501    return this;
502  }
503
504  /**
505   * For definition of rule only
506   */
507  public String[] getTags() {
508    return tags;
509  }
510
511  /**
512   * For definition of rule only
513   */
514  public Rule setTags(String[] tags) {
515    this.tags = tags;
516    return this;
517  }
518
519  /**
520   * For internal use only.
521   *
522   * @deprecated since 4.4, use {@link #getCharacteristicKey()}
523   * @since 4.3
524   */
525  @CheckForNull
526  @Deprecated
527  public Integer getCharacteristicId() {
528    return characteristicId;
529  }
530
531  /**
532   * For internal use only.
533   *
534   * @deprecated since 4.4, use {@link #setCharacteristicKey(@Nullable String characteristicKey)}
535   * @since 4.3
536   */
537  @Deprecated
538  public Rule setCharacteristicId(@Nullable Integer characteristicId) {
539    this.characteristicId = characteristicId;
540    return this;
541  }
542
543  /**
544   * For internal use only.
545   *
546   * @deprecated since 4.4, use {@link #getDefaultCharacteristicKey()}
547   * @since 4.3
548   */
549  @CheckForNull
550  @Deprecated
551  public Integer getDefaultCharacteristicId() {
552    return defaultCharacteristicId;
553  }
554
555  /**
556   * For internal use only.
557   *
558   * @deprecated since 4.4, use {@link #setDefaultCharacteristicKey(@Nullable String defaultCharacteristicKey)}
559   * @since 4.3
560   */
561  @Deprecated
562  public Rule setDefaultCharacteristicId(@Nullable Integer defaultCharacteristicId) {
563    this.defaultCharacteristicId = defaultCharacteristicId;
564    return this;
565  }
566
567  @Override
568  public boolean equals(Object obj) {
569    if (!(obj instanceof Rule)) {
570      return false;
571    }
572    if (this == obj) {
573      return true;
574    }
575    Rule other = (Rule) obj;
576    return new EqualsBuilder()
577      .append(pluginName, other.getRepositoryKey())
578      .append(key, other.getKey())
579      .isEquals();
580  }
581
582  @Override
583  public int hashCode() {
584    return new HashCodeBuilder(17, 37)
585      .append(pluginName)
586      .append(key)
587      .toHashCode();
588  }
589
590  @Override
591  public String toString() {
592    // Note that ReflectionToStringBuilder will not work here - see SONAR-3077
593    return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
594      .append("id", id)
595      .append("name", name)
596      .append("key", key)
597      .append("configKey", configKey)
598      .append("plugin", pluginName)
599      .append("severity", priority)
600      .append("isTemplate", isTemplate())
601      .append("status", status)
602      .append("language", language)
603      .append("template", template)
604      .toString();
605  }
606
607  @CheckForNull
608  private String removeNewLineCharacters(@Nullable String text) {
609    String removedCRLF = StringUtils.remove(text, "\n");
610    removedCRLF = StringUtils.remove(removedCRLF, "\r");
611    removedCRLF = StringUtils.remove(removedCRLF, "\n\r");
612    removedCRLF = StringUtils.remove(removedCRLF, "\r\n");
613    return removedCRLF;
614  }
615
616  public static Rule create() {
617    return new Rule();
618  }
619
620  /**
621   * Create with all required fields
622   */
623  public static Rule create(String repositoryKey, String key, String name) {
624    return new Rule().setUniqueKey(repositoryKey, key).setName(name);
625  }
626
627  /**
628   * Create with all required fields
629   *
630   * @since 2.10
631   */
632  public static Rule create(String repositoryKey, String key) {
633    return new Rule().setUniqueKey(repositoryKey, key);
634  }
635
636  /**
637   * @since 3.6
638   */
639  public RuleKey ruleKey() {
640    return RuleKey.of(getRepositoryKey(), getKey());
641  }
642
643  /**
644   * @since 4.4
645   */
646  @CheckForNull
647  public String getDefaultCharacteristicKey() {
648    return defaultCharacteristicKey;
649  }
650
651  /**
652   * @since 4.4
653   */
654  public Rule setDefaultCharacteristicKey(@Nullable String defaultCharacteristicKey) {
655    this.defaultCharacteristicKey = defaultCharacteristicKey;
656    return this;
657  }
658
659  /**
660   * @since 4.4
661   */
662  @CheckForNull
663  public String getDefaultSubCharacteristicKey() {
664    return defaultSubCharacteristicKey;
665  }
666
667  /**
668   * @since 4.4
669   */
670  public Rule setDefaultSubCharacteristicKey(@Nullable String defaultSubCharacteristicKey) {
671    this.defaultSubCharacteristicKey = defaultSubCharacteristicKey;
672    return this;
673  }
674
675  /**
676   * @since 4.4
677   */
678  @CheckForNull
679  public String getCharacteristicKey() {
680    return characteristicKey;
681  }
682
683  /**
684   * @since 4.4
685   */
686  public Rule setCharacteristicKey(@Nullable String characteristicKey) {
687    this.characteristicKey = characteristicKey;
688    return this;
689  }
690
691  /**
692   * @since 4.4
693   */
694  @CheckForNull
695  public String getSubCharacteristicKey() {
696    return subCharacteristicKey;
697  }
698
699  /**
700   * @since 4.4
701   */
702  public Rule setSubCharacteristicKey(@Nullable String subCharacteristicKey) {
703    this.subCharacteristicKey = subCharacteristicKey;
704    return this;
705  }
706}