/**
* Commits the DB transaction and indexes the specified projects, if needed (according to
* "cause" parameter).
- * IMPORTANT - UUIDs must relate to projects only. Modules, directories and files are forbidden
+ * IMPORTANT - UUIDs must relate to applications and projects only. Modules, directories and files are forbidden
* and will lead to lack of indexing.
*/
void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause);
package org.sonar.server.component.ws;
import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
});
if (QUALIFIERS_WITH_VISIBILITY.contains(project.getQualifier())) {
wsComponent.setVisibility(Visibility.getLabel(project.isPrivate()));
- if (Qualifiers.PROJECT.equals(project.getQualifier())) {
- wsComponent.getTagsBuilder().addAllTags(project.getTags());
- }
+ wsComponent.getTagsBuilder().addAllTags(project.getTags());
}
return wsComponent;
});
if (QUALIFIERS_WITH_VISIBILITY.contains(dto.qualifier())) {
wsComponent.setVisibility(Visibility.getLabel(dto.isPrivate()));
- if (Qualifiers.PROJECT.equals(dto.qualifier()) && dto.getBranch() != null && parentProjectDto != null) {
+ if (Arrays.asList(Qualifiers.PROJECT, Qualifiers.APP).contains(dto.qualifier()) && dto.getBranch() != null && parentProjectDto != null) {
wsComponent.getTagsBuilder().addAllTags(parentProjectDto.getTags());
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.projecttag;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.es.ProjectIndexers;
+import org.sonar.server.user.UserSession;
+
+import static java.util.Collections.singletonList;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_TAGS_UPDATE;
+import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+
+public class TagsWsSupport {
+
+ private final DbClient dbClient;
+ private final ComponentFinder componentFinder;
+ private final UserSession userSession;
+ private final ProjectIndexers projectIndexers;
+ private final System2 system2;
+
+ /**
+ * The characters allowed in project tags are lower-case
+ * letters, digits, plus (+), sharp (#), dash (-) and dot (.)
+ */
+ private static final Pattern VALID_TAG_REGEXP = Pattern.compile("[a-z0-9+#\\-.]+$");
+
+ public TagsWsSupport(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, ProjectIndexers projectIndexers, System2 system2) {
+ this.dbClient = dbClient;
+ this.componentFinder = componentFinder;
+ this.userSession = userSession;
+ this.projectIndexers = projectIndexers;
+ this.system2 = system2;
+ }
+
+ public void updateProjectTags(DbSession dbSession, String projectKey, List<String> providedTags) {
+ List<String> validatedTags = checkAndUnifyTags(providedTags);
+ ProjectDto project = componentFinder.getProjectByKey(dbSession, projectKey);
+ updateTagsForProjectsOrApplication(dbSession, validatedTags, project);
+ }
+
+ public void updateApplicationTags(DbSession dbSession, String applicationKey, List<String> providedTags) {
+ List<String> validatedTags = checkAndUnifyTags(providedTags);
+ ProjectDto application = componentFinder.getApplicationByKey(dbSession, applicationKey);
+ updateTagsForProjectsOrApplication(dbSession, validatedTags, application);
+ }
+
+ private void updateTagsForProjectsOrApplication(DbSession dbSession, List<String> tags, ProjectDto projectOrApplication) {
+ userSession.checkProjectPermission(UserRole.ADMIN, projectOrApplication);
+ projectOrApplication.setTags(tags);
+ projectOrApplication.setUpdatedAt(system2.now());
+ dbClient.projectDao().updateTags(dbSession, projectOrApplication);
+
+ projectIndexers.commitAndIndexProjects(dbSession, singletonList(projectOrApplication), PROJECT_TAGS_UPDATE);
+ }
+
+ public static List<String> checkAndUnifyTags(List<String> tags) {
+ return tags.stream()
+ .filter(StringUtils::isNotBlank)
+ .map(t -> t.toLowerCase(Locale.ENGLISH))
+ .map(TagsWsSupport::checkTag)
+ .distinct()
+ .collect(MoreCollectors.toList());
+ }
+
+ private static String checkTag(String tag) {
+ checkRequest(VALID_TAG_REGEXP.matcher(tag).matches(), "Tag '%s' is invalid. Tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'", tag);
+ return tag;
+ }
+}
package org.sonar.server.projecttag.ws;
import org.sonar.core.platform.Module;
+import org.sonar.server.projecttag.TagsWsSupport;
public class ProjectTagsWsModule extends Module {
@Override
protected void configureModule() {
add(
+ TagsWsSupport.class,
ProjectTagsWs.class,
SetAction.class,
SearchAction.class
package org.sonar.server.projecttag.ws;
import java.util.List;
-import java.util.Locale;
-import org.apache.commons.lang.StringUtils;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
-import org.sonar.api.utils.System2;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.es.ProjectIndexers;
-import org.sonar.server.user.UserSession;
+import org.sonar.server.projecttag.TagsWsSupport;
-import static java.util.Collections.singletonList;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_TAGS_UPDATE;
-import static org.sonar.server.exceptions.BadRequestException.checkRequest;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
public class SetAction implements ProjectTagsWsAction {
- /**
- * The characters allowed in project tags are lower-case
- * letters, digits, plus (+), sharp (#), dash (-) and dot (.)
- */
- private static final String VALID_TAG_REGEXP = "[a-z0-9+#\\-.]+$";
+
private static final String PARAM_PROJECT = "project";
private static final String PARAM_TAGS = "tags";
private final DbClient dbClient;
- private final ComponentFinder componentFinder;
- private final UserSession userSession;
- private final ProjectIndexers projectIndexers;
- private final System2 system2;
+ private final TagsWsSupport tagsWsSupport;
- public SetAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, ProjectIndexers projectIndexers, System2 system2) {
+ public SetAction(DbClient dbClient, TagsWsSupport tagsWsSupport) {
this.dbClient = dbClient;
- this.componentFinder = componentFinder;
- this.userSession = userSession;
- this.projectIndexers = projectIndexers;
- this.system2 = system2;
+ this.tagsWsSupport = tagsWsSupport;
}
@Override
@Override
public void handle(Request request, Response response) throws Exception {
String projectKey = request.mandatoryParam(PARAM_PROJECT);
- List<String> tags = request.mandatoryParamAsStrings(PARAM_TAGS).stream()
- .filter(StringUtils::isNotBlank)
- .map(t -> t.toLowerCase(Locale.ENGLISH))
- .map(SetAction::checkTag)
- .distinct()
- .collect(MoreCollectors.toList());
+ List<String> tags = request.mandatoryParamAsStrings(PARAM_TAGS);
try (DbSession dbSession = dbClient.openSession(false)) {
- ProjectDto project = componentFinder.getProjectByKey(dbSession, projectKey);
- userSession.checkProjectPermission(UserRole.ADMIN, project);
-
- project.setTags(tags);
- project.setUpdatedAt(system2.now());
- dbClient.projectDao().updateTags(dbSession, project);
-
- projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), PROJECT_TAGS_UPDATE);
+ tagsWsSupport.updateProjectTags(dbSession, projectKey, tags);
}
response.noContent();
}
-
- private static String checkTag(String tag) {
- checkRequest(tag.matches(VALID_TAG_REGEXP), "Tag '%s' is invalid. Project tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'", tag);
- return tag;
- }
}
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new ProjectTagsWsModule().configure(container);
- assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 4);
}
}
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.projecttag.TagsWsSupport;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static java.util.Optional.ofNullable;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
public class SetActionTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone().logIn().setRoot();
@Rule
private TestProjectIndexers projectIndexers = new TestProjectIndexers();
- private WsActionTester ws = new WsActionTester(new SetAction(dbClient, TestComponentFinder.from(db), userSession, projectIndexers, System2.INSTANCE));
+ private TagsWsSupport tagsWsSupport = new TagsWsSupport(dbClient, TestComponentFinder.from(db), userSession, projectIndexers, System2.INSTANCE);
+
+ private WsActionTester ws = new WsActionTester(new SetAction(dbClient, tagsWsSupport));
@Before
public void setUp() {
@Test
public void override_existing_tags() {
- project = db.components().insertPrivateProjectDto(c -> {}, p -> p.setTagsString("marketing,languages"));
+ project = db.components().insertPrivateProjectDto(c -> {
+ }, p -> p.setTagsString("marketing,languages"));
call(project.getKey(), "finance,offshore,platform");
@Test
public void fail_if_tag_does_not_respect_format() {
- expectedException.expect(BadRequestException.class);
- expectedException.expectMessage("_finance_' is invalid. Project tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'");
-
- call(project.getKey(), "_finance_");
+ assertThatThrownBy(() -> call(project.getKey(), "_finance_"))
+ .isInstanceOf(BadRequestException.class)
+ .hasMessage("Tag '_finance_' is invalid. Tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'");
}
@Test
public void fail_if_not_project_admin() {
userSession.logIn().addProjectPermission(UserRole.USER, project);
- expectedException.expect(ForbiddenException.class);
-
- call(project.getKey(), "platform");
+ assertThatThrownBy(() -> call(project.getKey(), "platform"))
+ .isInstanceOf(ForbiddenException.class);
}
@Test
public void fail_if_no_project() {
- expectedException.expect(IllegalArgumentException.class);
-
- call(null, "platform");
+ assertThatThrownBy(() -> call(null, "platform"))
+ .isInstanceOf(IllegalArgumentException.class);
}
@Test
public void fail_if_no_tags() {
- expectedException.expect(IllegalArgumentException.class);
-
- call(project.getKey(), null);
+ assertThatThrownBy(() -> call(project.getKey(), null))
+ .isInstanceOf(IllegalArgumentException.class);
}
@Test
public void fail_if_component_is_a_view() {
ComponentDto view = db.components().insertView(v -> v.setDbKey("VIEW_KEY"));
- expectedException.expect(NotFoundException.class);
- expectedException.expectMessage("Project 'VIEW_KEY' not found");
-
- call(view.getKey(), "point-of-view");
+ assertThatThrownBy(() -> call(view.getKey(), "point-of-view"))
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("Project 'VIEW_KEY' not found");
}
@Test
ComponentDto projectComponent = dbClient.componentDao().selectByUuid(dbSession, project.getUuid()).get();
ComponentDto module = db.components().insertComponent(newModuleDto(projectComponent).setDbKey("MODULE_KEY"));
- expectedException.expect(NotFoundException.class);
- expectedException.expectMessage("Project 'MODULE_KEY' not found");
-
- call(module.getKey(), "modz");
+ assertThatThrownBy(() -> call(module.getKey(), "modz"))
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("Project 'MODULE_KEY' not found");
}
@Test
ComponentDto projectComponent = dbClient.componentDao().selectByUuid(dbSession, project.getUuid()).get();
ComponentDto file = db.components().insertComponent(newFileDto(projectComponent).setDbKey("FILE_KEY"));
- expectedException.expect(NotFoundException.class);
- expectedException.expectMessage("Project 'FILE_KEY' not found");
-
- call(file.getKey(), "secret");
+ assertThatThrownBy(() -> call(file.getKey(), "secret"))
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("Project 'FILE_KEY' not found");
}
@Test
userSession.logIn().addProjectPermission(UserRole.USER, project);
ComponentDto branch = db.components().insertProjectBranch(project);
- expectedException.expect(NotFoundException.class);
- expectedException.expectMessage(format("Project '%s' not found", branch.getDbKey()));
-
- call(branch.getDbKey(), "secret");
+ assertThatThrownBy(() -> call(branch.getDbKey(), "secret"))
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage(format("Project '%s' not found", branch.getDbKey()));
}
@Test