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