]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9052 Rename IssueQueryService to IssueQueryFactory
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 30 Mar 2017 16:40:19 +0000 (18:40 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 31 Mar 2017 09:43:25 +0000 (11:43 +0200)
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/issue/ws/ComponentTagsAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java

diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java
new file mode 100644 (file)
index 0000000..a4e032c
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.ObjectUtils;
+import org.joda.time.DateTime;
+import org.joda.time.format.ISOPeriodFormat;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.util.RubyUtils;
+import org.sonarqube.ws.client.issue.IssuesWsParameters;
+import org.sonarqube.ws.client.issue.SearchWsRequest;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.lang.String.format;
+import static org.sonar.api.utils.DateUtils.longToDate;
+import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
+import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
+import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
+import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_ROOTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
+
+/**
+ * This component is used to create an IssueQuery, in order to transform the component and component roots keys into uuid.
+ */
+@ServerSide
+public class IssueQueryFactory {
+
+  public static final String LOGIN_MYSELF = "__me__";
+
+  private static final String UNKNOWN = "<UNKNOWN>";
+  private final DbClient dbClient;
+  private final ComponentService componentService;
+  private final System2 system;
+  private final UserSession userSession;
+
+  public IssueQueryFactory(DbClient dbClient, ComponentService componentService, System2 system, UserSession userSession) {
+    this.dbClient = dbClient;
+    this.componentService = componentService;
+    this.system = system;
+    this.userSession = userSession;
+  }
+
+  public IssueQuery createFromMap(Map<String, Object> params) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      IssueQuery.Builder builder = IssueQuery.builder()
+        .issueKeys(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_ISSUES)))
+        .severities(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_SEVERITIES)))
+        .statuses(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_STATUSES)))
+        .resolutions(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_RESOLUTIONS)))
+        .resolved(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_RESOLVED)))
+        .rules(toRules(params.get(IssuesWsParameters.PARAM_RULES)))
+        .assignees(buildAssignees(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_ASSIGNEES))))
+        .languages(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_LANGUAGES)))
+        .tags(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_TAGS)))
+        .types(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_TYPES)))
+        .assigned(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_ASSIGNED)))
+        .hideRules(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_HIDE_RULES)))
+        .createdAt(RubyUtils.toDate(params.get(IssuesWsParameters.PARAM_CREATED_AT)))
+        .createdAfter(buildCreatedAfterFromDates(RubyUtils.toDate(params.get(PARAM_CREATED_AFTER)), (String) params.get(PARAM_CREATED_IN_LAST)))
+        .createdBefore(RubyUtils.toDate(parseEndingDateOrDateTime((String) params.get(IssuesWsParameters.PARAM_CREATED_BEFORE))))
+        .organizationUuid(convertOrganizationKeyToUuid(dbSession, (String) params.get(IssuesWsParameters.PARAM_ORGANIZATION)));
+
+      Set<String> allComponentUuids = Sets.newHashSet();
+      boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession,
+        RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_ON_COMPONENT_ONLY)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENTS)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_UUIDS)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_KEYS)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_ROOT_UUIDS)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_ROOTS)),
+        allComponentUuids);
+
+      addComponentParameters(builder, dbSession,
+        effectiveOnComponentOnly,
+        allComponentUuids,
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_PROJECT_UUIDS)),
+        RubyUtils.toStrings(
+          ObjectUtils.defaultIfNull(
+            params.get(IssuesWsParameters.PARAM_PROJECT_KEYS),
+            params.get(IssuesWsParameters.PARAM_PROJECTS))),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_MODULE_UUIDS)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_DIRECTORIES)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_FILE_UUIDS)),
+        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_AUTHORS)));
+
+      String sort = (String) params.get(IssuesWsParameters.PARAM_SORT);
+      if (!Strings.isNullOrEmpty(sort)) {
+        builder.sort(sort);
+        builder.asc(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_ASC)));
+      }
+      String facetMode = (String) params.get(IssuesWsParameters.FACET_MODE);
+      if (!Strings.isNullOrEmpty(facetMode)) {
+        builder.facetMode(facetMode);
+      } else {
+        builder.facetMode(IssuesWsParameters.FACET_MODE_COUNT);
+      }
+      return builder.build();
+    }
+  }
+
+  @CheckForNull
+  private Date buildCreatedAfterFromDates(@Nullable Date createdAfter, @Nullable String createdInLast) {
+    checkArgument(createdAfter == null || createdInLast == null, format("%s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
+
+    Date actualCreatedAfter = createdAfter;
+    if (createdInLast != null) {
+      actualCreatedAfter = new DateTime(system.now()).minus(
+        ISOPeriodFormat.standard().parsePeriod("P" + createdInLast.toUpperCase(Locale.ENGLISH))).toDate();
+    }
+    return actualCreatedAfter;
+  }
+
+  public IssueQuery createFromRequest(SearchWsRequest request) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      IssueQuery.Builder builder = IssueQuery.builder()
+        .issueKeys(request.getIssues())
+        .severities(request.getSeverities())
+        .statuses(request.getStatuses())
+        .resolutions(request.getResolutions())
+        .resolved(request.getResolved())
+        .rules(stringsToRules(request.getRules()))
+        .assignees(buildAssignees(request.getAssignees()))
+        .languages(request.getLanguages())
+        .tags(request.getTags())
+        .types(request.getTypes())
+        .assigned(request.getAssigned())
+        .createdAt(parseDateOrDateTime(request.getCreatedAt()))
+        .createdBefore(parseEndingDateOrDateTime(request.getCreatedBefore()))
+        .facetMode(request.getFacetMode())
+        .organizationUuid(convertOrganizationKeyToUuid(dbSession, request.getOrganization()));
+
+      Set<String> allComponentUuids = Sets.newHashSet();
+      boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession,
+        request.getOnComponentOnly(),
+        request.getComponents(),
+        request.getComponentUuids(),
+        request.getComponentKeys(),
+        request.getComponentRootUuids(),
+        request.getComponentRoots(),
+        allComponentUuids);
+
+      addComponentParameters(builder, dbSession,
+        effectiveOnComponentOnly,
+        allComponentUuids,
+        request.getProjectUuids(),
+        request.getProjectKeys(),
+        request.getModuleUuids(),
+        request.getDirectories(),
+        request.getFileUuids(),
+        request.getAuthors());
+
+      builder.createdAfter(buildCreatedAfterFromRequest(dbSession, request, allComponentUuids));
+
+      String sort = request.getSort();
+      if (!Strings.isNullOrEmpty(sort)) {
+        builder.sort(sort);
+        builder.asc(request.getAsc());
+      }
+      return builder.build();
+
+    }
+  }
+
+  @CheckForNull
+  private String convertOrganizationKeyToUuid(DbSession dbSession, @Nullable String organizationKey) {
+    if (organizationKey == null) {
+      return null;
+    }
+    Optional<OrganizationDto> organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey);
+    return organization.map(OrganizationDto::getUuid).orElse(UNKNOWN);
+  }
+
+  private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, Set<String> componentUuids) {
+    Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter());
+    String createdInLast = request.getCreatedInLast();
+
+    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
+      return buildCreatedAfterFromDates(createdAfter, createdInLast);
+    }
+
+    checkRequest(createdAfter == null, "'%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
+
+    checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
+    String uuid = componentUuids.iterator().next();
+    Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, uuid);
+    return buildCreatedAfterFromDates(createdAfterFromSnapshot, createdInLast);
+  }
+
+  @CheckForNull
+  private Date findCreatedAfterFromComponentUuid(DbSession dbSession, String uuid) {
+    ComponentDto component = checkFoundWithOptional(dbClient.componentDao().selectByUuid(dbSession, uuid), "Component with id '%s' not found", uuid);
+    Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
+    return snapshot.map(s -> longToDate(s.getPeriodDate())).orElse(null);
+  }
+
+  private List<String> buildAssignees(@Nullable List<String> assigneesFromParams) {
+    List<String> assignees = Lists.newArrayList();
+    if (assigneesFromParams != null) {
+      assignees.addAll(assigneesFromParams);
+    }
+    if (assignees.contains(LOGIN_MYSELF)) {
+      String login = userSession.getLogin();
+      if (login == null) {
+        assignees.add(UNKNOWN);
+      } else {
+        assignees.add(login);
+      }
+    }
+    return assignees;
+  }
+
+  private boolean mergeDeprecatedComponentParameters(DbSession session, @Nullable Boolean onComponentOnly,
+    @Nullable Collection<String> components,
+    @Nullable Collection<String> componentUuids,
+    @Nullable Collection<String> componentKeys,
+    @Nullable Collection<String> componentRootUuids,
+    @Nullable Collection<String> componentRoots,
+    Set<String> allComponentUuids) {
+    boolean effectiveOnComponentOnly = false;
+
+    checkArgument(atMostOneNonNullElement(components, componentUuids, componentKeys, componentRootUuids, componentRoots),
+      "At most one of the following parameters can be provided: %s, %s, %s, %s, %s",
+      PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS, PARAM_COMPONENTS, PARAM_COMPONENT_ROOTS, PARAM_COMPONENT_UUIDS);
+
+    if (componentRootUuids != null) {
+      allComponentUuids.addAll(componentRootUuids);
+      effectiveOnComponentOnly = false;
+    } else if (componentRoots != null) {
+      allComponentUuids.addAll(componentUuids(session, componentRoots));
+      effectiveOnComponentOnly = false;
+    } else if (components != null) {
+      allComponentUuids.addAll(componentUuids(session, components));
+      effectiveOnComponentOnly = true;
+    } else if (componentUuids != null) {
+      allComponentUuids.addAll(componentUuids);
+      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
+    } else if (componentKeys != null) {
+      allComponentUuids.addAll(componentUuids(session, componentKeys));
+      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
+    }
+    return effectiveOnComponentOnly;
+  }
+
+  private static boolean atMostOneNonNullElement(Object... objects) {
+    return !from(Arrays.asList(objects))
+      .filter(notNull())
+      .anyMatch(new HasTwoOrMoreElements());
+  }
+
+  private void addComponentParameters(IssueQuery.Builder builder, DbSession session,
+    boolean onComponentOnly,
+    Collection<String> componentUuids,
+    @Nullable Collection<String> projectUuids, @Nullable Collection<String> projects,
+    @Nullable Collection<String> moduleUuids,
+    @Nullable Collection<String> directories,
+    @Nullable Collection<String> fileUuids,
+    @Nullable Collection<String> authors) {
+
+    builder.onComponentOnly(onComponentOnly);
+    if (onComponentOnly) {
+      builder.componentUuids(componentUuids);
+      return;
+    }
+
+    builder.authors(authors);
+    checkArgument(projectUuids == null || projects == null, "projects and projectUuids cannot be set simultaneously");
+    if (projectUuids != null) {
+      builder.projectUuids(projectUuids);
+    } else {
+      builder.projectUuids(componentUuids(session, projects));
+    }
+    builder.moduleUuids(moduleUuids);
+    builder.directories(directories);
+    builder.fileUuids(fileUuids);
+
+    if (!componentUuids.isEmpty()) {
+      addComponentsBasedOnQualifier(builder, session, componentUuids);
+    }
+  }
+
+  private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession session, Collection<String> componentUuids) {
+    Set<String> qualifiers = componentService.getDistinctQualifiers(session, componentUuids);
+    if (qualifiers.isEmpty()) {
+      // Qualifier not found, defaulting to componentUuids (e.g <UNKNOWN>)
+      builder.componentUuids(componentUuids);
+      return;
+    }
+    if (qualifiers.size() > 1) {
+      throw new IllegalArgumentException("All components must have the same qualifier, found " + Joiner.on(',').join(qualifiers));
+    }
+
+    String uniqueQualifier = qualifiers.iterator().next();
+    switch (uniqueQualifier) {
+      case Qualifiers.VIEW:
+      case Qualifiers.SUBVIEW:
+        addViewsOrSubViews(builder, componentUuids, uniqueQualifier);
+        break;
+      case Qualifiers.PROJECT:
+        builder.projectUuids(componentUuids);
+        break;
+      case Qualifiers.MODULE:
+        builder.moduleRootUuids(componentUuids);
+        break;
+      case Qualifiers.DIRECTORY:
+        addDirectories(builder, session, componentUuids);
+        break;
+      case Qualifiers.FILE:
+      case Qualifiers.UNIT_TEST_FILE:
+        builder.fileUuids(componentUuids);
+        break;
+      default:
+        throw new IllegalArgumentException("Unable to set search root context for components " + Joiner.on(',').join(componentUuids));
+    }
+  }
+
+  private void addViewsOrSubViews(IssueQuery.Builder builder, Collection<String> componentUuids, String uniqueQualifier) {
+    List<String> filteredViewUuids = newArrayList();
+    for (String viewUuid : componentUuids) {
+      if ((Qualifiers.VIEW.equals(uniqueQualifier) && userSession.hasComponentUuidPermission(UserRole.USER, viewUuid))
+        || (Qualifiers.SUBVIEW.equals(uniqueQualifier) && userSession.hasComponentUuidPermission(UserRole.USER, viewUuid))) {
+        filteredViewUuids.add(viewUuid);
+      }
+    }
+    if (filteredViewUuids.isEmpty()) {
+      filteredViewUuids.add(UNKNOWN);
+    }
+    builder.viewUuids(filteredViewUuids);
+  }
+
+  private void addDirectories(IssueQuery.Builder builder, DbSession session, Collection<String> componentUuids) {
+    Collection<String> directoryModuleUuids = Sets.newHashSet();
+    Collection<String> directoryPaths = Sets.newHashSet();
+    for (ComponentDto directory : componentService.getByUuids(session, componentUuids)) {
+      directoryModuleUuids.add(directory.moduleUuid());
+      directoryPaths.add(directory.path());
+    }
+    builder.moduleUuids(directoryModuleUuids);
+    builder.directories(directoryPaths);
+  }
+
+  private Collection<String> componentUuids(DbSession session, @Nullable Collection<String> componentKeys) {
+    Collection<String> componentUuids = Lists.newArrayList();
+    componentUuids.addAll(componentService.componentUuids(session, componentKeys, true));
+    // If unknown components are given, but no components are found, then all issues will be returned,
+    // so we add this hack in order to return no issue in this case.
+    if (componentKeys != null && !componentKeys.isEmpty() && componentUuids.isEmpty()) {
+      componentUuids.add(UNKNOWN);
+    }
+    return componentUuids;
+  }
+
+  @VisibleForTesting
+  static Collection<RuleKey> toRules(@Nullable Object o) {
+    Collection<RuleKey> result = null;
+    if (o != null) {
+      if (o instanceof List) {
+        // assume that it contains only strings
+        result = stringsToRules((List<String>) o);
+      } else if (o instanceof String) {
+        result = stringsToRules(newArrayList(Splitter.on(',').omitEmptyStrings().split((String) o)));
+      }
+    }
+    return result;
+  }
+
+  @CheckForNull
+  private static Collection<RuleKey> stringsToRules(@Nullable Collection<String> rules) {
+    if (rules != null) {
+      return Collections2.transform(rules, RuleKey::parse);
+    }
+    return null;
+  }
+
+  private static class HasTwoOrMoreElements implements Predicate<Object> {
+    private AtomicInteger counter;
+
+    private HasTwoOrMoreElements() {
+      this.counter = new AtomicInteger();
+    }
+
+    @Override
+    public boolean apply(@Nonnull Object input) {
+      Objects.requireNonNull(input);
+      return counter.incrementAndGet() >= 2;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java
deleted file mode 100644 (file)
index 74fa421..0000000
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.base.Splitter;
-import com.google.common.base.Strings;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.BooleanUtils;
-import org.apache.commons.lang.ObjectUtils;
-import org.joda.time.DateTime;
-import org.joda.time.format.ISOPeriodFormat;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.System2;
-import org.sonar.api.web.UserRole;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.component.ComponentService;
-import org.sonar.server.user.UserSession;
-import org.sonar.server.util.RubyUtils;
-import org.sonarqube.ws.client.issue.IssuesWsParameters;
-import org.sonarqube.ws.client.issue.SearchWsRequest;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Predicates.notNull;
-import static com.google.common.collect.FluentIterable.from;
-import static com.google.common.collect.Lists.newArrayList;
-import static java.lang.String.format;
-import static org.sonar.api.utils.DateUtils.longToDate;
-import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
-import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
-import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
-import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
-import static org.sonar.server.ws.WsUtils.checkRequest;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_ROOTS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_LAST;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
-
-/**
- * This component is used to create an IssueQuery, in order to transform the component and component roots keys into uuid.
- */
-@ServerSide
-public class IssueQueryService {
-
-  public static final String LOGIN_MYSELF = "__me__";
-
-  private static final String UNKNOWN = "<UNKNOWN>";
-  private final DbClient dbClient;
-  private final ComponentService componentService;
-  private final System2 system;
-  private final UserSession userSession;
-
-  public IssueQueryService(DbClient dbClient, ComponentService componentService, System2 system, UserSession userSession) {
-    this.dbClient = dbClient;
-    this.componentService = componentService;
-    this.system = system;
-    this.userSession = userSession;
-  }
-
-  public IssueQuery createFromMap(Map<String, Object> params) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      IssueQuery.Builder builder = IssueQuery.builder()
-        .issueKeys(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_ISSUES)))
-        .severities(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_SEVERITIES)))
-        .statuses(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_STATUSES)))
-        .resolutions(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_RESOLUTIONS)))
-        .resolved(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_RESOLVED)))
-        .rules(toRules(params.get(IssuesWsParameters.PARAM_RULES)))
-        .assignees(buildAssignees(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_ASSIGNEES))))
-        .languages(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_LANGUAGES)))
-        .tags(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_TAGS)))
-        .types(RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_TYPES)))
-        .assigned(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_ASSIGNED)))
-        .hideRules(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_HIDE_RULES)))
-        .createdAt(RubyUtils.toDate(params.get(IssuesWsParameters.PARAM_CREATED_AT)))
-        .createdAfter(buildCreatedAfterFromDates(RubyUtils.toDate(params.get(PARAM_CREATED_AFTER)), (String) params.get(PARAM_CREATED_IN_LAST)))
-        .createdBefore(RubyUtils.toDate(parseEndingDateOrDateTime((String) params.get(IssuesWsParameters.PARAM_CREATED_BEFORE))))
-        .organizationUuid(convertOrganizationKeyToUuid(dbSession, (String) params.get(IssuesWsParameters.PARAM_ORGANIZATION)));
-
-      Set<String> allComponentUuids = Sets.newHashSet();
-      boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession,
-        RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_ON_COMPONENT_ONLY)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENTS)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_UUIDS)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_KEYS)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_ROOT_UUIDS)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_COMPONENT_ROOTS)),
-        allComponentUuids);
-
-      addComponentParameters(builder, dbSession,
-        effectiveOnComponentOnly,
-        allComponentUuids,
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_PROJECT_UUIDS)),
-        RubyUtils.toStrings(
-          ObjectUtils.defaultIfNull(
-            params.get(IssuesWsParameters.PARAM_PROJECT_KEYS),
-            params.get(IssuesWsParameters.PARAM_PROJECTS))),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_MODULE_UUIDS)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_DIRECTORIES)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_FILE_UUIDS)),
-        RubyUtils.toStrings(params.get(IssuesWsParameters.PARAM_AUTHORS)));
-
-      String sort = (String) params.get(IssuesWsParameters.PARAM_SORT);
-      if (!Strings.isNullOrEmpty(sort)) {
-        builder.sort(sort);
-        builder.asc(RubyUtils.toBoolean(params.get(IssuesWsParameters.PARAM_ASC)));
-      }
-      String facetMode = (String) params.get(IssuesWsParameters.FACET_MODE);
-      if (!Strings.isNullOrEmpty(facetMode)) {
-        builder.facetMode(facetMode);
-      } else {
-        builder.facetMode(IssuesWsParameters.FACET_MODE_COUNT);
-      }
-      return builder.build();
-    }
-  }
-
-  @CheckForNull
-  private Date buildCreatedAfterFromDates(@Nullable Date createdAfter, @Nullable String createdInLast) {
-    checkArgument(createdAfter == null || createdInLast == null, format("%s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
-
-    Date actualCreatedAfter = createdAfter;
-    if (createdInLast != null) {
-      actualCreatedAfter = new DateTime(system.now()).minus(
-        ISOPeriodFormat.standard().parsePeriod("P" + createdInLast.toUpperCase(Locale.ENGLISH))).toDate();
-    }
-    return actualCreatedAfter;
-  }
-
-  public IssueQuery createFromRequest(SearchWsRequest request) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      IssueQuery.Builder builder = IssueQuery.builder()
-        .issueKeys(request.getIssues())
-        .severities(request.getSeverities())
-        .statuses(request.getStatuses())
-        .resolutions(request.getResolutions())
-        .resolved(request.getResolved())
-        .rules(stringsToRules(request.getRules()))
-        .assignees(buildAssignees(request.getAssignees()))
-        .languages(request.getLanguages())
-        .tags(request.getTags())
-        .types(request.getTypes())
-        .assigned(request.getAssigned())
-        .createdAt(parseDateOrDateTime(request.getCreatedAt()))
-        .createdBefore(parseEndingDateOrDateTime(request.getCreatedBefore()))
-        .facetMode(request.getFacetMode())
-        .organizationUuid(convertOrganizationKeyToUuid(dbSession, request.getOrganization()));
-
-      Set<String> allComponentUuids = Sets.newHashSet();
-      boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession,
-        request.getOnComponentOnly(),
-        request.getComponents(),
-        request.getComponentUuids(),
-        request.getComponentKeys(),
-        request.getComponentRootUuids(),
-        request.getComponentRoots(),
-        allComponentUuids);
-
-      addComponentParameters(builder, dbSession,
-        effectiveOnComponentOnly,
-        allComponentUuids,
-        request.getProjectUuids(),
-        request.getProjectKeys(),
-        request.getModuleUuids(),
-        request.getDirectories(),
-        request.getFileUuids(),
-        request.getAuthors());
-
-      builder.createdAfter(buildCreatedAfterFromRequest(dbSession, request, allComponentUuids));
-
-      String sort = request.getSort();
-      if (!Strings.isNullOrEmpty(sort)) {
-        builder.sort(sort);
-        builder.asc(request.getAsc());
-      }
-      return builder.build();
-
-    }
-  }
-
-  @CheckForNull
-  private String convertOrganizationKeyToUuid(DbSession dbSession, @Nullable String organizationKey) {
-    if (organizationKey == null) {
-      return null;
-    }
-    Optional<OrganizationDto> organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey);
-    return organization.map(OrganizationDto::getUuid).orElse(UNKNOWN);
-  }
-
-  private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, Set<String> componentUuids) {
-    Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter());
-    String createdInLast = request.getCreatedInLast();
-
-    if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
-      return buildCreatedAfterFromDates(createdAfter, createdInLast);
-    }
-
-    checkRequest(createdAfter == null, "'%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
-
-    checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
-    String uuid = componentUuids.iterator().next();
-    Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, uuid);
-    return buildCreatedAfterFromDates(createdAfterFromSnapshot, createdInLast);
-  }
-
-  @CheckForNull
-  private Date findCreatedAfterFromComponentUuid(DbSession dbSession, String uuid) {
-    ComponentDto component = checkFoundWithOptional(dbClient.componentDao().selectByUuid(dbSession, uuid), "Component with id '%s' not found", uuid);
-    Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
-    return snapshot.map(s -> longToDate(s.getPeriodDate())).orElse(null);
-  }
-
-  private List<String> buildAssignees(@Nullable List<String> assigneesFromParams) {
-    List<String> assignees = Lists.newArrayList();
-    if (assigneesFromParams != null) {
-      assignees.addAll(assigneesFromParams);
-    }
-    if (assignees.contains(LOGIN_MYSELF)) {
-      String login = userSession.getLogin();
-      if (login == null) {
-        assignees.add(UNKNOWN);
-      } else {
-        assignees.add(login);
-      }
-    }
-    return assignees;
-  }
-
-  private boolean mergeDeprecatedComponentParameters(DbSession session, @Nullable Boolean onComponentOnly,
-    @Nullable Collection<String> components,
-    @Nullable Collection<String> componentUuids,
-    @Nullable Collection<String> componentKeys,
-    @Nullable Collection<String> componentRootUuids,
-    @Nullable Collection<String> componentRoots,
-    Set<String> allComponentUuids) {
-    boolean effectiveOnComponentOnly = false;
-
-    checkArgument(atMostOneNonNullElement(components, componentUuids, componentKeys, componentRootUuids, componentRoots),
-      "At most one of the following parameters can be provided: %s, %s, %s, %s, %s",
-      PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS, PARAM_COMPONENTS, PARAM_COMPONENT_ROOTS, PARAM_COMPONENT_UUIDS);
-
-    if (componentRootUuids != null) {
-      allComponentUuids.addAll(componentRootUuids);
-      effectiveOnComponentOnly = false;
-    } else if (componentRoots != null) {
-      allComponentUuids.addAll(componentUuids(session, componentRoots));
-      effectiveOnComponentOnly = false;
-    } else if (components != null) {
-      allComponentUuids.addAll(componentUuids(session, components));
-      effectiveOnComponentOnly = true;
-    } else if (componentUuids != null) {
-      allComponentUuids.addAll(componentUuids);
-      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
-    } else if (componentKeys != null) {
-      allComponentUuids.addAll(componentUuids(session, componentKeys));
-      effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
-    }
-    return effectiveOnComponentOnly;
-  }
-
-  private static boolean atMostOneNonNullElement(Object... objects) {
-    return !from(Arrays.asList(objects))
-      .filter(notNull())
-      .anyMatch(new HasTwoOrMoreElements());
-  }
-
-  private void addComponentParameters(IssueQuery.Builder builder, DbSession session,
-    boolean onComponentOnly,
-    Collection<String> componentUuids,
-    @Nullable Collection<String> projectUuids, @Nullable Collection<String> projects,
-    @Nullable Collection<String> moduleUuids,
-    @Nullable Collection<String> directories,
-    @Nullable Collection<String> fileUuids,
-    @Nullable Collection<String> authors) {
-
-    builder.onComponentOnly(onComponentOnly);
-    if (onComponentOnly) {
-      builder.componentUuids(componentUuids);
-      return;
-    }
-
-    builder.authors(authors);
-    checkArgument(projectUuids == null || projects == null, "projects and projectUuids cannot be set simultaneously");
-    if (projectUuids != null) {
-      builder.projectUuids(projectUuids);
-    } else {
-      builder.projectUuids(componentUuids(session, projects));
-    }
-    builder.moduleUuids(moduleUuids);
-    builder.directories(directories);
-    builder.fileUuids(fileUuids);
-
-    if (!componentUuids.isEmpty()) {
-      addComponentsBasedOnQualifier(builder, session, componentUuids);
-    }
-  }
-
-  private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession session, Collection<String> componentUuids) {
-    Set<String> qualifiers = componentService.getDistinctQualifiers(session, componentUuids);
-    if (qualifiers.isEmpty()) {
-      // Qualifier not found, defaulting to componentUuids (e.g <UNKNOWN>)
-      builder.componentUuids(componentUuids);
-      return;
-    }
-    if (qualifiers.size() > 1) {
-      throw new IllegalArgumentException("All components must have the same qualifier, found " + Joiner.on(',').join(qualifiers));
-    }
-
-    String uniqueQualifier = qualifiers.iterator().next();
-    switch (uniqueQualifier) {
-      case Qualifiers.VIEW:
-      case Qualifiers.SUBVIEW:
-        addViewsOrSubViews(builder, componentUuids, uniqueQualifier);
-        break;
-      case Qualifiers.PROJECT:
-        builder.projectUuids(componentUuids);
-        break;
-      case Qualifiers.MODULE:
-        builder.moduleRootUuids(componentUuids);
-        break;
-      case Qualifiers.DIRECTORY:
-        addDirectories(builder, session, componentUuids);
-        break;
-      case Qualifiers.FILE:
-      case Qualifiers.UNIT_TEST_FILE:
-        builder.fileUuids(componentUuids);
-        break;
-      default:
-        throw new IllegalArgumentException("Unable to set search root context for components " + Joiner.on(',').join(componentUuids));
-    }
-  }
-
-  private void addViewsOrSubViews(IssueQuery.Builder builder, Collection<String> componentUuids, String uniqueQualifier) {
-    List<String> filteredViewUuids = newArrayList();
-    for (String viewUuid : componentUuids) {
-      if ((Qualifiers.VIEW.equals(uniqueQualifier) && userSession.hasComponentUuidPermission(UserRole.USER, viewUuid))
-        || (Qualifiers.SUBVIEW.equals(uniqueQualifier) && userSession.hasComponentUuidPermission(UserRole.USER, viewUuid))) {
-        filteredViewUuids.add(viewUuid);
-      }
-    }
-    if (filteredViewUuids.isEmpty()) {
-      filteredViewUuids.add(UNKNOWN);
-    }
-    builder.viewUuids(filteredViewUuids);
-  }
-
-  private void addDirectories(IssueQuery.Builder builder, DbSession session, Collection<String> componentUuids) {
-    Collection<String> directoryModuleUuids = Sets.newHashSet();
-    Collection<String> directoryPaths = Sets.newHashSet();
-    for (ComponentDto directory : componentService.getByUuids(session, componentUuids)) {
-      directoryModuleUuids.add(directory.moduleUuid());
-      directoryPaths.add(directory.path());
-    }
-    builder.moduleUuids(directoryModuleUuids);
-    builder.directories(directoryPaths);
-  }
-
-  private Collection<String> componentUuids(DbSession session, @Nullable Collection<String> componentKeys) {
-    Collection<String> componentUuids = Lists.newArrayList();
-    componentUuids.addAll(componentService.componentUuids(session, componentKeys, true));
-    // If unknown components are given, but no components are found, then all issues will be returned,
-    // so we add this hack in order to return no issue in this case.
-    if (componentKeys != null && !componentKeys.isEmpty() && componentUuids.isEmpty()) {
-      componentUuids.add(UNKNOWN);
-    }
-    return componentUuids;
-  }
-
-  @VisibleForTesting
-  static Collection<RuleKey> toRules(@Nullable Object o) {
-    Collection<RuleKey> result = null;
-    if (o != null) {
-      if (o instanceof List) {
-        // assume that it contains only strings
-        result = stringsToRules((List<String>) o);
-      } else if (o instanceof String) {
-        result = stringsToRules(newArrayList(Splitter.on(',').omitEmptyStrings().split((String) o)));
-      }
-    }
-    return result;
-  }
-
-  @CheckForNull
-  private static Collection<RuleKey> stringsToRules(@Nullable Collection<String> rules) {
-    if (rules != null) {
-      return Collections2.transform(rules, RuleKey::parse);
-    }
-    return null;
-  }
-
-  private static class HasTwoOrMoreElements implements Predicate<Object> {
-    private AtomicInteger counter;
-
-    private HasTwoOrMoreElements() {
-      this.counter = new AtomicInteger();
-    }
-
-    @Override
-    public boolean apply(@Nonnull Object input) {
-      Objects.requireNonNull(input);
-      return counter.incrementAndGet() >= 2;
-    }
-  }
-}
index 9aa8ea2a5ebfa81e85f7b6650573ea838cdc9af8..bb2df5a4e9944827f699e00dd0db84e680d8fe50 100644 (file)
@@ -29,7 +29,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.NewAction;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueQueryFactory;
 import org.sonar.server.issue.IssueService;
 import org.sonarqube.ws.client.issue.IssuesWsParameters;
 
