]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3718 Allow creation of components with branch with provisioning
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 9 Jan 2015 14:55:18 +0000 (15:55 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 9 Jan 2015 15:35:20 +0000 (16:35 +0100)
12 files changed:
server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java
server/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java
server/sonar-server/src/main/java/org/sonar/server/component/NewComponent.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/component/DefaultRubyComponentServiceTest.java
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/projects_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/provisioning_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_create_form.html.erb
server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/index.html.erb
sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java
sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index b8c8f269394f2dc7ba8e2376e16ebbdb74a95261..fe9eccbe6898ff8a1181bdda1a966289facf4499 100644 (file)
 package org.sonar.server.component;
 
 import org.sonar.api.ServerComponent;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.internal.Uuids;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentDto;
+import org.sonar.core.component.ComponentKeys;
+import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.preview.PreviewCache;
+import org.sonar.core.resource.ResourceIndexerDao;
 import org.sonar.core.resource.ResourceKeyUpdaterDao;
 import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.permission.InternalPermissionService;
 import org.sonar.server.user.UserSession;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 
+import java.util.Date;
+import java.util.Locale;
 import java.util.Map;
 
 public class ComponentService implements ServerComponent {
@@ -39,17 +50,24 @@ public class ComponentService implements ServerComponent {
 
   private final ResourceKeyUpdaterDao resourceKeyUpdaterDao;
   private final PreviewCache previewCache;
+  private final I18n i18n;
+  private final ResourceIndexerDao resourceIndexerDao;
+  private final InternalPermissionService permissionService;
 
-  public ComponentService(DbClient dbClient, ResourceKeyUpdaterDao resourceKeyUpdaterDao, PreviewCache previewCache) {
+  public ComponentService(DbClient dbClient, ResourceKeyUpdaterDao resourceKeyUpdaterDao, PreviewCache previewCache, I18n i18n, ResourceIndexerDao resourceIndexerDao,
+                          InternalPermissionService permissionService) {
     this.dbClient = dbClient;
     this.resourceKeyUpdaterDao = resourceKeyUpdaterDao;
     this.previewCache = previewCache;
+    this.i18n = i18n;
+    this.resourceIndexerDao = resourceIndexerDao;
+    this.permissionService = permissionService;
   }
 
   public ComponentDto getByKey(String key) {
     DbSession session = dbClient.openSession(false);
     try {
-      return dbClient.componentDao().getByKey(session, key);
+      return getByKey(session, key);
     } finally {
       session.close();
     }
@@ -59,7 +77,7 @@ public class ComponentService implements ServerComponent {
   public ComponentDto getNullableByKey(String key) {
     DbSession session = dbClient.openSession(false);
     try {
-      return dbClient.componentDao().getNullableByKey(session, key);
+      return getNullableByKey(session, key);
     } finally {
       session.close();
     }
@@ -89,7 +107,7 @@ public class ComponentService implements ServerComponent {
 
     DbSession session = dbClient.openSession(false);
     try {
-      ComponentDto projectOrModule = getByKey(projectOrModuleKey);
+      ComponentDto projectOrModule = getByKey(session, projectOrModuleKey);
       resourceKeyUpdaterDao.updateKey(projectOrModule.getId(), newKey);
       session.commit();
 
@@ -117,7 +135,7 @@ public class ComponentService implements ServerComponent {
 
     DbSession session = dbClient.openSession(false);
     try {
-      ComponentDto project = getByKey(projectKey);
+      ComponentDto project = getByKey(session, projectKey);
 
       resourceKeyUpdaterDao.bulkUpdateKey(project.getId(), stringToReplace, replacementString);
       session.commit();
@@ -131,4 +149,67 @@ public class ComponentService implements ServerComponent {
     }
   }
 
+  public String create(NewComponent newComponent) {
+    UserSession.get().checkGlobalPermission(GlobalPermissions.PROVISIONING);
+
+    DbSession session = dbClient.openSession(false);
+    try {
+      checkKeyFormat(newComponent.qualifier(), newComponent.key());
+      checkBranchFormat(newComponent.qualifier(), newComponent.branch());
+      String keyWithBranch = ComponentKeys.createKey(newComponent.key(), newComponent.branch());
+
+      ComponentDto existingComponent = getNullableByKey(keyWithBranch);
+      if (existingComponent != null) {
+        throw new BadRequestException(formatMessage("Could not create %s, key already exists: %s", newComponent.qualifier(), keyWithBranch));
+      }
+
+      String uuid = Uuids.create();
+      ComponentDto component = dbClient.componentDao().insert(session,
+        new ComponentDto()
+          .setUuid(uuid)
+          .setProjectUuid(uuid)
+          .setKey(keyWithBranch)
+          .setDeprecatedKey(keyWithBranch)
+          .setName(newComponent.name())
+          .setLongName(newComponent.name())
+          .setScope(Scopes.PROJECT)
+          .setQualifier(newComponent.qualifier())
+          .setCreatedAt(new Date()));
+      resourceIndexerDao.indexResource(session, component.getId());
+      session.commit();
+
+      permissionService.applyDefaultPermissionTemplate(component.key());
+      return component.key();
+    } finally {
+      session.close();
+    }
+  }
+
+  private void checkKeyFormat(String qualifier, String kee) {
+    if (!ComponentKeys.isValidModuleKey(kee)) {
+      throw new BadRequestException(formatMessage("Malformed key for %s: %s. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.",
+        qualifier, kee));
+    }
+  }
+
+  private void checkBranchFormat(String qualifier, @Nullable String branch) {
+    if (branch != null && !ComponentKeys.isValidBranch(branch)) {
+      throw new BadRequestException(formatMessage("Malformed branch for %s: %s. Allowed characters are alphanumeric, '-', '_', '.' and '/', with at least one non-digit.",
+        qualifier, branch));
+    }
+  }
+
+  private String formatMessage(String message, String qualifier, String key) {
+    return String.format(message, i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project"), key);
+  }
+
+  @CheckForNull
+  private ComponentDto getNullableByKey(DbSession session, String key) {
+    return dbClient.componentDao().getNullableByKey(session, key);
+  }
+
+  private ComponentDto getByKey(DbSession session, String key) {
+    return dbClient.componentDao().getByKey(session, key);
+  }
+
 }
index 8184d2cae0c2fd4c48c4e53418de039f2dafbddc..6ed564077856d7d69bbc132eb839a902a8c9868f 100644 (file)
@@ -24,33 +24,28 @@ import org.sonar.api.component.Component;
 import org.sonar.api.component.RubyComponentService;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Scopes;
-import org.sonar.api.utils.internal.Uuids;
 import org.sonar.core.component.ComponentDto;
-import org.sonar.core.component.ComponentKeys;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceDto;
-import org.sonar.core.resource.ResourceIndexerDao;
 import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.util.RubyUtils;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 
-import java.util.*;
+import java.util.List;
+import java.util.Map;
 
 public class DefaultRubyComponentService implements RubyComponentService {
 
   private final ResourceDao resourceDao;
   private final DefaultComponentFinder finder;
-  private final ResourceIndexerDao resourceIndexerDao;
   private final ComponentService componentService;
   private final I18n i18n;
 
-  public DefaultRubyComponentService(ResourceDao resourceDao, DefaultComponentFinder finder, ResourceIndexerDao resourceIndexerDao, ComponentService componentService, I18n i18n) {
+  public DefaultRubyComponentService(ResourceDao resourceDao, DefaultComponentFinder finder, ComponentService componentService, I18n i18n) {
     this.resourceDao = resourceDao;
     this.finder = finder;
-    this.resourceIndexerDao = resourceIndexerDao;
     this.componentService = componentService;
     this.i18n = i18n;
   }
@@ -66,48 +61,28 @@ public class DefaultRubyComponentService implements RubyComponentService {
     return componentService.getNullableByUuid(uuid);
   }
 
+  /**
+   * Be careful when updating this method, it's used by the Views plugin
+   */
   @CheckForNull
-  public Long createComponent(String kee, String name, String qualifier) {
+  public Long createComponent(String key, String name, String qualifier) {
+    return createComponent(key, null, name, qualifier);
+  }
+
+  @CheckForNull
+  public Long createComponent(String key, @Nullable String branch, String name, @Nullable String qualifier) {
     // Sub view should not be created with provisioning. Will be fixed by http://jira.sonarsource.com/browse/VIEWS-296
     if (!Qualifiers.SUBVIEW.equals(qualifier)) {
-      ComponentDto component = (ComponentDto) resourceDao.findByKey(kee);
-      if (component != null) {
-        throw new BadRequestException(formatMessage("Could not create %s, key already exists: %s", qualifier, kee));
-      }
-      checkKeyFormat(qualifier, kee);
-
-      String uuid = Uuids.create();
-      resourceDao.insertOrUpdate(
-        new ResourceDto()
-          .setUuid(uuid)
-          .setProjectUuid(uuid)
-          .setKey(kee)
-          .setDeprecatedKey(kee)
-          .setName(name)
-          .setLongName(name)
-          .setScope(Scopes.PROJECT)
-          .setQualifier(qualifier)
-          .setCreatedAt(new Date()));
-      component = (ComponentDto) resourceDao.findByKey(kee);
+      String createdKey = componentService.create(NewComponent.create(key, name).setQualifier(qualifier).setBranch(branch));
+      ComponentDto component = (ComponentDto) resourceDao.findByKey(createdKey);
       if (component == null) {
-        throw new BadRequestException(String.format("Component not created: %s", kee));
+        throw new BadRequestException(String.format("Component not created: %s", createdKey));
       }
-      resourceIndexerDao.indexResource(component.getId());
       return component.getId();
     }
     return null;
   }
 
-  public void updateComponent(Long id, String key, String name) {
-    ResourceDto resource = resourceDao.getResource(id);
-    if (resource == null) {
-      throw new NotFoundException();
-    }
-    checkKeyFormat(resource.getQualifier(), key);
-
-    resourceDao.insertOrUpdate(resource.setKey(key).setName(name));
-  }
-
   public DefaultComponentQueryResult find(Map<String, Object> params) {
     ComponentQuery query = toQuery(params);
     List<Component> components = resourceDao.selectProjectsByQualifiers(query.qualifiers());
@@ -158,14 +133,4 @@ public class DefaultRubyComponentService implements RubyComponentService {
     return builder.build();
   }
 
-  private void checkKeyFormat(String qualifier, String kee) {
-    if (!ComponentKeys.isValidModuleKey(kee)) {
-      throw new BadRequestException(formatMessage("Malformed key for %s: %s. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.",
-        qualifier, kee));
-    }
-  }
-
-  private String formatMessage(String message, String qualifier, String key) {
-    return String.format(message, i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project"), key);
-  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/NewComponent.java b/server/sonar-server/src/main/java/org/sonar/server/component/NewComponent.java
new file mode 100644 (file)
index 0000000..8f9dc59
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.component;
+
+import com.google.common.base.Preconditions;
+import org.sonar.api.resources.Qualifiers;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class NewComponent {
+
+  private String key;
+  private String branch;
+  private String qualifier;
+  private String name;
+
+  public NewComponent(String key, String name) {
+    this.key = key;
+    this.name = name;
+  }
+
+  public String key() {
+    return key;
+  }
+
+  public String name() {
+    return name;
+  }
+
+  @CheckForNull
+  public String branch() {
+    return branch;
+  }
+
+  public NewComponent setBranch(@Nullable String branch) {
+    this.branch = branch;
+    return this;
+  }
+
+  public String qualifier() {
+    return qualifier != null ? qualifier : Qualifiers.PROJECT;
+  }
+
+  public NewComponent setQualifier(@Nullable String qualifier) {
+    this.qualifier = qualifier;
+    return this;
+  }
+
+  public static NewComponent create(String key, String name) {
+    Preconditions.checkNotNull(key, "Key can't be null");
+    Preconditions.checkNotNull(name, "Name can't be null");
+    return new NewComponent(key, name);
+  }
+}
index adeb8d1370973a3326259330eb7f5dd5e17b695d..8cb2dcab6fc34766f160e5ac5e8a0b043620951c 100644 (file)
@@ -24,25 +24,26 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.security.DefaultGroups;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.DbSession;
-import org.sonar.core.rule.RuleDto;
 import org.sonar.server.component.db.ComponentDao;
 import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.permission.InternalPermissionService;
 import org.sonar.server.permission.PermissionChange;
-import org.sonar.server.rule.RuleTesting;
-import org.sonar.server.rule.db.RuleDao;
+import org.sonar.server.platform.Platform;
 import org.sonar.server.tester.ServerTester;
 import org.sonar.server.user.MockUserSession;
 
 import java.util.Map;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
 
 public class ComponentServiceMediumTest {
 
@@ -52,8 +53,6 @@ public class ComponentServiceMediumTest {
   DbClient db;
   DbSession session;
   ComponentService service;
-  ComponentDto project;
-  RuleDto rule;
 
   @Before
   public void setUp() throws Exception {
@@ -61,19 +60,6 @@ public class ComponentServiceMediumTest {
     db = tester.get(DbClient.class);
     session = db.openSession(false);
     service = tester.get(ComponentService.class);
-
-    project = ComponentTesting.newProjectDto().setKey("sample:root");
-    tester.get(ComponentDao.class).insert(session, project);
-    session.commit();
-
-    // project can be seen by anyone
-    MockUserSession.set().setLogin("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
-    tester.get(InternalPermissionService.class).addPermission(new PermissionChange().setComponentKey(project.getKey()).setGroup(DefaultGroups.ANYONE).setPermission(UserRole.USER));
-
-    rule = RuleTesting.newXooX1();
-    tester.get(RuleDao.class).insert(session, rule);
-
-    session.commit();
   }
 
   @After
@@ -83,28 +69,33 @@ public class ComponentServiceMediumTest {
 
   @Test
   public void get_by_key() throws Exception {
+    ComponentDto project = createProject("sample:root");
     assertThat(service.getByKey(project.getKey())).isNotNull();
   }
 
   @Test
   public void get_nullable_by_key() throws Exception {
+    ComponentDto project = createProject("sample:root");
     assertThat(service.getNullableByKey(project.getKey())).isNotNull();
     assertThat(service.getNullableByKey("unknown")).isNull();
   }
 
   @Test
   public void get_by_uuid() throws Exception {
+    ComponentDto project = createProject("sample:root");
     assertThat(service.getByUuid(project.uuid())).isNotNull();
   }
 
   @Test
   public void get_nullable_by_uuid() throws Exception {
+    ComponentDto project = createProject("sample:root");
     assertThat(service.getNullableByUuid(project.uuid())).isNotNull();
     assertThat(service.getNullableByUuid("unknown")).isNull();
   }
 
   @Test
   public void update_project_key() throws Exception {
+    ComponentDto project = createProject("sample:root");
     ComponentDto file = ComponentTesting.newFileDto(project).setKey("sample:root:src/File.xoo");
     tester.get(ComponentDao.class).insert(session, file);
 
@@ -128,6 +119,7 @@ public class ComponentServiceMediumTest {
 
   @Test
   public void update_module_key() throws Exception {
+    ComponentDto project = createProject("sample:root");
     ComponentDto module = ComponentTesting.newModuleDto(project).setKey("sample:root:module");
     tester.get(ComponentDao.class).insert(session, module);
 
@@ -176,12 +168,14 @@ public class ComponentServiceMediumTest {
 
   @Test(expected = ForbiddenException.class)
   public void fail_to_update_project_key_without_admin_permission() throws Exception {
+    ComponentDto project = createProject("sample:root");
     MockUserSession.set().setLogin("john").addComponentPermission(UserRole.USER, project.key(), project.key());
     service.updateKey(project.key(), "sample2:root");
   }
 
   @Test
   public void check_module_keys_before_renaming() throws Exception {
+    ComponentDto project = createProject("sample:root");
     ComponentDto module = ComponentTesting.newModuleDto(project).setKey("sample:root:module");
     tester.get(ComponentDao.class).insert(session, module);
 
@@ -200,6 +194,7 @@ public class ComponentServiceMediumTest {
 
   @Test
   public void check_module_keys_before_renaming_return_duplicate_key() throws Exception {
+    ComponentDto project = createProject("sample:root");
     ComponentDto module = ComponentTesting.newModuleDto(project).setKey("sample:root:module");
     tester.get(ComponentDao.class).insert(session, module);
 
@@ -218,12 +213,14 @@ public class ComponentServiceMediumTest {
 
   @Test(expected = ForbiddenException.class)
   public void fail_to_check_module_keys_before_renaming_without_admin_permission() throws Exception {
+    ComponentDto project = createProject("sample:root");
     MockUserSession.set().setLogin("john").addComponentPermission(UserRole.USER, project.key(), project.key());
     service.checkModuleKeysBeforeRenaming(project.key(), "sample", "sample2");
   }
 
   @Test
   public void bulk_update_project_key() throws Exception {
+    ComponentDto project = createProject("sample:root");
     ComponentDto module = ComponentTesting.newModuleDto(project).setKey("sample:root:module");
     tester.get(ComponentDao.class).insert(session, module);
 
@@ -273,8 +270,120 @@ public class ComponentServiceMediumTest {
 
   @Test(expected = ForbiddenException.class)
   public void fail_to_bulk_update_project_key_without_admin_permission() throws Exception {
+    ComponentDto project = createProject("sample:root");
     MockUserSession.set().setLogin("john").addProjectPermissions(UserRole.USER, project.key());
     service.bulkUpdateKey("sample:root", "sample", "sample2");
   }
 
+  @Test
+  public void create_project() throws Exception {
+    executeStartupTasksToCreateDefaultPermissionTemplate();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
+
+    String key = service.create(NewComponent.create("struts", "Struts project"));
+
+    ComponentDto project = service.getNullableByKey(key);
+    assertThat(project.key()).isEqualTo("struts");
+    assertThat(project.deprecatedKey()).isEqualTo("struts");
+    assertThat(project.uuid()).isNotNull();
+    assertThat(project.projectUuid()).isEqualTo(project.uuid());
+    assertThat(project.moduleUuid()).isNull();
+    assertThat(project.moduleUuidPath()).isNull();
+    assertThat(project.name()).isEqualTo("Struts project");
+    assertThat(project.longName()).isEqualTo("Struts project");
+    assertThat(project.scope()).isEqualTo("PRJ");
+    assertThat(project.qualifier()).isEqualTo("TRK");
+    assertThat(project.getCreatedAt()).isNotNull();
+  }
+
+  @Test
+  public void create_new_project_with_branch() throws Exception {
+    executeStartupTasksToCreateDefaultPermissionTemplate();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
+
+    String key = service.create(NewComponent.create("struts", "Struts project").setBranch("origin/branch"));
+
+    ComponentDto project = service.getNullableByKey(key);
+    assertThat(project.key()).isEqualTo("struts:origin/branch");
+    assertThat(project.deprecatedKey()).isEqualTo("struts:origin/branch");
+  }
+
+  @Test
+  public void create_view() throws Exception {
+    executeStartupTasksToCreateDefaultPermissionTemplate();
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
+
+    String key = service.create(NewComponent.create("all-project", "All Projects").setQualifier(Qualifiers.VIEW));
+
+    ComponentDto project = service.getNullableByKey(key);
+    assertThat(project.key()).isEqualTo("all-project");
+    assertThat(project.deprecatedKey()).isEqualTo("all-project");
+    assertThat(project.uuid()).isNotNull();
+    assertThat(project.projectUuid()).isEqualTo(project.uuid());
+    assertThat(project.moduleUuid()).isNull();
+    assertThat(project.moduleUuidPath()).isNull();
+    assertThat(project.name()).isEqualTo("All Projects");
+    assertThat(project.longName()).isEqualTo("All Projects");
+    assertThat(project.scope()).isEqualTo("PRJ");
+    assertThat(project.qualifier()).isEqualTo("VW");
+    assertThat(project.getCreatedAt()).isNotNull();
+  }
+
+  @Test
+  public void fail_to_create_new_component_on_invalid_key() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
+
+    try {
+      service.create(NewComponent.create("struts?parent", "Struts project"));
+      fail();
+    } catch (Exception e){
+      assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("Malformed key for Project: struts?parent. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
+    }
+  }
+
+  @Test
+  public void fail_to_create_new_component_on_invalid_branch() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
+
+    try {
+      service.create(NewComponent.create("struts", "Struts project").setBranch("origin?branch"));
+      fail();
+    } catch (Exception e){
+      assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("Malformed branch for Project: origin?branch. Allowed characters are alphanumeric, '-', '_', '.' and '/', with at least one non-digit.");
+    }
+  }
+
+  @Test
+  public void fail_to_create_new_component_if_key_already_exists() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.PROVISIONING);
+
+    ComponentDto project = ComponentTesting.newProjectDto().setKey("struts");
+    tester.get(ComponentDao.class).insert(session, project);
+    session.commit();
+
+    try {
+      service.create(NewComponent.create("struts", "Struts project"));
+      fail();
+    } catch (Exception e){
+      assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("Could not create Project, key already exists: struts");
+    }
+  }
+
+  private ComponentDto createProject(String key){
+    ComponentDto project = ComponentTesting.newProjectDto().setKey("sample:root");
+    tester.get(ComponentDao.class).insert(session, project);
+    session.commit();
+
+    // project can be seen by anyone
+    MockUserSession.set().setLogin("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+    tester.get(InternalPermissionService.class).addPermission(new PermissionChange().setComponentKey(project.getKey()).setGroup(DefaultGroups.ANYONE).setPermission(UserRole.USER));
+    MockUserSession.set();
+
+    return project;
+  }
+
+  private void executeStartupTasksToCreateDefaultPermissionTemplate(){
+    tester.get(Platform.class).executeStartupTasks();
+  }
+
 }
index a04ea302b3557aa0c81de205a9976088f2ca0e23..d41bc6beca94e1ea64dcc3baa09be197b65e299c 100644 (file)
@@ -26,13 +26,9 @@ import org.mockito.ArgumentCaptor;
 import org.sonar.api.component.Component;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Scopes;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.resource.ResourceDao;
-import org.sonar.core.resource.ResourceDto;
-import org.sonar.core.resource.ResourceIndexerDao;
 import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.NotFoundException;
 
 import java.util.List;
 import java.util.Map;
@@ -48,7 +44,6 @@ public class DefaultRubyComponentServiceTest {
 
   ResourceDao resourceDao;
   DefaultComponentFinder finder;
-  ResourceIndexerDao resourceIndexerDao;
   ComponentService componentService;
   I18n i18n;
 
@@ -58,10 +53,9 @@ public class DefaultRubyComponentServiceTest {
   public void before() {
     resourceDao = mock(ResourceDao.class);
     finder = mock(DefaultComponentFinder.class);
-    resourceIndexerDao = mock(ResourceIndexerDao.class);
     componentService = mock(ComponentService.class);
     i18n = mock(I18n.class);
-    service = new DefaultRubyComponentService(resourceDao, finder, resourceIndexerDao, componentService, i18n);
+    service = new DefaultRubyComponentService(resourceDao, finder, componentService, i18n);
   }
 
   @Test
@@ -81,49 +75,35 @@ public class DefaultRubyComponentServiceTest {
   }
 
   @Test
-  public void create_component_and_index_it() {
+  public void create_component() {
     String componentKey = "new-project";
     String componentName = "New Project";
     String qualifier = Qualifiers.PROJECT;
-    long componentId = Long.MAX_VALUE;
-    ComponentDto component = mock(ComponentDto.class);
-    when(component.getId()).thenReturn(componentId);
-    when(resourceDao.findByKey(componentKey)).thenReturn(null).thenReturn(component);
+    when(resourceDao.findByKey(componentKey)).thenReturn(ComponentTesting.newProjectDto());
+    when(componentService.create(any(NewComponent.class))).thenReturn(componentKey);
 
     service.createComponent(componentKey, componentName, qualifier);
 
-    ArgumentCaptor<ResourceDto> resourceCaptor = ArgumentCaptor.forClass(ResourceDto.class);
-    verify(resourceDao).insertOrUpdate(resourceCaptor.capture());
-    ResourceDto created = resourceCaptor.getValue();
-    assertThat(created.getUuid()).isNotNull();
-    assertThat(created.getProjectUuid()).isEqualTo(created.getUuid());
-    assertThat(created.getKey()).isEqualTo(componentKey);
-    assertThat(created.getName()).isEqualTo(componentName);
-    assertThat(created.getLongName()).isEqualTo(componentName);
-    assertThat(created.getScope()).isEqualTo(Scopes.PROJECT);
-    assertThat(created.getQualifier()).isEqualTo(qualifier);
-    verify(resourceDao, times(2)).findByKey(componentKey);
-    verify(resourceIndexerDao).indexResource(componentId);
+    ArgumentCaptor<NewComponent> newComponentArgumentCaptor = ArgumentCaptor.forClass(NewComponent.class);
+    verify(componentService).create(newComponentArgumentCaptor.capture());
+    NewComponent newComponent = newComponentArgumentCaptor.getValue();
+    assertThat(newComponent.key()).isEqualTo(componentKey);
+    assertThat(newComponent.name()).isEqualTo(componentName);
+    assertThat(newComponent.branch()).isNull();
+    assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT);
   }
 
   @Test
   public void not_create_component_on_sub_views() {
-    String componentKey = "new-project";
-    String componentName = "New Project";
-    String qualifier = Qualifiers.SUBVIEW;
-    long componentId = Long.MAX_VALUE;
-    ComponentDto component = mock(ComponentDto.class);
-    when(component.getId()).thenReturn(componentId);
-    when(resourceDao.findByKey(componentKey)).thenReturn(null).thenReturn(component);
+    when(resourceDao.findByKey(anyString())).thenReturn(ComponentTesting.newProjectDto());
 
-    service.createComponent(componentKey, componentName, qualifier);
+    service.createComponent("new-project", "New Project", Qualifiers.SUBVIEW);
 
-    verify(resourceDao, never()).insertOrUpdate(any(ResourceDto.class));
-    verifyZeroInteractions(resourceIndexerDao);
+    verify(componentService, never()).create(any(NewComponent.class));
   }
 
   @Test(expected = BadRequestException.class)
-  public void should_thow_if_create_fails() {
+  public void should_throw_exception_if_create_fails() {
     String componentKey = "new-project";
     String componentName = "New Project";
     String qualifier = Qualifiers.PROJECT;
@@ -132,53 +112,11 @@ public class DefaultRubyComponentServiceTest {
     service.createComponent(componentKey, componentName, qualifier);
   }
 
-  @Test(expected = BadRequestException.class)
-  public void should_throw_if_component_already_exists() {
-    String componentKey = "new-project";
-    String componentName = "New Project";
-    String qualifier = Qualifiers.PROJECT;
-    when(resourceDao.findByKey(componentKey)).thenReturn(mock(ComponentDto.class));
-
-    service.createComponent(componentKey, componentName, qualifier);
-  }
-
   @Test(expected = BadRequestException.class)
   public void should_throw_if_malformed_key1() {
     service.createComponent("1234", "New Project", Qualifiers.PROJECT);
   }
 
-  @Test(expected = NotFoundException.class)
-  public void should_throw_if_updating_unknown_component() {
-    final long componentId = 1234l;
-    when(resourceDao.getResource(componentId)).thenReturn(null);
-    service.updateComponent(componentId, "key", "name");
-  }
-
-  @Test
-  public void should_update_component() {
-    final long componentId = 1234l;
-    final String newKey = "newKey";
-    final String newName = "newName";
-    ResourceDto resource = mock(ResourceDto.class);
-    when(resourceDao.getResource(componentId)).thenReturn(resource);
-    when(resource.setKey(newKey)).thenReturn(resource);
-    when(resource.setName(newName)).thenReturn(resource);
-    service.updateComponent(componentId, newKey, newName);
-    verify(resource).setKey(newKey);
-    verify(resource).setName(newName);
-    verify(resourceDao).insertOrUpdate(resource);
-  }
-
-  @Test(expected=BadRequestException.class)
-  public void should_throw_if_malformed_key_in_update() {
-    final long componentId = 1234l;
-    final String newKey = "new/key";
-    final String newName = "newName";
-    ResourceDto resource = mock(ResourceDto.class);
-    when(resourceDao.getResource(componentId)).thenReturn(resource);
-    service.updateComponent(componentId, newKey, newName);
-  }
-
   @Test
   public void should_find() {
     List<String> qualifiers = newArrayList("TRK");
index 9e26b80df1f09827bc1e6ff7454bab431101ca47..cfd97e37cb5af81a9734320bec7fda553cbd63c7 100644 (file)
@@ -71,20 +71,16 @@ class Api::ProjectsController < Api::ApiController
   # POST /api/projects/create?key=<key>&name=<name>
   #
   # -- Example
-  # curl  -v -u admin:admin -X POST 'http://localhost:9000/api/projects/create?key=project1&name=Project%20One'
+  # curl  -v -u admin:admin -X POST 'http://localhost:9000/api/projects/create?key=project1&name=Project%20One&branch=origin/master'
   #
   # since 4.0
   #
   def create
     verify_post_request
     require_parameters :key, :name
-    access_denied unless has_role?("provisioning")
-    key = params[:key]
-    name = params[:name]
 
-    Internal.component_api.createComponent(key, name, 'TRK')
-    Internal.permissions.applyDefaultPermissionTemplate(key)
-    result = Project.by_key(key)
+    id = Internal.component_api.createComponent(params[:key], params[:branch], params[:name], nil)
+    result = Project.find(id.to_i)
 
     respond_to do |format|
       format.json { render :json => jsonp(to_json_hash(result)) }
index 4eb6116037945a2384b69e3830fa7229a43e08a2..1b91cc25eca6d3b618524f44499ba45a39e219b7 100644 (file)
@@ -33,34 +33,23 @@ class ProvisioningController < ApplicationController
     ) { |p| p.key }
   end
 
-  def create_or_update
+  def create
     verify_post_request
-    access_denied unless has_role?("provisioning")
     @id = params[:id]
     @key = params[:key]
     @name = params[:name]
+    @branch = params[:branch]
 
     begin
       bad_request('provisioning.missing.key') if @key.blank?
       bad_request('provisioning.missing.name') if @name.blank?
 
-      if @id.nil? or @id.empty?
-        Internal.component_api.createComponent(@key, @name, 'TRK')
-        begin
-          Internal.permissions.applyDefaultPermissionTemplate(@key)
-        rescue
-          # Programmatic transaction rollback
-          Java::OrgSonarServerUi::JRubyFacade.getInstance().deleteResourceTree(@key)
-          raise
-        end
-      else
-        Internal.component_api.updateComponent(@id.to_i, @key, @name)
-      end
+      Internal.component_api.createComponent(@key, @branch, @name, nil)
 
       redirect_to :action => 'index'
     rescue Exception => e
       flash.now[:error]= Api::Utils.message(e.message)
-      render :partial => 'create_form', :id => @id, :key => @key, :name => @name, :status => 400
+      render :partial => 'create_form', :key => @key, :branch => @branch, :name => @name, :status => 400
     end
   end
 
index b96dbce2506e54e3be14462e34e8dab1445384d4..5b2bd449e40985a5757b47c698fe759f343a5474 100644 (file)
@@ -1,8 +1,7 @@
-<form id="create-resource-form" method="post" action="<%= ApplicationController.root_context -%>/provisioning/create_or_update">
-  <input type="hidden" name="id" value="<%= @id -%>"/>
+<form id="create-resource-form" method="post" action="<%= ApplicationController.root_context -%>/provisioning/create">
   <fieldset>
     <div class="modal-head">
-      <h2><%= message((@id.nil? || @id.empty?) ? 'qualifiers.new.TRK' : 'qualifiers.update.TRK') -%></h2>
+      <h2><%= message('qualifiers.new.TRK') -%></h2>
     </div>
     <div class="modal-body">
       <% if flash.now[:error] %>
         <label for="key"><%= h message('key') -%> <em class="mandatory">*</em></label>
         <input id="key" name="key" value="<%= h @key -%>" type="text" size="50" maxlength="400" autofocus="autofocus"/>
       </div>
+      <div class="modal-field">
+        <label for="branch"><%= h message('branch') -%></label>
+        <input id="branch" name="branch" value="<%= h @branch -%>" type="text" size="50" maxlength="400" autofocus="autofocus"/>
+      </div>
       <div class="modal-field">
         <label for="name"><%= h message('name') -%> <em class="mandatory">*</em></label>
         <input id="name" name="name" value="<%= h @name -%>" type="text" size="50" maxlength="256" value=""/>
       </div>
     </div>
     <div class="modal-foot">
-      <input type="submit" value="<%= h message((@id.nil? || @id.empty?) ? 'qualifiers.create.TRK' : 'qualifiers.update.TRK') -%>" id="save-submit"/>
+      <input type="submit" value="<%= h message('qualifiers.create.TRK') -%>" id="save-submit"/>
       <a href="#" onclick="return closeModalWindow()" id="save-cancel"><%= h message('cancel') -%></a>
     </div>
   </fieldset>
index 39ae046435fb96cf9f6bee3e5dff7a44d28fe042..633629c0a24ea58374f22aae3c120d701f2866e0 100644 (file)
@@ -33,9 +33,6 @@
         <td><%= h resource.name -%></td>
         <td><%= format_datetime(resource.created_at) -%></td>
         <td class="operations">
-          <%= link_to message('edit'), {:action => :create_form, :id => resource.id, :key => resource.key, :name => resource.name},
-              {:id => "edit-#{resource.key.parameterize}", :class => 'open-modal link-action'} -%>
-          &nbsp;
           <%= link_to message('delete'), {:action => :delete_form, :id => resource.id},
               {:id => "delete-#{resource.key.parameterize}", :class => 'open-modal link-action link-red'} -%>
         </td>
index 615fe1304b43c6eac81fad7cda3fd422fcd58f39..9fbc4267b59e6a2c320b1e4d60081390244ba9a6 100644 (file)
@@ -105,8 +105,8 @@ public final class ComponentKeys {
    *    </ul>
    *  </li>
    * </ul>
-   * @param keyCandidate
-   * @return <code>true</code> if <code>keyCandidate</code> can be used for a project/module
+   * @param branchCandidate
+   * @return <code>true</code> if <code>branchCandidate</code> can be used for a project/module
    */
   public static boolean isValidBranch(String branchCandidate) {
     return branchCandidate.matches(VALID_BRANCH_REGEXP);
index c2f648720bba3b36f8ae70ef9a3827feca77faae..0befc3ad53ff254cd5100a2ff6e27017e5c9e173 100644 (file)
@@ -132,24 +132,28 @@ public class ResourceIndexerDao {
   }
 
   public boolean indexResource(long id) {
-    boolean indexed = false;
-    SqlSession session = mybatis.openSession(false);
+    DbSession session = mybatis.openSession(false);
     try {
-      ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
-      ResourceDto resource = mapper.selectResourceToIndex(id);
-      if (resource != null) {
-        Long rootId = resource.getRootId();
-        if (rootId == null) {
-          rootId = resource.getId();
-        }
-        indexed = indexResource(resource.getId(), resource.getName(), resource.getQualifier(), rootId, session, mapper);
-      }
-      return indexed;
+      return indexResource(session, id);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  public boolean indexResource(DbSession session, long id) {
+    boolean indexed = false;
+    ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
+    ResourceDto resource = mapper.selectResourceToIndex(id);
+    if (resource != null) {
+      Long rootId = resource.getRootId();
+      if (rootId == null) {
+        rootId = resource.getId();
+      }
+      indexed = indexResource(resource.getId(), resource.getName(), resource.getQualifier(), rootId, session, mapper);
+    }
+    return indexed;
+  }
+
   public boolean indexResource(int id, String name, String qualifier, int rootId) {
     boolean indexed = false;
     SqlSession session = mybatis.openSession(false);
index ade5dec40c9842a985e4d8e04143e6f6ee730afc..98c8bbb951a60b2b5f5a66902177bfb32b7b87eb 100644 (file)
@@ -25,6 +25,7 @@ backup=Backup
 backup_verb=Back up
 blocker=Blocker
 bold=Bold
+branch=Branch
 build_date=Build date
 build_time=Build time
 calendar=Calendar
@@ -379,7 +380,6 @@ qualifiers.create.TRK=Create Project
 qualifiers.create.VW=Create View
 qualifiers.create.DEV=Create Developer
 
-qualifiers.update.TRK=Update Project
 qualifiers.update.VW=Update View
 qualifiers.update.DEV=Update Developer