From 94b831e62c22a1465adbde298bdc2d1d804a73fc Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Fri, 29 Jun 2012 11:21:53 +0200 Subject: [PATCH] SONAR-1608 Make it possible to modify the key of projects --- .../plugins/core/DefaultResourceTypes.java | 5 +- .../resources/org/sonar/l10n/core.properties | 25 ++++ .../org/sonar/core/persistence/DaoUtils.java | 2 + .../org/sonar/core/persistence/MyBatis.java | 2 + .../org/sonar/core/resource/ResourceDto.java | 26 ++++ .../core/resource/ResourceKeyUpdaterDao.java | 119 +++++++++++++++++ .../resource/ResourceKeyUpdaterMapper.java | 39 ++++++ .../resource/ResourceKeyUpdaterMapper.xml | 38 ++++++ .../resource/ResourceKeyUpdaterDaoTest.java | 87 +++++++++++++ .../ResourceKeyUpdaterDaoTest/shared.xml | 50 ++++++++ .../shouldBulkUpdateKey-result.xml | 50 ++++++++ .../shouldNotUpdateAllSubmodules-result.xml | 44 +++++++ .../shouldNotUpdateAllSubmodules.xml | 44 +++++++ .../shouldUpdateKey-result.xml | 52 ++++++++ .../java/org/sonar/server/ui/JRubyFacade.java | 25 +++- .../app/controllers/project_controller.rb | 121 ++++++++++++++---- .../main/webapp/WEB-INF/app/models/project.rb | 7 + .../app/views/layouts/_layout.html.erb | 4 + .../app/views/project/_key_modules.html.erb | 19 +++ .../WEB-INF/app/views/project/key.html.erb | 92 +++++++++++++ .../app/views/project/settings.html.erb | 4 - 21 files changed, 820 insertions(+), 35 deletions(-) create mode 100644 sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java create mode 100644 sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterMapper.java create mode 100644 sonar-core/src/main/resources/org/sonar/core/resource/ResourceKeyUpdaterMapper.xml create mode 100644 sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java create mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shared.xml create mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml create mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml create mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml create mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java index 3ddd12dc63a..78ff963e9e0 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java @@ -38,8 +38,11 @@ public final class DefaultResourceTypes extends ExtensionProvider implements Bat .setProperty("deletable", "true") .setProperty("modifiable_history", "true") .setProperty("hasRolePolicy", "true") + .setProperty("updatable_key", "true") + .build()) + .addType(ResourceType.builder(Qualifiers.MODULE) + .setProperty("updatable_key", "true") .build()) - .addType(ResourceType.builder(Qualifiers.MODULE).build()) .addType(ResourceType.builder(Qualifiers.DIRECTORY).build()) .addType(ResourceType.builder(Qualifiers.PACKAGE).build()) .addType(ResourceType.builder(Qualifiers.FILE).hasSourceCode().build()) diff --git a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties index 74b8ddaf518..755f6f02243 100644 --- a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -329,6 +329,7 @@ update_center.page=Update Center lcom4_viewer.page=LCOM4 dependencies.page=Dependencies resource_deletion.page={0} Deletion +update_key.page=Update Key # GWT pages @@ -947,6 +948,30 @@ project_history.event_created=Event "{0}" was created. project_history.event_already_exists=Event "{0}" already exists. +#------------------------------------------------------------------------------ +# +# PROJECT / MODULE "UPDATE KEY" PAGE +# +#------------------------------------------------------------------------------ +update_key.update_resource_key=Update Key +update_key.bulk_update=Bulk Update +update_key.fine_grained_key_update=Fine-grained Update +update_key.old_key=Old key +update_key.new_key=New key +update_key.rename=Rename +update_key.reset=Reset +update_key.new_key_cant_be_blank_for_x=The new key can not be blank for "{0}". +update_key.same_key_for_x=The new key is the same as the original one ("{0}"), nothing has been updated. +update_key.cant_update_x_because_resource_already_exist_with_key_x="{0}" can not be renamed because "{1}" is the key of an existing resource. The update has been canceled. +update_key.error_occured_while_renaming_key_of_x=An error occurred while renaming the key "{0}": {1} +update_key.key_updated=The key has successfully been updated for all required resources. +update_key.fieds_cant_be_blank_for_bulk_update=The two fields can not be blank for the bulk update. +update_key.key_does_not_start_with_x=The current key ("{0}") does not start with "{1}". The update is impossible. +update_key.bulk_change_description=The bulk update allows to replace the beginning of the current key by another string on the current project and all its submodules - if applicable. +update_key.current_key_for_project_x_is_x=The key of the "{0}" project is currently "{1}". +update_key.are_you_sure_to_rename_x=Are you sure you want to rename "{0}", as well as all its modules and resources ? +update_key.are_you_sure_to_bulk_rename_x_into_x=Are you sure you want to rename "{0}{1}" into "{2}{1}", as well as all its modules and resources ? + #------------------------------------------------------------------------------ # # PROJECT (RESOURCE) DELETION PAGE diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java b/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java index 8014061c509..602181cdd44 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java @@ -28,6 +28,7 @@ import org.sonar.core.properties.PropertiesDao; import org.sonar.core.purge.PurgeDao; import org.sonar.core.resource.ResourceDao; import org.sonar.core.resource.ResourceIndexerDao; +import org.sonar.core.resource.ResourceKeyUpdaterDao; import org.sonar.core.review.ReviewCommentDao; import org.sonar.core.review.ReviewDao; import org.sonar.core.rule.RuleDao; @@ -54,6 +55,7 @@ public final class DaoUtils { PurgeDao.class, ResourceIndexerDao.class, ResourceDao.class, + ResourceKeyUpdaterDao.class, ReviewCommentDao.class, ReviewDao.class, RuleDao.class); diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java index 6f1d450e016..dd7d58cb20a 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java @@ -61,6 +61,7 @@ import org.sonar.core.purge.PurgeableSnapshotDto; import org.sonar.core.resource.ResourceDto; import org.sonar.core.resource.ResourceIndexDto; import org.sonar.core.resource.ResourceIndexerMapper; +import org.sonar.core.resource.ResourceKeyUpdaterMapper; import org.sonar.core.resource.ResourceMapper; import org.sonar.core.resource.SnapshotDto; import org.sonar.core.review.ReviewCommentDto; @@ -130,6 +131,7 @@ public class MyBatis implements BatchComponent, ServerComponent { loadMapper(conf, PurgeMapper.class); loadMapper(conf, PurgeVendorMapper.class); loadMapper(conf, ResourceMapper.class); + loadMapper(conf, ResourceKeyUpdaterMapper.class); loadMapper(conf, ReviewCommentMapper.class); loadMapper(conf, ReviewMapper.class); loadMapper(conf, ResourceIndexerMapper.class); diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceDto.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceDto.java index 212b954fc88..193984ccbff 100644 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceDto.java +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceDto.java @@ -91,4 +91,30 @@ public final class ResourceDto { this.qualifier = qualifier; return this; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ResourceDto other = (ResourceDto) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + } diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java new file mode 100644 index 00000000000..d6d8dbf1a70 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java @@ -0,0 +1,119 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.core.resource; + +import com.google.common.collect.Sets; +import org.apache.ibatis.session.SqlSession; +import org.sonar.core.persistence.MyBatis; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Class used to rename the key of a project and its resources. + * + * @since 3.2 + */ +public class ResourceKeyUpdaterDao { + private MyBatis mybatis; + + public ResourceKeyUpdaterDao(MyBatis mybatis) { + this.mybatis = mybatis; + } + + public void updateKey(long projectId, String newKey) { + SqlSession session = mybatis.openBatchSession(); + ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class); + try { + if (mapper.countResourceByKey(newKey) > 0) { + throw new IllegalStateException("Impossible to update key: a resource with \"" + newKey + "\" key already exists."); + } + + // must SELECT first everything + ResourceDto project = mapper.selectProject(projectId); + String projectOldKey = project.getKey(); + List resources = mapper.selectProjectResources(projectId); + resources.add(project); + + // and then proceed with the batch UPDATE at once + runBatchUpdateForAllResources(resources, projectOldKey, newKey, mapper); + + session.commit(); + } finally { + MyBatis.closeQuietly(session); + } + } + + public void bulkUpdateKey(long projectId, String oldPrefix, String newPrefix) { + SqlSession session = mybatis.openBatchSession(); + ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class); + try { + // must SELECT first everything + Set modules = collectAllModules(projectId, oldPrefix, mapper); + checkNewNameOfAllModules(modules, oldPrefix, newPrefix, mapper); + Set allResources = Sets.newHashSet(modules); + for (ResourceDto module : modules) { + allResources.addAll(mapper.selectProjectResources(module.getId())); + } + + // and then proceed with the batch UPDATE at once + runBatchUpdateForAllResources(allResources, oldPrefix, newPrefix, mapper); + + session.commit(); + } finally { + MyBatis.closeQuietly(session); + } + } + + private String computeNewKey(ResourceDto resource, String oldPrefix, String newPrefix) { + String resourceKey = resource.getKey(); + return newPrefix + resourceKey.substring(oldPrefix.length(), resourceKey.length()); + } + + private void runBatchUpdateForAllResources(Collection resources, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) { + for (ResourceDto resource : resources) { + resource.setKey(computeNewKey(resource, oldPrefix, newPrefix)); + mapper.update(resource); + } + } + + private Set collectAllModules(long projectId, String oldPrefix, ResourceKeyUpdaterMapper mapper) { + ResourceDto project = mapper.selectProject(projectId); + Set modules = Sets.newHashSet(); + if (project.getKey().startsWith(oldPrefix)) { + modules.add(project); + } + for (ResourceDto submodule : mapper.selectDescendantProjects(projectId)) { + modules.addAll(collectAllModules(submodule.getId(), oldPrefix, mapper)); + } + return modules; + } + + private void checkNewNameOfAllModules(Set modules, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) { + for (ResourceDto module : modules) { + String newName = computeNewKey(module, oldPrefix, newPrefix); + if (mapper.countResourceByKey(newName) > 0) { + throw new IllegalStateException("Impossible to update key: a resource with \"" + newName + "\" key already exists."); + } + } + } + +} diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterMapper.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterMapper.java new file mode 100644 index 00000000000..a6ccff97ed2 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterMapper.java @@ -0,0 +1,39 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.core.resource; + +import java.util.List; + +/** + * @since 3.2 + */ +public interface ResourceKeyUpdaterMapper { + + int countResourceByKey(String key); + + ResourceDto selectProject(long projectId); + + List selectProjectResources(long projectId); + + List selectDescendantProjects(long projectId); + + void update(ResourceDto resource); + +} diff --git a/sonar-core/src/main/resources/org/sonar/core/resource/ResourceKeyUpdaterMapper.xml b/sonar-core/src/main/resources/org/sonar/core/resource/ResourceKeyUpdaterMapper.xml new file mode 100644 index 00000000000..2fc0a3f4231 --- /dev/null +++ b/sonar-core/src/main/resources/org/sonar/core/resource/ResourceKeyUpdaterMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + update projects + set kee = #{key, jdbcType=VARCHAR} + where id = #{id} + + + + diff --git a/sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java b/sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java new file mode 100644 index 00000000000..bfa64d86dc4 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java @@ -0,0 +1,87 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.core.resource; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.core.persistence.DaoTestCase; + +public class ResourceKeyUpdaterDaoTest extends DaoTestCase { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ResourceKeyUpdaterDao dao; + + @Before + public void createDao() { + dao = new ResourceKeyUpdaterDao(getMyBatis()); + } + + @Test + public void shouldUpdateKey() { + setupData("shared"); + + dao.updateKey(2, "struts:core"); + + checkTables("shouldUpdateKey", "projects"); + } + + @Test + public void shouldNotUpdateKey() { + setupData("shared"); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Impossible to update key: a resource with \"org.struts:struts-ui\" key already exists."); + + dao.updateKey(2, "org.struts:struts-ui"); + } + + @Test + public void shouldBulkUpdateKey() { + setupData("shared"); + + dao.bulkUpdateKey(1, "org.struts", "org.apache.struts"); + + checkTables("shouldBulkUpdateKey", "projects"); + } + + @Test + public void shouldFailBulkUpdateKeyIfKeyAlreadyExist() { + setupData("shared"); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Impossible to update key: a resource with \"foo:struts-core\" key already exists."); + + dao.bulkUpdateKey(1, "org.struts", "foo"); + } + + @Test + public void shouldNotUpdateAllSubmodules() throws Exception { + setupData("shouldNotUpdateAllSubmodules"); + + dao.bulkUpdateKey(1, "org.struts", "org.apache.struts"); + + checkTables("shouldNotUpdateAllSubmodules", "projects"); + } + +} diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shared.xml new file mode 100644 index 00000000000..e81e23c7b4d --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shared.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml new file mode 100644 index 00000000000..8c2ef069840 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml new file mode 100644 index 00000000000..b8f9d1d9d3b --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml new file mode 100644 index 00000000000..1b33265baeb --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml new file mode 100644 index 00000000000..229d8f44941 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index f254097120a..4c126d653ca 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -37,7 +37,11 @@ import org.sonar.api.resources.ResourceTypes; import org.sonar.api.rules.RulePriority; import org.sonar.api.rules.RuleRepository; import org.sonar.api.utils.ValidationMessages; -import org.sonar.api.web.*; +import org.sonar.api.web.Footer; +import org.sonar.api.web.NavigationSection; +import org.sonar.api.web.Page; +import org.sonar.api.web.RubyRailsWebservice; +import org.sonar.api.web.Widget; import org.sonar.api.workflow.Review; import org.sonar.api.workflow.internal.DefaultReview; import org.sonar.api.workflow.internal.DefaultWorkflowContext; @@ -47,6 +51,7 @@ import org.sonar.core.persistence.Database; import org.sonar.core.persistence.DatabaseMigrator; import org.sonar.core.purge.PurgeDao; import org.sonar.core.resource.ResourceIndexerDao; +import org.sonar.core.resource.ResourceKeyUpdaterDao; import org.sonar.core.workflow.WorkflowEngine; import org.sonar.markdown.Markdown; import org.sonar.server.configuration.Backup; @@ -58,12 +63,17 @@ import org.sonar.server.notifications.reviews.ReviewsNotificationManager; import org.sonar.server.platform.GlobalSettingsUpdater; import org.sonar.server.platform.Platform; import org.sonar.server.platform.ServerIdGenerator; -import org.sonar.server.plugins.*; +import org.sonar.server.plugins.DefaultServerPluginRepository; +import org.sonar.server.plugins.PluginDeployer; +import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.UpdateCenterMatrix; +import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.rules.ProfilesConsole; import org.sonar.server.rules.RulesConsole; import org.sonar.updatecenter.common.Version; import javax.annotation.Nullable; + import java.net.InetAddress; import java.sql.Connection; import java.util.Collection; @@ -494,4 +504,15 @@ public final class JRubyFacade { throw e; } } + + // UPDATE PROJECT KEY ------------------------------------------------------------------ + + public void updateResourceKey(long projectId, String newKey) { + getContainer().getComponentByType(ResourceKeyUpdaterDao.class).updateKey(projectId, newKey); + } + + public void bulkUpdateKey(long projectId, String oldPrefix, String newPrefix) { + getContainer().getComponentByType(ResourceKeyUpdaterDao.class).bulkUpdateKey(projectId, oldPrefix, newPrefix); + } + } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb index 69d70119bf6..dbe65bc7a5f 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb @@ -18,7 +18,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 # class ProjectController < ApplicationController - verify :method => :post, :only => [:set_links, :set_exclusions, :delete_exclusions], :redirect_to => {:action => :index} + verify :method => :post, :only => [:set_links, :set_exclusions, :delete_exclusions, :update_key, :perform_key_bulk_update], + :redirect_to => {:action => :index} verify :method => :delete, :only => [:delete], :redirect_to => {:action => :index} SECTION=Navigation::SECTION_RESOURCE @@ -28,9 +29,7 @@ class ProjectController < ApplicationController end def deletion - @project=Project.by_key(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) unless java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'deletable') redirect_to :action => 'index', :id => params[:id] @@ -48,11 +47,84 @@ class ProjectController < ApplicationController end redirect_to_default end + + def key + @project = get_current_project(params[:id]) + + if params[:old_prefix] && params[:new_prefix] + @old_prefix = params[:old_prefix] + @new_prefix = params[:new_prefix] + end + end + + def update_key + project = get_current_project(params[:id]) + + new_key = params[:new_key].strip + if new_key.blank? + flash[:error] = message('update_key.new_key_cant_be_blank_for_x', :params => project.key) + elsif new_key == project.key + flash[:warning] = message('update_key.same_key_for_x', :params => project.key) + elsif Project.by_key(new_key) + flash[:error] = message('update_key.cant_update_x_because_resource_already_exist_with_key_x', :params => [project.key, new_key]) + else + begin + java_facade.updateResourceKey(project.id, new_key) + flash[:notice] = message('update_key.key_updated') + rescue Exception => e + flash[:error] = message('update_key.error_occured_while_renaming_key_of_x', + :params => [project.key, Api::Utils.exception_message(e, :backtrace => false)]) + end + end + + redirect_to :action => 'key', :id => project.root_project.id + end + + def prepare_key_bulk_update + project = get_current_project(params[:id]) + + old_prefix = params[:old_prefix].strip + new_prefix = params[:new_prefix].strip + if old_prefix.blank? || new_prefix.blank? + error_message = message('update_key.fieds_cant_be_blank_for_bulk_update') + elsif !project.key.start_with?(old_prefix) + error_message = message('update_key.key_does_not_start_with_x', :params => [project.key, old_prefix]) + else + new_key = new_prefix + project.key[old_prefix.size..project.key.size] + if Project.by_key(new_key) + error_message = message('update_key.cant_update_x_because_resource_already_exist_with_key_x', :params => [project.key, new_key]) + end + end + + if error_message + flash[:error] = error_message + redirect_to :action => 'key', :id => project.id + else + redirect_to :action => 'key', :id => project.id, :old_prefix => old_prefix, :new_prefix => new_prefix + end + end + + def perform_key_bulk_update + project = get_current_project(params[:id]) + + old_prefix = params[:old_prefix].strip + new_prefix = params[:new_prefix].strip + + unless old_prefix.blank? || new_prefix.blank? || !project.key.start_with?(old_prefix) + begin + java_facade.bulkUpdateKey(project.id, old_prefix, new_prefix) + flash[:notice] = message('update_key.key_updated') + rescue Exception => e + flash[:error] = message('update_key.error_occured_while_renaming_key_of_x', + :params => [project.key, Api::Utils.exception_message(e, :backtrace => false)]) + end + end + + redirect_to :action => 'key', :id => project.id + end def history - @project=Project.by_key(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) # NOTE: we keep "@project.view? || @project.subview?" in the test for backward compatibility with the Views plugin unless java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'modifiable_history') || @project.view? || @project.subview? @@ -65,9 +137,7 @@ class ProjectController < ApplicationController end def delete_snapshot_history - @project=Project.by_key(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) sid = params[:snapshot_id] if sid @@ -79,9 +149,7 @@ class ProjectController < ApplicationController end def links - @project=Project.by_key(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) @snapshot=@project.last_snapshot if !@project.project? @@ -90,9 +158,7 @@ class ProjectController < ApplicationController end def set_links - project = Project.by_key(params[:project_id]) - not_found("Project not found") unless project - access_denied unless is_admin?(project) + project = get_current_project(params[:project_id]) project.links.clear @@ -118,9 +184,7 @@ class ProjectController < ApplicationController def settings - @project=Project.by_key(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) @snapshot=@project.last_snapshot if !@project.project? && !@project.module? @@ -166,9 +230,7 @@ class ProjectController < ApplicationController def exclusions - @project=Project.by_key(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) @snapshot=@project.last_snapshot if !@project.project? && !@project.module? @@ -177,9 +239,7 @@ class ProjectController < ApplicationController end def set_exclusions - @project = Project.find(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) patterns=params['patterns'].reject { |p| p.blank? }.uniq if patterns.empty? @@ -193,9 +253,7 @@ class ProjectController < ApplicationController end def delete_exclusions - @project = Project.find(params[:id]) - not_found("Project not found") unless @project - access_denied unless is_admin?(@project) + @project = get_current_project(params[:id]) Property.clear('sonar.exclusions', @project.id) flash[:notice]='Filters deleted' @@ -310,6 +368,13 @@ class ProjectController < ApplicationController protected + def get_current_project(project_id) + project=Project.by_key(project_id) + not_found("Project not found") unless project + access_denied unless is_admin?(project) + project + end + def find_project_snapshots(root_snapshot_id) snapshots = Snapshot.find(:all, :include => 'events', :conditions => ["(root_snapshot_id = ? OR id = ?) AND scope = 'PRJ'", root_snapshot_id, root_snapshot_id]) end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb index d482c8670aa..07e3a251282 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb @@ -62,6 +62,13 @@ class Project < ActiveRecord::Base end end + def modules + @modules ||= + begin + Project.find(:all, :conditions => {:root_id => self.id, :scope => 'PRJ'}) + end + end + # bottom-up array of projects, def ancestor_projects node, nodes = self, [] diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index 3039a5a1570..67cb894ef68 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -104,6 +104,10 @@
  • <%= message('project_history.page') -%>
  • <% end %> + <% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key') %> +
  • + <%= message('update_key.page') -%>
  • + <% end %> <% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'deletable') %>
  • <%= message('resource_deletion.page', :params => message('qualifier.' + @project.qualifier)) -%>
  • diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb new file mode 100644 index 00000000000..59065112d11 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb @@ -0,0 +1,19 @@ + + + <%= h(current_module.key) -%> + + + <% form_tag( {:action => 'update_key', :id => current_module.id }) do -%> + + <%= submit_tag message('update_key.rename'), :id => 'update_key_' + id_prefix, :class => 'action', + :onclick => "update_launched();$('loading_#{id_prefix}').show();", + :confirm => message('update_key.are_you_sure_to_rename_x', :params => current_module.key) %> + <%= message('update_key.reset') -%> + + <% end %> + + + <% current_module.modules.each_with_index do |sub_module, index| %> + <%= render :partial => 'key_modules', :locals => {:current_module => sub_module, :module_depth => module_depth+1, + :id_prefix => id_prefix + (index+1).to_s} -%> + <% end %> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb new file mode 100644 index 00000000000..e827469bece --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb @@ -0,0 +1,92 @@ +<% + if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key') + has_modules = !@project.modules.empty? +%> + + + +<% + if @old_prefix && @new_prefix + # validation screen for bulk update + key_suffix = @project.key[@old_prefix.size..@project.key.size] +%> + +

    <%= message('update_key.bulk_update') -%>

    +
    +

    + <%= message('update_key.are_you_sure_to_bulk_rename_x_into_x', :params => [@old_prefix, key_suffix, @new_prefix]) -%> +

    + <% form_tag( {:action => 'perform_key_bulk_update', :id => @project.id }) do -%> + + + +
    + <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action', + :onclick => "update_launched();$('loading_bulk_update').show();" %> + <%= message('cancel') -%> + + <% end %> + +<% + else + # main screen + reset_cycle 'modules_tree' +%> + +<% if has_modules %> +

    <%= message('update_key.bulk_update') -%>

    +
    +

    + <%= message('update_key.bulk_change_description') -%> +

    + <%= message('update_key.current_key_for_project_x_is_x', :params => [@project.name, @project.key]) -%> +

    +
    + <% form_tag( {:action => 'prepare_key_bulk_update', :id => @project.id }) do -%> + + + + + + + + + + + + + +
    Replace:
    By:
    + <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action', + :onclick => "update_launched();$('loading_bulk_update').show();" %> + +
    + <% end %> +
    +
    +<% end %> + +

    <%= has_modules ? message('update_key.fine_grained_key_update') : message('update_key.update_resource_key') -%>

    +
    + + + + + + + + + <%= render :partial => 'key_modules', :locals => {:current_module => @project, :module_depth => 0, :id_prefix => "0"} -%> + +
    <%= message('update_key.old_key') -%><%= message('update_key.new_key') -%>
    + +<% + end + end +%> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb index 8e2f68f1870..bf155ec2999 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb @@ -1,9 +1,5 @@
    - -

    Settings

    -
    <%= render :partial => 'settings/settings', :locals => {:project=>@project} %>
    -
    \ No newline at end of file -- 2.39.5