From 981885cad7f17858718c7e905868befc568e9f8f Mon Sep 17 00:00:00 2001 From: simonbrandhof Date: Fri, 30 Dec 2011 18:55:14 +0100 Subject: [PATCH] SONAR-983 index the project from a dedicated post-job in order to optimize SQL requests --- .../org/sonar/plugins/core/CorePlugin.java | 2 + .../core/batch/IndexProjectPostJob.java | 44 +++++ .../core/batch/IndexProjectPostJobTest.java | 53 +++++ .../batch/bootstrap/BootstrapModule.java | 2 - .../batch/index/DefaultResourcePersister.java | 8 +- .../index/DefaultResourcePersisterTest.java | 14 +- .../batch/phases/UpdateStatusJobTest.java | 3 +- .../sonar/core/resource/ResourceIndexer.java | 74 ------- .../core/resource/ResourceIndexerDao.java | 181 +++++++++++------- .../core/resource/ResourceIndexerFilter.java | 26 ++- .../core/resource/ResourceIndexerMapper.java | 4 +- .../core/resource/ResourceIndexerMapper.xml | 44 +++-- .../core/resource/ResourceIndexerDaoTest.java | 20 +- .../core/resource/ResourceIndexerTest.java | 65 ------- .../shouldIndexAllResources.xml | 13 -- .../shouldIndexMultiModulesProject-result.xml | 43 +++-- .../shouldIndexMultiModulesProject.xml | 15 +- ...ult.xml => shouldIndexProjects-result.xml} | 42 ++-- .../shouldIndexProjects.xml | 24 +++ ...ult.xml => shouldIndexResource-result.xml} | 0 ...leResource.xml => shouldIndexResource.xml} | 0 ...ouldReindexProjectAfterRenaming-result.xml | 1 + .../shouldReindexProjectAfterRenaming.xml | 2 + .../org/sonar/server/platform/Platform.java | 2 - .../java/org/sonar/server/ui/JRubyFacade.java | 6 +- ...dex_resources.rb => 241_index_projects.rb} | 4 +- 26 files changed, 389 insertions(+), 303 deletions(-) create mode 100644 plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/IndexProjectPostJob.java create mode 100644 plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/IndexProjectPostJobTest.java delete mode 100644 sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexer.java delete mode 100644 sonar-core/src/test/java/org/sonar/core/resource/ResourceIndexerTest.java delete mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceIndexerDaoTest/shouldIndexAllResources.xml rename sonar-core/src/test/resources/org/sonar/core/resource/ResourceIndexerDaoTest/{shouldIndexAllResources-result.xml => shouldIndexProjects-result.xml} (56%) create mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceIndexerDaoTest/shouldIndexProjects.xml rename sonar-core/src/test/resources/org/sonar/core/resource/ResourceIndexerDaoTest/{shouldIndexSingleResource-result.xml => shouldIndexResource-result.xml} (100%) rename sonar-core/src/test/resources/org/sonar/core/resource/ResourceIndexerDaoTest/{shouldIndexSingleResource.xml => shouldIndexResource.xml} (100%) rename sonar-server/src/main/webapp/WEB-INF/db/migrate/{241_index_resources.rb => 241_index_projects.rb} (88%) diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index c876dd10c87..41e3477bf26 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -27,6 +27,7 @@ import org.sonar.api.SonarPlugin; import org.sonar.api.checks.NoSonarFilter; import org.sonar.api.resources.Java; import org.sonar.plugins.core.batch.ExcludedResourceFilter; +import org.sonar.plugins.core.batch.IndexProjectPostJob; import org.sonar.plugins.core.batch.MavenInitializer; import org.sonar.plugins.core.batch.ProjectFileSystemLogger; import org.sonar.plugins.core.charts.DistributionAreaChart; @@ -289,6 +290,7 @@ public class CorePlugin extends SonarPlugin { extensions.add(ManualViolationInjector.class); extensions.add(UpdateReviewsDecorator.class); extensions.add(ViolationSeverityUpdater.class); + extensions.add(IndexProjectPostJob.class); // time machine extensions.add(TendencyDecorator.class); diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/IndexProjectPostJob.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/IndexProjectPostJob.java new file mode 100644 index 00000000000..5beb4ac709a --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/IndexProjectPostJob.java @@ -0,0 +1,44 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 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.plugins.core.batch; + +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Project; +import org.sonar.core.NotDryRun; +import org.sonar.core.resource.ResourceIndexerDao; + +/** + * @since 2.13 + */ +@NotDryRun +public class IndexProjectPostJob implements PostJob { + private ResourceIndexerDao indexer; + + public IndexProjectPostJob(ResourceIndexerDao indexer) { + this.indexer = indexer; + } + + public void executeOn(Project project, SensorContext context) { + if (project.getId() != null) { + indexer.indexProject(project.getId()); + } + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/IndexProjectPostJobTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/IndexProjectPostJobTest.java new file mode 100644 index 00000000000..cdb23d6fd6e --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/IndexProjectPostJobTest.java @@ -0,0 +1,53 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 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.plugins.core.batch; + +import org.junit.Test; +import org.sonar.api.resources.Project; +import org.sonar.core.resource.ResourceIndexerDao; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class IndexProjectPostJobTest { + @Test + public void shouldIndexProject() { + ResourceIndexerDao indexer = mock(ResourceIndexerDao.class); + IndexProjectPostJob job = new IndexProjectPostJob(indexer); + Project project = new Project("foo"); + project.setId(123); + + job.executeOn(project, null); + + verify(indexer).indexProject(123); + } + + @Test + public void shouldNotIndexProjectIfMissingId() { + ResourceIndexerDao indexer = mock(ResourceIndexerDao.class); + IndexProjectPostJob job = new IndexProjectPostJob(indexer); + + job.executeOn(new Project("foo"), null); + + verifyZeroInteractions(indexer); + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java index b40aa67a45a..21f6824c713 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java @@ -27,7 +27,6 @@ import org.sonar.batch.MavenPluginExecutor; import org.sonar.batch.ServerMetadata; import org.sonar.batch.config.BatchSettings; import org.sonar.batch.config.BatchSettingsEnhancer; -import org.sonar.core.resource.ResourceIndexer; import org.sonar.jpa.session.DatabaseSessionProvider; import org.sonar.jpa.session.DefaultDatabaseConnector; import org.sonar.jpa.session.ThreadLocalDatabaseSessionFactory; @@ -67,7 +66,6 @@ public class BootstrapModule extends Module { addCoreSingleton(BatchDatabase.class); addCoreSingleton(MyBatis.class); - addCoreSingleton(ResourceIndexer.class); addCoreSingleton(DefaultDatabaseConnector.class); addCoreSingleton(ThreadLocalDatabaseSessionFactory.class); for (Class daoClass : DaoUtils.getDaoClasses()) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java index 5386b61414d..57037c987fd 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java @@ -27,8 +27,6 @@ import org.sonar.api.database.model.ResourceModel; import org.sonar.api.database.model.Snapshot; import org.sonar.api.resources.*; import org.sonar.api.utils.SonarException; -import org.sonar.core.resource.ResourceIndexer; -import org.sonar.core.resource.ResourceIndexerDao; import javax.persistence.NonUniqueResultException; import javax.persistence.Query; @@ -41,11 +39,9 @@ public final class DefaultResourcePersister implements ResourcePersister { private DatabaseSession session; private Map snapshotsByResource = Maps.newHashMap(); - private ResourceIndexer indexer; - public DefaultResourcePersister(DatabaseSession session, ResourceIndexer indexer) { + public DefaultResourcePersister(DatabaseSession session) { this.session = session; - this.indexer = indexer; } public Snapshot saveProject(Project project, Project parent) { @@ -84,7 +80,6 @@ public final class DefaultResourcePersister implements ResourcePersister { snapshot.setCreatedAt(project.getAnalysisDate()); snapshot = session.save(snapshot); session.commit(); - indexer.index(project.getName(), snapshot.getQualifier(), snapshot.getResourceId(), snapshot.getRootProjectId()); return snapshot; } @@ -134,7 +129,6 @@ public final class DefaultResourcePersister implements ResourcePersister { } else { snapshot = persistFileOrDirectory(project, resource, parent); } - indexer.index(resource.getName(), snapshot.getQualifier(), snapshot.getResourceId(), snapshot.getRootProjectId()); return snapshot; } diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java index 35277ff3436..3491738c922 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java @@ -26,7 +26,6 @@ import org.sonar.api.resources.JavaPackage; import org.sonar.api.resources.Library; import org.sonar.api.resources.Project; import org.sonar.jpa.test.AbstractDbUnitTestCase; -import org.sonar.core.resource.ResourceIndexer; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -34,7 +33,6 @@ import java.text.SimpleDateFormat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; -import static org.mockito.Mockito.mock; public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { @@ -67,7 +65,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { public void shouldSaveNewProject() { setupData("shared"); - ResourcePersister persister = new DefaultResourcePersister(getSession(), mock(ResourceIndexer.class)); + ResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(singleProject, null); checkTables("shouldSaveNewProject", "projects", "snapshots"); @@ -77,7 +75,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { public void shouldSaveNewMultiModulesProject() throws ParseException { setupData("shared"); - ResourcePersister persister = new DefaultResourcePersister(getSession(), mock(ResourceIndexer.class)); + ResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(multiModuleProject, null); persister.saveProject(moduleA, multiModuleProject); persister.saveProject(moduleB, multiModuleProject); @@ -90,7 +88,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { public void shouldSaveNewDirectory() { setupData("shared"); - ResourcePersister persister = new DefaultResourcePersister(getSession(), mock(ResourceIndexer.class)); + ResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(singleProject, null); persister.saveResource(singleProject, new JavaPackage("org.foo").setEffectiveKey("foo:org.foo")); @@ -102,7 +100,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { public void shouldSaveNewLibrary() { setupData("shared"); - ResourcePersister persister = new DefaultResourcePersister(getSession(), mock(ResourceIndexer.class)); + ResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(singleProject, null); persister.saveResource(singleProject, new Library("junit:junit", "4.8.2").setEffectiveKey("junit:junit")); persister.saveResource(singleProject, new Library("junit:junit", "4.8.2").setEffectiveKey("junit:junit"));// do nothing, already saved @@ -115,7 +113,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { public void shouldClearResourcesExceptProjects() { setupData("shared"); - DefaultResourcePersister persister = new DefaultResourcePersister(getSession(), mock(ResourceIndexer.class)); + DefaultResourcePersister persister = new DefaultResourcePersister(getSession()); persister.saveProject(multiModuleProject, null); persister.saveProject(moduleA, multiModuleProject); persister.saveResource(moduleA, new JavaPackage("org.foo").setEffectiveKey("a:org.foo")); @@ -131,7 +129,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { public void shouldUpdateExistingResource() { setupData("shouldUpdateExistingResource"); - ResourcePersister persister = new DefaultResourcePersister(getSession(), mock(ResourceIndexer.class)); + ResourcePersister persister = new DefaultResourcePersister(getSession()); singleProject.setName("new name"); singleProject.setDescription("new description"); persister.saveProject(singleProject, null); diff --git a/sonar-batch/src/test/java/org/sonar/batch/phases/UpdateStatusJobTest.java b/sonar-batch/src/test/java/org/sonar/batch/phases/UpdateStatusJobTest.java index 21b784d1ff3..320db8e9608 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/phases/UpdateStatusJobTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/phases/UpdateStatusJobTest.java @@ -24,7 +24,6 @@ import org.sonar.api.database.DatabaseSession; import org.sonar.api.database.model.Snapshot; import org.sonar.batch.ServerMetadata; import org.sonar.batch.index.DefaultResourcePersister; -import org.sonar.core.resource.ResourceIndexer; import org.sonar.jpa.test.AbstractDbUnitTestCase; import javax.persistence.Query; @@ -51,7 +50,7 @@ public class UpdateStatusJobTest extends AbstractDbUnitTestCase { private void assertAnalysis(int snapshotId, String fixture) { setupData("sharedFixture", fixture); DatabaseSession session = getSession(); - UpdateStatusJob sensor = new UpdateStatusJob(mock(ServerMetadata.class), session, new DefaultResourcePersister(session, mock(ResourceIndexer.class)), loadSnapshot(snapshotId)); + UpdateStatusJob sensor = new UpdateStatusJob(mock(ServerMetadata.class), session, new DefaultResourcePersister(session), loadSnapshot(snapshotId)); sensor.execute(); getSession().stop(); diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexer.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexer.java deleted file mode 100644 index d184792df99..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexer.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 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.annotations.Beta; -import org.apache.commons.lang.ArrayUtils; -import org.sonar.api.BatchComponent; -import org.sonar.api.ServerComponent; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Scopes; - -/** - * This component will be automatically called in v3.0 when a resource is created or updated. - * It means that it will not be exposed to plugin API. - * - * @since 2.13 - */ -@Beta -public class ResourceIndexer implements BatchComponent, ServerComponent { - private ResourceIndexerDao dao; - - /** - * Hardcoded list of qualifiers to index. Need to be configurable. - * Directories and packages are explicitly excluded. - */ - static final String[] INDEXABLE_QUALIFIERS = { - Qualifiers.VIEW, - Qualifiers.SUBVIEW, - Qualifiers.PROJECT, - Qualifiers.MODULE, - Qualifiers.FILE, - Qualifiers.CLASS, - Qualifiers.UNIT_TEST_FILE - }; - - public ResourceIndexer(ResourceIndexerDao dao) { - this.dao = dao; - } - - public ResourceIndexer index(String resourceName, String qualifier, int resourceId, int rootProjectId) { - if (ArrayUtils.contains(INDEXABLE_QUALIFIERS, qualifier)) { - dao.index(resourceName, qualifier, resourceId, rootProjectId); - } - return this; - } - - /** - * Used only for the migration from a version less than 2.13. - */ - public ResourceIndexer indexAll() { - ResourceIndexerFilter filter = ResourceIndexerFilter.create() - .setScopes(new String[]{Scopes.PROJECT, Scopes.FILE}) - .setQualifiers(INDEXABLE_QUALIFIERS); - dao.index(filter); - return this; - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java index 0aa11816fd6..8e737501011 100644 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java @@ -19,123 +19,174 @@ */ package org.sonar.core.resource; -import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ResultContext; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.SqlSession; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; import org.sonar.core.persistence.MyBatis; public class ResourceIndexerDao { public static final int MINIMUM_KEY_SIZE = 3; + // The scopes and qualifiers that are not in the following constants are not indexed at all. + // Directories and packages are explicitly excluded. + private static final String[] RENAMABLE_QUALIFIERS = {Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.VIEW, Qualifiers.SUBVIEW}; + private static final String[] RENAMABLE_SCOPES = {Scopes.PROJECT}; + private static final String[] NOT_RENAMABLE_QUALIFIERS = {Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE, Qualifiers.CLASS}; + private static final String[] NOT_RENAMABLE_SCOPES = {Scopes.FILE}; + private final MyBatis mybatis; public ResourceIndexerDao(MyBatis mybatis) { this.mybatis = mybatis; } - public ResourceIndexerDao index(String resourceName, String qualifier, int resourceId, int rootProjectId) { - SqlSession sqlSession = mybatis.openSession(); + /** + * This method is reentrant. It can be executed even if the project is already indexed. + */ + public ResourceIndexerDao indexProject(final int rootProjectId) { + SqlSession session = mybatis.openSession(ExecutorType.BATCH); try { - ResourceDto resource = new ResourceDto() - .setId(resourceId) - .setQualifier(qualifier) - .setName(resourceName) - .setRootId(rootProjectId); - index(resource, sqlSession, true); + ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class); + doIndexProject(rootProjectId, session, mapper); + session.commit(); + return this; } finally { - sqlSession.close(); + session.close(); } - return this; } - - public ResourceIndexerDao index(ResourceIndexerFilter filter) { - final SqlSession sqlSession = mybatis.openSession(ExecutorType.BATCH); + /** + * This method is reentrant. It can be executed even if some projects are already indexed. + */ + public ResourceIndexerDao indexProjects() { + final SqlSession session = mybatis.openSession(ExecutorType.BATCH); try { - sqlSession.select("selectResourcesToIndex", filter, new ResultHandler() { + final ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class); + session.select("selectRootProjectIds", /* workaround to get booleans */ResourceIndexerFilter.create(), new ResultHandler() { public void handleResult(ResultContext context) { - ResourceDto resource = (ResourceDto) context.getResultObject(); - - // The column PROJECTS.ROOT_ID references the module but not the root project in a multi-modules project. - boolean correctRootProjectId = false; - - index(resource, sqlSession, correctRootProjectId); + Integer rootProjectId = (Integer) context.getResultObject(); + doIndexProject(rootProjectId, session, mapper); + session.commit(); } }); + return this; + } finally { - sqlSession.close(); + session.close(); } - return this; } - void index(ResourceDto resource, SqlSession session, boolean correctProjectRootId) { - String name = resource.getName(); - if (StringUtils.isBlank(name) || resource.getId() == null) { - return; - } + private void doIndexProject(int rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) { + // non indexed resources + ResourceIndexerFilter filter = ResourceIndexerFilter.create() + .setNonIndexedOnly(true) + .setQualifiers(NOT_RENAMABLE_QUALIFIERS) + .setScopes(NOT_RENAMABLE_SCOPES) + .setRootProjectId(rootProjectId); + + session.select("selectResources", filter, new ResultHandler() { + public void handleResult(ResultContext context) { + ResourceDto resource = (ResourceDto) context.getResultObject(); + doIndex(resource, mapper); + } + }); + + // some resources can be renamed, so index must be regenerated + // -> delete existing rows and create them again + filter = ResourceIndexerFilter.create() + .setNonIndexedOnly(false) + .setQualifiers(RENAMABLE_QUALIFIERS) + .setScopes(RENAMABLE_SCOPES) + .setRootProjectId(rootProjectId); + + session.select("selectResources", filter, new ResultHandler() { + public void handleResult(ResultContext context) { + ResourceDto resource = (ResourceDto) context.getResultObject(); + + mapper.deleteByResourceId(resource.getId()); + doIndex(resource, mapper); + } + }); + } - String key = toKey(name); - if (key.length() >= MINIMUM_KEY_SIZE) { - ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class); - boolean toBeIndexed = sanitizeIndex(resource, key, mapper); - if (toBeIndexed) { - - ResourceIndexDto dto = new ResourceIndexDto() - .setResourceId(resource.getId()) - .setQualifier(resource.getQualifier()) - .setRootProjectId(loadRootProjectId(resource, mapper, correctProjectRootId)) - .setNameSize(name.length()); - - for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) { - dto.setPosition(position); - dto.setKey(StringUtils.substring(key, position)); - mapper.insert(dto); - } - session.commit(); + void doIndex(ResourceDto resource, ResourceIndexerMapper mapper) { + String key = nameToKey(resource.getName()); + if (key.length() >= MINIMUM_KEY_SIZE) { + ResourceIndexDto dto = new ResourceIndexDto() + .setResourceId(resource.getId()) + .setQualifier(resource.getQualifier()) + .setRootProjectId(resource.getRootId()) + .setNameSize(resource.getName().length()); + + for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) { + dto.setPosition(position); + dto.setKey(StringUtils.substring(key, position)); + mapper.insert(dto); } } } - private Integer loadRootProjectId(ResourceDto resource, ResourceIndexerMapper mapper, boolean correctProjectRootId) { - if (correctProjectRootId) { - return resource.getRootId(); - } - Integer rootId; - if (resource.getRootId() != null) { - ResourceDto root = mapper.selectRootId(resource.getRootId()); - if (root != null) { - rootId = (Integer) ObjectUtils.defaultIfNull(root.getRootId(), root.getId()); - } else { - rootId = resource.getRootId(); + public boolean indexResource(int id, String name, String qualifier, int rootProjectId) { + boolean indexed = false; + if (isIndexableQualifier(qualifier)) { + SqlSession session = mybatis.openSession(); + try { + String key = nameToKey(name); + if (key.length() >= MINIMUM_KEY_SIZE) { + indexed = true; + ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class); + boolean toBeIndexed = sanitizeIndex(id, key, mapper); + if (toBeIndexed) { + ResourceIndexDto dto = new ResourceIndexDto() + .setResourceId(id) + .setQualifier(qualifier) + .setRootProjectId(rootProjectId) + .setNameSize(name.length()); + + for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) { + dto.setPosition(position); + dto.setKey(StringUtils.substring(key, position)); + mapper.insert(dto); + } + session.commit(); + } + } + } finally { + session.close(); } - } else { - rootId = resource.getId(); } - return rootId; + return indexed; } + /** * Return true if the resource must be indexed, false if the resource is already indexed. * If the resource is indexed with a different key, then this index is dropped and the * resource must be indexed again. */ - private boolean sanitizeIndex(ResourceDto resource, String key, ResourceIndexerMapper mapper) { - ResourceIndexDto masterIndex = mapper.selectMasterIndexByResourceId(resource.getId()); + private boolean sanitizeIndex(int resourceId, String key, ResourceIndexerMapper mapper) { + ResourceIndexDto masterIndex = mapper.selectMasterIndexByResourceId(resourceId); if (masterIndex != null && !StringUtils.equals(key, masterIndex.getKey())) { // resource has been renamed -> drop existing indexes - mapper.deleteByResourceId(resource.getId()); + mapper.deleteByResourceId(resourceId); masterIndex = null; } return masterIndex == null; } - static String toKey(String input) { - return StringUtils.lowerCase(input); + static String nameToKey(String input) { + return StringUtils.lowerCase(StringUtils.trimToEmpty(input)); + } + + static boolean isIndexableQualifier(String qualifier) { + return ArrayUtils.contains(RENAMABLE_QUALIFIERS, qualifier) || ArrayUtils.contains(NOT_RENAMABLE_QUALIFIERS, qualifier); } } diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerFilter.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerFilter.java index 73b2aea748b..a8a823078db 100644 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerFilter.java +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerFilter.java @@ -20,9 +20,11 @@ package org.sonar.core.resource; public final class ResourceIndexerFilter { - private boolean enabled = true; + private boolean _true = true; + private Integer rootProjectId = null; private String[] scopes = null; private String[] qualifiers = null; + private boolean nonIndexedOnly=false; private ResourceIndexerFilter() { } @@ -31,10 +33,6 @@ public final class ResourceIndexerFilter { return new ResourceIndexerFilter(); } - public boolean isEnabled() { - return enabled; - } - public String[] getScopes() { return scopes; } @@ -52,4 +50,22 @@ public final class ResourceIndexerFilter { this.qualifiers = qualifiers; return this; } + + public Integer getRootProjectId() { + return rootProjectId; + } + + public ResourceIndexerFilter setRootProjectId(Integer i) { + this.rootProjectId = i; + return this; + } + + public boolean isNonIndexedOnly() { + return nonIndexedOnly; + } + + public ResourceIndexerFilter setNonIndexedOnly(boolean b) { + this.nonIndexedOnly = b; + return this; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerMapper.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerMapper.java index b4d0ef63b0b..a9b044f23fa 100644 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerMapper.java @@ -19,9 +19,9 @@ */ package org.sonar.core.resource; -public interface ResourceIndexerMapper { +import java.util.List; - ResourceDto selectRootId(int id); +public interface ResourceIndexerMapper { ResourceIndexDto selectMasterIndexByResourceId(int resourceId); diff --git a/sonar-core/src/main/resources/org/sonar/core/resource/ResourceIndexerMapper.xml b/sonar-core/src/main/resources/org/sonar/core/resource/ResourceIndexerMapper.xml index 4fcf08b84fd..fee6f471405 100644 --- a/sonar-core/src/main/resources/org/sonar/core/resource/ResourceIndexerMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/resource/ResourceIndexerMapper.xml @@ -3,27 +3,47 @@ - + select p.name as "name", p.id as "id", p.scope as "scope", p.qualifier as "qualifier", s.root_project_id as "rootId" + from projects p, snapshots s - enabled=#{enabled} - and copy_resource_id is null - and scope in + p.enabled=#{_true} + and p.copy_resource_id is null + and p.id=s.project_id + and s.islast=#{_true} + + and p.scope in #{scope} - and qualifier in - #{qualifier} + + and p.qualifier in + #{qualifier} + + + + and s.root_project_id=#{rootProjectId} + + + and not exists(select * from resource_index ri where ri.resource_id=p.id) - + select distinct root_project_id + from snapshots + where islast=#{_true} + and scope='PRJ' + and qualifier in ('TRK', 'VW', 'SVW') + + +