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