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