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