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