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    import java.util.ArrayList;
039    import java.util.Date;
040    import java.util.List;
041    import java.util.Set;
042    
043    @Entity
044    @Table(name = "rules")
045    public final class Rule {
046    
047      /**
048       * @since 3.6
049       */
050      public static final String STATUS_BETA = "BETA";
051      /**
052       * @since 3.6
053       */
054      public static final String STATUS_DEPRECATED = "DEPRECATED";
055      /**
056       * @since 3.6
057       */
058      public static final String STATUS_READY = "READY";
059    
060      /**
061       * For internal use only.
062       *
063       * @since 3.6
064       */
065      public static final String STATUS_REMOVED = "REMOVED";
066    
067      /**
068       * List of available status
069       *
070       * @since 3.6
071       */
072      private static final Set<String> STATUS_LIST = ImmutableSet.of(STATUS_READY, STATUS_BETA, STATUS_DEPRECATED, STATUS_REMOVED);
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      public Integer getId() {
153        return id;
154      }
155    
156      /**
157       * @deprecated since 2.3. visibility should be decreased to protected or package
158       */
159      @Deprecated
160      public void setId(Integer id) {
161        this.id = id;
162      }
163    
164      @CheckForNull
165      public String getName() {
166        return name;
167      }
168    
169      /**
170       * Sets the rule name
171       */
172      public Rule setName(@Nullable String name) {
173        this.name = removeNewLineCharacters(name);
174        return this;
175      }
176    
177      public String getKey() {
178        return key;
179      }
180    
181      /**
182       * Sets the rule key
183       */
184      public Rule setKey(String key) {
185        this.key = key;
186        return this;
187      }
188    
189      /**
190       * @deprecated since 2.5 use {@link #getRepositoryKey()} instead
191       */
192      @Deprecated
193      public String getPluginName() {
194        return pluginName;
195      }
196    
197      /**
198       * @deprecated since 2.5 use {@link #setRepositoryKey(String)} instead
199       */
200      @Deprecated
201      public Rule setPluginName(String pluginName) {
202        this.pluginName = pluginName;
203        return this;
204      }
205    
206      public String getConfigKey() {
207        return configKey;
208      }
209    
210      /**
211       * Sets the configuration key
212       */
213      public Rule setConfigKey(String configKey) {
214        this.configKey = configKey;
215        return this;
216      }
217    
218      public String getDescription() {
219        return description;
220      }
221    
222      /**
223       * Sets the rule description
224       */
225      public Rule setDescription(String description) {
226        this.description = StringUtils.strip(description);
227        return this;
228      }
229    
230      /**
231       * @deprecated in 3.6. Replaced by {@link #setStatus(String status)}.
232       */
233      @Deprecated
234      public Rule setEnabled(Boolean enabled) {
235        throw new UnsupportedOperationException("No more supported since version 3.6.");
236      }
237    
238      public Boolean isEnabled() {
239        return !STATUS_REMOVED.equals(status);
240      }
241    
242      public List<RuleParam> getParams() {
243        return params;
244      }
245    
246      public RuleParam getParam(String key) {
247        for (RuleParam param : params) {
248          if (StringUtils.equals(key, param.getKey())) {
249            return param;
250          }
251        }
252        return null;
253      }
254    
255      /**
256       * Sets the rule parameters
257       */
258      public Rule setParams(List<RuleParam> params) {
259        this.params.clear();
260        for (RuleParam param : params) {
261          param.setRule(this);
262          this.params.add(param);
263        }
264        return this;
265      }
266    
267      public RuleParam createParameter() {
268        RuleParam parameter = new RuleParam()
269          .setRule(this);
270        params.add(parameter);
271        return parameter;
272      }
273    
274      public RuleParam createParameter(String key) {
275        RuleParam parameter = new RuleParam()
276          .setKey(key)
277          .setRule(this);
278        params.add(parameter);
279        return parameter;
280      }
281    
282      /**
283       * @deprecated since 2.5. See http://jira.codehaus.org/browse/SONAR-2007
284       */
285      @Deprecated
286      public Integer getCategoryId() {
287        return null;
288      }
289    
290      /**
291       * @since 2.5
292       */
293      public RulePriority getSeverity() {
294        return priority;
295      }
296    
297      /**
298       * @param severity severity to set, if null, uses the default priority.
299       * @since 2.5
300       */
301      public Rule setSeverity(RulePriority severity) {
302        if (severity == null) {
303          this.priority = DEFAULT_PRIORITY;
304        } else {
305          this.priority = severity;
306        }
307        return this;
308      }
309    
310      /**
311       * @deprecated since 2.5 use {@link #getSeverity()} instead. See http://jira.codehaus.org/browse/SONAR-1829
312       */
313      @Deprecated
314      public RulePriority getPriority() {
315        return priority;
316      }
317    
318      /**
319       * Sets the rule priority. If null, uses the default priority
320       *
321       * @deprecated since 2.5 use {@link #setSeverity(RulePriority)} instead. See http://jira.codehaus.org/browse/SONAR-1829
322       */
323      @Deprecated
324      public Rule setPriority(RulePriority priority) {
325        return setSeverity(priority);
326      }
327    
328      public String getRepositoryKey() {
329        return pluginName;
330      }
331    
332      public Rule setRepositoryKey(String s) {
333        this.pluginName = s;
334        return this;
335      }
336    
337      public Rule setUniqueKey(String repositoryKey, String key) {
338        return setRepositoryKey(repositoryKey).setKey(key).setConfigKey(key);
339      }
340    
341      public Cardinality getCardinality() {
342        return cardinality;
343      }
344    
345      public Rule setCardinality(Cardinality c) {
346        this.cardinality = c;
347        return this;
348      }
349    
350      public Rule getParent() {
351        return parent;
352      }
353    
354      public Rule setParent(Rule parent) {
355        this.parent = parent;
356        return this;
357      }
358    
359      /**
360       * @since 3.6
361       */
362      public String getStatus() {
363        return status;
364      }
365    
366      /**
367       * @since 3.6
368       */
369      public Rule setStatus(String status) {
370        if (!STATUS_LIST.contains(status)) {
371          throw new SonarException("The status of a rule can only contain : " + Joiner.on(", ").join(STATUS_LIST));
372        }
373        this.status = status;
374        return this;
375      }
376    
377      /**
378       * @since 3.6
379       */
380      public Date getCreatedAt() {
381        return createdAt;
382      }
383    
384      /**
385       * @since 3.6
386       */
387      public Rule setCreatedAt(Date d) {
388        this.createdAt = d;
389        return this;
390      }
391    
392      /**
393       * @since 3.6
394       */
395      public Date getUpdatedAt() {
396        return updatedAt;
397      }
398    
399      /**
400       * @since 3.6
401       */
402      public Rule setUpdatedAt(Date updatedAt) {
403        this.updatedAt = updatedAt;
404        return this;
405      }
406    
407      /**
408       * @since 3.6
409       */
410      public String getLanguage() {
411        return language;
412      }
413    
414      /**
415       * For internal use only.
416       *
417       * @since 3.6
418       */
419      public Rule setLanguage(String language) {
420        this.language = language;
421        return this;
422      }
423    
424      @Override
425      public boolean equals(Object obj) {
426        if (!(obj instanceof Rule)) {
427          return false;
428        }
429        if (this == obj) {
430          return true;
431        }
432        Rule other = (Rule) obj;
433        return new EqualsBuilder()
434          .append(pluginName, other.getRepositoryKey())
435          .append(key, other.getKey())
436          .isEquals();
437      }
438    
439      @Override
440      public int hashCode() {
441        return new HashCodeBuilder(17, 37)
442          .append(pluginName)
443          .append(key)
444          .toHashCode();
445      }
446    
447      @Override
448      public String toString() {
449        // Note that ReflectionToStringBuilder will not work here - see SONAR-3077
450        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
451          .append("id", id)
452          .append("name", name)
453          .append("key", key)
454          .append("configKey", configKey)
455          .append("plugin", pluginName)
456          .append("severity", priority)
457          .append("cardinality", cardinality)
458          .append("status", status)
459          .append("language", language)
460          .append("parent", parent)
461          .toString();
462      }
463    
464      @CheckForNull
465      private String removeNewLineCharacters(@Nullable String text) {
466        String removedCRLF = StringUtils.remove(text, "\n");
467        removedCRLF = StringUtils.remove(removedCRLF, "\r");
468        removedCRLF = StringUtils.remove(removedCRLF, "\n\r");
469        removedCRLF = StringUtils.remove(removedCRLF, "\r\n");
470        return removedCRLF;
471      }
472    
473      public static Rule create() {
474        return new Rule();
475      }
476    
477      /**
478       * Create with all required fields
479       */
480      public static Rule create(String repositoryKey, String key, String name) {
481        return new Rule().setUniqueKey(repositoryKey, key).setName(name);
482      }
483    
484      /**
485       * Create with all required fields
486       *
487       * @since 2.10
488       */
489      public static Rule create(String repositoryKey, String key) {
490        return new Rule().setUniqueKey(repositoryKey, key);
491      }
492    
493      /**
494       * @since 3.6
495       */
496      public RuleKey ruleKey() {
497        return RuleKey.of(getRepositoryKey(), getKey());
498      }
499    }