.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())
lcom4_viewer.page=LCOM4
dependencies.page=Dependencies
resource_deletion.page={0} Deletion
+update_key.page=Update Key
# GWT pages
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 "<b>{1}</b>".
+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 "<b>{0}</b>{1}" into "<b>{2}</b>{1}", as well as all its modules and resources ?
+
#------------------------------------------------------------------------------
#
# PROJECT (RESOURCE) DELETION PAGE
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;
PurgeDao.class,
ResourceIndexerDao.class,
ResourceDao.class,
+ ResourceKeyUpdaterDao.class,
ReviewCommentDao.class,
ReviewDao.class,
RuleDao.class);
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;
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);
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;
+ }
+
}
--- /dev/null
+/*
+ * 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<ResourceDto> 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<ResourceDto> modules = collectAllModules(projectId, oldPrefix, mapper);
+ checkNewNameOfAllModules(modules, oldPrefix, newPrefix, mapper);
+ Set<ResourceDto> 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<ResourceDto> resources, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) {
+ for (ResourceDto resource : resources) {
+ resource.setKey(computeNewKey(resource, oldPrefix, newPrefix));
+ mapper.update(resource);
+ }
+ }
+
+ private Set<ResourceDto> collectAllModules(long projectId, String oldPrefix, ResourceKeyUpdaterMapper mapper) {
+ ResourceDto project = mapper.selectProject(projectId);
+ Set<ResourceDto> 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<ResourceDto> 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.");
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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<ResourceDto> selectProjectResources(long projectId);
+
+ List<ResourceDto> selectDescendantProjects(long projectId);
+
+ void update(ResourceDto resource);
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.core.resource.ResourceKeyUpdaterMapper">
+
+ <resultMap id="resourceResultMap" type="Resource">
+ <id property="id" column="id"/>
+ <result property="key" column="kee"/>
+ <result property="rootId" column="root_id"/>
+ <result property="scope" column="scope"/>
+ </resultMap>
+
+ <select id="countResourceByKey" parameterType="String" resultType="int">
+ SELECT count(*)
+ FROM projects
+ WHERE kee = #{key}
+ </select>
+
+ <select id="selectProject" parameterType="long" resultMap="resourceResultMap">
+ select * from projects where id=#{id}
+ </select>
+
+ <select id="selectProjectResources" parameterType="long" resultMap="resourceResultMap">
+ select * from projects where root_id=#{id} AND scope!='PRJ'
+ </select>
+
+ <select id="selectDescendantProjects" parameterType="long" resultMap="resourceResultMap">
+ select * from projects where scope='PRJ' and root_id=#{id}
+ </select>
+
+ <update id="update" parameterType="Resource">
+ update projects
+ set kee = #{key, jdbcType=VARCHAR}
+ where id = #{id}
+ </update>
+
+</mapper>
+
--- /dev/null
+/*
+ * 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");
+ }
+
+}
--- /dev/null
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="[null]" long_name="Apache Struts" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.struts:struts-core:org.struts"
+ name="org.struts" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA" kee="org.struts:struts-core:org.struts.RequestContext"
+ name="RequestContext" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Second sub project **************** -->
+ <projects id="5" root_id="1" kee="org.struts:struts-ui" name="Struts UI"
+ scope="PRJ" qualifier="BRC" long_name="Struts UI" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="org.struts:struts-ui:org.struts"
+ name="org.struts" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA" kee="org.struts:struts-ui:org.struts.RequestContext"
+ name="RequestContext" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Another independent project **************** -->
+ <projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Foo Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+</dataset>
--- /dev/null
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache.struts:struts" name="Struts"
+ description="[null]" long_name="Apache Struts" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.apache.struts:struts-core" name="Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.apache.struts:struts-core:org.struts"
+ name="org.struts" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA" kee="org.apache.struts:struts-core:org.struts.RequestContext"
+ name="RequestContext" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Second sub project **************** -->
+ <projects id="5" root_id="1" kee="org.apache.struts:struts-ui" name="Struts UI"
+ scope="PRJ" qualifier="BRC" long_name="Struts UI" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="org.apache.struts:struts-ui:org.struts"
+ name="org.struts" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA" kee="org.apache.struts:struts-ui:org.struts.RequestContext"
+ name="RequestContext" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Another independent project **************** -->
+ <projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Foo Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+</dataset>
--- /dev/null
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache.struts:struts" name="Struts"
+ description="[null]" long_name="Apache Struts" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.apache.struts:struts-core" name="Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.apache.struts:struts-core:org.struts"
+ name="org.struts" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA" kee="org.apache.struts:struts-core:org.struts.RequestContext"
+ name="RequestContext" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Second sub project THAT HAS A DIFFERENT GROUP ID => MUST NOT BE UPDATED **************** -->
+ <projects id="5" root_id="1" kee="foo:struts-ui" name="Struts UI"
+ scope="PRJ" qualifier="BRC" long_name="Struts UI" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="foo:struts-ui:org.struts"
+ name="org.struts" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA" kee="foo:struts-ui:org.struts.RequestContext"
+ name="RequestContext" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+</dataset>
--- /dev/null
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="[null]" long_name="Apache Struts" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.struts:struts-core:org.struts"
+ name="org.struts" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA" kee="org.struts:struts-core:org.struts.RequestContext"
+ name="RequestContext" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Second sub project THAT HAS A DIFFERENT GROUP ID => MUST NOT BE UPDATED **************** -->
+ <projects id="5" root_id="1" kee="foo:struts-ui" name="Struts UI"
+ scope="PRJ" qualifier="BRC" long_name="Struts UI" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="foo:struts-ui:org.struts"
+ name="org.struts" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA" kee="foo:struts-ui:org.struts.RequestContext"
+ name="RequestContext" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+</dataset>
--- /dev/null
+<dataset>
+
+ <!-- root project -->
+ <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ description="[null]" long_name="Apache Struts" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** First sub project **************** -->
+ <!-- ONLY THIS PROJECT MUST HAVE BEEN UPDATED -->
+ <!-- -->
+ <projects id="2" root_id="1" kee="struts:core" name="Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="struts:core:org.struts"
+ name="org.struts" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA" kee="struts:core:org.struts.RequestContext"
+ name="RequestContext" root_id="2"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Second sub project **************** -->
+ <projects id="5" root_id="1" kee="org.struts:struts-ui" name="Struts UI"
+ scope="PRJ" qualifier="BRC" long_name="Struts UI" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- directory -->
+ <projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="org.struts:struts-ui:org.struts"
+ name="org.struts" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA" kee="org.struts:struts-ui:org.struts.RequestContext"
+ name="RequestContext" root_id="5"
+ description="[null]" profile_id="1"
+ enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <!-- **************** Another independent project **************** -->
+ <projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core"
+ scope="PRJ" qualifier="BRC" long_name="Foo Struts Core" profile_id="1"
+ description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+</dataset>
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;
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;
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;
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);
+ }
+
}
# 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
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]
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?
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
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?
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
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?
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?
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?
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'
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
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, []
<li class="<%= 'selected' if request.request_uri.include?('/project/history') -%>">
<a href="<%= ApplicationController.root_context -%>/project/history/<%= @project.id -%>"><%= message('project_history.page') -%></a></li>
<% end %>
+ <% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key') %>
+ <li class="<%= 'selected' if request.request_uri.include?('/project/key') -%>">
+ <a href="<%= ApplicationController.root_context -%>/project/key/<%= @project.id -%>"><%= message('update_key.page') -%></a></li>
+ <% end %>
<% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'deletable') %>
<li class="<%= 'selected' if request.request_uri.include?('/project/deletion') -%>">
<a href="<%= ApplicationController.root_context -%>/project/deletion/<%= @project.id -%>"><%= message('resource_deletion.page', :params => message('qualifier.' + @project.qualifier)) -%></a></li>
--- /dev/null
+ <tr class="<%= cycle 'even', 'odd', :name => 'modules_tree' -%>">
+ <td class="thin nowrap" style="padding-left: <%= 3+ module_depth*15 -%>px">
+ <%= h(current_module.key) -%>
+ </td>
+ <td class="thin nowrap">
+ <% form_tag( {:action => 'update_key', :id => current_module.id }) do -%>
+ <input type="text" value="<%= h(current_module.key) -%>" name="new_key" id="key_<%= id_prefix -%>" size="40">
+ <%= 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) %>
+ <a href="#" onclick="$('key_<%= id_prefix -%>').value='<%= h(current_module.key) -%>';"><%= message('update_key.reset') -%></a>
+ <span class="loading" id="loading_<%= id_prefix -%>" style="display: none; padding: 3px 10px;"></span>
+ <% end %>
+ </td>
+ </tr>
+ <% 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
--- /dev/null
+<%
+ if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key')
+ has_modules = !@project.modules.empty?
+%>
+
+ <script type="text/javascript">
+ function update_launched() {
+ $$('input.action').each(function(input) {
+ input.disabled=true;
+ });
+ }
+ </script>
+
+<%
+ if @old_prefix && @new_prefix
+ # validation screen for bulk update
+ key_suffix = @project.key[@old_prefix.size..@project.key.size]
+%>
+
+ <h1><%= message('update_key.bulk_update') -%></h1>
+ <br/>
+ <p>
+ <%= message('update_key.are_you_sure_to_bulk_rename_x_into_x', :params => [@old_prefix, key_suffix, @new_prefix]) -%>
+ </p>
+ <% form_tag( {:action => 'perform_key_bulk_update', :id => @project.id }) do -%>
+ <input type="hidden" value="<%= @project.id -%>" name="id" id="project_id">
+ <input type="hidden" value="<%= @old_prefix -%>" name="old_prefix" id="old_prefix">
+ <input type="hidden" value="<%= @new_prefix -%>" name="new_prefix" id="new_prefix">
+ <br/>
+ <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action',
+ :onclick => "update_launched();$('loading_bulk_update').show();" %>
+ <a href="<%= url_for :action => 'key', :id => @project.key -%>"><%= message('cancel') -%></a>
+ <span class="loading" id="loading_bulk_update" style="display: none; padding: 3px 10px;"></span>
+ <% end %>
+
+<%
+ else
+ # main screen
+ reset_cycle 'modules_tree'
+%>
+
+<% if has_modules %>
+ <h1><%= message('update_key.bulk_update') -%></h1>
+ <br/>
+ <p>
+ <%= message('update_key.bulk_change_description') -%>
+ <br/><br/>
+ <%= message('update_key.current_key_for_project_x_is_x', :params => [@project.name, @project.key]) -%>
+ </p>
+ <br/>
+ <% form_tag( {:action => 'prepare_key_bulk_update', :id => @project.id }) do -%>
+ <table>
+ <tr>
+ <td style="padding-right: 20px">Replace:</td>
+ <td><input type="text" value="" name="old_prefix" id="old_prefix" size="40"></td>
+ </tr>
+ <tr>
+ <td style="padding-right: 20px">By:</td>
+ <td><input type="text" value="" name="new_prefix" id="new_prefix" size="40"></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td style="padding-top: 5px">
+ <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action',
+ :onclick => "update_launched();$('loading_bulk_update').show();" %>
+ <span class="loading" id="loading_bulk_update" style="display: none; padding: 3px 10px;"></span>
+ </td>
+ </tr>
+ </table>
+ <% end %>
+ <br/>
+ <br/>
+<% end %>
+
+ <h1><%= has_modules ? message('update_key.fine_grained_key_update') : message('update_key.update_resource_key') -%></h1>
+ <br/>
+ <table id="project-history" class="data" style="width:1%">
+ <thead>
+ <tr>
+ <th><%= message('update_key.old_key') -%></th>
+ <th><%= message('update_key.new_key') -%></th>
+ </tr>
+ </thead>
+ <tbody>
+ <%= render :partial => 'key_modules', :locals => {:current_module => @project, :module_depth => 0, :id_prefix => "0"} -%>
+ </tbody>
+ </table>
+
+<%
+ end
+ end
+%>
\ No newline at end of file
<div name="settings">
-
- <h1 class="marginbottom10">Settings</h1>
-
<div class="yui-g widget" id="widget_plugins">
<%= render :partial => 'settings/settings', :locals => {:project=>@project} %>
</div>
-
</div>
\ No newline at end of file