@@ -45,9 +45,9 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFT
 public class ComponentTagsAction implements IssuesWsAction {
 
   private final IssueService service;
-  private final IssueQueryService queryService;
+  private final IssueQueryFactory queryService;
 
-  public ComponentTagsAction(IssueService service, IssueQueryService queryService) {
+  public ComponentTagsAction(IssueService service, IssueQueryFactory queryService) {
     this.service = service;
     this.queryService = queryService;
   }
index ef9be0fea08e8bde3c85bfc552775387f5761dee..be764c053ed5471288b9a50cff22f9cc6fdf9e37 100644 (file)
@@ -23,7 +23,7 @@ import org.sonar.core.platform.Module;
 import org.sonar.server.issue.ActionFinder;
 import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.IssueFinder;
-import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueQueryFactory;
 import org.sonar.server.issue.IssueService;
 import org.sonar.server.issue.IssueUpdater;
 import org.sonar.server.issue.ServerIssueStorage;
@@ -45,7 +45,7 @@ public class IssueWsModule extends Module {
       FunctionExecutor.class,
       IssueWorkflow.class,
       IssueService.class,
-      IssueQueryService.class,
+      IssueQueryFactory.class,
       IssuesWs.class,
       AvatarFactoryImpl.class,
       SearchResponseLoader.class,
index 14d549abe0d3b72437251cae25d2e073b979125e..2436c9d5450ac7aea3df49fdf23bf954c6be6d1b 100644 (file)
@@ -42,7 +42,7 @@ import org.sonar.server.es.Facets;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.es.SearchResult;
 import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueQueryFactory;
 import org.sonar.server.issue.index.IssueDoc;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.user.UserSession;
@@ -103,15 +103,15 @@ public class SearchAction implements IssuesWsAction {
 
   private final UserSession userSession;
   private final IssueIndex issueIndex;
-  private final IssueQueryService issueQueryService;
+  private final IssueQueryFactory issueQueryFactory;
   private final SearchResponseLoader searchResponseLoader;
   private final SearchResponseFormat searchResponseFormat;
 
-  public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryService issueQueryService,
+  public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory,
     SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat) {
     this.userSession = userSession;
     this.issueIndex = issueIndex;
-    this.issueQueryService = issueQueryService;
+    this.issueQueryFactory = issueQueryFactory;
     this.searchResponseLoader = searchResponseLoader;
     this.searchResponseFormat = searchResponseFormat;
   }
@@ -305,7 +305,7 @@ public class SearchAction implements IssuesWsAction {
     // prepare the Elasticsearch request
     SearchOptions options = createSearchOptionsFromRequest(request);
     EnumSet<SearchAdditionalField> additionalFields = SearchAdditionalField.getFromRequest(request);
-    IssueQuery query = issueQueryService.createFromRequest(request);
+    IssueQuery query = issueQueryFactory.createFromRequest(request);
 
     // execute request
     SearchResult<IssueDoc> result = issueIndex.search(query, options);
@@ -371,7 +371,7 @@ public class SearchAction implements IssuesWsAction {
     List<String> assigneesFromRequest = request.getAssignees();
     if (assigneesFromRequest != null) {
       assignees.addAll(assigneesFromRequest);
-      assignees.remove(IssueQueryService.LOGIN_MYSELF);
+      assignees.remove(IssueQueryFactory.LOGIN_MYSELF);
     }
     addMandatoryValuesToFacet(facets, PARAM_ASSIGNEES, assignees);
     addMandatoryValuesToFacet(facets, FACET_ASSIGNED_TO_ME, singletonList(userSession.getLogin()));
@@ -394,7 +394,7 @@ public class SearchAction implements IssuesWsAction {
           return;
         }
         requestParams.stream()
-          .filter(param -> !buckets.containsKey(param) && !IssueQueryService.LOGIN_MYSELF.equals(param))
+          .filter(param -> !buckets.containsKey(param) && !IssueQueryFactory.LOGIN_MYSELF.equals(param))
           // Prevent appearance of a glitch value due to dedicated parameter for this facet
           .forEach(param -> buckets.put(param, 0L));
       });
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java
new file mode 100644 (file)
index 0000000..06eefe5
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonarqube.ws.client.issue.SearchWsRequest;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class IssueQueryFactoryTest {
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private ComponentService componentService = new ComponentService(db.getDbClient(), userSession);
+  private System2 system = mock(System2.class);
+  private IssueQueryFactory underTest = new IssueQueryFactory(db.getDbClient(), componentService, system, userSession);
+
+  @Test
+  public void create_from_parameters() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertProject(organization);
+    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+
+    Map<String, Object> map = new HashMap<>();
+    map.put("issues", newArrayList("anIssueKey"));
+    map.put("severities", newArrayList("MAJOR", "MINOR"));
+    map.put("statuses", newArrayList("CLOSED"));
+    map.put("resolutions", newArrayList("FALSE-POSITIVE"));
+    map.put("resolved", true);
+    map.put("projectKeys", newArrayList(project.key()));
+    map.put("moduleUuids", newArrayList(module.uuid()));
+    map.put("directories", newArrayList("aDirPath"));
+    map.put("fileUuids", newArrayList(file.uuid()));
+    map.put("assignees", newArrayList("joanna"));
+    map.put("languages", newArrayList("xoo"));
+    map.put("tags", newArrayList("tag1", "tag2"));
+    map.put("organization", organization.getKey());
+    map.put("assigned", true);
+    map.put("planned", true);
+    map.put("hideRules", true);
+    map.put("createdAfter", "2013-04-16T09:08:24+0200");
+    map.put("createdBefore", "2013-04-17T09:08:24+0200");
+    map.put("rules", "squid:AvoidCycle,findbugs:NullReference");
+    map.put("sort", "CREATION_DATE");
+    map.put("asc", true);
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.issueKeys()).containsOnly("anIssueKey");
+    assertThat(query.severities()).containsOnly("MAJOR", "MINOR");
+    assertThat(query.statuses()).containsOnly("CLOSED");
+    assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
+    assertThat(query.resolved()).isTrue();
+    assertThat(query.projectUuids()).containsOnly(project.uuid());
+    assertThat(query.moduleUuids()).containsOnly(module.uuid());
+    assertThat(query.fileUuids()).containsOnly(file.uuid());
+    assertThat(query.assignees()).containsOnly("joanna");
+    assertThat(query.languages()).containsOnly("xoo");
+    assertThat(query.tags()).containsOnly("tag1", "tag2");
+    assertThat(query.organizationUuid()).isEqualTo(organization.getUuid());
+    assertThat(query.onComponentOnly()).isFalse();
+    assertThat(query.assigned()).isTrue();
+    assertThat(query.hideRules()).isTrue();
+    assertThat(query.rules()).hasSize(2);
+    assertThat(query.directories()).containsOnly("aDirPath");
+    assertThat(query.createdAfter()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
+    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDateTime("2013-04-17T09:08:24+0200"));
+    assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
+    assertThat(query.asc()).isTrue();
+  }
+
+  @Test
+  public void dates_are_inclusive() {
+    SearchWsRequest request = new SearchWsRequest()
+      .setCreatedAfter("2013-04-16")
+      .setCreatedBefore("2013-04-17");
+
+    IssueQuery query = underTest.createFromRequest(request);
+
+    assertThat(query.createdAfter()).isEqualTo(DateUtils.parseDate("2013-04-16"));
+    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDate("2013-04-18"));
+  }
+
+  @Test
+  public void add_unknown_when_no_component_found() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("components", newArrayList("does_not_exist"));
+
+    IssueQuery query = underTest.createFromMap(map);
+    assertThat(query.componentUuids()).containsOnly("<UNKNOWN>");
+  }
+
+  @Test
+  public void parse_list_of_rules() {
+    assertThat(IssueQueryFactory.toRules(null)).isNull();
+    assertThat(IssueQueryFactory.toRules("")).isEmpty();
+    assertThat(IssueQueryFactory.toRules("squid:AvoidCycle")).containsOnly(RuleKey.of("squid", "AvoidCycle"));
+    assertThat(IssueQueryFactory.toRules("squid:AvoidCycle,findbugs:NullRef")).containsOnly(RuleKey.of("squid", "AvoidCycle"), RuleKey.of("findbugs", "NullRef"));
+    assertThat(IssueQueryFactory.toRules(asList("squid:AvoidCycle", "findbugs:NullRef"))).containsOnly(RuleKey.of("squid", "AvoidCycle"), RuleKey.of("findbugs", "NullRef"));
+  }
+
+  @Test
+  public void fail_if_components_and_components_uuid_params_are_set_at_the_same_time() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("components", newArrayList("foo"));
+    map.put("componentUuids", newArrayList("bar"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
+
+    underTest.createFromMap(map);
+  }
+
+  @Test
+  public void fail_if_both_projects_and_projectUuids_params_are_set() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("projects", newArrayList("foo"));
+    map.put("projectUuids", newArrayList("bar"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("projects and projectUuids cannot be set simultaneously");
+
+    underTest.createFromMap(map);
+  }
+
+  @Test
+  public void fail_if_both_componentRoots_and_componentRootUuids_params_are_set() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentRoots", newArrayList("foo"));
+    map.put("componentRootUuids", newArrayList("bar"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
+
+    underTest.createFromMap(map);
+  }
+
+  @Test
+  public void fail_if_componentRoots_references_components_with_different_qualifier() {
+    ComponentDto project = db.components().insertProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentRoots", newArrayList(project.key(), file.key()));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("All components must have the same qualifier, found FIL,TRK");
+
+    underTest.createFromMap(map);
+  }
+
+  @Test
+  public void param_componentRootUuids_enables_search_in_view_tree_if_user_has_permission_on_view() {
+    ComponentDto view = db.components().insertView();
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentRootUuids", newArrayList(view.uuid()));
+    userSession.addProjectUuidPermissions(UserRole.USER, view.uuid());
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.viewUuids()).containsOnly(view.uuid());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void return_empty_results_if_not_allowed_to_search_for_subview() {
+    ComponentDto view = db.components().insertView();
+    ComponentDto subView = db.components().insertComponent(ComponentTesting.newSubView(view));
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentRootUuids", newArrayList(subView.uuid()));
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.viewUuids()).containsOnly("<UNKNOWN>");
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_on_project_tree_by_default() {
+    ComponentDto project = db.components().insertProject();
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentUuids", newArrayList(project.uuid()));
+
+    IssueQuery query = underTest.createFromMap(map);
+    assertThat(query.projectUuids()).containsExactly(project.uuid());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void onComponentOnly_restricts_search_to_specified_componentKeys() {
+    ComponentDto project = db.components().insertProject();
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentKeys", newArrayList(project.key()));
+    map.put("onComponentOnly", true);
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.projectUuids()).isEmpty();
+    assertThat(query.componentUuids()).containsExactly(project.uuid());
+    assertThat(query.onComponentOnly()).isTrue();
+  }
+
+  @Test
+  public void should_search_in_tree_with_module_uuid() {
+    ComponentDto project = db.components().insertProject();
+    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentUuids", newArrayList(module.uuid()));
+
+    IssueQuery query = underTest.createFromMap(map);
+    assertThat(query.moduleRootUuids()).containsExactly(module.uuid());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_in_directory_tree() {
+    ComponentDto project = db.components().insertProject();
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentUuids", newArrayList(dir.uuid()));
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.moduleUuids()).containsOnly(dir.moduleUuid());
+    assertThat(query.directories()).containsOnly(dir.path());
+    assertThat(query.onComponentOnly()).isFalse();
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_by_file() {
+    ComponentDto project = db.components().insertProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentUuids", newArrayList(file.uuid()));
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.fileUuids()).containsExactly(file.uuid());
+  }
+
+  @Test
+  public void param_componentUuids_enables_search_by_test_file() {
+    ComponentDto project = db.components().insertProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project).setQualifier(Qualifiers.UNIT_TEST_FILE));
+    Map<String, Object> map = new HashMap<>();
+    map.put("componentUuids", newArrayList(file.uuid()));
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.fileUuids()).containsExactly(file.uuid());
+  }
+
+  @Test
+  public void fail_if_created_after_and_created_since_are_both_set() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("createdAfter", "2013-07-25T07:35:00+0100");
+    map.put("createdInLast", "palap");
+
+    try {
+      underTest.createFromMap(map);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("createdAfter and createdInLast cannot be set simultaneously");
+    }
+  }
+
+  @Test
+  public void set_created_after_from_created_since() {
+    Date now = DateUtils.parseDateTime("2013-07-25T07:35:00+0100");
+    when(system.now()).thenReturn(now.getTime());
+    Map<String, Object> map = new HashMap<>();
+
+    map.put("createdInLast", "1y2m3w4d");
+    assertThat(underTest.createFromMap(map).createdAfter()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100"));
+  }
+
+  @Test
+  public void fail_if_since_leak_period_and_created_after_set_at_the_same_time() {
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("'createdAfter' and 'sinceLeakPeriod' cannot be set simultaneously");
+
+    underTest.createFromRequest(new SearchWsRequest()
+      .setSinceLeakPeriod(true)
+      .setCreatedAfter("2013-07-25T07:35:00+0100"));
+  }
+
+  @Test
+  public void fail_if_no_component_provided_with_since_leak_period() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
+
+    underTest.createFromRequest(new SearchWsRequest().setSinceLeakPeriod(true));
+  }
+
+  @Test
+  public void fail_if_several_components_provided_with_since_leak_period() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
+
+    underTest.createFromRequest(new SearchWsRequest()
+      .setSinceLeakPeriod(true)
+      .setComponentUuids(newArrayList("component-uuid", "project-uuid")));
+  }
+
+  @Test
+  public void fail_if_date_is_not_formatted_correctly() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("'unknown-date' cannot be parsed as either a date or date+time");
+
+    underTest.createFromRequest(new SearchWsRequest()
+      .setCreatedAfter("unknown-date"));
+  }
+
+  @Test
+  public void return_empty_results_if_organization_with_specified_key_does_not_exist() {
+    Map<String, Object> map = new HashMap<>();
+    map.put("organization", "does_not_exist");
+
+    IssueQuery query = underTest.createFromMap(map);
+
+    assertThat(query.organizationUuid()).isEqualTo("<UNKNOWN>");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java
deleted file mode 100644 (file)
index 6a2fc68..0000000
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue;
-
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.api.web.UserRole;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.component.ComponentService;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonarqube.ws.client.issue.SearchWsRequest;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class IssueQueryServiceTest {
-
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private ComponentService componentService = new ComponentService(db.getDbClient(), userSession);
-  private System2 system = mock(System2.class);
-  private IssueQueryService underTest = new IssueQueryService(db.getDbClient(), componentService, system, userSession);
-
-  @Test
-  public void create_from_parameters() {
-    OrganizationDto organization = db.organizations().insert();
-    ComponentDto project = db.components().insertProject(organization);
-    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-
-    Map<String, Object> map = new HashMap<>();
-    map.put("issues", newArrayList("anIssueKey"));
-    map.put("severities", newArrayList("MAJOR", "MINOR"));
-    map.put("statuses", newArrayList("CLOSED"));
-    map.put("resolutions", newArrayList("FALSE-POSITIVE"));
-    map.put("resolved", true);
-    map.put("projectKeys", newArrayList(project.key()));
-    map.put("moduleUuids", newArrayList(module.uuid()));
-    map.put("directories", newArrayList("aDirPath"));
-    map.put("fileUuids", newArrayList(file.uuid()));
-    map.put("assignees", newArrayList("joanna"));
-    map.put("languages", newArrayList("xoo"));
-    map.put("tags", newArrayList("tag1", "tag2"));
-    map.put("organization", organization.getKey());
-    map.put("assigned", true);
-    map.put("planned", true);
-    map.put("hideRules", true);
-    map.put("createdAfter", "2013-04-16T09:08:24+0200");
-    map.put("createdBefore", "2013-04-17T09:08:24+0200");
-    map.put("rules", "squid:AvoidCycle,findbugs:NullReference");
-    map.put("sort", "CREATION_DATE");
-    map.put("asc", true);
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.issueKeys()).containsOnly("anIssueKey");
-    assertThat(query.severities()).containsOnly("MAJOR", "MINOR");
-    assertThat(query.statuses()).containsOnly("CLOSED");
-    assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
-    assertThat(query.resolved()).isTrue();
-    assertThat(query.projectUuids()).containsOnly(project.uuid());
-    assertThat(query.moduleUuids()).containsOnly(module.uuid());
-    assertThat(query.fileUuids()).containsOnly(file.uuid());
-    assertThat(query.assignees()).containsOnly("joanna");
-    assertThat(query.languages()).containsOnly("xoo");
-    assertThat(query.tags()).containsOnly("tag1", "tag2");
-    assertThat(query.organizationUuid()).isEqualTo(organization.getUuid());
-    assertThat(query.onComponentOnly()).isFalse();
-    assertThat(query.assigned()).isTrue();
-    assertThat(query.hideRules()).isTrue();
-    assertThat(query.rules()).hasSize(2);
-    assertThat(query.directories()).containsOnly("aDirPath");
-    assertThat(query.createdAfter()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
-    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDateTime("2013-04-17T09:08:24+0200"));
-    assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
-    assertThat(query.asc()).isTrue();
-  }
-
-  @Test
-  public void dates_are_inclusive() {
-    SearchWsRequest request = new SearchWsRequest()
-      .setCreatedAfter("2013-04-16")
-      .setCreatedBefore("2013-04-17");
-
-    IssueQuery query = underTest.createFromRequest(request);
-
-    assertThat(query.createdAfter()).isEqualTo(DateUtils.parseDate("2013-04-16"));
-    assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDate("2013-04-18"));
-  }
-
-  @Test
-  public void add_unknown_when_no_component_found() {
-    Map<String, Object> map = new HashMap<>();
-    map.put("components", newArrayList("does_not_exist"));
-
-    IssueQuery query = underTest.createFromMap(map);
-    assertThat(query.componentUuids()).containsOnly("<UNKNOWN>");
-  }
-
-  @Test
-  public void parse_list_of_rules() {
-    assertThat(IssueQueryService.toRules(null)).isNull();
-    assertThat(IssueQueryService.toRules("")).isEmpty();
-    assertThat(IssueQueryService.toRules("squid:AvoidCycle")).containsOnly(RuleKey.of("squid", "AvoidCycle"));
-    assertThat(IssueQueryService.toRules("squid:AvoidCycle,findbugs:NullRef")).containsOnly(RuleKey.of("squid", "AvoidCycle"), RuleKey.of("findbugs", "NullRef"));
-    assertThat(IssueQueryService.toRules(asList("squid:AvoidCycle", "findbugs:NullRef"))).containsOnly(RuleKey.of("squid", "AvoidCycle"), RuleKey.of("findbugs", "NullRef"));
-  }
-
-  @Test
-  public void fail_if_components_and_components_uuid_params_are_set_at_the_same_time() {
-    Map<String, Object> map = new HashMap<>();
-    map.put("components", newArrayList("foo"));
-    map.put("componentUuids", newArrayList("bar"));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
-
-    underTest.createFromMap(map);
-  }
-
-  @Test
-  public void fail_if_both_projects_and_projectUuids_params_are_set() {
-    Map<String, Object> map = new HashMap<>();
-    map.put("projects", newArrayList("foo"));
-    map.put("projectUuids", newArrayList("bar"));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("projects and projectUuids cannot be set simultaneously");
-
-    underTest.createFromMap(map);
-  }
-
-  @Test
-  public void fail_if_both_componentRoots_and_componentRootUuids_params_are_set() {
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentRoots", newArrayList("foo"));
-    map.put("componentRootUuids", newArrayList("bar"));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids");
-
-    underTest.createFromMap(map);
-  }
-
-  @Test
-  public void fail_if_componentRoots_references_components_with_different_qualifier() {
-    ComponentDto project = db.components().insertProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentRoots", newArrayList(project.key(), file.key()));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("All components must have the same qualifier, found FIL,TRK");
-
-    underTest.createFromMap(map);
-  }
-
-  @Test
-  public void param_componentRootUuids_enables_search_in_view_tree_if_user_has_permission_on_view() {
-    ComponentDto view = db.components().insertView();
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentRootUuids", newArrayList(view.uuid()));
-    userSession.addProjectUuidPermissions(UserRole.USER, view.uuid());
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.viewUuids()).containsOnly(view.uuid());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void return_empty_results_if_not_allowed_to_search_for_subview() {
-    ComponentDto view = db.components().insertView();
-    ComponentDto subView = db.components().insertComponent(ComponentTesting.newSubView(view));
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentRootUuids", newArrayList(subView.uuid()));
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.viewUuids()).containsOnly("<UNKNOWN>");
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_on_project_tree_by_default() {
-    ComponentDto project = db.components().insertProject();
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentUuids", newArrayList(project.uuid()));
-
-    IssueQuery query = underTest.createFromMap(map);
-    assertThat(query.projectUuids()).containsExactly(project.uuid());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void onComponentOnly_restricts_search_to_specified_componentKeys() {
-    ComponentDto project = db.components().insertProject();
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentKeys", newArrayList(project.key()));
-    map.put("onComponentOnly", true);
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.projectUuids()).isEmpty();
-    assertThat(query.componentUuids()).containsExactly(project.uuid());
-    assertThat(query.onComponentOnly()).isTrue();
-  }
-
-  @Test
-  public void should_search_in_tree_with_module_uuid() {
-    ComponentDto project = db.components().insertProject();
-    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentUuids", newArrayList(module.uuid()));
-
-    IssueQuery query = underTest.createFromMap(map);
-    assertThat(query.moduleRootUuids()).containsExactly(module.uuid());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_in_directory_tree() {
-    ComponentDto project = db.components().insertProject();
-    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentUuids", newArrayList(dir.uuid()));
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.moduleUuids()).containsOnly(dir.moduleUuid());
-    assertThat(query.directories()).containsOnly(dir.path());
-    assertThat(query.onComponentOnly()).isFalse();
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_by_file() {
-    ComponentDto project = db.components().insertProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentUuids", newArrayList(file.uuid()));
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.fileUuids()).containsExactly(file.uuid());
-  }
-
-  @Test
-  public void param_componentUuids_enables_search_by_test_file() {
-    ComponentDto project = db.components().insertProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project).setQualifier(Qualifiers.UNIT_TEST_FILE));
-    Map<String, Object> map = new HashMap<>();
-    map.put("componentUuids", newArrayList(file.uuid()));
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.fileUuids()).containsExactly(file.uuid());
-  }
-
-  @Test
-  public void fail_if_created_after_and_created_since_are_both_set() {
-    Map<String, Object> map = new HashMap<>();
-    map.put("createdAfter", "2013-07-25T07:35:00+0100");
-    map.put("createdInLast", "palap");
-
-    try {
-      underTest.createFromMap(map);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("createdAfter and createdInLast cannot be set simultaneously");
-    }
-  }
-
-  @Test
-  public void set_created_after_from_created_since() {
-    Date now = DateUtils.parseDateTime("2013-07-25T07:35:00+0100");
-    when(system.now()).thenReturn(now.getTime());
-    Map<String, Object> map = new HashMap<>();
-
-    map.put("createdInLast", "1y2m3w4d");
-    assertThat(underTest.createFromMap(map).createdAfter()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100"));
-  }
-
-  @Test
-  public void fail_if_since_leak_period_and_created_after_set_at_the_same_time() {
-    expectedException.expect(BadRequestException.class);
-    expectedException.expectMessage("'createdAfter' and 'sinceLeakPeriod' cannot be set simultaneously");
-
-    underTest.createFromRequest(new SearchWsRequest()
-      .setSinceLeakPeriod(true)
-      .setCreatedAfter("2013-07-25T07:35:00+0100"));
-  }
-
-  @Test
-  public void fail_if_no_component_provided_with_since_leak_period() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
-
-    underTest.createFromRequest(new SearchWsRequest().setSinceLeakPeriod(true));
-  }
-
-  @Test
-  public void fail_if_several_components_provided_with_since_leak_period() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("One and only one component must be provided when searching since leak period");
-
-    underTest.createFromRequest(new SearchWsRequest()
-      .setSinceLeakPeriod(true)
-      .setComponentUuids(newArrayList("component-uuid", "project-uuid")));
-  }
-
-  @Test
-  public void fail_if_date_is_not_formatted_correctly() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("'unknown-date' cannot be parsed as either a date or date+time");
-
-    underTest.createFromRequest(new SearchWsRequest()
-      .setCreatedAfter("unknown-date"));
-  }
-
-  @Test
-  public void return_empty_results_if_organization_with_specified_key_does_not_exist() {
-    Map<String, Object> map = new HashMap<>();
-    map.put("organization", "does_not_exist");
-
-    IssueQuery query = underTest.createFromMap(map);
-
-    assertThat(query.organizationUuid()).isEqualTo("<UNKNOWN>");
-  }
-}
index 85d8f18bc11d85cd6a8cb2ef726737ca4faaac18..6617dd0b7da8cbff73617da9f7efb2e782bcd2d9 100644 (file)
@@ -30,7 +30,7 @@ import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.server.ws.WebService.Action;
 import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueQueryFactory;
 import org.sonar.server.issue.IssueService;
 import org.sonar.server.ws.WsTester;
 
