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