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 */
020package org.sonar.api.issue;
021
022import com.google.common.base.Preconditions;
023import com.google.common.collect.ImmutableSet;
024import org.apache.commons.lang.builder.ReflectionToStringBuilder;
025import org.sonar.api.rule.RuleKey;
026import org.sonar.api.web.UserRole;
027
028import javax.annotation.CheckForNull;
029import javax.annotation.Nullable;
030
031import java.util.Collection;
032import java.util.Collections;
033import java.util.Date;
034import java.util.Set;
035
036/**
037 * @since 3.6
038 */
039public class IssueQuery {
040
041  public static final int DEFAULT_PAGE_INDEX = 1;
042  public static final int DEFAULT_PAGE_SIZE = 100;
043  public static final int MAX_RESULTS = 10000;
044  public static final int MAX_PAGE_SIZE = 500;
045
046  /**
047   * @deprecated since 3.7. It's replaced by IssueQuery#MAX_PAGE_SIZE.
048   */
049  @Deprecated
050  public static final int MAX_ISSUE_KEYS = MAX_PAGE_SIZE;
051
052  public static final String SORT_BY_CREATION_DATE = "CREATION_DATE";
053  public static final String SORT_BY_UPDATE_DATE = "UPDATE_DATE";
054  public static final String SORT_BY_CLOSE_DATE = "CLOSE_DATE";
055  public static final String SORT_BY_ASSIGNEE = "ASSIGNEE";
056  public static final String SORT_BY_SEVERITY = "SEVERITY";
057  public static final String SORT_BY_STATUS = "STATUS";
058  public static final Set<String> SORTS = ImmutableSet.of(SORT_BY_CREATION_DATE, SORT_BY_UPDATE_DATE, SORT_BY_CLOSE_DATE, SORT_BY_ASSIGNEE, SORT_BY_SEVERITY, SORT_BY_STATUS);
059
060  private final Collection<String> issueKeys;
061  private final Collection<String> severities;
062  private final Collection<String> statuses;
063  private final Collection<String> resolutions;
064  private final Collection<String> components;
065  private final Collection<String> componentRoots;
066  private final Collection<RuleKey> rules;
067  private final Collection<String> actionPlans;
068  private final Collection<String> reporters;
069  private final Collection<String> assignees;
070  private final Boolean assigned;
071  private final Boolean planned;
072  private final Boolean resolved;
073  private final Boolean hideRules;
074  private final Date createdAt;
075  private final Date createdAfter;
076  private final Date createdBefore;
077  private final String sort;
078  private final Boolean asc;
079  private final String requiredRole;
080
081  // max results per page
082  private final int pageSize;
083
084  // index of selected page. Start with 1.
085  private final int pageIndex;
086
087  private IssueQuery(Builder builder) {
088    this.issueKeys = defaultCollection(builder.issueKeys);
089    this.severities = defaultCollection(builder.severities);
090    this.statuses = defaultCollection(builder.statuses);
091    this.resolutions = defaultCollection(builder.resolutions);
092    this.components = defaultCollection(builder.components);
093    this.componentRoots = defaultCollection(builder.componentRoots);
094    this.rules = defaultCollection(builder.rules);
095    this.actionPlans = defaultCollection(builder.actionPlans);
096    this.reporters = defaultCollection(builder.reporters);
097    this.assignees = defaultCollection(builder.assignees);
098    this.assigned = builder.assigned;
099    this.planned = builder.planned;
100    this.resolved = builder.resolved;
101    this.hideRules = builder.hideRules;
102    this.createdAt = builder.createdAt;
103    this.createdAfter = builder.createdAfter;
104    this.createdBefore = builder.createdBefore;
105    this.sort = builder.sort;
106    this.asc = builder.asc;
107    this.pageSize = builder.pageSize;
108    this.pageIndex = builder.pageIndex;
109    this.requiredRole = builder.requiredRole;
110  }
111
112  public Collection<String> issueKeys() {
113    return issueKeys;
114  }
115
116  public Collection<String> severities() {
117    return severities;
118  }
119
120  public Collection<String> statuses() {
121    return statuses;
122  }
123
124  public Collection<String> resolutions() {
125    return resolutions;
126  }
127
128  public Collection<String> components() {
129    return components;
130  }
131
132  public Collection<String> componentRoots() {
133    return componentRoots;
134  }
135
136  public Collection<RuleKey> rules() {
137    return rules;
138  }
139
140  public Collection<String> actionPlans() {
141    return actionPlans;
142  }
143
144  public Collection<String> reporters() {
145    return reporters;
146  }
147
148  public Collection<String> assignees() {
149    return assignees;
150  }
151
152  @CheckForNull
153  public Boolean assigned() {
154    return assigned;
155  }
156
157  @CheckForNull
158  public Boolean planned() {
159    return planned;
160  }
161
162  @CheckForNull
163  public Boolean resolved() {
164    return resolved;
165  }
166
167  /**
168   * @since 4.2
169   */
170  @CheckForNull
171  public Boolean hideRules() {
172    return hideRules;
173  }
174
175  @CheckForNull
176  public Date createdAfter() {
177    return createdAfter == null ? null : new Date(createdAfter.getTime());
178  }
179
180  @CheckForNull
181  public Date createdAt() {
182    return createdAt == null ? null : new Date(createdAt.getTime());
183  }
184
185  @CheckForNull
186  public Date createdBefore() {
187    return createdBefore == null ? null : new Date(createdBefore.getTime());
188  }
189
190  @CheckForNull
191  public String sort() {
192    return sort;
193  }
194
195  @CheckForNull
196  public Boolean asc() {
197    return asc;
198  }
199
200  public int pageSize() {
201    return pageSize;
202  }
203
204  public int pageIndex() {
205    return pageIndex;
206  }
207
208  public int maxResults() {
209    return MAX_RESULTS;
210  }
211
212  public String requiredRole() {
213    return requiredRole;
214  }
215
216  @Override
217  public String toString() {
218    return ReflectionToStringBuilder.toString(this);
219  }
220
221  public static Builder builder() {
222    return new Builder();
223  }
224
225  public static class Builder {
226    private Collection<String> issueKeys;
227    private Collection<String> severities;
228    private Collection<String> statuses;
229    private Collection<String> resolutions;
230    private Collection<String> components;
231    private Collection<String> componentRoots;
232    private Collection<RuleKey> rules;
233    private Collection<String> actionPlans;
234    private Collection<String> reporters;
235    private Collection<String> assignees;
236    private Boolean assigned = null;
237    private Boolean planned = null;
238    private Boolean resolved = null;
239    private Boolean hideRules = false;
240    private Date createdAt;
241    private Date createdAfter;
242    private Date createdBefore;
243    private String sort;
244    private Boolean asc = false;
245    private Integer pageSize;
246    private Integer pageIndex;
247    private String requiredRole = UserRole.USER;
248
249    private Builder() {
250    }
251
252    public Builder issueKeys(@Nullable Collection<String> l) {
253      this.issueKeys = l;
254      return this;
255    }
256
257    public Builder severities(@Nullable Collection<String> l) {
258      this.severities = l;
259      return this;
260    }
261
262    public Builder statuses(@Nullable Collection<String> l) {
263      this.statuses = l;
264      return this;
265    }
266
267    public Builder resolutions(@Nullable Collection<String> l) {
268      this.resolutions = l;
269      return this;
270    }
271
272    public Builder components(@Nullable Collection<String> l) {
273      this.components = l;
274      return this;
275    }
276
277    public Builder componentRoots(@Nullable Collection<String> l) {
278      this.componentRoots = l;
279      return this;
280    }
281
282    public Builder rules(@Nullable Collection<RuleKey> rules) {
283      this.rules = rules;
284      return this;
285    }
286
287    public Builder actionPlans(@Nullable Collection<String> l) {
288      this.actionPlans = l;
289      return this;
290    }
291
292    public Builder reporters(@Nullable Collection<String> l) {
293      this.reporters = l;
294      return this;
295    }
296
297    public Builder assignees(@Nullable Collection<String> l) {
298      this.assignees = l;
299      return this;
300    }
301
302    /**
303     * If true, it will return all issues assigned to someone
304     * If false, it will return all issues not assigned to someone
305     */
306    public Builder assigned(@Nullable Boolean b) {
307      this.assigned = b;
308      return this;
309    }
310
311    /**
312     * If true, it will return all issues linked to an action plan
313     * If false, it will return all issues not linked to an action plan
314     */
315    public Builder planned(@Nullable Boolean planned) {
316      this.planned = planned;
317      return this;
318    }
319
320    /**
321     * If true, it will return all resolved issues
322     * If false, it will return all none resolved issues
323     */
324    public Builder resolved(@Nullable Boolean resolved) {
325      this.resolved = resolved;
326      return this;
327    }
328
329    /**
330     * If true, rules will not be loaded
331     * If false, rules will be loaded
332     *
333     * @since 4.2
334     *
335     */
336    public Builder hideRules(@Nullable Boolean b) {
337      this.hideRules = b;
338      return this;
339    }
340
341    public Builder createdAt(@Nullable Date d) {
342      this.createdAt = d == null ? null : new Date(d.getTime());
343      return this;
344    }
345
346    public Builder createdAfter(@Nullable Date d) {
347      this.createdAfter = d == null ? null : new Date(d.getTime());
348      return this;
349    }
350
351    public Builder createdBefore(@Nullable Date d) {
352      this.createdBefore = d == null ? null : new Date(d.getTime());
353      return this;
354    }
355
356    public Builder sort(@Nullable String s) {
357      if (s != null && !SORTS.contains(s)) {
358        throw new IllegalArgumentException("Bad sort field: " + s);
359      }
360      this.sort = s;
361      return this;
362    }
363
364    public Builder asc(@Nullable Boolean asc) {
365      this.asc = asc;
366      return this;
367    }
368
369    public Builder pageSize(@Nullable Integer i) {
370      this.pageSize = i;
371      return this;
372    }
373
374    public Builder pageIndex(@Nullable Integer i) {
375      this.pageIndex = i;
376      return this;
377    }
378
379    public Builder requiredRole(@Nullable String s) {
380      this.requiredRole = s;
381      return this;
382    }
383
384    public IssueQuery build() {
385      initPageIndex();
386      initPageSize();
387      if (issueKeys != null) {
388        Preconditions.checkArgument(issueKeys.size() <= MAX_PAGE_SIZE, "Number of issue keys must be less than " + MAX_PAGE_SIZE + " (got " + issueKeys.size() + ")");
389      }
390      return new IssueQuery(this);
391    }
392
393    private void initPageSize() {
394      if (components != null && components.size() == 1 && pageSize == null) {
395        pageSize = 999999;
396      } else {
397        if (pageSize == null) {
398          pageSize = DEFAULT_PAGE_SIZE;
399        } else if (pageSize <= 0 || pageSize > MAX_PAGE_SIZE) {
400          pageSize = MAX_PAGE_SIZE;
401        }
402      }
403    }
404
405    private void initPageIndex() {
406      if (pageIndex == null) {
407        pageIndex = DEFAULT_PAGE_INDEX;
408      }
409      Preconditions.checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")");
410    }
411  }
412
413  private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
414    return c == null ? Collections.<T>emptyList() : Collections.unmodifiableCollection(c);
415  }
416}