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