@@ -46,7 +46,7 @@ public class ComponentTagsActionTest {
   private IssueService service;
 
   @Mock
-  private IssueQueryService queryService;
+  private IssueQueryFactory queryService;
 
   private ComponentTagsAction componentTagsAction;
 
index 9c13cbf1e502f00fd2d5b4b016cd4a56acaf1e5d..f2a4231b0a45c65f0766619689aa9a04e65ab589 100644 (file)
@@ -23,7 +23,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueQueryFactory;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsActionTester;
@@ -40,10 +40,10 @@ public class SearchActionTest {
   public UserSessionRule userSession = UserSessionRule.standalone();
 
   private IssueIndex index = mock(IssueIndex.class);
-  private IssueQueryService issueQueryService = mock(IssueQueryService.class);
+  private IssueQueryFactory issueQueryFactory = mock(IssueQueryFactory.class);
   private SearchResponseLoader searchResponseLoader = mock(SearchResponseLoader.class);
   private SearchResponseFormat searchResponseFormat = mock(SearchResponseFormat.class);
-  private SearchAction underTest = new SearchAction(userSession, index, issueQueryService, searchResponseLoader, searchResponseFormat);
+  private SearchAction underTest = new SearchAction(userSession, index, issueQueryFactory, searchResponseLoader, searchResponseFormat);
   private WsActionTester wsTester = new WsActionTester(underTest);
 
   @Test