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