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    
021    package org.sonar.api.rules;
022    
023    import com.google.common.base.Joiner;
024    import com.google.common.collect.ImmutableSet;
025    import org.apache.commons.lang.StringUtils;
026    import org.apache.commons.lang.builder.EqualsBuilder;
027    import org.apache.commons.lang.builder.HashCodeBuilder;
028    import org.apache.commons.lang.builder.ToStringBuilder;
029    import org.apache.commons.lang.builder.ToStringStyle;
030    import org.sonar.api.database.DatabaseProperties;
031    import org.sonar.api.rule.RuleKey;
032    import org.sonar.api.utils.SonarException;
033    import org.sonar.check.Cardinality;
034    
035    import javax.annotation.CheckForNull;
036    import javax.annotation.Nullable;
037    import javax.persistence.Column;
038    import javax.persistence.Entity;
039    import javax.persistence.EnumType;
040    import javax.persistence.Enumerated;
041    import javax.persistence.FetchType;
042    import javax.persistence.GeneratedValue;
043    import javax.persistence.Id;
044    import javax.persistence.JoinColumn;
045    import javax.persistence.ManyToOne;
046    import javax.persistence.OneToMany;
047    import javax.persistence.Table;
048    import javax.persistence.Temporal;
049    import javax.persistence.TemporalType;
050    import javax.persistence.Transient;
051    import java.util.ArrayList;
052    import java.util.Date;
053    import java.util.List;
054    import java.util.Set;
055    
056    @Entity
057    @Table(name = "rules")
058    public 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       *
076       * @since 3.6
077       */
078      public static final String STATUS_REMOVED = "REMOVED";
079    
080      /**
081       * List of available status
082       *
083       * @since 3.6
084       */
085      private static final Set<String> STATUS_LIST = ImmutableSet.of(STATUS_READY, STATUS_BETA, STATUS_DEPRECATED, STATUS_REMOVED);
086    
087      /**
088       * @since 4.2
089       */
090      private static final String[] DEFAULT_TAGS = new String[0];
091    
092      @Id
093      @Column(name = "id")
094      @GeneratedValue
095      private Integer id;
096    
097      /**
098       * The default priority given to a rule if not explicitly set
099       */
100      public static final RulePriority DEFAULT_PRIORITY = RulePriority.MAJOR;
101    
102      @Column(name = "name", updatable = true, nullable = true, length = 200)
103      private String name;
104    
105      @Column(name = "plugin_rule_key", updatable = false, nullable = true, length = 200)
106      private String key;
107    
108      @Column(name = "plugin_config_key", updatable = true, nullable = true, length = 500)
109      private String configKey;
110    
111      @Column(name = "priority", updatable = true, nullable = true)
112      @Enumerated(EnumType.ORDINAL)
113      private RulePriority priority = DEFAULT_PRIORITY;
114    
115      @Column(name = "description", updatable = true, nullable = true, length = DatabaseProperties.MAX_TEXT_SIZE)
116      private String description;
117    
118      @Column(name = "plugin_name", updatable = true, nullable = false)
119      private String pluginName;
120    
121      @Enumerated(EnumType.STRING)
122      @Column(name = "is_template", updatable = true, nullable = false)
123      private boolean isTemplate = false;
124    
125      @Column(name = "status", updatable = true, nullable = true)
126      private String status = STATUS_READY;
127    
128      @Column(name = "language", updatable = true, nullable = true)
129      private String language;
130    
131      @ManyToOne(fetch = FetchType.EAGER)
132      @JoinColumn(name = "template_id", updatable = true, nullable = true)
133      private Rule template = null;
134    
135      @Column(name = "characteristic_id", updatable = true, nullable = true)
136      private Integer characteristicId;
137    
138      @Column(name = "default_characteristic_id", updatable = true, nullable = true)
139      private Integer defaultCharacteristicId;
140    
141      @org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
142      @OneToMany(mappedBy = "rule")
143      private List<RuleParam> params = new ArrayList<RuleParam>();
144    
145      @Temporal(TemporalType.TIMESTAMP)
146      @Column(name = "created_at", updatable = true, nullable = true)
147      private Date createdAt;
148    
149      @Temporal(TemporalType.TIMESTAMP)
150      @Column(name = "updated_at", updatable = true, nullable = true)
151      private Date updatedAt;
152    
153      @Transient
154      private String defaultCharacteristicKey;
155      @Transient
156      private String defaultSubCharacteristicKey;
157      @Transient
158      private String characteristicKey;
159      @Transient
160      private String subCharacteristicKey;
161    
162      private transient String[] tags = DEFAULT_TAGS;
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      @CheckForNull
199      public String getName() {
200        return name;
201      }
202    
203      /**
204       * Sets the rule name
205       */
206      public Rule setName(@Nullable 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    }