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    
052    import java.util.ArrayList;
053    import java.util.Date;
054    import java.util.List;
055    import java.util.Set;
056    
057    @Entity
058    @Table(name = "rules")
059    public 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      public String getConfigKey() {
224        return configKey;
225      }
226    
227      /**
228       * Sets the configuration key
229       */
230      public Rule setConfigKey(String configKey) {
231        this.configKey = configKey;
232        return this;
233      }
234    
235      public String getDescription() {
236        return description;
237      }
238    
239      /**
240       * Sets the rule description
241       */
242      public Rule setDescription(String description) {
243        this.description = StringUtils.strip(description);
244        return this;
245      }
246    
247      public Boolean isEnabled() {
248        return !STATUS_REMOVED.equals(status);
249      }
250    
251      public List<RuleParam> getParams() {
252        return params;
253      }
254    
255      public RuleParam getParam(String key) {
256        for (RuleParam param : params) {
257          if (StringUtils.equals(key, param.getKey())) {
258            return param;
259          }
260        }
261        return null;
262      }
263    
264      /**
265       * Sets the rule parameters
266       */
267      public Rule setParams(List<RuleParam> params) {
268        this.params.clear();
269        for (RuleParam param : params) {
270          param.setRule(this);
271          this.params.add(param);
272        }
273        return this;
274      }
275    
276      public RuleParam createParameter() {
277        RuleParam parameter = new RuleParam()
278          .setRule(this);
279        params.add(parameter);
280        return parameter;
281      }
282    
283      public RuleParam createParameter(String key) {
284        RuleParam parameter = new RuleParam()
285          .setKey(key)
286          .setRule(this);
287        params.add(parameter);
288        return parameter;
289      }
290    
291      /**
292       * @since 2.5
293       */
294      public RulePriority getSeverity() {
295        return priority;
296      }
297    
298      /**
299       * @param severity severity to set, if null, uses the default priority.
300       * @since 2.5
301       */
302      public Rule setSeverity(RulePriority severity) {
303        if (severity == null) {
304          this.priority = DEFAULT_PRIORITY;
305        } else {
306          this.priority = severity;
307        }
308        return this;
309      }
310    
311      public String getRepositoryKey() {
312        return pluginName;
313      }
314    
315      public Rule setRepositoryKey(String s) {
316        this.pluginName = s;
317        return this;
318      }
319    
320      public Rule setUniqueKey(String repositoryKey, String key) {
321        return setRepositoryKey(repositoryKey).setKey(key).setConfigKey(key);
322      }
323    
324      /**
325       * @since 4.4
326       */
327      public boolean isTemplate() {
328        return isTemplate;
329      }
330    
331      /**
332       * @since 4.4
333       */
334      public Rule setIsTemplate(boolean isTemplate) {
335        this.isTemplate = isTemplate;
336        return this;
337      }
338    
339      /**
340       * @deprecated since 4.4, use {@link #isTemplate()}
341       */
342      @Deprecated
343      public Cardinality getCardinality() {
344        return isTemplate ? Cardinality.MULTIPLE : Cardinality.SINGLE;
345      }
346    
347      /**
348       * @deprecated since 4.4, use {@link #setIsTemplate(boolean)}
349       */
350      @Deprecated
351      public Rule setCardinality(Cardinality c) {
352        this.isTemplate = Cardinality.MULTIPLE.equals(c);
353        return this;
354      }
355    
356      /**
357       * @deprecated since 4.4, use {@link #getTemplate()}
358       */
359      @Deprecated
360      public Rule getParent() {
361        return template;
362      }
363    
364      /**
365       * @deprecated since 4.4, use {@link #setTemplate(Rule)}}
366       */
367      @Deprecated
368      public Rule setParent(Rule parent) {
369        this.template = parent;
370        return this;
371      }
372    
373      /**
374       * @since 4.4
375       */
376      public Rule getTemplate() {
377        return template;
378      }
379    
380      /**
381       * @since 4.4
382       */
383      public Rule setTemplate(Rule template) {
384        this.template = template;
385        return this;
386      }
387    
388      /**
389       * @since 3.6
390       */
391      public String getStatus() {
392        return status;
393      }
394    
395      /**
396       * @since 3.6
397       */
398      public Rule setStatus(String status) {
399        if (!STATUS_LIST.contains(status)) {
400          throw new SonarException("The status of a rule can only contain : " + Joiner.on(", ").join(STATUS_LIST));
401        }
402        this.status = status;
403        return this;
404      }
405    
406      /**
407       * @since 3.6
408       */
409      public Date getCreatedAt() {
410        return createdAt;
411      }
412    
413      /**
414       * @since 3.6
415       */
416      public Rule setCreatedAt(Date d) {
417        this.createdAt = d;
418        return this;
419      }
420    
421      /**
422       * @since 3.6
423       */
424      public Date getUpdatedAt() {
425        return updatedAt;
426      }
427    
428      /**
429       * @since 3.6
430       */
431      public Rule setUpdatedAt(Date updatedAt) {
432        this.updatedAt = updatedAt;
433        return this;
434      }
435    
436      /**
437       * @since 3.6
438       */
439      public String getLanguage() {
440        return language;
441      }
442    
443      /**
444       * For internal use only.
445       *
446       * @since 3.6
447       */
448      public Rule setLanguage(String language) {
449        this.language = language;
450        return this;
451      }
452    
453      /**
454       * For definition of rule only
455       */
456      public String[] getTags() {
457        return tags;
458      }
459    
460      /**
461       * For definition of rule only
462       */
463      public Rule setTags(String[] tags) {
464        this.tags = tags;
465        return this;
466      }
467    
468      /**
469       * For internal use only.
470       *
471       * @deprecated since 4.4, use {@link #getCharacteristicKey()}
472       * @since 4.3
473       */
474      @CheckForNull
475      @Deprecated
476      public Integer getCharacteristicId() {
477        return characteristicId;
478      }
479    
480      /**
481       * For internal use only.
482       *
483       * @deprecated since 4.4, use {@link #setCharacteristicKey(String)}
484       * @since 4.3
485       */
486      @Deprecated
487      public Rule setCharacteristicId(@Nullable Integer characteristicId) {
488        this.characteristicId = characteristicId;
489        return this;
490      }
491    
492      /**
493       * For internal use only.
494       *
495       * @deprecated since 4.4, use {@link #getDefaultCharacteristicKey()}
496       * @since 4.3
497       */
498      @CheckForNull
499      @Deprecated
500      public Integer getDefaultCharacteristicId() {
501        return defaultCharacteristicId;
502      }
503    
504      /**
505       * For internal use only.
506       *
507       * @deprecated since 4.4, use {@link #setDefaultCharacteristicKey(String)}
508       * @since 4.3
509       */
510      @Deprecated
511      public Rule setDefaultCharacteristicId(@Nullable Integer defaultCharacteristicId) {
512        this.defaultCharacteristicId = defaultCharacteristicId;
513        return this;
514      }
515    
516      @Override
517      public boolean equals(Object obj) {
518        if (!(obj instanceof Rule)) {
519          return false;
520        }
521        if (this == obj) {
522          return true;
523        }
524        Rule other = (Rule) obj;
525        return new EqualsBuilder()
526          .append(pluginName, other.getRepositoryKey())
527          .append(key, other.getKey())
528          .isEquals();
529      }
530    
531      @Override
532      public int hashCode() {
533        return new HashCodeBuilder(17, 37)
534          .append(pluginName)
535          .append(key)
536          .toHashCode();
537      }
538    
539      @Override
540      public String toString() {
541        // Note that ReflectionToStringBuilder will not work here - see SONAR-3077
542        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
543          .append("id", id)
544          .append("name", name)
545          .append("key", key)
546          .append("configKey", configKey)
547          .append("plugin", pluginName)
548          .append("severity", priority)
549          .append("isTemplate", isTemplate())
550          .append("status", status)
551          .append("language", language)
552          .append("template", template)
553          .toString();
554      }
555    
556      @CheckForNull
557      private String removeNewLineCharacters(@Nullable String text) {
558        String removedCRLF = StringUtils.remove(text, "\n");
559        removedCRLF = StringUtils.remove(removedCRLF, "\r");
560        removedCRLF = StringUtils.remove(removedCRLF, "\n\r");
561        removedCRLF = StringUtils.remove(removedCRLF, "\r\n");
562        return removedCRLF;
563      }
564    
565      public static Rule create() {
566        return new Rule();
567      }
568    
569      /**
570       * Create with all required fields
571       */
572      public static Rule create(String repositoryKey, String key, String name) {
573        return new Rule().setUniqueKey(repositoryKey, key).setName(name);
574      }
575    
576      /**
577       * Create with all required fields
578       *
579       * @since 2.10
580       */
581      public static Rule create(String repositoryKey, String key) {
582        return new Rule().setUniqueKey(repositoryKey, key);
583      }
584    
585      /**
586       * @since 3.6
587       */
588      public RuleKey ruleKey() {
589        return RuleKey.of(getRepositoryKey(), getKey());
590      }
591    
592      /**
593       * @since 4.4
594       */
595      @CheckForNull
596      public String getDefaultCharacteristicKey() {
597        return defaultCharacteristicKey;
598      }
599    
600      /**
601       * @since 4.4
602       */
603      public Rule setDefaultCharacteristicKey(@Nullable String defaultCharacteristicKey) {
604        this.defaultCharacteristicKey = defaultCharacteristicKey;
605        return this;
606      }
607    
608      /**
609       * @since 4.4
610       */
611      @CheckForNull
612      public String getDefaultSubCharacteristicKey() {
613        return defaultSubCharacteristicKey;
614      }
615    
616      /**
617       * @since 4.4
618       */
619      public Rule setDefaultSubCharacteristicKey(@Nullable String defaultSubCharacteristicKey) {
620        this.defaultSubCharacteristicKey = defaultSubCharacteristicKey;
621        return this;
622      }
623    
624      /**
625       * @since 4.4
626       */
627      @CheckForNull
628      public String getCharacteristicKey() {
629        return characteristicKey;
630      }
631    
632      /**
633       * @since 4.4
634       */
635      public Rule setCharacteristicKey(@Nullable String characteristicKey) {
636        this.characteristicKey = characteristicKey;
637        return this;
638      }
639    
640      /**
641       * @since 4.4
642       */
643      @CheckForNull
644      public String getSubCharacteristicKey() {
645        return subCharacteristicKey;
646      }
647    
648      /**
649       * @since 4.4
650       */
651      public Rule setSubCharacteristicKey(@Nullable String subCharacteristicKey) {
652        this.subCharacteristicKey = subCharacteristicKey;
653        return this;
654      }
655    }