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 Date createdAfter;
074  private final Date createdBefore;
075  private final String sort;
076  private final Boolean asc;
077  private final String requiredRole;
078
079  // max results per page
080  private final int pageSize;
081
082  // index of selected page. Start with 1.
083  private final int pageIndex;
084
085  private IssueQuery(Builder builder) {
086    this.issueKeys = defaultCollection(builder.issueKeys);
087    this.severities = defaultCollection(builder.severities);
088    this.statuses = defaultCollection(builder.statuses);
089    this.resolutions = defaultCollection(builder.resolutions);
090    this.components = defaultCollection(builder.components);
091    this.componentRoots = defaultCollection(builder.componentRoots);
092    this.rules = defaultCollection(builder.rules);
093    this.actionPlans = defaultCollection(builder.actionPlans);
094    this.reporters = defaultCollection(builder.reporters);
095    this.assignees = defaultCollection(builder.assignees);
096    this.assigned = builder.assigned;
097    this.planned = builder.planned;
098    this.resolved = builder.resolved;
099    this.createdAfter = builder.createdAfter;
100    this.createdBefore = builder.createdBefore;
101    this.sort = builder.sort;
102    this.asc = builder.asc;
103    this.pageSize = builder.pageSize;
104    this.pageIndex = builder.pageIndex;
105    this.requiredRole = builder.requiredRole;
106  }
107
108  public Collection<String> issueKeys() {
109    return issueKeys;
110  }
111
112  public Collection<String> severities() {
113    return severities;
114  }
115
116  public Collection<String> statuses() {
117    return statuses;
118  }
119
120  public Collection<String> resolutions() {
121    return resolutions;
122  }
123
124  public Collection<String> components() {
125    return components;
126  }
127
128  public Collection<String> componentRoots() {
129    return componentRoots;
130  }
131
132  public Collection<RuleKey> rules() {
133    return rules;
134  }
135
136  public Collection<String> actionPlans() {
137    return actionPlans;
138  }
139
140  public Collection<String> reporters() {
141    return reporters;
142  }
143
144  public Collection<String> assignees() {
145    return assignees;
146  }
147
148  @CheckForNull
149  public Boolean assigned() {
150    return assigned;
151  }
152
153  @CheckForNull
154  public Boolean planned() {
155    return planned;
156  }
157
158  @CheckForNull
159  public Boolean resolved() {
160    return resolved;
161  }
162
163  @CheckForNull
164  public Date createdAfter() {
165    return (createdAfter == null ? null : new Date(createdAfter.getTime()));
166  }
167
168  @CheckForNull
169  public Date createdBefore() {
170    return (createdBefore == null ? null : new Date(createdBefore.getTime()));
171  }
172
173  @CheckForNull
174  public String sort() {
175    return sort;
176  }
177
178  @CheckForNull
179  public Boolean asc() {
180    return asc;
181  }
182
183  public int pageSize() {
184    return pageSize;
185  }
186
187  public int pageIndex() {
188    return pageIndex;
189  }
190
191  public int maxResults() {
192    return MAX_RESULTS;
193  }
194
195  public String requiredRole() {
196    return requiredRole;
197  }
198
199  @Override
200  public String toString() {
201    return ReflectionToStringBuilder.toString(this);
202  }
203
204  public static Builder builder() {
205    return new Builder();
206  }
207
208  public static class Builder {
209    private Collection<String> issueKeys;
210    private Collection<String> severities;
211    private Collection<String> statuses;
212    private Collection<String> resolutions;
213    private Collection<String> components;
214    private Collection<String> componentRoots;
215    private Collection<RuleKey> rules;
216    private Collection<String> actionPlans;
217    private Collection<String> reporters;
218    private Collection<String> assignees;
219    private Boolean assigned = null;
220    private Boolean planned = null;
221    private Boolean resolved = null;
222    private Date createdAfter;
223    private Date createdBefore;
224    private String sort;
225    private Boolean asc = false;
226    private Integer pageSize;
227    private Integer pageIndex;
228    private String requiredRole = UserRole.USER;
229
230    private Builder() {
231    }
232
233    public Builder issueKeys(@Nullable Collection<String> l) {
234      this.issueKeys = l;
235      return this;
236    }
237
238    public Builder severities(@Nullable Collection<String> l) {
239      this.severities = l;
240      return this;
241    }
242
243    public Builder statuses(@Nullable Collection<String> l) {
244      this.statuses = l;
245      return this;
246    }
247
248    public Builder resolutions(@Nullable Collection<String> l) {
249      this.resolutions = l;
250      return this;
251    }
252
253    public Builder components(@Nullable Collection<String> l) {
254      this.components = l;
255      return this;
256    }
257
258    public Builder componentRoots(@Nullable Collection<String> l) {
259      this.componentRoots = l;
260      return this;
261    }
262
263    public Builder rules(@Nullable Collection<RuleKey> rules) {
264      this.rules = rules;
265      return this;
266    }
267
268    public Builder actionPlans(@Nullable Collection<String> l) {
269      this.actionPlans = l;
270      return this;
271    }
272
273    public Builder reporters(@Nullable Collection<String> l) {
274      this.reporters = l;
275      return this;
276    }
277
278    public Builder assignees(@Nullable Collection<String> l) {
279      this.assignees = l;
280      return this;
281    }
282
283    /**
284     * If true, it will return all issues assigned to someone
285     * If false, it will return all issues not assigned to someone
286     */
287    public Builder assigned(@Nullable Boolean b) {
288      this.assigned = b;
289      return this;
290    }
291
292    /**
293     * If true, it will return all issues linked to an action plan
294     * If false, it will return all issues not linked to an action plan
295     */
296    public Builder planned(@Nullable Boolean planned) {
297      this.planned = planned;
298      return this;
299    }
300
301    /**
302     * If true, it will return all resolved issues
303     * If false, it will return all none resolved issues
304     */
305    public Builder resolved(@Nullable Boolean resolved) {
306      this.resolved = resolved;
307      return this;
308    }
309
310    public Builder createdAfter(@Nullable Date d) {
311      this.createdAfter = (d == null ? null : new Date(d.getTime()));
312      return this;
313    }
314
315    public Builder createdBefore(@Nullable Date d) {
316      this.createdBefore = (d == null ? null : new Date(d.getTime()));
317      return this;
318    }
319
320    public Builder sort(@Nullable String s) {
321      if (s != null && !SORTS.contains(s)) {
322        throw new IllegalArgumentException("Bad sort field: " + s);
323      }
324      this.sort = s;
325      return this;
326    }
327
328    public Builder asc(@Nullable Boolean asc) {
329      this.asc = asc;
330      return this;
331    }
332
333    public Builder pageSize(@Nullable Integer i) {
334      this.pageSize = i;
335      return this;
336    }
337
338    public Builder pageIndex(@Nullable Integer i) {
339      this.pageIndex = i;
340      return this;
341    }
342
343    public Builder requiredRole(@Nullable String s) {
344      this.requiredRole = s;
345      return this;
346    }
347
348    public IssueQuery build() {
349      initPageIndex();
350      initPageSize();
351      if (issueKeys != null) {
352        Preconditions.checkArgument(issueKeys.size() <= MAX_PAGE_SIZE, "Number of issue keys must be less than " + MAX_PAGE_SIZE + " (got " + issueKeys.size() + ")");
353      }
354      return new IssueQuery(this);
355    }
356
357    private void initPageSize() {
358      if (components != null && components.size() == 1 && pageSize == null) {
359        pageSize = 999999;
360      } else {
361        if (pageSize == null) {
362          pageSize = DEFAULT_PAGE_SIZE;
363        } else if (pageSize <= 0 || pageSize > MAX_PAGE_SIZE) {
364          pageSize = MAX_PAGE_SIZE;
365        }
366      }
367    }
368
369    private void initPageIndex() {
370      if (pageIndex == null) {
371        pageIndex = DEFAULT_PAGE_INDEX;
372      }
373      Preconditions.checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")");
374    }
375  }
376
377  private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
378    return c == null ? Collections.<T>emptyList() : Collections.unmodifiableCollection(c);
379  }
380}