aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-batch
diff options
context:
space:
mode:
authorsimonbrandhof <simon.brandhof@gmail.com>2010-09-06 14:08:06 +0000
committersimonbrandhof <simon.brandhof@gmail.com>2010-09-06 14:08:06 +0000
commitaeadc1f9129274949daaa57738c7c4550bdfbc7b (patch)
tree08dadf5ef7474fc41d1d48f74648f1ba8b55f34d /sonar-batch
downloadsonarqube-aeadc1f9129274949daaa57738c7c4550bdfbc7b.tar.gz
sonarqube-aeadc1f9129274949daaa57738c7c4550bdfbc7b.zip
SONAR-236 remove deprecated code from checkstyle plugin + display default value of rule parameters in Q profile console
Diffstat (limited to 'sonar-batch')
-rw-r--r--sonar-batch/pom.xml61
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/Batch.java114
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java95
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/CheckProfileProvider.java64
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/CoreJob.java29
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/CoreJobs.java43
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/DecoratorsExecutor.java81
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java62
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/DefaultDecoratorContext.java177
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/DefaultSensorContext.java155
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java116
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/FinalizeSnapshotsJob.java122
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/MavenPhaseExecutor.java42
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/MavenPluginExecutor.java32
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/MavenPluginsConfigurator.java75
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/PostJobsExecutor.java81
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java66
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java141
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProjectBuilder.java150
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProjectConfiguration.java68
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java163
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/RemoteClassLoader.java69
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ResourceDatabaseConfiguration.java83
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ResourceFilters.java65
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/SensorsExecutor.java74
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ServerMetadata.java73
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ViolationFilters.java60
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ViolationsDao.java99
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/indexer/Bucket.java150
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultPersister.java52
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultSonarIndex.java465
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/indexer/LibraryPersister.java68
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/indexer/ProjectPersister.java58
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersister.java96
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersisters.java50
-rw-r--r--sonar-batch/src/main/resources/org/sonar/batch/logback.xml44
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/CoreJobsTest.java29
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java114
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/DefaultSensorContextTest.java244
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/DefaultTimeMachineTest.java108
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/FinalizeSnapshotsJobTest.java78
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/MavenPhaseExecutorTest.java58
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/MavenPluginsConfiguratorTest.java77
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/PostJobsExecutorTest.java66
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/ProfileProviderTest.java95
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/ProjectBuilderTest.java210
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/ProjectConfigurationTest.java117
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/ProjectTreeTest.java209
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/RemoteClassLoaderTest.java43
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/ServerMetadataTest.java61
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/ViolationFiltersTest.java68
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultPersisterTest.java61
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultSonarIndexTest.java56
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/indexer/LibraryPersisterTest.java94
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/indexer/ResourcePersistersTest.java67
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject-result.xml53
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject.xml35
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures-result.xml20
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures.xml14
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures-result.xml24
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures.xml24
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources-result.xml14
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources.xml14
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues-result.xml39
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues.xml24
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency-result.xml36
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency.xml22
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource-result.xml28
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource.xml18
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields-result.xml37
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields.xml22
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure-result.xml20
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure.xml14
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree-result.xml30
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree.xml14
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourcesBeforeBuildingDependencies.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures-result.xml26
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures.xml14
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields-result.xml22
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields.xml18
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/DefaultTimeMachineTest/loadMeasuresFromDate.xml65
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast-result.xml22
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast.xml23
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot-result.xml173
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot.xml174
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/sharedFixture.xml38
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot-result.xml55
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot.xml57
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/MavenPluginsConfiguratorTest/pom.xml19
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysis.xml15
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysisIfNeverAnalysed.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isNotLatestAnalysis.xml15
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/global-properties.xml18
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/modules-properties.xml26
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/project-properties.xml17
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesBranch/pom.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesDeprecatedBranch/pom.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path1/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path2/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/pom.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module1/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module2/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/pom.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path1/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path2/pom.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/pom.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectHasNoModules/pom.xml8
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectWithoutModules/pom.xml8
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/RemoteClassLoaderTest/foo.jarbin0 -> 537 bytes
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource-result.xml23
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource.xml12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource-result.xml23
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource.xml18
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion-result.xml23
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion.xml19
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary-result.xml19
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary-result.xml19
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary.xml19
119 files changed, 6948 insertions, 0 deletions
diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml
new file mode 100644
index 00000000000..5ff838014a5
--- /dev/null
+++ b/sonar-batch/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-batch</artifactId>
+ <packaging>jar</packaging>
+ <name>Sonar :: Batch</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-deprecated</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-squid</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-project</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-testing-harness</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/main/java/org/sonar/batch/Batch.java b/sonar-batch/src/main/java/org/sonar/batch/Batch.java
new file mode 100644
index 00000000000..cf199af94be
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/Batch.java
@@ -0,0 +1,114 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.Configuration;
+import org.picocontainer.Characteristics;
+import org.picocontainer.MutablePicoContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.Plugins;
+import org.sonar.api.resources.Project;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.api.utils.IocContainer;
+import org.sonar.api.utils.ServerHttpClient;
+import org.sonar.batch.indexer.DefaultSonarIndex;
+import org.sonar.core.plugin.JpaPluginDao;
+import org.sonar.jpa.session.DatabaseSessionProvider;
+import org.sonar.jpa.session.DriverDatabaseConnector;
+import org.sonar.jpa.session.ThreadLocalDatabaseSessionFactory;
+
+import java.net.URLClassLoader;
+
+public class Batch {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Batch.class);
+
+ private Configuration configuration;
+ private Object[] components;
+
+ public Batch(Configuration configuration, Object... components) {
+ this.configuration = configuration;
+ this.components = components;
+ }
+
+ public void execute() {
+ MutablePicoContainer container = null;
+ try {
+ container = buildPicoContainer();
+ container.start();
+ analyzeProjects(container);
+
+ } finally {
+ if (container != null) {
+ container.stop();
+ }
+ }
+ }
+
+ private void analyzeProjects(MutablePicoContainer container) {
+ // a child container is built to ensure database connector is up
+ MutablePicoContainer batchContainer = container.makeChildContainer();
+ batchContainer.as(Characteristics.CACHE).addComponent(ServerMetadata.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(ProjectTree.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(DefaultSonarIndex.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(JpaPluginDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(BatchPluginRepository.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(Plugins.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(ServerHttpClient.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(HttpDownloader.class);
+ batchContainer.start();
+
+ ProjectTree projectTree = batchContainer.getComponent(ProjectTree.class);
+ DefaultSonarIndex index = batchContainer.getComponent(DefaultSonarIndex.class);
+ analyzeProject(batchContainer, index, projectTree.getRootProject());
+
+ // batchContainer is stopped by its parent
+ }
+
+ private MutablePicoContainer buildPicoContainer() {
+ MutablePicoContainer container = IocContainer.buildPicoContainer();
+
+ register(container, configuration);
+ URLClassLoader fullClassloader = RemoteClassLoader.createForJdbcDriver(configuration).getClassLoader();
+ // set as the current context classloader for hibernate, else it does not find the JDBC driver.
+ Thread.currentThread().setContextClassLoader(fullClassloader);
+
+ register(container, new DriverDatabaseConnector(configuration, fullClassloader));
+ register(container, ThreadLocalDatabaseSessionFactory.class);
+ container.as(Characteristics.CACHE).addAdapter(new DatabaseSessionProvider());
+ for (Object component : components) {
+ register(container, component);
+ }
+ return container;
+ }
+
+ private void register(MutablePicoContainer container, Object component) {
+ container.as(Characteristics.CACHE).addComponent(component);
+ }
+
+ private void analyzeProject(MutablePicoContainer container, DefaultSonarIndex index, Project project) {
+ for (Project module : project.getModules()) {
+ analyzeProject(container, index, module);
+ }
+ LOG.info("------------- Analyzing " + project.getName());
+ new ProjectBatch(container).execute(index, project);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java b/sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java
new file mode 100644
index 00000000000..105686834e4
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java
@@ -0,0 +1,95 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import com.google.common.collect.HashMultimap;
+import org.picocontainer.MutablePicoContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.Plugin;
+import org.sonar.api.platform.PluginRepository;
+import org.sonar.core.plugin.JpaPlugin;
+import org.sonar.core.plugin.JpaPluginDao;
+import org.sonar.core.plugin.JpaPluginFile;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class BatchPluginRepository extends PluginRepository {
+
+ private Map<String, ClassLoader> classloaders;
+ private String baseUrl;
+ private JpaPluginDao dao;
+
+ public BatchPluginRepository(JpaPluginDao dao, ServerMetadata server) {
+ this.dao= dao;
+ this.baseUrl = server.getUrl() + "/deploy/plugins/";
+ }
+
+ public void start() {
+ HashMultimap<String, URL> urlsByKey = HashMultimap.create();
+ for (JpaPluginFile pluginFile : dao.getPluginFiles()) {
+ try {
+ String key = getClassloaderKey(pluginFile.getPluginKey());
+ URL url = new URL(baseUrl + pluginFile.getPath());
+ urlsByKey.put(key, url);
+
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Can not build the classloader of the plugin " + pluginFile.getPluginKey(), e);
+ }
+ }
+
+ classloaders = new HashMap<String, ClassLoader>();
+ for (String key : urlsByKey.keySet()) {
+ Set<URL> urls = urlsByKey.get(key);
+
+ Logger logger = LoggerFactory.getLogger(getClass());
+ if (logger.isDebugEnabled()) {
+ logger.debug("Classloader of plugin " + key + ":");
+ for (URL url : urls) {
+ logger.debug(" -> " + url);
+ }
+ }
+ classloaders.put(key, new RemoteClassLoader(urls, Thread.currentThread().getContextClassLoader()).getClassLoader());
+ }
+ }
+
+ private String getClassloaderKey(String pluginKey) {
+ return "sonar-plugin-" + pluginKey;
+ }
+
+ public void registerPlugins(MutablePicoContainer pico) {
+ try {
+ for (JpaPlugin pluginMetadata : dao.getPlugins()) {
+ String classloaderKey = getClassloaderKey(pluginMetadata.getKey());
+ Class claz = classloaders.get(classloaderKey).loadClass(pluginMetadata.getPluginClass());
+ Plugin plugin = (Plugin) claz.newInstance();
+ registerPlugin(pico, plugin, BatchExtension.class);
+ }
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/CheckProfileProvider.java b/sonar-batch/src/main/java/org/sonar/batch/CheckProfileProvider.java
new file mode 100644
index 00000000000..3ec7d8b522b
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/CheckProfileProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.checks.profiles.Check;
+import org.sonar.api.checks.profiles.CheckProfile;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.ActiveRuleParam;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CheckProfileProvider extends ProviderAdapter {
+
+ private CheckProfile profile = null;
+
+ public CheckProfile provide(RulesProfile ruleProfile) {
+ if (profile == null) {
+ profile = new CheckProfile(ruleProfile.getName(), ruleProfile.getLanguage());
+ for (ActiveRule activeRule : ruleProfile.getActiveRules()) {
+ Check check = toCheck(activeRule);
+ profile.addCheck(check);
+ }
+ }
+ return profile;
+ }
+
+ private Check toCheck(ActiveRule activeRule) {
+ Check check = new Check(activeRule.getPluginName(), activeRule.getRuleKey());
+ check.setPriority(activeRule.getPriority().toCheckPriority());
+ check.setProperties(toParameters(activeRule.getActiveRuleParams()));
+ return check;
+ }
+
+ private Map<String, String> toParameters(List<ActiveRuleParam> params) {
+ Map<String, String> map = new HashMap<String, String>();
+ if (params != null) {
+ for (ActiveRuleParam param : params) {
+ map.put(param.getRuleParam().getKey(), param.getValue());
+ }
+ }
+ return map;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/CoreJob.java b/sonar-batch/src/main/java/org/sonar/batch/CoreJob.java
new file mode 100644
index 00000000000..f7231d63e79
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/CoreJob.java
@@ -0,0 +1,29 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+
+public interface CoreJob {
+
+ void execute(Project project, SensorContext context);
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/CoreJobs.java b/sonar-batch/src/main/java/org/sonar/batch/CoreJobs.java
new file mode 100644
index 00000000000..e3beb96ce2a
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/CoreJobs.java
@@ -0,0 +1,43 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Core batch extensions, instanciated by picocontainer.
+ */
+public final class CoreJobs {
+
+ private CoreJobs() {
+ }
+
+ public static List<Class<? extends CoreJob>> allJobs() {
+ List<Class<? extends CoreJob>> classes = new ArrayList<Class<? extends CoreJob>>();
+ classes.add(MavenPluginsConfigurator.class);
+ classes.add(MavenPhaseExecutor.class);
+ classes.add(SensorsExecutor.class);
+ classes.add(DecoratorsExecutor.class);
+ classes.add(FinalizeSnapshotsJob.class);
+ classes.add(PostJobsExecutor.class);
+ return classes;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DecoratorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/DecoratorsExecutor.java
new file mode 100644
index 00000000000..f6f5233ae18
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/DecoratorsExecutor.java
@@ -0,0 +1,81 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.BatchExtensionDictionnary;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.batch.indexer.DefaultSonarIndex;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class DecoratorsExecutor implements CoreJob {
+
+ private DecoratorsSelector decoratorsSelector;
+ private DatabaseSession session;
+ private ViolationsDao violationsDao;
+ private static final Logger LOG = LoggerFactory.getLogger(DecoratorsExecutor.class);
+ private DefaultSonarIndex index;
+
+ public DecoratorsExecutor(BatchExtensionDictionnary extensionDictionnary, DefaultSonarIndex index, DatabaseSession session, ViolationsDao violationsDao) {
+ this.decoratorsSelector = new DecoratorsSelector(extensionDictionnary);
+ this.session = session;
+ this.violationsDao = violationsDao;
+ this.index = index;
+ }
+
+
+ public void execute(Project project, SensorContext sensorContext) {
+ LoggerFactory.getLogger(DecoratorsExecutor.class).info("Execute decorators...");
+ Collection<Decorator> decorators = decoratorsSelector.select(project);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Decorators: {}", StringUtils.join(decorators, " -> "));
+ }
+
+ decorateResource(project, decorators, true);
+ }
+
+ private DecoratorContext decorateResource(Resource resource, Collection<Decorator> decorators, boolean executeDecorators) {
+ List<DecoratorContext> childrenContexts = new ArrayList<DecoratorContext>();
+ for (Resource child : index.getChildren(resource)) {
+ boolean isModule = (child instanceof Project);
+ DefaultDecoratorContext childContext = (DefaultDecoratorContext) decorateResource(child, decorators, !isModule);
+ childrenContexts.add(childContext.setReadOnly(true));
+ }
+
+ DefaultDecoratorContext context = new DefaultDecoratorContext(resource, index, childrenContexts, session, violationsDao);
+ if (executeDecorators) {
+ for (Decorator decorator : decorators) {
+ decorator.decorate(resource, context);
+ }
+ }
+ return context;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java b/sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java
new file mode 100644
index 00000000000..8f8c000099e
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java
@@ -0,0 +1,62 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.sonar.api.batch.BatchExtensionDictionnary;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.FormulaDecorator;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+
+import java.util.*;
+
+public class DecoratorsSelector {
+
+ private BatchExtensionDictionnary dictionnary;
+
+ public DecoratorsSelector(BatchExtensionDictionnary dictionnary) {
+ this.dictionnary = dictionnary;
+ }
+
+ public Collection<Decorator> select(Project project) {
+ List<Decorator> decorators = new ArrayList<Decorator>(dictionnary.select(Decorator.class, project, false));
+ Set<Metric> coveredMetrics = getMetricsCoveredByPlugins(decorators);
+ for (Metric metric : dictionnary.select(Metric.class)) {
+ if (metric.getFormula() != null && !coveredMetrics.contains(metric)) {
+ decorators.add(new FormulaDecorator(metric));
+ }
+ }
+
+ return dictionnary.sort(decorators);
+ }
+
+ private Set<Metric> getMetricsCoveredByPlugins(Collection<Decorator> pluginDecorators) {
+ Set<Metric> coveredMetrics = new HashSet<Metric>();
+ for (Decorator pluginDecorator : pluginDecorators) {
+ List dependents = dictionnary.getDependents(pluginDecorator);
+ for (Object dependent : dependents) {
+ if (dependent instanceof Metric) {
+ coveredMetrics.add((Metric) dependent);
+ }
+ }
+ }
+ return coveredMetrics;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultDecoratorContext.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultDecoratorContext.java
new file mode 100644
index 00000000000..05314d3040e
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultDecoratorContext.java
@@ -0,0 +1,177 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.Event;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.design.Dependency;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Violation;
+import org.sonar.batch.indexer.Bucket;
+import org.sonar.batch.indexer.DefaultSonarIndex;
+
+import java.util.*;
+
+public class DefaultDecoratorContext implements DecoratorContext {
+
+ private DatabaseSession session;
+ private DefaultSonarIndex index;
+ private Resource resource;
+ private boolean readOnly = false;
+
+ private List<DecoratorContext> childrenContexts;
+ private List<Violation> violations;
+ private ViolationsDao violationsDao;
+
+ public DefaultDecoratorContext(Resource resource,
+ DefaultSonarIndex index,
+ List<DecoratorContext> childrenContexts,
+ DatabaseSession session,
+ ViolationsDao violationsDao) {
+ this.index = index;
+ this.session = session;
+ this.resource = resource;
+ this.childrenContexts = childrenContexts;
+ this.violationsDao = violationsDao;
+ }
+
+ public DefaultDecoratorContext setReadOnly(boolean b) {
+ readOnly = b;
+ violations = null;
+ childrenContexts = null;
+ return this;
+ }
+
+ public Project getProject() {
+ return index.getProject();
+ }
+
+ public List<DecoratorContext> getChildren() {
+ checkReadOnly("getModules");
+ return childrenContexts;
+ }
+
+ private void checkReadOnly(String methodName) {
+ if (readOnly) {
+ throw new IllegalStateException("Method DecoratorContext." + methodName + "() can not be executed on children.");
+ }
+ }
+
+ public <M> M getMeasures(MeasuresFilter<M> filter) {
+ return index.getMeasures(resource, filter);
+ }
+
+ public Measure getMeasure(Metric metric) {
+ return index.getMeasures(resource, MeasuresFilters.metric(metric));
+ }
+
+ public Collection<Measure> getChildrenMeasures(MeasuresFilter filter) {
+ List<Measure> result = new ArrayList<Measure>();
+ for (DecoratorContext childContext : childrenContexts) {
+ Object childResult = childContext.getMeasures(filter);
+ if (childResult != null) {
+ if (childResult instanceof Collection) {
+ result.addAll((Collection) childResult);
+ } else {
+ result.add((Measure) childResult);
+ }
+ }
+ }
+ return result;
+ }
+
+ public Collection<Measure> getChildrenMeasures(Metric metric) {
+ return getChildrenMeasures(MeasuresFilters.metric(metric));
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ public DecoratorContext saveMeasure(Measure measure) {
+ checkReadOnly("saveMeasure");
+ index.saveMeasure(resource, measure);
+ return this;
+ }
+
+ public DecoratorContext saveMeasure(Metric metric, Double value) {
+ checkReadOnly("saveMeasure");
+ index.saveMeasure(resource, new Measure(metric, value));
+ return this;
+ }
+
+
+ public List<Violation> getViolations() {
+ if (violations == null) {
+ Bucket bucket = index.getBucket(resource);
+ if (bucket != null && bucket.getSnapshotId() != null) {
+ violations = violationsDao.getViolations(resource, bucket.getSnapshotId());
+ }
+ }
+ return violations;
+ }
+
+ public Dependency saveDependency(Dependency dependency) {
+ checkReadOnly("saveDependency");
+ return index.saveDependency(dependency);
+ }
+
+ public Set<Dependency> getDependencies() {
+ return index.getDependencies();
+ }
+
+ public Collection<Dependency> getIncomingDependencies() {
+ return index.getIncomingEdges(resource);
+ }
+
+ public Collection<Dependency> getOutgoingDependencies() {
+ return index.getOutgoingEdges(resource);
+ }
+
+ protected DatabaseSession getSession() {
+ return session;
+ }
+
+ public List<Event> getEvents() {
+ return index.getEvents(resource);
+ }
+
+ public Event createEvent(String name, String description, String category, Date date) {
+ return index.createEvent(resource, name, description, category, date);
+ }
+
+ public void deleteEvent(Event event) {
+ index.deleteEvent(event);
+ }
+
+ public DefaultDecoratorContext saveViolation(Violation violation) {
+ if (violation.getResource() == null) {
+ violation.setResource(resource);
+ }
+ index.addViolation(violation);
+ return this;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultSensorContext.java
new file mode 100644
index 00000000000..de2629ed336
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultSensorContext.java
@@ -0,0 +1,155 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.sonar.api.batch.Event;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.design.Dependency;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.ProjectLink;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Violation;
+import org.sonar.batch.indexer.DefaultSonarIndex;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultSensorContext implements SensorContext {
+
+ private DefaultSonarIndex index;
+ private Project project;
+
+ public DefaultSensorContext(DefaultSonarIndex index, Project project) {
+ this.index = index;
+ this.project = project;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public Measure getMeasure(Metric metric) {
+ return index.getMeasure(project, metric);
+ }
+
+ public <M> M getMeasures(MeasuresFilter<M> filter) {
+ return index.getMeasures(project, filter);
+ }
+
+ public Measure saveMeasure(Measure measure) {
+ return index.saveMeasure(project, measure);
+ }
+
+ public Measure saveMeasure(Metric metric, Double value) {
+ return index.saveMeasure(project, new Measure(metric, value));
+ }
+
+ public Measure getMeasure(Resource resource, Metric metric) {
+ return index.getMeasure(resource, metric);
+ }
+
+ public String saveResource(Resource resource) {
+ Resource persistedResource = index.addResource(resource);
+ if (persistedResource!=null) {
+ return persistedResource.getEffectiveKey();
+ }
+ return null;
+ }
+
+ public Resource getResource(Resource resource) {
+ return index.getResource(resource);
+ }
+
+ public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) {
+ return index.getMeasures(resource, filter);
+ }
+
+ public Measure saveMeasure(Resource resource, Metric metric, Double value) {
+ return index.saveMeasure(resourceOrProject(resource), new Measure(metric, value));
+ }
+
+ public Measure saveMeasure(Resource resource, Measure measure) {
+ return index.saveMeasure(resourceOrProject(resource), measure);
+ }
+
+ public void saveViolation(Violation violation) {
+ if (violation.getResource()==null) {
+ violation.setResource(resourceOrProject(violation.getResource()));
+ }
+ index.addViolation(violation);
+ }
+
+ public void saveViolations(Collection<Violation> violations) {
+ if (violations!=null) {
+ for (Violation violation : violations) {
+ saveViolation(violation);
+ }
+ }
+ }
+
+ public Dependency saveDependency(Dependency dependency) {
+ return index.saveDependency(dependency);
+ }
+
+ public Set<Dependency> getDependencies() {
+ return index.getDependencies();
+ }
+
+ public Collection<Dependency> getIncomingDependencies(Resource to) {
+ return index.getIncomingEdges(resourceOrProject(to));
+ }
+
+ public Collection<Dependency> getOutgoingDependencies(Resource from) {
+ return index.getOutgoingEdges(resourceOrProject(from));
+ }
+
+ public void saveSource(Resource resource, String source) {
+ index.setSource(resource, source);
+ }
+
+ public void saveLink(ProjectLink link) {
+ index.saveLink(link);
+ }
+
+ public void deleteLink(String key) {
+ index.deleteLink(key);
+ }
+
+ public List<Event> getEvents(Resource resource) {
+ return index.getEvents(resource);
+ }
+
+ public Event createEvent(Resource resource, String name, String description, String category, Date date) {
+ return index.createEvent(resource, name, description, category, date);
+ }
+
+ public void deleteEvent(Event event) {
+ index.deleteEvent(event);
+ }
+
+ private Resource resourceOrProject(Resource resource) {
+ return (resource!=null ? resource : project);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java
new file mode 100644
index 00000000000..092b389f1f3
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java
@@ -0,0 +1,116 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.sonar.api.batch.TimeMachine;
+import org.sonar.api.batch.TimeMachineQuery;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Resource;
+import org.sonar.batch.indexer.DefaultSonarIndex;
+import org.sonar.jpa.dao.MeasuresDao;
+
+import java.util.*;
+import javax.persistence.Query;
+
+public class DefaultTimeMachine implements TimeMachine {
+
+ private DatabaseSession session;
+ private DefaultSonarIndex index;
+ private MeasuresDao measuresDao;
+
+ public DefaultTimeMachine(DatabaseSession session, DefaultSonarIndex index, MeasuresDao measuresDao) {
+ this.session = session;
+ this.index = index;
+ this.measuresDao = measuresDao;
+ }
+
+ public List<Measure> getMeasures(TimeMachineQuery query) {
+ List<Object[]> objects = execute(query, true);
+ List<Measure> result = new ArrayList<Measure>();
+
+ for (Object[] object : objects) {
+ MeasureModel model = (MeasureModel) object[0];
+ Measure measure = model.toMeasure();
+ measure.setDate((Date) object[1]);
+ result.add(measure);
+ }
+ return result;
+ }
+
+ public List<Object[]> getMeasuresFields(TimeMachineQuery query) {
+ return execute(query, false);
+ }
+
+ protected List execute(TimeMachineQuery query, boolean selectAllFields) {
+ Resource resource = index.getResource(query.getResource());
+ if (resource == null) {
+ return Collections.emptyList();
+ }
+
+ StringBuilder sb = new StringBuilder();
+ Map<String, Object> params = new HashMap<String, Object>();
+
+ if (selectAllFields) {
+ sb.append("SELECT m, s.createdAt ");
+ } else {
+ sb.append("SELECT s.createdAt, m.metric, m.value ");
+ }
+ sb.append(" FROM " + MeasureModel.class.getSimpleName() + " m, " + Snapshot.class.getSimpleName() + " s WHERE m.snapshotId=s.id AND s.resourceId=:resourceId AND s.status=:status AND m.characteristic IS NULL ");
+ params.put("resourceId", resource.getId());
+ params.put("status", Snapshot.STATUS_PROCESSED);
+
+ sb.append(" AND m.rule IS NULL AND m.rulePriority IS NULL AND m.rulesCategoryId IS NULL ");
+ if (query.getMetrics() != null) {
+ sb.append(" AND m.metric IN (:metrics) ");
+ params.put("metrics", measuresDao.getMetrics(query.getMetrics()));
+ }
+ if (query.isFromCurrentAnalysis()) {
+ sb.append(" AND s.createdAt>=:from ");
+ params.put("from", index.getProject().getAnalysisDate());
+
+ } else if (query.getFrom() != null) {
+ sb.append(" AND s.createdAt>=:from ");
+ params.put("from", query.getFrom());
+ }
+ if (query.isToCurrentAnalysis()) {
+ sb.append(" AND s.createdAt<=:to ");
+ params.put("to", index.getProject().getAnalysisDate());
+
+ } else if (query.getTo() != null) {
+ sb.append(" AND s.createdAt<=:to ");
+ params.put("to", query.getTo());
+ }
+ if (query.isOnlyLastAnalysis()) {
+ sb.append(" AND s.last=:last ");
+ params.put("last", Boolean.TRUE);
+ }
+ sb.append(" ORDER BY s.createdAt ");
+
+ Query jpaQuery = session.createQuery(sb.toString());
+
+ for (Map.Entry<String, Object> entry : params.entrySet()) {
+ jpaQuery.setParameter(entry.getKey(), entry.getValue());
+ }
+ return jpaQuery.getResultList();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/FinalizeSnapshotsJob.java b/sonar-batch/src/main/java/org/sonar/batch/FinalizeSnapshotsJob.java
new file mode 100644
index 00000000000..e1763c5f403
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/FinalizeSnapshotsJob.java
@@ -0,0 +1,122 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.Purge;
+import org.sonar.api.batch.PurgeContext;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Project;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.core.purge.DefaultPurgeContext;
+
+import javax.persistence.Query;
+
+public class FinalizeSnapshotsJob implements CoreJob {
+
+ private DatabaseSession session;
+ private Purge[] purges;
+ private ServerMetadata server;
+ private Snapshot snapshot;
+
+ public FinalizeSnapshotsJob(ServerMetadata server, DatabaseSession session, Purge[] purges, Snapshot snapshot) {
+ this.session = session;
+ this.purges = purges;
+ this.server = server;
+ this.snapshot = snapshot;
+ }
+
+ public void execute(Project project, SensorContext context) {
+ if (shouldExecuteOn(project)) {
+ Snapshot previousLastSnapshot = getPreviousLastSnapshot(snapshot);
+ updateFlags(snapshot, previousLastSnapshot);
+ purge(snapshot, previousLastSnapshot);
+ }
+ }
+
+ private boolean shouldExecuteOn(Project project) {
+ return project.isRoot();
+ }
+
+ private Snapshot getPreviousLastSnapshot(Snapshot snapshot) {
+ Query query = session.createQuery(
+ "SELECT s FROM " + Snapshot.class.getSimpleName() + " s " +
+ "WHERE s.last=true AND s.resourceId=:resourceId");
+ query.setParameter("resourceId", snapshot.getResourceId());
+ return session.getSingleResult(query, null);
+ }
+
+
+ private void updateFlags(Snapshot rootSnapshot, Snapshot previousLastSnapshot) {
+ if (previousLastSnapshot != null && previousLastSnapshot.getCreatedAt().before(rootSnapshot.getCreatedAt())) {
+ setFlags(previousLastSnapshot, false, null);
+ }
+
+ boolean isLast = (previousLastSnapshot == null || previousLastSnapshot.getCreatedAt().before(rootSnapshot.getCreatedAt()));
+ setFlags(rootSnapshot, isLast, Snapshot.STATUS_PROCESSED);
+ LoggerFactory.getLogger(getClass()).info("ANALYSIS SUCCESSFUL, you can browse {}", server.getUrl());
+ }
+
+ private void setFlags(Snapshot snapshot, boolean last, String status) {
+ String hql = "UPDATE " + Snapshot.class.getSimpleName() + " SET last=:last";
+ if (status != null) {
+ hql += ", status=:status ";
+ }
+ hql += " WHERE root_snapshot_id=:rootId OR id=:rootId OR (path LIKE :path AND root_snapshot_id=:pathRootId)";
+
+ Query query = session.createQuery(hql);
+ if (status != null) {
+ query.setParameter("status", status);
+ snapshot.setStatus(status);
+ }
+ query.setParameter("last", last);
+ query.setParameter("rootId", snapshot.getId());
+ query.setParameter("path", snapshot.getPath() + snapshot.getId() + ".%");
+ query.setParameter("pathRootId", (snapshot.getRootId() == null ? snapshot.getId() : snapshot.getRootId()));
+ query.executeUpdate();
+ session.commit();
+
+ snapshot.setLast(last);
+ }
+
+ private void purge(Snapshot currentSnapshot, Snapshot previousLastSnapshot) {
+ final Logger logger = LoggerFactory.getLogger(FinalizeSnapshotsJob.class);
+ TimeProfiler profiler = new TimeProfiler(logger).start("Database optimization");
+ PurgeContext context = createPurgeContext(currentSnapshot, previousLastSnapshot);
+ logger.debug("Snapshots to purge: " + context);
+ for (Purge purge : purges) {
+ logger.debug("Executing {}...", purge.getClass().getName());
+ purge.purge(context);
+ }
+ profiler.stop();
+ }
+
+ private PurgeContext createPurgeContext(Snapshot currentSnapshot, Snapshot previousLastSnapshot) {
+ DefaultPurgeContext context = new DefaultPurgeContext(currentSnapshot);
+ if (previousLastSnapshot != null && previousLastSnapshot.getCreatedAt().before(currentSnapshot.getCreatedAt())) {
+ context.setLastSnapshotId(previousLastSnapshot.getId());
+ }
+ return context;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/MavenPhaseExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/MavenPhaseExecutor.java
new file mode 100644
index 00000000000..78b490b5428
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/MavenPhaseExecutor.java
@@ -0,0 +1,42 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+
+public class MavenPhaseExecutor implements CoreJob {
+
+ public static final String PROP_PHASE = "sonar.phase";
+
+ private MavenPluginExecutor executor;
+
+ public MavenPhaseExecutor(MavenPluginExecutor executor) {
+ this.executor = executor;
+ }
+
+ public void execute(Project project, SensorContext context) {
+ String mavenPhase = (String) project.getProperty(PROP_PHASE);
+ if (!StringUtils.isBlank(mavenPhase)) {
+ executor.execute(project, mavenPhase);
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/MavenPluginExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/MavenPluginExecutor.java
new file mode 100644
index 00000000000..12404e58534
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/MavenPluginExecutor.java
@@ -0,0 +1,32 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.resources.Project;
+
+public interface MavenPluginExecutor extends BatchComponent {
+
+ void execute(Project project, String goal);
+
+ MavenPluginHandler execute(Project project, MavenPluginHandler handler);
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/MavenPluginsConfigurator.java b/sonar-batch/src/main/java/org/sonar/batch/MavenPluginsConfigurator.java
new file mode 100644
index 00000000000..ea263a21325
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/MavenPluginsConfigurator.java
@@ -0,0 +1,75 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.project.MavenProject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.BatchExtensionDictionnary;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.maven.MavenPlugin;
+import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.resources.Project;
+import org.sonar.api.utils.SonarException;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+public class MavenPluginsConfigurator implements CoreJob {
+
+ private BatchExtensionDictionnary dictionnary = null;
+
+ public MavenPluginsConfigurator(BatchExtensionDictionnary dictionnary) {
+ this.dictionnary = dictionnary;
+ }
+
+ public void execute(Project project, SensorContext context) {
+ Logger logger = LoggerFactory.getLogger(getClass());
+ logger.info("Configure maven plugins...");
+
+ for (MavenPluginHandler handler : dictionnary.selectMavenPluginHandlers(project)) {
+ logger.debug("Configure {}...", handler);
+ configureHandler(project, handler);
+ }
+ savePom(project);
+ }
+
+ protected void configureHandler(Project project, MavenPluginHandler handler) {
+ MavenPlugin plugin = MavenPlugin.registerPlugin(project.getPom(), handler.getGroupId(), handler.getArtifactId(), handler.getVersion(), handler.isFixedVersion());
+ handler.configure(project, plugin);
+ }
+
+ protected void savePom(Project project) {
+ MavenProject pom = project.getPom();
+ File targetPom = new File(project.getFileSystem().getSonarWorkingDirectory(), "sonar-pom.xml");
+ FileWriter fileWriter = null;
+ try {
+ fileWriter = new FileWriter(targetPom, false);
+ pom.writeModel(fileWriter);
+
+ } catch (IOException e) {
+ throw new SonarException("Can not save pom to " + targetPom, e);
+ } finally {
+ IOUtils.closeQuietly(fileWriter);
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/PostJobsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/PostJobsExecutor.java
new file mode 100644
index 00000000000..1df8d3fd773
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/PostJobsExecutor.java
@@ -0,0 +1,81 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.BatchExtensionDictionnary;
+import org.sonar.api.batch.PostJob;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.maven.DependsUponMavenPlugin;
+import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.resources.Project;
+
+import java.util.Collection;
+
+public class PostJobsExecutor implements CoreJob {
+ private static final Logger LOG = LoggerFactory.getLogger(PostJobsExecutor.class);
+
+ private Collection<PostJob> postJobs;
+ private MavenPluginExecutor mavenExecutor;
+
+ public PostJobsExecutor(Project project, BatchExtensionDictionnary selector, MavenPluginExecutor mavenExecutor) {
+ postJobs = selector.select(PostJob.class, project, true);
+ this.mavenExecutor = mavenExecutor;
+ }
+
+ protected PostJobsExecutor(Collection<PostJob> postJobs, MavenPluginExecutor mavenExecutor) {
+ this.postJobs = postJobs;
+ this.mavenExecutor = mavenExecutor;
+ }
+
+ public void execute(Project project, SensorContext context) {
+ if (shouldExecuteOn(project)) {
+ logPostJobs();
+
+ for (PostJob postJob : postJobs) {
+ LOG.info("Executing post-job {}", postJob.getClass());
+ executeMavenPlugin(project, postJob);
+ postJob.executeOn(project, context);
+ }
+ }
+ }
+
+ private void logPostJobs() {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Post-jobs : {}", StringUtils.join(postJobs, " -> "));
+ }
+ }
+
+ private boolean shouldExecuteOn(Project project) {
+ return postJobs != null && project.isRoot();
+ }
+
+
+ private void executeMavenPlugin(Project project, PostJob job) {
+ if (job instanceof DependsUponMavenPlugin) {
+ MavenPluginHandler handler = ((DependsUponMavenPlugin) job).getMavenPluginHandler(project);
+ if (handler != null) {
+ mavenExecutor.execute(project, handler);
+ }
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java b/sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java
new file mode 100644
index 00000000000..2059f562c69
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java
@@ -0,0 +1,66 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.jpa.dao.ProfilesDao;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rules.ActiveRule;
+
+public class ProfileProvider extends ProviderAdapter {
+
+ public static final String PARAM_PROFILE = "sonar.profile";
+
+ private static final Logger LOG = LoggerFactory.getLogger(ProfileProvider.class);
+ private RulesProfile profile;
+
+ public RulesProfile provide(Project project, ProfilesDao dao) {
+ if (profile == null) {
+ String profileName = (String) project.getProperty(PARAM_PROFILE);
+ if (profileName == null) {
+ Project root = project.getRoot();
+ profile = dao.getActiveProfile(root.getLanguageKey(), root.getKey());
+ if (profile == null) {
+ throw new RuntimeException("Quality profile not found for " + root.getKey() + ", language " + root.getLanguageKey());
+ }
+
+ } else {
+ profile = dao.getProfile(project.getLanguageKey(), profileName);
+ if (profile == null) {
+ throw new RuntimeException("Quality profile not found : " + profileName + ", language " + project.getLanguageKey());
+ }
+ }
+
+ // hack to lazy initialize the profile collections
+ profile.getActiveRules().size();
+ for (ActiveRule activeRule : profile.getActiveRules()) {
+ activeRule.getActiveRuleParams().size();
+ activeRule.getRule().getParams().size();
+ }
+ profile.getAlerts().size();
+
+ LOG.info("Selected quality profile : {}", profile);
+ }
+ return profile;
+ }
+} \ No newline at end of file
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java b/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
new file mode 100644
index 00000000000..2f0894a2c61
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
@@ -0,0 +1,141 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.picocontainer.Characteristics;
+import org.picocontainer.MutablePicoContainer;
+import org.sonar.api.batch.BatchExtensionDictionnary;
+import org.sonar.api.batch.ProjectClasspath;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.Metrics;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rules.DefaultRulesManager;
+import org.sonar.api.utils.IocContainer;
+import org.sonar.batch.indexer.DefaultSonarIndex;
+import org.sonar.core.qualitymodel.DefaultModelProvider;
+import org.sonar.core.rule.DefaultRuleProvider;
+import org.sonar.jpa.dao.*;
+
+public class ProjectBatch {
+
+ private MutablePicoContainer globalContainer;
+ private MutablePicoContainer batchContainer;
+
+ public ProjectBatch(MutablePicoContainer container) {
+ this.globalContainer = container;
+ }
+
+ public void execute(DefaultSonarIndex index, Project project) {
+ try {
+ startChildContainer(index, project);
+ SensorContext sensorContext = batchContainer.getComponent(SensorContext.class);
+ for (Class<? extends CoreJob> clazz : CoreJobs.allJobs()) {
+ CoreJob job = getComponent(clazz);
+ job.execute(project, sensorContext);
+ commit();
+ }
+
+ } finally {
+ index.clear();
+ stop();
+ }
+ }
+
+ public void startChildContainer(DefaultSonarIndex index, Project project) {
+ batchContainer = globalContainer.makeChildContainer();
+ batchContainer.getComponent(BatchPluginRepository.class).registerPlugins(batchContainer);
+
+ batchContainer.as(Characteristics.CACHE).addComponent(project);
+ batchContainer.as(Characteristics.CACHE).addComponent(project.getPom());
+ batchContainer.as(Characteristics.CACHE).addComponent(ProjectClasspath.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(index.getBucket(project).getSnapshot());
+ batchContainer.as(Characteristics.CACHE).addComponent(project.getConfiguration());
+
+ batchContainer.as(Characteristics.CACHE).addComponent(DaoFacade.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(RulesDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(org.sonar.api.database.daos.RulesDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(MeasuresDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(org.sonar.api.database.daos.MeasuresDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(ProfilesDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(AsyncMeasuresDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(AsyncMeasuresService.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(DefaultRulesManager.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(DefaultSensorContext.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(Languages.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(BatchExtensionDictionnary.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(DefaultTimeMachine.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(ViolationsDao.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(ViolationFilters.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(ResourceFilters.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(DefaultModelProvider.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(DefaultRuleProvider.class);
+ batchContainer.addAdapter(new ProfileProvider());
+ batchContainer.addAdapter(new CheckProfileProvider());
+ loadCoreComponents(batchContainer);
+ batchContainer.as(Characteristics.CACHE).addComponent(new IocContainer(batchContainer));
+ batchContainer.start();
+
+ // post-initializations
+ project.setLanguage(getComponent(Languages.class).get(project.getLanguageKey()));
+ index.selectProject(project, getComponent(ResourceFilters.class), getComponent(ViolationFilters.class), getComponent(MeasuresDao.class), getComponent(ViolationsDao.class));
+ }
+
+ private void loadCoreComponents(MutablePicoContainer container) {
+ for (Class<?> clazz : CoreJobs.allJobs()) {
+ container.as(Characteristics.CACHE).addComponent(clazz);
+ }
+ for (Metric metric : CoreMetrics.getMetrics()) {
+ container.as(Characteristics.CACHE).addComponent(metric.getKey(), metric);
+ }
+ for (Metrics metricRepo : container.getComponents(Metrics.class)) {
+ for (Metric metric : metricRepo.getMetrics()) {
+ container.as(Characteristics.CACHE).addComponent(metric.getKey(), metric);
+ }
+ }
+ }
+
+ private void stop() {
+ if (batchContainer != null) {
+ commit();
+ try {
+ globalContainer.removeChildContainer(batchContainer);
+ batchContainer.stop();
+ batchContainer=null;
+ } catch (Exception e) {
+ // do not log
+ }
+ }
+ }
+
+ public void commit() {
+ getComponent(DatabaseSession.class).commit();
+ }
+
+ public <T> T getComponent(Class<T> clazz) {
+ if (batchContainer != null) {
+ return batchContainer.getComponent(clazz);
+ }
+ return globalContainer.getComponent(clazz);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProjectBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/ProjectBuilder.java
new file mode 100644
index 00000000000..3389db5e268
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProjectBuilder.java
@@ -0,0 +1,150 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.*;
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.maven.project.MavenProject;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.DefaultProjectFileSystem;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.Project;
+import org.sonar.api.utils.SonarException;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class ProjectBuilder {
+
+ private DatabaseSession databaseSession;
+
+ public ProjectBuilder(DatabaseSession databaseSession) {
+ this.databaseSession = databaseSession;
+ }
+
+ public Project create(MavenProject pom) {
+ Configuration configuration = getStartupConfiguration(pom);
+ return new Project(loadProjectKey(pom), loadProjectBranch(configuration), pom.getName())
+ .setPom(pom)
+ .setDescription(pom.getDescription())
+ .setPackaging(pom.getPackaging());
+ }
+
+ Configuration getStartupConfiguration(MavenProject pom) {
+ CompositeConfiguration configuration = new CompositeConfiguration();
+ configuration.addConfiguration(new SystemConfiguration());
+ configuration.addConfiguration(new EnvironmentConfiguration());
+ configuration.addConfiguration(new MapConfiguration(pom.getModel().getProperties()));
+ return configuration;
+ }
+
+ String loadProjectKey(MavenProject pom) {
+ return new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString();
+ }
+
+ String loadProjectBranch(Configuration configuration) {
+ return configuration.getString(CoreProperties.PROJECT_BRANCH_PROPERTY, configuration.getString("branch" /* deprecated property */));
+ }
+
+
+ public void configure(Project project) {
+ ProjectConfiguration projectConfiguration = new ProjectConfiguration(databaseSession, project);
+ configure(project, projectConfiguration);
+ }
+
+
+ void configure(Project project, Configuration projectConfiguration) {
+ Date analysisDate = loadAnalysisDate(projectConfiguration);
+ project.setConfiguration(projectConfiguration)
+ .setExclusionPatterns(loadExclusionPatterns(projectConfiguration))
+ .setAnalysisDate(analysisDate)
+ .setLatestAnalysis(isLatestAnalysis(project.getKey(), analysisDate))
+ .setAnalysisVersion(loadAnalysisVersion(projectConfiguration, project.getPom()))
+ .setAnalysisType(loadAnalysisType(projectConfiguration))
+ .setLanguageKey(loadLanguageKey(projectConfiguration))
+ .setFileSystem(new DefaultProjectFileSystem(project));
+ }
+
+ static String[] loadExclusionPatterns(Configuration configuration) {
+ String[] exclusionPatterns = configuration.getStringArray(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY);
+ if (exclusionPatterns == null) {
+ exclusionPatterns = new String[0];
+ }
+ return exclusionPatterns;
+ }
+
+ boolean isLatestAnalysis(String projectKey, Date analysisDate) {
+ ResourceModel persistedProject = databaseSession.getSingleResult(ResourceModel.class, "key", projectKey, "enabled", true);
+ if (persistedProject != null) {
+ Snapshot lastSnapshot = databaseSession.getSingleResult(Snapshot.class, "resourceId", persistedProject.getId(), "last", true);
+ return lastSnapshot == null || lastSnapshot.getCreatedAt().before(analysisDate);
+ }
+ return true;
+ }
+
+
+ Date loadAnalysisDate(Configuration configuration) {
+ String formattedDate = configuration.getString(CoreProperties.PROJECT_DATE_PROPERTY);
+ if (formattedDate == null) {
+ return new Date();
+ }
+
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ try {
+ // see SONAR-908 make sure that a time is defined for the date.
+ Date date = DateUtils.setHours(format.parse(formattedDate), 0);
+ return DateUtils.setMinutes(date, 1);
+
+ } catch (ParseException e) {
+ throw new SonarException("The property " + CoreProperties.PROJECT_DATE_PROPERTY + " does not respect the format yyyy-MM-dd (for example 2008-05-23) : " + formattedDate, e);
+ }
+ }
+
+ Project.AnalysisType loadAnalysisType(Configuration configuration) {
+ String value = configuration.getString(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY);
+ if (value == null) {
+ return (configuration.getBoolean("sonar.light", false) ? Project.AnalysisType.STATIC : Project.AnalysisType.DYNAMIC);
+ }
+ if ("true".equals(value)) {
+ return Project.AnalysisType.DYNAMIC;
+ }
+ if ("reuseReports".equals(value)) {
+ return Project.AnalysisType.REUSE_REPORTS;
+ }
+ return Project.AnalysisType.STATIC;
+ }
+
+ String loadAnalysisVersion(Configuration configuration, MavenProject pom) {
+ String version = configuration.getString(CoreProperties.PROJECT_VERSION_PROPERTY);
+ if (version == null && pom != null) {
+ version = pom.getVersion();
+ }
+ return version;
+ }
+
+ String loadLanguageKey(Configuration configuration) {
+ return configuration.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY, Java.KEY);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProjectConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/ProjectConfiguration.java
new file mode 100644
index 00000000000..4226a14fe7c
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProjectConfiguration.java
@@ -0,0 +1,68 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.*;
+import org.apache.maven.project.MavenProject;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.resources.Project;
+
+public class ProjectConfiguration extends CompositeConfiguration {
+ private PropertiesConfiguration runtimeConfiguration;
+
+ public ProjectConfiguration(DatabaseSession session, Project project) {
+ runtimeConfiguration = new PropertiesConfiguration();
+ addConfiguration(runtimeConfiguration);
+
+ loadSystemSettings();
+ loadProjectDatabaseSettings(session, project);
+ loadMavenSettings(project.getPom());
+ loadGlobalDatabaseSettings(session);
+ }
+
+ private void loadProjectDatabaseSettings(DatabaseSession session, Project project) {
+ addConfiguration(new ResourceDatabaseConfiguration(session, project.getKey()));
+
+ Project parent = project.getParent();
+ while (parent != null && parent.getKey() != null) {
+ addConfiguration(new ResourceDatabaseConfiguration(session, parent.getKey()));
+ parent = parent.getParent();
+ }
+ }
+
+ private void loadGlobalDatabaseSettings(DatabaseSession session) {
+ addConfiguration(new org.sonar.api.database.configuration.DatabaseConfiguration(session));
+ }
+
+ private void loadSystemSettings() {
+ addConfiguration(new SystemConfiguration());
+ addConfiguration(new EnvironmentConfiguration());
+ }
+
+ private void loadMavenSettings(MavenProject pom) {
+ addConfiguration(new MapConfiguration(pom.getModel().getProperties()));
+ }
+
+ @Override
+ public void setProperty(String s, Object o) {
+ runtimeConfiguration.setProperty(s, o);
+ }
+}
+
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java b/sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java
new file mode 100644
index 00000000000..497c4656f58
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProjectTree.java
@@ -0,0 +1,163 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.resources.Project;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+public class ProjectTree {
+
+ private List<Project> projects;
+ private List<MavenProject> poms;
+ private ProjectBuilder projectBuilder;
+
+ public ProjectTree(MavenSession mavenSession, DatabaseSession databaseSession) {
+ this.poms = mavenSession.getSortedProjects();
+ this.projectBuilder = new ProjectBuilder(databaseSession);
+ }
+
+ /**
+ * for unit tests
+ */
+ protected ProjectTree(ProjectBuilder projectBuilder, List<MavenProject> poms) {
+ this.projectBuilder = projectBuilder;
+ this.poms = poms;
+ }
+
+ /**
+ * for unit tests
+ */
+ protected ProjectTree(List<Project> projects) {
+ this.projects = new ArrayList<Project>(projects);
+ }
+
+ public void start() throws IOException {
+ projects = Lists.newArrayList();
+ Map<String, Project> paths = Maps.newHashMap(); // projects by canonical path
+
+ for (MavenProject pom : poms) {
+ Project project = projectBuilder.create(pom);
+ projects.add(project);
+ paths.put(pom.getBasedir().getCanonicalPath(), project);
+ }
+
+ for (Map.Entry<String, Project> entry : paths.entrySet()) {
+ Project project = entry.getValue();
+ MavenProject pom = project.getPom();
+ for (Object moduleId : pom.getModules()) {
+ File modulePath = new File(pom.getBasedir(), (String) moduleId);
+ Project module = paths.get(modulePath.getCanonicalPath());
+ if (module != null) {
+ module.setParent(project);
+ }
+ }
+ }
+
+ configureProjects();
+ applyModuleExclusions();
+ }
+
+ private void configureProjects() {
+ for (Project project : projects) {
+ projectBuilder.configure(project);
+ }
+ }
+
+ void applyModuleExclusions() {
+ for (Project project : projects) {
+ String[] excludedArtifactIds = project.getConfiguration().getStringArray("sonar.skippedModules");
+ String[] includedArtifactIds = project.getConfiguration().getStringArray("sonar.includedModules");
+
+ Set<String> includedModulesIdSet = new HashSet<String>();
+ Set<String> excludedModulesIdSet = new HashSet<String>();
+
+ if (includedArtifactIds != null) {
+ includedModulesIdSet.addAll(Arrays.asList(includedArtifactIds));
+ }
+
+ if (excludedArtifactIds != null) {
+ excludedModulesIdSet.addAll(Arrays.asList(excludedArtifactIds));
+ includedModulesIdSet.removeAll(excludedModulesIdSet);
+ }
+
+ if (!includedModulesIdSet.isEmpty()) {
+ for (Project currentProject : projects) {
+ if (!includedModulesIdSet.contains(currentProject.getPom().getArtifactId())) {
+ exclude(currentProject);
+ }
+ }
+ } else {
+ for (String excludedArtifactId : excludedModulesIdSet) {
+ Project excludedProject = getProjectByArtifactId(excludedArtifactId);
+ exclude(excludedProject);
+ }
+ }
+ }
+
+ for (Iterator<Project> it = projects.iterator(); it.hasNext();) {
+ Project project = it.next();
+ if (project.isExcluded()) {
+ LoggerFactory.getLogger(getClass()).info("Module {} is excluded from analysis", project.getName());
+ project.removeFromParent();
+ it.remove();
+ }
+ }
+ }
+
+ private void exclude(Project project) {
+ if (project != null) {
+ project.setExcluded(true);
+ for (Project module : project.getModules()) {
+ exclude(module);
+ }
+ }
+ }
+
+ public List<Project> getProjects() {
+ return projects;
+ }
+
+ public Project getProjectByArtifactId(String artifactId) {
+ for (Project project : projects) {
+ if (project.getPom().getArtifactId().equals(artifactId)) {
+ return project;
+ }
+ }
+ return null;
+ }
+
+ public Project getRootProject() {
+ for (Project project : projects) {
+ if (project.getParent() == null) {
+ return project;
+ }
+ }
+ throw new IllegalStateException("Can not find the root project from the list of Maven modules");
+ }
+} \ No newline at end of file
diff --git a/sonar-batch/src/main/java/org/sonar/batch/RemoteClassLoader.java b/sonar-batch/src/main/java/org/sonar/batch/RemoteClassLoader.java
new file mode 100644
index 00000000000..09987c09ecb
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/RemoteClassLoader.java
@@ -0,0 +1,69 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.Configuration;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+
+/**
+ * Create classloader from remote URLs.
+ *
+ * IMPORTANT : it generates URLClassLoaders, which use the parent first delegation mode. It finds classes in the parent classloader THEN
+ * in the plugin classloader.
+ * Using a child-first delegation mode can avoid some conflicts with API dependencies (xml-api, antlr). It's
+ * not possible for now, but it would be simple to implement by replacing the URLClassLoader by
+ * the class ChildFirstClassLoader (see http://articles.qos.ch/classloader.html)
+ */
+public class RemoteClassLoader {
+
+ private URLClassLoader classLoader;
+
+ public RemoteClassLoader(URL[] urls, ClassLoader parent) {
+ ClassLoader parentClassLoader = (parent==null ? RemoteClassLoader.class.getClassLoader() : parent);
+ classLoader = URLClassLoader.newInstance(urls, parentClassLoader);
+ }
+
+ public RemoteClassLoader(URL[] urls) {
+ this(urls, null);
+ }
+
+ public RemoteClassLoader(Collection<URL> urls, ClassLoader parent) {
+ this(urls.toArray(new URL[urls.size()]), parent);
+ }
+
+ public URLClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ public static RemoteClassLoader createForJdbcDriver(Configuration conf) {
+ String baseUrl = ServerMetadata.getUrl(conf);
+ String url = baseUrl + "/deploy/jdbc-driver.jar";
+ try {
+ return new RemoteClassLoader(new URL[]{new URL(url)});
+
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Fail to download the JDBC driver from server: " + url, e);
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ResourceDatabaseConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/ResourceDatabaseConfiguration.java
new file mode 100644
index 00000000000..77359f427e3
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ResourceDatabaseConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.BaseConfiguration;
+
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.configuration.Property;
+import org.sonar.api.database.model.ResourceModel;
+
+import java.util.List;
+
+public class ResourceDatabaseConfiguration extends BaseConfiguration {
+ private final DatabaseSession session;
+ private Integer resourceId = null;
+
+ public ResourceDatabaseConfiguration(DatabaseSession session, ResourceModel resource) {
+ this.session = session;
+ if (resource != null) {
+ this.resourceId = resource.getId();
+ }
+ load();
+ }
+
+ public ResourceDatabaseConfiguration(DatabaseSession session, Integer resourceId) {
+ this.session = session;
+ this.resourceId = resourceId;
+ load();
+ }
+
+ public ResourceDatabaseConfiguration(DatabaseSession session, String resourceKey) {
+ this.session = session;
+
+ ResourceModel resource = session.getSingleResult(ResourceModel.class, "key", resourceKey);
+ if (resource != null) {
+ this.resourceId = resource.getId();
+ }
+ load();
+ }
+
+ public void load() {
+ clear();
+
+ loadResourceProperties();
+ }
+
+ private void loadResourceProperties() {
+ if (resourceId != null) {
+ List<Property> properties = session
+ .createQuery("from " + Property.class.getSimpleName() + " p where p.resourceId=:resourceId")
+ .setParameter("resourceId", resourceId)
+ .getResultList();
+
+ registerProperties(properties);
+ }
+ }
+
+ private void registerProperties(List<Property> properties) {
+ if (properties != null) {
+ for (Property property : properties) {
+ setProperty(property.getKey(), property.getValue());
+ }
+ }
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ResourceFilters.java b/sonar-batch/src/main/java/org/sonar/batch/ResourceFilters.java
new file mode 100644
index 00000000000..395ffd2f38b
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ResourceFilters.java
@@ -0,0 +1,65 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.ResourceFilter;
+import org.sonar.api.resources.Resource;
+
+/**
+ * @since 1.12
+ */
+public class ResourceFilters {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ResourceFilters.class);
+
+ private ResourceFilter[] filters;
+
+ public ResourceFilters(ResourceFilter[] filters) {
+ this.filters = (filters==null ? new ResourceFilter[0] : filters);
+ }
+
+ public ResourceFilters() {
+ this(null);
+ }
+
+ public ResourceFilter[] getFilters() {
+ return filters;
+ }
+
+ /**
+ * Return true if the violation must be saved. If false then it is ignored.
+ */
+ public boolean isExcluded(Resource resource) {
+ boolean ignored = false;
+ int index = 0;
+ while (!ignored && index < filters.length) {
+ ResourceFilter filter = filters[index];
+ ignored = filter.isIgnored(resource);
+ if (ignored && LOG.isDebugEnabled()) {
+ LOG.debug("Resource {} is excluded by the filter {}", resource, filter);
+ }
+ index++;
+ }
+ return ignored;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/SensorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/SensorsExecutor.java
new file mode 100644
index 00000000000..4bcdc39e9a6
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/SensorsExecutor.java
@@ -0,0 +1,74 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.BatchExtensionDictionnary;
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.maven.DependsUponMavenPlugin;
+import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.resources.Project;
+import org.sonar.api.utils.TimeProfiler;
+
+import java.util.Collection;
+
+public class SensorsExecutor implements CoreJob {
+ private static final Logger logger = LoggerFactory.getLogger(SensorsExecutor.class);
+
+ private Collection<Sensor> sensors;
+ private DatabaseSession session;
+ private MavenPluginExecutor mavenExecutor;
+
+ public SensorsExecutor(BatchExtensionDictionnary selector, Project project, DatabaseSession session, MavenPluginExecutor mavenExecutor) {
+ this.sensors = selector.select(Sensor.class, project, true);
+ this.session = session;
+ this.mavenExecutor = mavenExecutor;
+ }
+
+ public void execute(Project project, SensorContext context) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Sensors : {}", StringUtils.join(sensors, " -> "));
+ }
+
+ for (Sensor sensor : sensors) {
+ executeMavenPlugin(project, sensor);
+
+ TimeProfiler profiler = new TimeProfiler(logger).start("Sensor "+ sensor);
+ sensor.analyse(project, context);
+ session.commit();
+ profiler.stop();
+ }
+ }
+
+ private void executeMavenPlugin(Project project, Sensor sensor) {
+ if (sensor instanceof DependsUponMavenPlugin) {
+ MavenPluginHandler handler = ((DependsUponMavenPlugin) sensor).getMavenPluginHandler(project);
+ if (handler != null) {
+ TimeProfiler profiler = new TimeProfiler(logger).start("Execute maven plugin " + handler.getArtifactId());
+ mavenExecutor.execute(project, handler);
+ profiler.stop();
+ }
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ServerMetadata.java b/sonar-batch/src/main/java/org/sonar/batch/ServerMetadata.java
new file mode 100644
index 00000000000..d06e9212e91
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ServerMetadata.java
@@ -0,0 +1,73 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.platform.Server;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class ServerMetadata extends Server {
+
+ private String id;
+ private String version;
+ private String url;
+ private Date startTime;
+
+ public ServerMetadata(Configuration conf) {
+ id = conf.getString(CoreProperties.SERVER_ID);
+ version = conf.getString(CoreProperties.SERVER_VERSION);
+ url = getUrl(conf);
+ String dateString = conf.getString(CoreProperties.SERVER_STARTTIME);
+ if (dateString!=null) {
+ try {
+ this.startTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateString);
+
+ } catch (ParseException e) {
+ LoggerFactory.getLogger(getClass()).error("The property " + CoreProperties.SERVER_STARTTIME + " is badly formatted.", e);
+ }
+ }
+ }
+
+ public static String getUrl(Configuration conf) {
+ return StringUtils.removeEnd(conf.getString("sonar.host.url", "http://localhost:9000"), "/");
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public Date getStartedAt() {
+ return startTime;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ViolationFilters.java b/sonar-batch/src/main/java/org/sonar/batch/ViolationFilters.java
new file mode 100644
index 00000000000..784b01ae8b5
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ViolationFilters.java
@@ -0,0 +1,60 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.rules.Violation;
+import org.sonar.api.rules.ViolationFilter;
+
+public class ViolationFilters {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ViolationFilters.class);
+
+ private ViolationFilter[] filters;
+
+ public ViolationFilters(ViolationFilter[] filters) {
+ this.filters = (filters==null ? new ViolationFilter[0] : filters);
+ }
+
+ public ViolationFilters() {
+ this(null);
+ }
+
+ public ViolationFilter[] getFilters() {
+ return filters;
+ }
+
+ /**
+ * Return true if the violation must be saved. If false then it is ignored.
+ */
+ public boolean isIgnored(Violation violation) {
+ boolean ignored = false;
+ int index = 0;
+ while (!ignored && index < filters.length) {
+ ignored = filters[index].isIgnored(violation);
+ if (ignored && LOG.isDebugEnabled()) {
+ LOG.debug("Violation {} is excluded by the filter {}", violation, filters[index]);
+ }
+ index++;
+ }
+ return ignored;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ViolationsDao.java b/sonar-batch/src/main/java/org/sonar/batch/ViolationsDao.java
new file mode 100644
index 00000000000..bdca03188ca
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/ViolationsDao.java
@@ -0,0 +1,99 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.slf4j.LoggerFactory;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.RuleFailureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+import org.sonar.jpa.dao.RulesDao;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ViolationsDao {
+
+ private RulesProfile profile;
+ private DatabaseSession session;
+ private RulesDao rulesDao;
+ private boolean reuseExistingRulesConfig;
+
+ public ViolationsDao(RulesProfile profile, DatabaseSession session, RulesDao rulesDao, Project project) {
+ this.profile = profile;
+ this.session = session;
+ this.rulesDao = rulesDao;
+ this.reuseExistingRulesConfig = project.getReuseExistingRulesConfig();
+ }
+
+ public List<Violation> getViolations(Resource resource, Integer snapshotId) {
+ List<RuleFailureModel> models = session.getResults(RuleFailureModel.class, "snapshotId", snapshotId);
+ List<Violation> violations = new ArrayList<Violation>();
+ for (RuleFailureModel model : models) {
+ Violation violation = new Violation(model.getRule(), resource);
+ violation.setLineId(model.getLine());
+ violation.setMessage(model.getMessage());
+ violation.setPriority(model.getPriority());
+ violations.add(violation);
+ }
+ return violations;
+ }
+
+ public void saveViolation(Snapshot snapshot, Violation violation) {
+ if (profile == null || snapshot == null || violation == null) {
+ throw new IllegalArgumentException("Missing data to save violation : profile=" + profile + ",snapshot=" + snapshot + ",violation=" + violation);
+ }
+
+ ActiveRule activeRule = profile.getActiveRule(violation.getRule());
+ if (activeRule == null) {
+ if (reuseExistingRulesConfig) {
+ activeRule = new ActiveRule(profile, violation.getRule(), violation.getRule().getPriority());
+ } else {
+ LoggerFactory.getLogger(getClass()).debug("Violation is not saved because rule is not activated : violation={}", violation);
+ }
+ }
+ if (activeRule != null) {
+ RuleFailureModel model = toModel(snapshot, violation, activeRule);
+ session.save(model);
+ }
+ }
+
+ private RuleFailureModel toModel(Snapshot snapshot, Violation violation, ActiveRule activeRule) {
+ Rule rule = reload(violation.getRule());
+ if (rule == null) {
+ throw new IllegalArgumentException("Rule does not exist : " + violation.getRule());
+ }
+ RuleFailureModel model = new RuleFailureModel(rule, activeRule.getPriority());
+ violation.setPriority(activeRule.getPriority());
+ model.setLine(violation.getLineId());
+ model.setMessage(violation.getMessage());
+ model.setSnapshotId(snapshot.getId());
+ return model;
+ }
+
+ private Rule reload(Rule rule) {
+ return rule.getId() != null ? rule : rulesDao.getRuleByKey(rule.getPluginName(), rule.getKey());
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/indexer/Bucket.java b/sonar-batch/src/main/java/org/sonar/batch/indexer/Bucket.java
new file mode 100644
index 00000000000..98e107a2e4d
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/indexer/Bucket.java
@@ -0,0 +1,150 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class Bucket<RESOURCE extends Resource> {
+
+ private RESOURCE resource;
+ private Snapshot snapshot;
+ private ListMultimap<String, Measure> measuresByMetric = ArrayListMultimap.create();
+ private boolean sourceSaved = false;
+ private Bucket<Project> project;
+ private Bucket<?> parent;
+ private List<Bucket<?>> children;
+
+ public Bucket(RESOURCE resource) {
+ this.resource = resource;
+ }
+
+ public RESOURCE getResource() {
+ return resource;
+ }
+
+ public Bucket<Project> getProject() {
+ return project;
+ }
+
+ public Bucket<RESOURCE> setProject(Bucket<Project> project) {
+ this.project = project;
+ return this;
+ }
+
+ public Snapshot getSnapshot() {
+ return snapshot;
+ }
+
+ public Integer getSnapshotId() {
+ if (snapshot != null) {
+ return snapshot.getId();
+ }
+ return null;
+ }
+
+ public void setSnapshot(Snapshot snapshot) {
+ this.snapshot = snapshot;
+ }
+
+ public void setParent(Bucket parent) {
+ this.parent = parent;
+ if (parent != null) {
+ parent.addChild(this);
+ }
+ }
+
+ private void addChild(Bucket bucket) {
+ if (children == null) {
+ children = Lists.newArrayList();
+ }
+ children.add(bucket);
+ }
+
+ private void removeChild(Bucket bucket) {
+ if (children != null) {
+ children.remove(bucket);
+ }
+ }
+
+ public List<Bucket<?>> getChildren() {
+ if (children == null) {
+ return Collections.emptyList();
+ }
+ return children;
+ }
+
+ public Bucket getParent() {
+ return parent;
+ }
+
+ public void addMeasure(Measure measure) {
+ measuresByMetric.put(measure.getMetric().getKey(), measure);
+ }
+
+ public boolean isSourceSaved() {
+ return sourceSaved;
+ }
+
+ public void setSourceSaved(boolean b) {
+ this.sourceSaved = b;
+ }
+
+ public void clear() {
+ measuresByMetric = null;
+ children = null;
+ if (parent != null) {
+ parent.removeChild(this);
+ }
+ }
+
+ public boolean isExcluded() {
+ return resource.isExcluded();
+ }
+
+ public boolean isPersisted() {
+ return resource.getId() != null;
+ }
+
+ public Integer getResourceId() {
+ return resource.getId();
+ }
+
+ public <M> M getMeasures(final MeasuresFilter<M> filter) {
+ Collection<Measure> unfiltered;
+ if (filter instanceof MeasuresFilters.MetricFilter) {
+ unfiltered = measuresByMetric.get(((MeasuresFilters.MetricFilter) filter).filterOnMetric().getKey());
+ } else {
+ unfiltered = measuresByMetric.values();
+ }
+ return filter.filter(unfiltered);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultPersister.java b/sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultPersister.java
new file mode 100644
index 00000000000..36f8419e083
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultPersister.java
@@ -0,0 +1,52 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+
+public class DefaultPersister extends ResourcePersister {
+
+ public DefaultPersister(DatabaseSession session) {
+ super(session);
+ }
+
+ @Override
+ protected Snapshot createSnapshot(Bucket bucket, ResourceModel resourceModel) {
+ Snapshot parentSnapshot = (bucket.getParent() != null ? bucket.getParent().getSnapshot() : null);
+ Snapshot snapshot = new Snapshot(resourceModel, parentSnapshot);
+ return snapshot;
+ }
+
+ @Override
+ protected void prepareResourceModel(ResourceModel resourceModel, Bucket bucket) {
+ resourceModel.setRootId(bucket.getProject() != null ? bucket.getProject().getResourceId() : null);
+ }
+
+ @Override
+ protected String generateEffectiveKey(Bucket bucket) {
+ return new StringBuilder(ResourceModel.KEY_SIZE)
+ .append(bucket.getProject().getResource().getKey())
+ .append(':')
+ .append(bucket.getResource().getKey())
+ .toString();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultSonarIndex.java b/sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultSonarIndex.java
new file mode 100644
index 00000000000..48a6bc72997
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/indexer/DefaultSonarIndex.java
@@ -0,0 +1,465 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.Event;
+import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.jpa.dao.MeasuresDao;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.database.model.SnapshotSource;
+import org.sonar.api.design.Dependency;
+import org.sonar.api.design.DependencyDto;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.ProjectLink;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rules.Violation;
+import org.sonar.batch.ProjectTree;
+import org.sonar.batch.ResourceFilters;
+import org.sonar.batch.ViolationFilters;
+import org.sonar.batch.ViolationsDao;
+
+import java.util.*;
+
+public class DefaultSonarIndex extends SonarIndex {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultSonarIndex.class);
+
+ private DatabaseSession session;
+ private ResourcePersisters resourcePersisters;
+ private Bucket<Project> rootProjectBucket;
+ private Bucket<Project> selectedProjectBucket;
+
+ private ViolationFilters violationFilters;
+ private ResourceFilters resourceFilters;
+
+ // data
+ private Map<Resource, Bucket> buckets = Maps.newHashMap();
+ private Set<Dependency> dependencies = Sets.newHashSet();
+ private Map<Resource, Map<Resource, Dependency>> outgoingDependenciesByResource = new HashMap<Resource, Map<Resource, Dependency>>();
+ private Map<Resource, Map<Resource, Dependency>> incomingDependenciesByResource = new HashMap<Resource, Map<Resource, Dependency>>();
+
+ // dao
+ private ViolationsDao violationsDao;
+ private MeasuresDao measuresDao;
+ private ProjectTree projectTree;
+
+ public DefaultSonarIndex(DatabaseSession session, ProjectTree projectTree) {
+ this.session = session;
+ this.projectTree = projectTree;
+ this.resourcePersisters = new ResourcePersisters(session);
+ }
+
+ public void start() {
+ Project rootProject = projectTree.getRootProject();
+
+ this.rootProjectBucket = new Bucket<Project>(rootProject);
+ persist(rootProjectBucket);
+ this.buckets.put(rootProject, rootProjectBucket);
+ this.selectedProjectBucket = rootProjectBucket;
+
+ for (Project project : rootProject.getModules()) {
+ addProject(project);
+ }
+ session.commit();
+ }
+
+ private void addProject(Project project) {
+ addResource(project);
+ for (Project module : project.getModules()) {
+ addProject(module);
+ }
+ }
+
+
+ public void selectProject(Project project, ResourceFilters resourceFilters, ViolationFilters violationFilters, MeasuresDao measuresDao, ViolationsDao violationsDao) {
+ this.selectedProjectBucket = buckets.get(project);
+ this.resourceFilters = resourceFilters;
+ this.violationFilters = violationFilters;
+ this.violationsDao = violationsDao;
+ this.measuresDao = measuresDao;
+ }
+
+ /**
+ * Keep only project stuff
+ */
+ public void clear() {
+ Iterator<Map.Entry<Resource, Bucket>> it = buckets.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Resource, Bucket> entry = it.next();
+ Resource resource = entry.getKey();
+ if (!ResourceUtils.isSet(resource)) {
+ entry.getValue().clear();
+ it.remove();
+ }
+ }
+
+ Set<Dependency> projectDependencies = getDependenciesBetweenProjects();
+ dependencies.clear();
+ incomingDependenciesByResource.clear();
+ outgoingDependenciesByResource.clear();
+ for (Dependency projectDependency : projectDependencies) {
+ projectDependency.setId(null);
+ registerDependency(projectDependency);
+ }
+ }
+
+ /* ------------ RESOURCES */
+ public Project getRootProject() {
+ return rootProjectBucket.getResource();
+ }
+
+ public Project getProject() {
+ return selectedProjectBucket.getResource();
+ }
+
+ public Bucket getBucket(Resource resource) {
+ return buckets.get(resource);
+ }
+
+ public Resource getResource(Resource resource) {
+ Bucket bucket = buckets.get(resource);
+ if (bucket != null) {
+ return bucket.getResource();
+ }
+ return null;
+ }
+
+ public List<Resource> getChildren(Resource resource) {
+ List<Resource> children = Lists.newArrayList();
+ Bucket<?> bucket = buckets.get(resource);
+ if (bucket != null) {
+ for (Bucket childBucket : bucket.getChildren()) {
+ children.add(childBucket.getResource());
+ }
+ }
+ return children;
+ }
+
+ public Resource addResource(Resource resource) {
+ return getOrCreateBucket(resource).getResource();
+ }
+
+ private Bucket<Resource> getOrCreateBucket(Resource resource) {
+ Bucket bucket = buckets.get(resource);
+ if (bucket != null) {
+ return bucket;
+ }
+
+ prepareResource(resource);
+ bucket = new Bucket<Resource>(resource);
+ buckets.put(resource, bucket);
+
+ Bucket parentBucket = null;
+ Resource parent = resource.getParent();
+ if (parent != null) {
+ parentBucket = getOrCreateBucket(parent);
+ } else if (!ResourceUtils.isLibrary(resource)) {
+ parentBucket = selectedProjectBucket;
+ }
+ bucket.setParent(parentBucket);
+ bucket.setProject(selectedProjectBucket);
+
+ persist(bucket);
+ return bucket;
+ }
+
+ private void prepareResource(Resource resource) {
+ resource.setExcluded(resourceFilters != null && resourceFilters.isExcluded(resource));
+ }
+
+ private void persist(Bucket bucket) {
+ resourcePersisters.get(bucket).persist(bucket);
+ }
+
+ /* ------------ MEASURES */
+ public Measure getMeasure(Resource resource, Metric metric) {
+ return getOrCreateBucket(resource).getMeasures(MeasuresFilters.metric(metric));
+ }
+
+ public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) {
+ return getOrCreateBucket(resource).getMeasures(filter);
+ }
+
+
+ /* ------------ SOURCE CODE */
+
+ public void setSource(Resource resource, String source) {
+ Bucket bucket = getOrCreateBucket(resource);
+
+ if (!bucket.isExcluded()) {
+ if (bucket.isSourceSaved()) {
+ LOG.warn("Trying to save twice the source of " + resource);
+
+ } else {
+ session.save(new SnapshotSource(bucket.getSnapshotId(), source));
+ bucket.setSourceSaved(true);
+ }
+ }
+ }
+
+
+ /* ------------ RULE VIOLATIONS */
+
+ public void addViolation(Violation violation) {
+ Bucket bucket;
+ Resource resource = violation.getResource();
+ if (resource == null) {
+ bucket = selectedProjectBucket;
+ } else {
+ bucket = getOrCreateBucket(resource);
+ }
+ if (!bucket.isExcluded()) {
+ persistViolation(violation, bucket.getSnapshot());
+ }
+ }
+
+ private void persistViolation(Violation violation, Snapshot snapshot) {
+ boolean isIgnored = violationFilters != null && violationFilters.isIgnored(violation);
+ if (!isIgnored) {
+ violationsDao.saveViolation(snapshot, violation);
+ }
+ }
+
+
+ /* ------------ MEASURES */
+ public Measure saveMeasure(Resource resource, Measure measure) {
+ if (measure.getId() == null) {
+ return addMeasure(resource, measure);
+ }
+ return updateMeasure(measure);
+ }
+
+ public Measure addMeasure(Resource resource, Measure measure) {
+ Bucket bucket = getOrCreateBucket(resource);
+ if (!bucket.isExcluded()) {
+ if (bucket.getMeasures(MeasuresFilters.measure(measure))!=null) {
+ throw new IllegalArgumentException("This measure has already been saved: " + measure + ",resource: " + resource);
+ }
+ if (shouldPersistMeasure(resource, measure)) {
+ persistMeasure(bucket, measure);
+ }
+
+ if (measure.getPersistenceMode().useMemory()) {
+ bucket.addMeasure(measure);
+ }
+ }
+ return measure;
+ }
+
+ public Measure updateMeasure(Measure measure) {
+ if (measure.getId() == null) {
+ throw new IllegalStateException("Measure can not be updated because it has never been saved");
+ }
+
+ MeasureModel model = session.reattach(MeasureModel.class, measure.getId());
+ model = MeasureModel.build(measure, model);
+ model.setMetric(measuresDao.getMetric(measure.getMetric()));
+ model.save(session);
+ return measure;
+ }
+
+ private void persistMeasure(Bucket bucket, Measure measure) {
+ Metric metric = measuresDao.getMetric(measure.getMetric());
+ MeasureModel measureModel = MeasureModel.build(measure);
+ measureModel.setMetric(metric); // hibernate synchronized metric
+ measureModel.setSnapshotId(bucket.getSnapshotId());
+ measureModel.save(session);
+
+ // the id is saved for future updates
+ measure.setId(measureModel.getId());
+ }
+
+ private boolean shouldPersistMeasure(Resource resource, Measure measure) {
+ Metric metric = measure.getMetric();
+ return measure.getPersistenceMode().useDatabase() && !(
+ ResourceUtils.isEntity(resource) &&
+ metric.isOptimizedBestValue() == Boolean.TRUE &&
+ metric.getBestValue() != null &&
+ metric.getBestValue().equals(measure.getValue()) &&
+ !measure.hasOptionalData());
+ }
+
+
+ /* --------------- DEPENDENCIES */
+ public Dependency saveDependency(Dependency dependency) {
+ Dependency persistedDep = getEdge(dependency.getFrom(), dependency.getTo());
+ if (persistedDep != null && persistedDep.getId()!=null) {
+ return persistedDep;
+ }
+ Bucket from = getOrCreateBucket(dependency.getFrom());
+ Bucket to = getOrCreateBucket(dependency.getTo());
+
+ DependencyDto dto = new DependencyDto();
+ dto.setFromResourceId(from.getResourceId());
+ dto.setFromScope(from.getResource().getScope());
+ dto.setFromSnapshotId(from.getSnapshotId());
+ dto.setToResourceId(to.getResourceId());
+ dto.setToSnapshotId(to.getSnapshotId());
+ dto.setToScope(to.getResource().getScope());
+ dto.setProjectSnapshotId(selectedProjectBucket.getSnapshotId());
+ dto.setUsage(dependency.getUsage());
+ dto.setWeight(dependency.getWeight());
+
+ Dependency parentDependency = dependency.getParent();
+ if (parentDependency != null) {
+ saveDependency(parentDependency);
+ dto.setParentDependencyId(parentDependency.getId());
+ }
+ session.save(dto);
+ dependency.setId(dto.getId());
+ registerDependency(dependency);
+
+ return dependency;
+ }
+
+ protected void registerDependency(Dependency dependency) {
+ dependencies.add(dependency);
+ registerOutgoingDependency(dependency);
+ registerIncomingDependency(dependency);
+ }
+
+ private void registerOutgoingDependency(Dependency dependency) {
+ Map<Resource, Dependency> outgoingDeps = outgoingDependenciesByResource.get(dependency.getFrom());
+ if (outgoingDeps == null) {
+ outgoingDeps = new HashMap<Resource, Dependency>();
+ outgoingDependenciesByResource.put(dependency.getFrom(), outgoingDeps);
+ }
+ outgoingDeps.put(dependency.getTo(), dependency);
+ }
+
+ private void registerIncomingDependency(Dependency dependency) {
+ Map<Resource, Dependency> incomingDeps = incomingDependenciesByResource.get(dependency.getTo());
+ if (incomingDeps == null) {
+ incomingDeps = new HashMap<Resource, Dependency>();
+ incomingDependenciesByResource.put(dependency.getTo(), incomingDeps);
+ }
+ incomingDeps.put(dependency.getFrom(), dependency);
+ }
+
+ public Set<Dependency> getDependencies() {
+ return dependencies;
+ }
+
+
+ /* ------------ LINKS */
+
+ public void saveLink(ProjectLink link) {
+ ResourceModel projectDao = session.reattach(ResourceModel.class, selectedProjectBucket.getResourceId());
+ ProjectLink dbLink = projectDao.getProjectLink(link.getKey());
+ if (dbLink == null) {
+ link.setResource(projectDao);
+ projectDao.getProjectLinks().add(link);
+ session.save(link);
+
+ } else {
+ dbLink.copyFieldsFrom(link);
+ session.save(dbLink);
+ }
+ }
+
+ public void deleteLink(String key) {
+ ResourceModel projectDao = session.reattach(ResourceModel.class, selectedProjectBucket.getResourceId());
+ ProjectLink dbLink = projectDao.getProjectLink(key);
+ if (dbLink != null) {
+ session.remove(dbLink);
+ projectDao.getProjectLinks().remove(dbLink);
+ }
+ }
+
+
+ /* ----------- EVENTS */
+ public List<Event> getEvents(Resource resource) {
+ Bucket bucket = getOrCreateBucket(resource);
+ return session.getResults(Event.class, "resourceId", bucket.getResourceId());
+ }
+
+ public void deleteEvent(Event event) {
+ session.remove(event);
+ }
+
+ public Event createEvent(Resource resource, String name, String description, String category, Date date) {
+ Bucket bucket = getOrCreateBucket(resource);
+ Event event;
+ if (date == null) {
+ event = new Event(name, description, category, bucket.getSnapshot());
+ } else {
+ event = new Event(name, description, category, date, bucket.getResourceId());
+ }
+ return session.save(event);
+ }
+
+ public Dependency getEdge(Resource from, Resource to) {
+ Map<Resource, Dependency> map = outgoingDependenciesByResource.get(from);
+ if (map != null) {
+ return map.get(to);
+ }
+ return null;
+ }
+
+ public boolean hasEdge(Resource from, Resource to) {
+ return getEdge(from, to) != null;
+ }
+
+ public Set<Resource> getVertices() {
+ return buckets.keySet();
+ }
+
+ public Collection<Dependency> getOutgoingEdges(Resource from) {
+ Map<Resource, Dependency> deps = outgoingDependenciesByResource.get(from);
+ if (deps != null) {
+ return deps.values();
+ }
+ return Collections.emptyList();
+ }
+
+ public Collection<Dependency> getIncomingEdges(Resource to) {
+ Map<Resource, Dependency> deps = incomingDependenciesByResource.get(to);
+ if (deps != null) {
+ return deps.values();
+ }
+ return Collections.emptyList();
+ }
+
+ public Set<Dependency> getDependenciesBetweenProjects() {
+ Set<Dependency> result = new HashSet<Dependency>();
+ for (Project project : projectTree.getProjects()) {
+ Collection<Dependency> deps = getOutgoingDependencies(project);
+ for (Dependency dep : deps) {
+ if (ResourceUtils.isSet(dep.getTo())) {
+ result.add(dep);
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/indexer/LibraryPersister.java b/sonar-batch/src/main/java/org/sonar/batch/indexer/LibraryPersister.java
new file mode 100644
index 00000000000..640997e2f8f
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/indexer/LibraryPersister.java
@@ -0,0 +1,68 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Library;
+import org.sonar.api.resources.Resource;
+
+import java.util.Date;
+
+public class LibraryPersister extends ResourcePersister<Library> {
+
+ private Date now;
+
+ public LibraryPersister(DatabaseSession session) {
+ super(session);
+ this.now = new Date();
+ }
+
+ LibraryPersister(DatabaseSession session, Date now) {
+ super(session);
+ this.now = now;
+ }
+
+ @Override
+ protected String generateEffectiveKey(Bucket<Library> bucket) {
+ return bucket.getResource().getKey();
+ }
+
+ @Override
+ protected void prepareResourceModel(ResourceModel resourceModel, Bucket<Library> bucket) {
+ }
+
+ @Override
+ protected Snapshot createSnapshot(Bucket<Library> bucket, ResourceModel resourceModel) {
+ Snapshot snapshot = getSession().getSingleResult(Snapshot.class,
+ "resourceId", resourceModel.getId(),
+ "version", bucket.getResource().getVersion(),
+ "scope", Resource.SCOPE_SET,
+ "qualifier", Resource.QUALIFIER_LIB);
+ if (snapshot == null) {
+ snapshot = new Snapshot(resourceModel, null);
+ snapshot.setCreatedAt(now);
+ snapshot.setVersion(bucket.getResource().getVersion());
+ snapshot.setStatus(Snapshot.STATUS_PROCESSED);
+ }
+ return snapshot;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/indexer/ProjectPersister.java b/sonar-batch/src/main/java/org/sonar/batch/indexer/ProjectPersister.java
new file mode 100644
index 00000000000..821910b4538
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/indexer/ProjectPersister.java
@@ -0,0 +1,58 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Project;
+
+public class ProjectPersister extends ResourcePersister<Project> {
+
+ public ProjectPersister(DatabaseSession session) {
+ super(session);
+ }
+
+ @Override
+ protected String generateEffectiveKey(Bucket<Project> bucket) {
+ return bucket.getResource().getKey();
+ }
+
+ @Override
+ protected void prepareResourceModel(ResourceModel resourceModel, Bucket<Project> bucket) {
+ if (bucket.getProject() != null) {
+ resourceModel.setRootId(bucket.getProject().getResourceId());
+ }
+
+ // LIMITATION : project.getLanguage() is set only in ProjectBatch, not in AggregatorBatch, so we
+ // have to explicitely use project.getLanguageKey()
+ resourceModel.setLanguageKey(bucket.getResource().getLanguageKey());
+ }
+
+ @Override
+ protected Snapshot createSnapshot(Bucket<Project> bucket, ResourceModel resourceModel) {
+ Project project = bucket.getResource();
+ Snapshot parentSnapshot = (bucket.getParent() != null ? bucket.getParent().getSnapshot() : null);
+ Snapshot snapshot = new Snapshot(resourceModel, parentSnapshot);
+ snapshot.setVersion(project.getAnalysisVersion());
+ snapshot.setCreatedAt(project.getAnalysisDate());
+ return snapshot;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersister.java b/sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersister.java
new file mode 100644
index 00000000000..6ad3be1344d
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersister.java
@@ -0,0 +1,96 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import javax.persistence.NonUniqueResultException;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.utils.SonarException;
+
+public abstract class ResourcePersister<RESOURCE extends Resource> {
+
+ private DatabaseSession session;
+
+ public ResourcePersister(DatabaseSession session) {
+ this.session = session;
+ }
+
+ protected DatabaseSession getSession() {
+ return session;
+ }
+
+ public final void persist(Bucket<RESOURCE> bucket) {
+ String effectiveKey = generateEffectiveKey(bucket);
+ ResourceModel model;
+ try {
+ model = session.getSingleResult(ResourceModel.class, "key", effectiveKey);
+ } catch (NonUniqueResultException e) {
+ throw new SonarException("The resource '" + effectiveKey + "' is duplicated in the Sonar DB.");
+ }
+
+ RESOURCE resource = bucket.getResource();
+ if (model == null) {
+ model = ResourceModel.build(resource);
+ model.setKey(effectiveKey);
+
+ } else {
+ // update existing record
+ model.setEnabled(true);
+ if (StringUtils.isNotBlank(resource.getName())) {
+ model.setName(resource.getName());
+ }
+ if (StringUtils.isNotBlank(resource.getLongName())) {
+ model.setLongName(resource.getLongName());
+ }
+ if (StringUtils.isNotBlank(resource.getDescription())) {
+ model.setDescription(resource.getDescription());
+ }
+ if ( !ResourceUtils.isLibrary(resource)) {
+ model.setScope(resource.getScope());
+ model.setQualifier(resource.getQualifier());
+ }
+ if (resource.getLanguage() != null) {
+ model.setLanguageKey(resource.getLanguage().getKey());
+ }
+ }
+
+ prepareResourceModel(model, bucket);
+ model = session.save(model);
+ resource.setId(model.getId());
+ resource.setEffectiveKey(model.getKey());
+
+ Snapshot snapshot = createSnapshot(bucket, model);
+ if (snapshot.getId() == null) {
+ session.save(snapshot);
+ }
+ bucket.setSnapshot(snapshot);
+ }
+
+ protected abstract void prepareResourceModel(ResourceModel resourceModel, Bucket<RESOURCE> bucket);
+
+ protected abstract Snapshot createSnapshot(Bucket<RESOURCE> bucket, ResourceModel resourceModel);
+
+ protected abstract String generateEffectiveKey(Bucket<RESOURCE> bucket);
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersisters.java b/sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersisters.java
new file mode 100644
index 00000000000..f359a0ae623
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/indexer/ResourcePersisters.java
@@ -0,0 +1,50 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.resources.Library;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class ResourcePersisters {
+
+ private Map<Class<? extends Resource>, ResourcePersister> persistersByClass;
+ private ResourcePersister defaultPersister;
+
+ public ResourcePersisters(DatabaseSession session) {
+ defaultPersister = new DefaultPersister(session);
+ persistersByClass = new HashMap<Class<? extends Resource>, ResourcePersister>();
+ persistersByClass.put(Project.class, new ProjectPersister(session));
+ persistersByClass.put(Library.class, new LibraryPersister(session));
+ }
+
+ public ResourcePersister get(Bucket bucket) {
+ return get(bucket.getResource());
+ }
+
+ public ResourcePersister get(Resource resource) {
+ ResourcePersister persister = persistersByClass.get(resource.getClass());
+ return persister != null ? persister : defaultPersister;
+ }
+}
diff --git a/sonar-batch/src/main/resources/org/sonar/batch/logback.xml b/sonar-batch/src/main/resources/org/sonar/batch/logback.xml
new file mode 100644
index 00000000000..cd79fb299d6
--- /dev/null
+++ b/sonar-batch/src/main/resources/org/sonar/batch/logback.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<configuration debug="false">
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <layout class="ch.qos.logback.classic.PatternLayout">
+ <pattern>[%level] %X{module} %msg%n</pattern>
+ </layout>
+ </appender>
+
+ <logger name="org.hibernate">
+ <level value="WARN"/>
+ </logger>
+
+ <!-- set INFO to activate SQL logs. NOT RECOMMENDED -->
+ <logger name="org.hibernate.SQL">
+ <level value="ERROR"/>
+ </logger>
+
+ <!-- set INFO to activate SQL statistics. NOT RECOMMENDED -->
+ <logger name="org.sonar.DBSTATISTICS">
+ <level value="ERROR"/>
+ </logger>
+
+ <logger name="net.sf.ehcache">
+ <level value="WARN"/>
+ </logger>
+
+ <logger name="org.hibernate.cache.ReadWriteCache">
+ <!-- removing "An item was expired by the cache while it was locked (increase your cache timeout)" msg -->
+ <level value="ERROR"/>
+ </logger>
+ <logger name="org.hibernate.cache.EhCacheProvider">
+ <!-- removing "org.hibernate.cache.EhCacheProvider - Could not find configuratio)" message -->
+ <level value="ERROR"/>
+ </logger>
+
+ <!-- see org.sonar.mojo.InternalMojo#initLogging -->
+ <root>
+ <level value="${ROOT_LOGGER_LEVEL}"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+
+</configuration> \ No newline at end of file
diff --git a/sonar-batch/src/test/java/org/sonar/batch/CoreJobsTest.java b/sonar-batch/src/test/java/org/sonar/batch/CoreJobsTest.java
new file mode 100644
index 00000000000..25c2e046e47
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/CoreJobsTest.java
@@ -0,0 +1,29 @@
+package org.sonar.batch;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+import java.util.List;
+
+public class CoreJobsTest {
+
+ @Test
+ public void mavenPluginsAreExecutedAfterBeingConfigured() {
+ List<Class<? extends CoreJob>> jobs = CoreJobs.allJobs();
+ assertThat(jobs.indexOf(FinalizeSnapshotsJob.class),
+ greaterThan(jobs.indexOf(DecoratorsExecutor.class)));
+ }
+
+ @Test
+ public void finalizeJobIsExecutedAfterDecorators() {
+ List<Class<? extends CoreJob>> jobs = CoreJobs.allJobs();
+ assertThat(jobs.indexOf(FinalizeSnapshotsJob.class),
+ greaterThan(jobs.indexOf(DecoratorsExecutor.class)));
+ }
+
+ @Test
+ public void allJobs() {
+ assertThat(CoreJobs.allJobs().size(), greaterThan(3));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java b/sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java
new file mode 100644
index 00000000000..90edc924f10
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java
@@ -0,0 +1,114 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import static org.junit.internal.matchers.IsCollectionContaining.hasItem;
+import static org.mockito.Mockito.mock;
+import org.picocontainer.containers.TransientPicoContainer;
+import org.sonar.api.batch.*;
+import org.sonar.api.measures.*;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class DecoratorsSelectorTest {
+
+ private Metric withFormula1 = new Metric("metric1").setFormula(new FakeFormula());
+ private Metric withFormula2 = new Metric("metric2").setFormula(new FakeFormula());
+ private Metric withoutFormula = new Metric("metric3");
+
+ @Test
+ public void selectAndSortFormulas() {
+ Project project = new Project("key");
+ BatchExtensionDictionnary dictionnary = newDictionnary(withFormula1, withoutFormula, withFormula2);
+
+ Collection<Decorator> decorators = new DecoratorsSelector(dictionnary).select(project);
+ assertThat(decorators.size(), is(2));
+ assertThat(decorators, hasItem((Decorator) new FormulaDecorator(withFormula1)));
+ assertThat(decorators, hasItem((Decorator) new FormulaDecorator(withFormula2)));
+ }
+
+ @Test
+ public void pluginDecoratorsCanOverrideFormulas() {
+ Project project = new Project("key");
+ Decorator fakeDecorator = new FakeDecorator();
+ Decorator metric1Decorator = new Metric1Decorator();
+ BatchExtensionDictionnary dictionnary = newDictionnary(fakeDecorator, metric1Decorator, withFormula1, withoutFormula, withFormula2);
+
+ Collection<Decorator> decorators = new DecoratorsSelector(dictionnary).select(project);
+
+ assertThat(decorators.size(), is(3));
+ assertThat(decorators, hasItem(fakeDecorator));
+ assertThat(decorators, hasItem(metric1Decorator));
+ assertThat(decorators, hasItem((Decorator) new FormulaDecorator(withFormula2)));
+ }
+
+ private BatchExtensionDictionnary newDictionnary(Object... extensions) {
+ TransientPicoContainer ioc = new TransientPicoContainer();
+ int index = 0;
+ for (Object extension : extensions) {
+ ioc.addComponent("" + index, extension);
+ index++;
+ }
+ return new BatchExtensionDictionnary(ioc);
+ }
+
+
+ class FakeFormula implements Formula {
+ public List<Metric> dependsUponMetrics() {
+ return Arrays.asList();
+ }
+
+ public Measure calculate(FormulaData data, FormulaContext context) {
+ return null;
+ }
+ }
+
+ public class Metric1Decorator implements Decorator {
+ @DependedUpon
+ public Metric generatesMetric1Measure() {
+ return withFormula1;
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+ }
+
+ public class FakeDecorator implements Decorator {
+ public void decorate(Resource resource, DecoratorContext context) {
+
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/DefaultSensorContextTest.java b/sonar-batch/src/test/java/org/sonar/batch/DefaultSensorContextTest.java
new file mode 100644
index 00000000000..dba2db3e375
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/DefaultSensorContextTest.java
@@ -0,0 +1,244 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.design.Dependency;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.PersistenceMode;
+import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.JavaPackage;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import javax.persistence.Query;
+import java.text.ParseException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+@Ignore
+public class DefaultSensorContextTest extends AbstractDbUnitTestCase {
+ private DefaultSensorContext context;
+ private Project project;
+
+ @Before
+ public void before() {
+ project = null;
+ context = null;
+ }
+
+ @Test
+ public void saveProjectMeasure() throws ParseException {
+ setup("saveProjectMeasure");
+ context.saveMeasure(CoreMetrics.NCLOC, 500.0);
+ check("saveProjectMeasure", "projects", "snapshots", "project_measures");
+ }
+
+ @Test
+ public void saveMeasureOnExistingResource() throws ParseException {
+ setup("saveMeasureOnExistingResource");
+ context.saveMeasure(new JavaPackage("org.sonar"), CoreMetrics.NCLOC, 200.0);
+ check("saveMeasureOnExistingResource", "projects", "snapshots", "project_measures");
+ }
+
+ @Test
+ public void avoidConflictWithResourceFromOtherProject() throws ParseException {
+ setup("avoidConflictWithResourceFromOtherProject");
+ context.saveMeasure(new JavaPackage("org.sonar"), CoreMetrics.NCLOC, 200.0);
+ context.saveMeasure(new JavaPackage("org.sonar"), CoreMetrics.COVERAGE, 80.0);
+ check("avoidConflictWithResourceFromOtherProject", "projects", "snapshots", "project_measures");
+ }
+
+ @Test
+ public void doNotPersistInMemoryMeasures() throws ParseException {
+ setup("doNotPersistInMemoryMeasures");
+ Measure measure = new Measure(CoreMetrics.NCLOC, 30.0).setPersistenceMode(PersistenceMode.MEMORY);
+ context.saveMeasure(measure);
+
+ check("doNotPersistInMemoryMeasures", "projects", "snapshots", "project_measures");
+ assertThat(context.getMeasure(CoreMetrics.NCLOC).getValue(), is(30.0));
+ }
+
+ @Test
+ public void doNotCacheDatabaseMeasures() throws ParseException {
+ setup("doNotCacheDatabaseMeasures");
+ Measure measure = new Measure(CoreMetrics.NCLOC, 500.0).setPersistenceMode(PersistenceMode.DATABASE);
+ context.saveMeasure(measure);
+
+ check("doNotCacheDatabaseMeasures", "projects", "snapshots", "project_measures");
+ assertThat(context.getMeasure(CoreMetrics.NCLOC), nullValue());
+ }
+
+ @Test
+ public void saveRuleMeasures() throws ParseException {
+ setup("saveRuleMeasures");
+ context.saveMeasure(RuleMeasure.createForPriority(CoreMetrics.VIOLATIONS, RulePriority.CRITICAL, 500.0));
+ context.saveMeasure(RuleMeasure.createForCategory(CoreMetrics.VIOLATIONS, 3, 200.0));
+ //FIXME context.saveMeasure(RuleMeasure.createForRule(CoreMetrics.VIOLATIONS, 3).setIntValue(50.0));
+ check("saveRuleMeasures", "projects", "snapshots", "project_measures");
+ }
+
+ @Test
+ public void saveResourceTree() throws ParseException {
+// setup("saveResourceTree");
+//
+// assertThat(context.getResource("org.foo.Bar"), nullValue());
+// context.saveResource(new JavaFile("org.foo.Bar"));
+// assertThat(context.getResource("org.foo.Bar"), is((Resource) new JavaFile("org.foo.Bar")));
+//
+// check("saveResourceTree", "projects", "snapshots");
+ }
+//
+// @Test
+// public void doNotSaveExcludedResources() throws ParseException {
+// setup("doNotSaveExcludedResources");
+//
+// JavaFile javaFile = new JavaFile("org.excluded.Bar");
+// ResourceFilters resourceFilters = mock(ResourceFilters.class);
+// when(resourceFilters.isExcluded(javaFile)).thenReturn(true);
+// context.setResourceFilters(resourceFilters);
+//
+// assertThat(context.getResource("org.excluded.Bar"), nullValue());
+// assertThat(context.saveResource(javaFile), nullValue());
+// assertThat(context.getResource("org.excluded.Bar"), nullValue());
+//
+// check("doNotSaveExcludedResources", "projects", "snapshots");
+// }
+
+ @Test
+ public void updateExistingResourceFields() throws ParseException {
+ setup("updateExistingResourceFields");
+
+ context.saveResource(new JavaPackage("org.foo"));
+
+ check("updateExistingResourceFields", "projects", "snapshots");
+ }
+
+ @Test
+ public void doNotSaveOptimizedBestValues() throws ParseException {
+ setup("doNotSaveOptimizedBestValues");
+
+ // best values of the metrics violations and blocker_violations are set as optimized
+ assertThat(CoreMetrics.VIOLATIONS.getBestValue(), is(0.0));
+ assertThat(CoreMetrics.BLOCKER_VIOLATIONS.getBestValue(), is(0.0));
+ assertThat(CoreMetrics.VIOLATIONS.isOptimizedBestValue(), is(true));
+ assertThat(CoreMetrics.BLOCKER_VIOLATIONS.isOptimizedBestValue(), is(true));
+
+ final Resource javaFile = new JavaFile("org.foo.Bar");
+ assertThat(context.getMeasure(javaFile, CoreMetrics.VIOLATIONS), nullValue());
+ context.saveMeasure(javaFile, CoreMetrics.VIOLATIONS, 60.0); // saved
+ assertThat(context.getMeasure(javaFile, CoreMetrics.VIOLATIONS).getValue(), is(60.0));
+
+ assertThat(context.getMeasure(javaFile, CoreMetrics.BLOCKER_VIOLATIONS), nullValue());
+ context.saveMeasure(javaFile, CoreMetrics.BLOCKER_VIOLATIONS, 0.0); // not saved in database
+ assertThat(context.getMeasure(javaFile, CoreMetrics.BLOCKER_VIOLATIONS).getValue(), is(0.0));
+
+ check("doNotSaveOptimizedBestValues", "projects", "snapshots", "project_measures");
+ }
+
+ @Test
+ public void saveOptimizedBestValuesIfOptionalFields() throws ParseException {
+ setup("saveOptimizedBestValuesIfOptionalFields");
+
+ // best value of the metric violations is set as optimized
+ assertThat(CoreMetrics.VIOLATIONS.getBestValue(), is(0.0));
+ assertThat(CoreMetrics.VIOLATIONS.isOptimizedBestValue(), is(true));
+
+ final Resource javaFile = new JavaFile("org.foo.Bar");
+ assertThat(context.getMeasure(javaFile, CoreMetrics.VIOLATIONS), nullValue());
+ Measure measure = new Measure(CoreMetrics.VIOLATIONS, 0.0).setTendency(1);
+
+ context.saveMeasure(javaFile, measure); // saved
+
+ assertThat(context.getMeasure(javaFile, CoreMetrics.VIOLATIONS).getValue(), is(0.0));
+ assertThat(context.getMeasure(javaFile, CoreMetrics.VIOLATIONS).getTendency(), is(1));
+
+ check("saveOptimizedBestValuesIfOptionalFields", "projects", "snapshots", "project_measures");
+ }
+
+
+ @Test
+ public void saveDependency() throws ParseException {
+ setup("saveDependency");
+
+ JavaPackage pac1 = new JavaPackage("org.sonar.source");
+ JavaPackage pac2 = new JavaPackage("org.sonar.target");
+ context.saveResource(pac1);
+ context.saveResource(pac2);
+
+ Dependency dep = new Dependency(pac1, pac2)
+ .setUsage("INHERITS")
+ .setWeight(3);
+ context.saveDependency(dep);
+
+ assertThat(dep.getId(), not(nullValue()));
+
+ check("saveDependency", "projects", "snapshots", "dependencies");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void saveResourcesBeforeBuildingDependencies() throws ParseException {
+ setup("saveResourcesBeforeBuildingDependencies");
+
+ JavaPackage pac1 = new JavaPackage("org.sonar.source");
+ JavaPackage pac2 = new JavaPackage("org.sonar.target");
+ context.saveDependency(new Dependency(pac1, pac2));
+ }
+
+
+ private void setup(String unitTest) throws ParseException {
+// setupData(unitTest);
+// project = mock(Project.class);
+// when(project.getAnalysisVersion()).thenReturn("1.0");
+// when(project.getAnalysisDate()).thenReturn(new SimpleDateFormat("yyyy-MM-dd").parse("2008-12-25"));
+// when(project.getKey()).thenReturn("group:artifact");
+// when(project.getScope()).thenReturn(Resource.SCOPE_SET);
+// when(project.getQualifier()).thenReturn(Resource.QUALIFIER_PROJECT);
+// when(project.getLanguage()).thenReturn(Java.INSTANCE);
+// when(project.getId()).thenReturn(10);
+// when(project.getName()).thenReturn("my project");
+// when(project.isRoot()).thenReturn(true);
+// ProjectBootstrap projectBootstrap = new ProjectBootstrap(null);
+// projectBootstrap.setProject(project);
+// projectBootstrap.setSnapshot(getSnapshot(1));
+// context = new DefaultSensorContext(getSession(), projectBootstrap.setProject(project), getDao().getMeasuresDao(), null, null, null);
+ }
+
+ private void check(String unitTest, String... tables) {
+ getSession().commit();
+ checkTables(unitTest, tables);
+ }
+
+ private Snapshot getSnapshot(int id) {
+ Query query = getSession().createQuery("SELECT s FROM Snapshot s WHERE s.id=:id");
+ query.setParameter("id", id);
+ return (Snapshot) query.getSingleResult();
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/DefaultTimeMachineTest.java b/sonar-batch/src/test/java/org/sonar/batch/DefaultTimeMachineTest.java
new file mode 100644
index 00000000000..8e097ddccc3
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/DefaultTimeMachineTest.java
@@ -0,0 +1,108 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.junit.Test;
+import org.sonar.api.batch.TimeMachineQuery;
+import org.sonar.jpa.dao.MeasuresDao;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.batch.indexer.DefaultSonarIndex;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.number.OrderingComparisons.greaterThan;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DefaultTimeMachineTest extends AbstractDbUnitTestCase {
+
+ @Test(timeout = 3000)
+ public void loadMeasureFieldsFromDate() throws ParseException {
+ setupData("loadMeasuresFromDate");
+ DefaultTimeMachine timeMachine = initTimeMachine();
+
+ TimeMachineQuery query = new TimeMachineQuery(null).setFrom(date("2008-02-01")).setMetrics(Arrays.asList(CoreMetrics.NCLOC));
+ List<Object[]> measures = timeMachine.getMeasuresFields(query);
+
+ assertThat(measures.size(), is(3));
+ for (Object[] measure : measures) {
+ assertThat(measure.length, is(3)); // 3 fields
+ assertThat(measure[1], is((Object) CoreMetrics.NCLOC));
+ }
+ assertThat(measures.get(0)[2], is((Object) 200d));
+ assertThat(measures.get(1)[2], is((Object) 230d));
+ assertThat(measures.get(2)[2], is((Object) 180d));
+ }
+
+ private DefaultTimeMachine initTimeMachine() {
+ DefaultSonarIndex index = mock(DefaultSonarIndex.class);
+ when(index.getResource((Resource) anyObject())).thenReturn(new Project("group:artifact").setId(1));
+ DefaultTimeMachine timeMachine = new DefaultTimeMachine(getSession(), index, new MeasuresDao(getSession()));
+ return timeMachine;
+ }
+
+ @Test(timeout = 3000)
+ public void loadMeasuresFromDate() throws ParseException {
+ setupData("loadMeasuresFromDate");
+ DefaultTimeMachine timeMachine = initTimeMachine();
+
+
+ TimeMachineQuery query = new TimeMachineQuery(null).setFrom(date("2008-02-01")).setMetrics(Arrays.asList(CoreMetrics.NCLOC));
+ List<Measure> measures = timeMachine.getMeasures(query);
+
+ assertThat(measures.size(), is(3));
+ long previous = 0;
+ for (Measure measure : measures) {
+ assertThat(measure.getMetric(), is(CoreMetrics.NCLOC));
+ assertThat(measure.getDate().getTime(), greaterThan(previous));
+ previous = measure.getDate().getTime();
+ }
+ assertThat(measures.get(0).getValue(), is(200d));
+ assertThat(measures.get(1).getValue(), is(230d));
+ assertThat(measures.get(2).getValue(), is(180d));
+ }
+
+ @Test(timeout = 3000)
+ public void loadMeasuresFromDateInterval() throws ParseException {
+ setupData("loadMeasuresFromDate");
+ DefaultTimeMachine timeMachine = initTimeMachine();
+
+
+ TimeMachineQuery query = new TimeMachineQuery(null).setFrom(date("2008-01-01")).setTo(date("2008-12-25")).setMetrics(Arrays.asList(CoreMetrics.NCLOC));
+ List<Measure> measures = timeMachine.getMeasures(query);
+ assertThat(measures.size(), is(1));
+ assertThat(measures.get(0).getValue(), is(200d));
+ }
+
+ private Date date(String date) throws ParseException {
+ return new SimpleDateFormat("yyyy-MM-dd").parse(date);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/FinalizeSnapshotsJobTest.java b/sonar-batch/src/test/java/org/sonar/batch/FinalizeSnapshotsJobTest.java
new file mode 100644
index 00000000000..9158839ea1a
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/FinalizeSnapshotsJobTest.java
@@ -0,0 +1,78 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.Purge;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Project;
+import org.sonar.core.purge.DefaultPurgeContext;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import javax.persistence.Query;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class FinalizeSnapshotsJobTest extends AbstractDbUnitTestCase {
+
+ private Purge purgeMock=null;
+
+ @Before
+ public void before() {
+ purgeMock = mock(Purge.class);
+ }
+
+ @Test
+ public void shouldUnflagPenultimateLastSnapshot() throws Exception {
+ assertAnalysis(11, "shouldUnflagPenultimateLastSnapshot");
+ verify(purgeMock).purge(new DefaultPurgeContext(11, 1));
+ }
+
+ @Test
+ public void doNotFailIfNoPenultimateLast() throws Exception {
+ assertAnalysis(5, "doNotFailIfNoPenultimateLast");
+ verify(purgeMock).purge(new DefaultPurgeContext(5, null));
+ }
+
+ @Test
+ public void lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot() {
+ assertAnalysis(6, "lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot");
+ verify(purgeMock).purge(new DefaultPurgeContext(6, null));
+ }
+
+ private void assertAnalysis(int snapshotId, String fixture) {
+ setupData("sharedFixture", fixture);
+
+ FinalizeSnapshotsJob sensor = new FinalizeSnapshotsJob(mock(ServerMetadata.class), getSession(), new Purge[]{purgeMock}, loadSnapshot(snapshotId));
+ sensor.execute(new Project("key"), null);
+
+ getSession().stop();
+ checkTables(fixture, "snapshots");
+ }
+
+
+ private Snapshot loadSnapshot(int id) {
+ Query query = getSession().createQuery("SELECT s FROM Snapshot s WHERE s.id=:id");
+ query.setParameter("id", id);
+ return (Snapshot) query.getSingleResult();
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/MavenPhaseExecutorTest.java b/sonar-batch/src/test/java/org/sonar/batch/MavenPhaseExecutorTest.java
new file mode 100644
index 00000000000..77d1e333e9c
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/MavenPhaseExecutorTest.java
@@ -0,0 +1,58 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Test;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.*;
+
+public class MavenPhaseExecutorTest {
+
+ @Test
+ public void doNothingIfNoPhase() {
+ MavenPluginExecutor mavenPluginExecutor = mock(MavenPluginExecutor.class);
+ MavenPhaseExecutor phaseExecutor = new MavenPhaseExecutor(mavenPluginExecutor);
+
+
+ Project project = new Project("key");
+ phaseExecutor.execute(project, mock(SensorContext.class));
+
+ verify(mavenPluginExecutor, never()).execute(eq(project), anyString());
+ }
+
+ @Test
+ public void executePhase() {
+ MavenPluginExecutor mavenPluginExecutor = mock(MavenPluginExecutor.class);
+ MavenPhaseExecutor phaseExecutor = new MavenPhaseExecutor(mavenPluginExecutor);
+
+ Project project = new Project("key");
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty(MavenPhaseExecutor.PROP_PHASE, "myphase");
+ project.setConfiguration(conf);
+
+ phaseExecutor.execute(project, mock(SensorContext.class));
+
+ verify(mavenPluginExecutor).execute(project, "myphase");
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/MavenPluginsConfiguratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/MavenPluginsConfiguratorTest.java
new file mode 100644
index 00000000000..aea7b42c6cb
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/MavenPluginsConfiguratorTest.java
@@ -0,0 +1,77 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Test;
+import org.sonar.api.batch.BatchExtensionDictionnary;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.maven.MavenPlugin;
+import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.resources.Project;
+import org.sonar.api.test.MavenTestUtils;
+import org.sonar.api.utils.ServerHttpClient;
+
+import java.util.Arrays;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+
+public class MavenPluginsConfiguratorTest {
+
+ private MavenPluginsConfigurator newConfigurator(MavenPluginHandler... handlers) {
+ BatchExtensionDictionnary selector = mock(BatchExtensionDictionnary.class);
+ when(selector.selectMavenPluginHandlers((Project) anyObject())).thenReturn(Arrays.asList(handlers));
+ return new MavenPluginsConfigurator(selector);
+ }
+
+ @Test
+ public void configureHandlers() {
+ MavenPluginHandler handler1 = mock(MavenPluginHandler.class);
+ when(handler1.getArtifactId()).thenReturn("myartifact1");
+
+ MavenPluginHandler handler2 = mock(MavenPluginHandler.class);
+ when(handler2.getArtifactId()).thenReturn("myartifact2");
+
+ Project project = MavenTestUtils.loadProjectFromPom(getClass(), "pom.xml");
+
+ newConfigurator(handler1, handler2).execute(project, mock(SensorContext.class));
+
+ verify(handler1).configure(eq(project), argThat(new IsMavenPlugin("myartifact1")));
+ verify(handler2).configure(eq(project), argThat(new IsMavenPlugin("myartifact2")));
+ }
+
+ private class IsMavenPlugin extends BaseMatcher<MavenPlugin> {
+ private String artifactId;
+
+ public IsMavenPlugin(String artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ public boolean matches(Object o) {
+ return artifactId.equals(((MavenPlugin) o).getPlugin().getArtifactId());
+ }
+
+ public void describeTo(Description description) {
+
+ }
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/PostJobsExecutorTest.java b/sonar-batch/src/test/java/org/sonar/batch/PostJobsExecutorTest.java
new file mode 100644
index 00000000000..6a1d72cbc58
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/PostJobsExecutorTest.java
@@ -0,0 +1,66 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.junit.Test;
+import org.sonar.api.batch.PostJob;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.*;
+
+public class PostJobsExecutorTest {
+
+ @Test
+ public void doNotExecuteOnModules() {
+ PostJob job1 = mock(PostJob.class);
+ Project module = new Project("module").setParent(new Project("project"));
+
+ PostJobsExecutor executor = new PostJobsExecutor(Arrays.<PostJob>asList(job1), mock(MavenPluginExecutor.class));
+ executor.execute(module, mock(SensorContext.class));
+
+ verify(job1, never()).executeOn((Project) anyObject(), (SensorContext) anyObject());
+ }
+
+ @Test
+ public void executeAllPostJobs() {
+ PostJob job1 = mock(PostJob.class);
+ PostJob job2 = mock(PostJob.class);
+ List<PostJob> jobs = Arrays.asList(job1, job2);
+
+ PostJobsExecutor executor = new PostJobsExecutor(jobs, mock(MavenPluginExecutor.class));
+ Project project = new Project("project");
+ SensorContext context = mock(SensorContext.class);
+ executor.execute(project, context);
+
+ verify(job1).executeOn(project, context);
+ verify(job2).executeOn(project, context);
+
+ }
+
+ static class FakePostJob implements PostJob {
+ public void executeOn(Project project, SensorContext context) {
+ }
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/ProfileProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/ProfileProviderTest.java
new file mode 100644
index 00000000000..bdefef5c6c8
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/ProfileProviderTest.java
@@ -0,0 +1,95 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.MapConfiguration;
+import org.junit.Test;
+import org.sonar.jpa.dao.ProfilesDao;
+import org.sonar.api.profiles.Alert;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rules.ActiveRule;
+
+import java.util.Collections;
+import java.util.HashMap;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.*;
+
+public class ProfileProviderTest {
+
+ @Test
+ public void shouldGetProjectProfile() {
+ ProfileProvider provider = new ProfileProvider();
+ Project project = new Project("project").setLanguageKey(Java.KEY);
+ Project module = new Project("module").setParent(project).setLanguageKey(Java.KEY);
+ ProfilesDao dao = mock(ProfilesDao.class);
+
+ when(dao.getActiveProfile(Java.KEY, "project")).thenReturn(newProfile());
+
+ assertNotNull(provider.provide(module, dao));
+
+ verify(dao, never()).getActiveProfile(Java.KEY, "module");
+ verify(dao).getActiveProfile(Java.KEY, "project");
+ }
+
+ private RulesProfile newProfile() {
+ RulesProfile profile = new RulesProfile();
+ profile.setAlerts(Collections.<Alert>emptyList());
+ profile.setActiveRules(Collections.<ActiveRule>emptyList());
+ return profile;
+ }
+
+ @Test
+ public void mavenPropertyShouldOverrideProfile() {
+ ProfileProvider provider = new ProfileProvider();
+ ProfilesDao dao = mock(ProfilesDao.class);
+ Project project = new Project("project").setLanguageKey(Java.KEY);
+
+ MapConfiguration conf = new MapConfiguration(new HashMap());
+ conf.addProperty(ProfileProvider.PARAM_PROFILE, "profile1");
+ project.setConfiguration(conf);
+
+ when(dao.getProfile(Java.KEY, "profile1")).thenReturn(newProfile());
+
+ provider.provide(project, dao);
+
+ verify(dao).getProfile(Java.KEY, "profile1");
+ verify(dao, never()).getActiveProfile(Java.KEY, "project");
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void shouldFailIfProfileIsNotFound() {
+ ProfileProvider provider = new ProfileProvider();
+ Project project = new Project("project").setLanguageKey(Java.KEY);
+ ProfilesDao dao = mock(ProfilesDao.class);
+
+ MapConfiguration conf = new MapConfiguration(new HashMap());
+ conf.addProperty(ProfileProvider.PARAM_PROFILE, "unknown");
+ project.setConfiguration(conf);
+
+
+ when(dao.getProfile(Java.KEY, "profile1")).thenReturn(null);
+
+ provider.provide(project, dao);
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/ProjectBuilderTest.java b/sonar-batch/src/test/java/org/sonar/batch/ProjectBuilderTest.java
new file mode 100644
index 00000000000..586edaa9300
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/ProjectBuilderTest.java
@@ -0,0 +1,210 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.Project;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ProjectBuilderTest extends AbstractDbUnitTestCase {
+
+ private ProjectBuilder builder = null;
+
+ @Before
+ public void before() {
+ builder = new ProjectBuilder(getSession());
+ }
+
+ @Test
+ public void noExclusionPatterns() {
+ Project project = new Project("key");
+ builder.configure(project, new PropertiesConfiguration());
+
+ assertThat(project.getExclusionPatterns().length, is(0));
+ }
+
+ @Test
+ public void manyExclusionPatterns() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "**/*,foo,*/bar");
+
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+
+ assertThat(project.getExclusionPatterns().length, is(3));
+ assertThat(project.getExclusionPatterns()[0], is("**/*"));
+ assertThat(project.getExclusionPatterns()[1], is("foo"));
+ assertThat(project.getExclusionPatterns()[2], is("*/bar"));
+ }
+
+ @Test
+ public void getLanguageFromConfiguration() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "foo");
+
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+
+ assertThat(project.getLanguageKey(), is("foo"));
+ }
+
+ @Test
+ public void defaultLanguageIsJava() {
+ Project project = new Project("key");
+ builder.configure(project, new PropertiesConfiguration());
+
+ assertThat(project.getLanguageKey(), is(Java.KEY));
+ }
+
+ @Test
+ public void analysisIsTodayByDefault() {
+ Project project = new Project("key");
+ builder.configure(project, new PropertiesConfiguration());
+ Date today = new Date();
+ assertTrue(today.getTime() - project.getAnalysisDate().getTime() < 1000);
+ }
+
+ @Test
+ public void analysisDateCouldBeExplicitlySet() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2005-01-30");
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+
+ assertEquals("30012005", new SimpleDateFormat("ddMMyyyy").format(project.getAnalysisDate()));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void failIfAnalyisDateIsNotValid() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2005/30/01");
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+
+ project.getAnalysisDate();
+ }
+
+ @Test
+ public void sonarLightIsDeprecated() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty("sonar.light", "true");
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+
+ assertThat(project.getAnalysisType(), is(Project.AnalysisType.STATIC));
+ }
+
+ @Test
+ public void defaultAnalysisTypeIsDynamic() {
+ Project project = new Project("key");
+ builder.configure(project, new PropertiesConfiguration());
+ assertThat(project.getAnalysisType(), is(Project.AnalysisType.DYNAMIC));
+ }
+
+ @Test
+ public void explicitDynamicAnalysis() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY, "true");
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+ assertThat(project.getAnalysisType(), is(Project.AnalysisType.DYNAMIC));
+ }
+
+ @Test
+ public void explicitStaticAnalysis() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY, "false");
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+ assertThat(project.getAnalysisType(), is(Project.AnalysisType.STATIC));
+ }
+
+ @Test
+ public void explicitDynamicAnalysisReusingReports() {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY, "reuseReports");
+ Project project = new Project("key");
+ builder.configure(project, configuration);
+ assertThat(project.getAnalysisType(), is(Project.AnalysisType.REUSE_REPORTS));
+ }
+
+ @Test
+ public void isDynamicAnalysis() {
+ assertThat(Project.AnalysisType.DYNAMIC.isDynamic(false), is(true));
+ assertThat(Project.AnalysisType.DYNAMIC.isDynamic(true), is(true));
+
+ assertThat(Project.AnalysisType.STATIC.isDynamic(false), is(false));
+ assertThat(Project.AnalysisType.STATIC.isDynamic(true), is(false));
+
+ assertThat(Project.AnalysisType.REUSE_REPORTS.isDynamic(false), is(false));
+ assertThat(Project.AnalysisType.REUSE_REPORTS.isDynamic(true), is(true));
+ }
+
+ @Test
+ public void isLatestAnalysis() {
+ setupData("isLatestAnalysis");
+
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2010-12-25");
+
+ Project project = new Project("my:key");
+ builder.configure(project, configuration);
+
+ assertThat(project.isLatestAnalysis(), is(true));
+ }
+
+ @Test
+ public void isLatestAnalysisIfNeverAnalysed() {
+ setupData("isLatestAnalysisIfNeverAnalysed");
+
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2010-12-25");
+
+ Project project = new Project("my:key");
+ builder.configure(project, configuration);
+
+ assertThat(project.isLatestAnalysis(), is(true));
+ }
+
+ @Test
+ public void isNotLatestAnalysis() {
+ setupData("isNotLatestAnalysis");
+
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2005-12-25");
+
+ Project project = new Project("my:key");
+ builder.configure(project, configuration);
+
+ assertThat(project.isLatestAnalysis(), is(false));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/ProjectConfigurationTest.java b/sonar-batch/src/test/java/org/sonar/batch/ProjectConfigurationTest.java
new file mode 100644
index 00000000000..247f0d09c0d
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/ProjectConfigurationTest.java
@@ -0,0 +1,117 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.maven.project.MavenProject;
+import org.junit.Test;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.resources.Project;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+public class ProjectConfigurationTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void loadSystemProperties() {
+ System.setProperty("foo", "bar");
+ setupData("global-properties");
+
+ ProjectConfiguration config = new ProjectConfiguration(getSession(), newProject());
+ assertThat(config.getString("foo"), is("bar"));
+ assertNull(config.getString("unknown"));
+ }
+
+ @Test
+ public void loadDatabaseProperties() {
+ setupData("global-properties");
+ ProjectConfiguration config = new ProjectConfiguration(getSession(), newProject());
+ assertThat(config.getString("key1"), is("value1"));
+ assertNull(config.getString("key3"));
+ }
+
+ @Test
+ public void loadProjectDatabaseProperties() {
+ setupData("project-properties");
+ ProjectConfiguration config = new ProjectConfiguration(getSession(), newProject());
+ assertThat(config.getString("key1"), is("overriden_value1"));
+ assertThat(config.getString("key2"), is("value2"));
+ assertThat(config.getString("key3"), is("value3"));
+ }
+
+ @Test
+ public void loadModuleDatabaseProperties() {
+ setupData("modules-properties");
+ ProjectConfiguration moduleConfig = new ProjectConfiguration(getSession(), newModule());
+
+ assertThat(moduleConfig.getString("key1"), is("project_value_1"));
+ assertThat(moduleConfig.getString("key2"), is("value_2"));
+ assertThat(moduleConfig.getString("key3"), is("module_value_3"));
+ assertThat(moduleConfig.getString("key4"), is("module_value_4"));
+ }
+
+ @Test
+ public void mavenSettingsLoadedBeforeGlobalSettings() {
+ setupData("global-properties");
+ Project project = newProject();
+ project.getPom().getProperties().put("key1", "maven1");
+ ProjectConfiguration config = new ProjectConfiguration(getSession(), project);
+ assertThat(config.getString("key1"), is("maven1"));
+ }
+
+ @Test
+ public void projectSettingsLoadedBeforeMavenSettings() {
+ setupData("project-properties");
+ Project project = newProject();
+ project.getPom().getProperties().put("key1", "maven1");
+ ProjectConfiguration config = new ProjectConfiguration(getSession(), project);
+ assertThat(config.getString("key1"), is("overriden_value1"));
+ }
+
+ @Test
+ public void addPropertyAtRuntime() {
+ setupData("global-properties");
+ ProjectConfiguration config = new ProjectConfiguration(getSession(), newProject());
+
+ config.getInMemoryConfiguration().setProperty("new-key", "new-value");
+ assertThat(config.getString("new-key"), is("new-value"));
+ }
+
+ @Test
+ public void overridePropertyAtRuntime() {
+ setupData("global-properties");
+ ProjectConfiguration config = new ProjectConfiguration(getSession(), newProject());
+
+ assertThat(config.getString("key1"), is("value1"));
+ config.setProperty("key1", "new1");
+ assertThat(config.getString("key1"), is("new1"));
+ }
+
+ private Project newProject() {
+ return new Project("mygroup:myproject").setPom(new MavenProject());
+ }
+
+ private Project newModule() {
+ Project module = new Project("mygroup:mymodule").setPom(new MavenProject());
+ module.setParent(newProject());
+ return module;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/ProjectTreeTest.java b/sonar-batch/src/test/java/org/sonar/batch/ProjectTreeTest.java
new file mode 100644
index 00000000000..8473b959f3d
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/ProjectTreeTest.java
@@ -0,0 +1,209 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.junit.Test;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.resources.Project;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNull;
+import static org.junit.internal.matchers.IsCollectionContaining.hasItem;
+
+
+public class ProjectTreeTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void moduleNameShouldEqualArtifactId() throws Exception {
+ MavenProject parent = loadProject("/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/pom.xml", true);
+ MavenProject module1 = loadProject("/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module1/pom.xml", false);
+ MavenProject module2 = loadProject("/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module2/pom.xml", false);
+
+ ProjectTree tree = new ProjectTree(newProjectBuilder(), Arrays.asList(parent, module1, module2));
+ tree.start();
+
+ Project root = tree.getRootProject();
+ assertThat(root.getModules().size(), is(2));
+ assertThat(root.getKey(), is("org.test:parent"));
+ assertNull(root.getParent());
+ assertThat(tree.getProjectByArtifactId("module1").getKey(), is("org.test:module1"));
+ assertThat(tree.getProjectByArtifactId("module1").getParent(), is(root));
+ assertThat(tree.getProjectByArtifactId("module2").getKey(), is("org.test:module2"));
+ assertThat(tree.getProjectByArtifactId("module2").getParent(), is(root));
+ }
+
+ @Test
+ public void moduleNameDifferentThanArtifactId() throws Exception {
+ MavenProject parent = loadProject("/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/pom.xml", true);
+ MavenProject module1 = loadProject("/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path1/pom.xml", false);
+ MavenProject module2 = loadProject("/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path2/pom.xml", false);
+
+ ProjectTree tree = new ProjectTree(newProjectBuilder(), Arrays.asList(parent, module1, module2));
+ tree.start();
+
+
+ Project root = tree.getRootProject();
+ assertThat(root.getModules().size(), is(2));
+ assertThat(root.getKey(), is("org.test:parent"));
+ assertNull(root.getParent());
+ assertThat(tree.getProjectByArtifactId("module1").getKey(), is("org.test:module1"));
+ assertThat(tree.getProjectByArtifactId("module1").getParent(), is(root));
+ assertThat(tree.getProjectByArtifactId("module2").getKey(), is("org.test:module2"));
+ assertThat(tree.getProjectByArtifactId("module2").getParent(), is(root));
+ }
+
+ @Test
+ public void singleProjectWithoutModules() throws Exception {
+ MavenProject parent = loadProject("/org/sonar/batch/ProjectTreeTest/singleProjectWithoutModules/pom.xml", true);
+
+ ProjectTree tree = new ProjectTree(newProjectBuilder(), Arrays.asList(parent));
+ tree.start();
+
+ Project root = tree.getRootProject();
+ assertThat(root.getModules().size(), is(0));
+ assertThat(root.getKey(), is("org.test:parent"));
+ }
+
+ @Test
+ public void keyIncludesBranch() throws IOException, XmlPullParserException, URISyntaxException {
+ MavenProject pom = loadProject("/org/sonar/batch/ProjectTreeTest/keyIncludesBranch/pom.xml", true);
+
+ ProjectTree tree = new ProjectTree(newProjectBuilder(), Arrays.asList(pom));
+ tree.start();
+
+ assertThat(tree.getRootProject().getKey(), is("org.test:project:BRANCH-1.X"));
+ assertThat(tree.getRootProject().getName(), is("Project BRANCH-1.X"));
+ }
+
+
+ @Test
+ public void keyIncludesDeprecatedBranch() throws IOException, XmlPullParserException, URISyntaxException {
+ MavenProject pom = loadProject("/org/sonar/batch/ProjectTreeTest/keyIncludesDeprecatedBranch/pom.xml", true);
+
+ ProjectTree tree = new ProjectTree(newProjectBuilder(), Arrays.asList(pom));
+ tree.start();
+
+ assertThat(tree.getRootProject().getKey(), is("org.test:project:BRANCH-1.X"));
+ assertThat(tree.getRootProject().getName(), is("Project BRANCH-1.X"));
+ }
+
+ @Test
+ public void doNotSkipAnyModules() {
+ Project foo = newProjectWithArtifactId("root");
+ Project bar = newProjectWithArtifactId("sub1");
+ Project baz = newProjectWithArtifactId("sub2");
+
+ ProjectTree tree = new ProjectTree(Arrays.asList(foo, bar, baz));
+ tree.applyModuleExclusions();
+
+ assertThat(tree.getProjects().size(), is(3));
+ }
+
+ @Test
+ public void skipModule() {
+ Project root = newProjectWithArtifactId("root");
+ root.getConfiguration().setProperty("sonar.skippedModules", "sub1");
+ Project sub1 = newProjectWithArtifactId("sub1");
+ Project sub2 = newProjectWithArtifactId("sub2");
+
+ ProjectTree tree = new ProjectTree(Arrays.asList(root, sub1, sub2));
+ tree.applyModuleExclusions();
+
+ assertThat(tree.getProjects().size(), is(2));
+ assertThat(tree.getProjects(), hasItem(root));
+ assertThat(tree.getProjects(), hasItem(sub2));
+ }
+
+ @Test
+ public void skipModules() {
+ Project root = newProjectWithArtifactId("root");
+ root.getConfiguration().setProperty("sonar.skippedModules", "sub1,sub2");
+ Project sub1 = newProjectWithArtifactId("sub1");
+ Project sub2 = newProjectWithArtifactId("sub2");
+
+ ProjectTree tree = new ProjectTree(Arrays.asList(root, sub1, sub2));
+ tree.applyModuleExclusions();
+
+ assertThat(tree.getProjects().size(), is(1));
+ assertThat(tree.getProjects(), hasItem(root));
+ }
+
+ @Test
+ public void includeModules() {
+ Project root = newProjectWithArtifactId("root");
+ root.getConfiguration().setProperty("sonar.includedModules", "sub1,sub2");
+ Project sub1 = newProjectWithArtifactId("sub1");
+ Project sub2 = newProjectWithArtifactId("sub2");
+
+ ProjectTree tree = new ProjectTree(Arrays.asList(root, sub1, sub2));
+ tree.applyModuleExclusions();
+
+ assertThat(tree.getProjects().size(), is(2));
+ assertThat(tree.getProjects(), hasItem(sub1));
+ assertThat(tree.getProjects(), hasItem(sub2));
+ }
+
+ @Test
+ public void skippedModulesTakePrecedenceOverIncludedModules() {
+ Project root = newProjectWithArtifactId("root");
+ root.getConfiguration().setProperty("sonar.includedModules", "sub1,sub2");
+ root.getConfiguration().setProperty("sonar.skippedModules", "sub1");
+ Project sub1 = newProjectWithArtifactId("sub1");
+ Project sub2 = newProjectWithArtifactId("sub2");
+
+ ProjectTree tree = new ProjectTree(Arrays.asList(root, sub1, sub2));
+ tree.applyModuleExclusions();
+
+ assertThat(tree.getProjects().size(), is(1));
+ assertThat(tree.getProjects(), hasItem(sub2));
+ }
+
+ private Project newProjectWithArtifactId(String artifactId) {
+ MavenProject pom = new MavenProject();
+ pom.setArtifactId(artifactId);
+ return new Project(artifactId).setPom(pom).setConfiguration(new PropertiesConfiguration());
+ }
+
+ private MavenProject loadProject(String pomPath, boolean isRoot) throws URISyntaxException, IOException, XmlPullParserException {
+ File pomFile = new File(getClass().getResource(pomPath).toURI());
+ Model model = new MavenXpp3Reader().read(new StringReader(FileUtils.readFileToString(pomFile)));
+ MavenProject pom = new MavenProject(model);
+ pom.setFile(pomFile);
+ pom.setExecutionRoot(isRoot);
+ return pom;
+ }
+
+ private ProjectBuilder newProjectBuilder() {
+ return new ProjectBuilder(getSession());
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/RemoteClassLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/RemoteClassLoaderTest.java
new file mode 100644
index 00000000000..f79d4535a0e
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/RemoteClassLoaderTest.java
@@ -0,0 +1,43 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.junit.Test;
+
+import java.net.URL;
+import java.util.Arrays;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+public class RemoteClassLoaderTest {
+
+ @Test
+ public void testClassLoader() {
+ /* foo.jar has just one file /foo/foo.txt */
+ assertNull(getClass().getClassLoader().getResource("foo/foo.txt"));
+
+ URL url = getClass().getResource("/org/sonar/batch/RemoteClassLoaderTest/foo.jar");
+ RemoteClassLoader classloader = new RemoteClassLoader(Arrays.asList(url), null);
+ assertNotNull(classloader.getClassLoader());
+ assertNotNull(classloader.getClassLoader().getResource("foo/foo.txt"));
+
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/ServerMetadataTest.java b/sonar-batch/src/test/java/org/sonar/batch/ServerMetadataTest.java
new file mode 100644
index 00000000000..cef7537c63a
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/ServerMetadataTest.java
@@ -0,0 +1,61 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+
+import java.text.ParseException;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ServerMetadataTest {
+
+ @Test
+ public void testLoadProperties() throws ParseException {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty(CoreProperties.SERVER_ID, "123");
+ conf.setProperty(CoreProperties.SERVER_VERSION, "2.2");
+ conf.setProperty(CoreProperties.SERVER_STARTTIME, "2010-05-18T17:59:00+0000");
+ conf.setProperty("sonar.host.url", "http://foo.com");
+
+ ServerMetadata server = new ServerMetadata(conf);
+
+ assertThat(server.getId(), is("123"));
+ assertThat(server.getVersion(), is("2.2"));
+ assertThat(server.getStartedAt().getDate(), is(18));
+ assertThat(server.getUrl(), is("http://foo.com"));
+ }
+
+ /**
+ * http://jira.codehaus.org/browse/SONAR-1685
+ * The maven plugin fails if the property sonar.host.url ends with a slash
+ */
+ @Test
+ public void urlMustNotEndWithSlash() throws ParseException {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.host.url", "http://localhost:80/");
+
+ ServerMetadata server = new ServerMetadata(conf);
+ assertThat(server.getUrl(), is("http://localhost:80"));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/ViolationFiltersTest.java b/sonar-batch/src/test/java/org/sonar/batch/ViolationFiltersTest.java
new file mode 100644
index 00000000000..53eb38959b2
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/ViolationFiltersTest.java
@@ -0,0 +1,68 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch;
+
+import org.junit.Test;
+import org.sonar.api.rules.Violation;
+import org.sonar.api.rules.ViolationFilter;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public class ViolationFiltersTest {
+
+ @Test
+ public void doNotFailIfNoFilters() {
+ ViolationFilters filters = new ViolationFilters();
+ assertThat(filters.isIgnored(new Violation(null)), is(false));
+ }
+
+ @Test
+ public void ignoreViolation() {
+ ViolationFilters filters = new ViolationFilters(new ViolationFilter[]{
+ new FakeFilter(false),
+ new FakeFilter(true),
+ new FakeFilter(false),
+ });
+ assertThat(filters.isIgnored(new Violation(null)), is(true));
+ }
+
+ @Test
+ public void doNotIgnoreValidViolations() {
+ ViolationFilters filters = new ViolationFilters(new ViolationFilter[]{
+ new FakeFilter(false),
+ new FakeFilter(false),
+ new FakeFilter(false),
+ });
+ assertThat(filters.isIgnored(new Violation(null)), is(false));
+ }
+
+ private static class FakeFilter implements ViolationFilter {
+ private boolean ignore;
+
+ private FakeFilter(boolean ignore) {
+ this.ignore = ignore;
+ }
+
+ public boolean isIgnored(Violation violation) {
+ return ignore;
+ }
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultPersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultPersisterTest.java
new file mode 100644
index 00000000000..efeec499d64
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultPersisterTest.java
@@ -0,0 +1,61 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.junit.Test;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.JavaPackage;
+import org.sonar.api.resources.Project;
+
+public class DefaultPersisterTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void createResource() {
+ setupData("createResource");
+
+ Bucket bucket = createBucket(new JavaPackage("org.foo"));
+
+ new DefaultPersister(getSession()).persist(bucket);
+
+ checkTables("createResource", "projects", "snapshots");
+ }
+
+ private Bucket createBucket(JavaPackage resource) {
+ Bucket projectBucket = new Bucket(new Project("my:key").setId(5));
+ projectBucket.setSnapshot(getSession().getSingleResult(Snapshot.class, "id", 30));
+
+ Bucket bucket = new Bucket(resource);
+ bucket.setProject(projectBucket);
+ bucket.setParent(projectBucket);
+ return bucket;
+ }
+
+ @Test
+ public void updateExistingResource() {
+ setupData("updateExistingResource");
+
+ Bucket bucket = createBucket(new JavaPackage("org.foo"));
+
+ new DefaultPersister(getSession()).persist(bucket);
+
+ checkTables("updateExistingResource", "projects", "snapshots");
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultSonarIndexTest.java b/sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultSonarIndexTest.java
new file mode 100644
index 00000000000..fdbc09cc47c
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/indexer/DefaultSonarIndexTest.java
@@ -0,0 +1,56 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.junit.Test;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.design.Dependency;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Resource;
+
+import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class DefaultSonarIndexTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void indexDependencies() {
+ DefaultSonarIndex index = new DefaultSonarIndex(getSession(), null);
+
+ Resource from = new JavaFile("org.foo.Foo");
+ Resource to = new JavaFile("org.bar.Bar");
+ Dependency dependency = new Dependency(from, to);
+
+ index.registerDependency(dependency);
+
+ assertThat(index.getDependencies().size(), is(1));
+ assertTrue(index.getDependencies().contains(dependency));
+ assertThat(index.getEdge(from, to), is(dependency));
+
+ assertThat(index.getIncomingEdges(to).size(), is(1));
+ assertTrue(index.getIncomingEdges(to).contains(dependency));
+ assertThat(index.getIncomingEdges(from).isEmpty(), is(true));
+
+ assertThat(index.getOutgoingEdges(from).size(), is(1));
+ assertTrue(index.getOutgoingEdges(from).contains(dependency));
+ assertThat(index.getOutgoingEdges(to).isEmpty(), is(true));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/indexer/LibraryPersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/indexer/LibraryPersisterTest.java
new file mode 100644
index 00000000000..49933e44a7d
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/indexer/LibraryPersisterTest.java
@@ -0,0 +1,94 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Library;
+import org.sonar.api.resources.Project;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+public class LibraryPersisterTest extends AbstractDbUnitTestCase {
+
+ private Bucket<Project> projectBucket;
+ private LibraryPersister persister;
+
+ @Before
+ public void before() throws ParseException {
+ persister = new LibraryPersister(getSession(), new SimpleDateFormat("yyyy-MM-dd HH:mm").parse( "2010-05-18 17:00"));
+ }
+
+ @Test
+ public void createLibrary() throws Exception {
+ setup("createLibrary");
+
+ Library library = new Library("commons-lang:commons-lang", "1.1")
+ .setName("Commons Lang");
+
+ Bucket<Library> bucket = new Bucket<Library>(library).setProject(projectBucket);
+ persister.persist(bucket);
+
+ check("createLibrary", "projects", "snapshots");
+ }
+
+ @Test
+ public void reuseExistingLibrary() throws Exception {
+ setup("reuseExistingLibrary");
+
+ Library library = new Library("commons-lang:commons-lang", "1.1")
+ .setName("Commons Lang");
+
+ Bucket<Library> bucket = new Bucket<Library>(library).setProject(projectBucket);
+ persister.persist(bucket);
+
+ check("reuseExistingLibrary", "projects", "snapshots");
+ }
+
+ @Test
+ public void addNewLibraryVersion() throws Exception {
+ setup("addNewLibraryVersion");
+
+ Library library = new Library("commons-lang:commons-lang", "1.2")
+ .setName("Commons Lang");
+
+ Bucket<Library> bucket = new Bucket<Library>(library).setProject(projectBucket);
+ persister.persist(bucket);
+
+ check("addNewLibraryVersion", "projects", "snapshots");
+ }
+
+ private void setup(String unitTest) throws Exception {
+ setupData(unitTest);
+
+ Project project = new Project("my:project");
+ project.setId(1);
+ projectBucket = new Bucket<Project>(project);
+ projectBucket.setSnapshot(getSession().getSingleResult(Snapshot.class, "id", 1));
+ }
+
+ private void check(String unitTest, String... tables) {
+ getSession().commit();
+ checkTables(unitTest, tables);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/indexer/ResourcePersistersTest.java b/sonar-batch/src/test/java/org/sonar/batch/indexer/ResourcePersistersTest.java
new file mode 100644
index 00000000000..cb75095aef4
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/indexer/ResourcePersistersTest.java
@@ -0,0 +1,67 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * 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.batch.indexer;
+
+import org.junit.Test;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import org.sonar.api.resources.*;
+
+import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ResourcePersistersTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void getDefaultPersisterForFilesAndPackages() {
+ ResourcePersisters persisters = new ResourcePersisters(getSession());
+
+
+ JavaFile file = new JavaFile("org.foo.Bar");
+ assertThat(persisters.get(file), is(DefaultPersister.class));
+ assertThat(persisters.get(new JavaPackage("org.foo")), is(DefaultPersister.class));
+ assertThat(persisters.get(new File("org/foo/Bar.sql")), is(DefaultPersister.class));
+
+ // always the same instance
+ assertTrue(persisters.get(file)==persisters.get(file));
+ }
+
+ @Test
+ public void getForProjects() {
+ ResourcePersisters persisters = new ResourcePersisters(getSession());
+
+ Project project = new Project("my:project");
+ assertThat(persisters.get(project), is(ProjectPersister.class));
+
+ // always the same instance
+ assertTrue(persisters.get(project)==persisters.get(project));
+ }
+
+ @Test
+ public void getForLibraries() {
+ ResourcePersisters persisters = new ResourcePersisters(getSession());
+
+ Library lib = new Library("commons-lang:commons-lang", "1.0");
+ assertThat(persisters.get(lib), is(LibraryPersister.class));
+
+ // always the same instance
+ assertTrue(persisters.get(lib)==persisters.get(lib));
+ }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject-result.xml
new file mode 100644
index 00000000000..635c73687ec
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject-result.xml
@@ -0,0 +1,53 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="2" NAME="coverage" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <!-- conflicting resources -->
+ <projects long_name="[null]" id="1" scope="PRJ" kee="othergroup:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+ <projects long_name="[null]" id="2" scope="DIR" kee="othergroup:artifact:org.sonar" qualifier="PAC" name="org.sonar"
+ root_id="2"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.sonar" qualifier="PAC" name="org.sonar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <!-- the project snapshot -->
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <!-- conflicting snapshots -->
+ <snapshots id="2" created_at="2007-10-02 13:58:00.00" version="2.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+ <snapshots id="3" created_at="2007-10-02 13:58:00.00" version="[null]" project_id="2" scope="DIR" qualifier="PAC"
+ root_project_id="1" root_snapshot_id="2" parent_snapshot_id="2" STATUS="U" ISLAST="false" path="3."
+ depth="1"/>
+
+ <!-- the package snapshot -->
+ <snapshots id="4" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="11" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false" path="1."
+ depth="1"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="200" METRIC_ID="1" SNAPSHOT_ID="4" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="2" VALUE="80" METRIC_ID="2" SNAPSHOT_ID="4" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject.xml
new file mode 100644
index 00000000000..d47ef4c43a8
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/avoidConflictWithResourceFromOtherProject.xml
@@ -0,0 +1,35 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="2" NAME="coverage" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <!-- conflicting resources -->
+ <projects long_name="[null]" id="1" scope="PRJ" kee="othergroup:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+ <projects long_name="[null]" id="2" scope="DIR" kee="othergroup:artifact:org.sonar" qualifier="PAC" name="org.sonar"
+ root_id="2"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <!-- the project -->
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <!-- the project snapshot -->
+ <snapshots depth="0" id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ"
+ qualifier="TRK" root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U"
+ ISLAST="false" path=""/>
+
+ <!-- conflicting snapshots -->
+ <snapshots depth="0" id="2" created_at="2007-10-02 13:58:00.00" version="2.0" project_id="1" scope="PRJ"
+ qualifier="TRK" root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U"
+ ISLAST="false" path=""/>
+ <snapshots depth="1" id="3" created_at="2007-10-02 13:58:00.00" version="[null]" project_id="2" scope="DIR"
+ qualifier="PAC" root_project_id="1" root_snapshot_id="2" parent_snapshot_id="2" STATUS="U"
+ ISLAST="false"
+ path="3."/>
+
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures-result.xml
new file mode 100644
index 00000000000..31381039d29
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures-result.xml
@@ -0,0 +1,20 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="500" METRIC_ID="1" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures.xml
new file mode 100644
index 00000000000..0bd9abf865a
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotCacheDatabaseMeasures.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures-result.xml
new file mode 100644
index 00000000000..7b32c5b2c5f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures-result.xml
@@ -0,0 +1,24 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <metrics id="2" NAME="coverage" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+
+ <!-- other measure, just to avoid dbunit to fail -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="100" METRIC_ID="2" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures.xml
new file mode 100644
index 00000000000..7b32c5b2c5f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotPersistInMemoryMeasures.xml
@@ -0,0 +1,24 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <metrics id="2" NAME="coverage" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+
+ <!-- other measure, just to avoid dbunit to fail -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="100" METRIC_ID="2" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources-result.xml
new file mode 100644
index 00000000000..0bd9abf865a
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources-result.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources.xml
new file mode 100644
index 00000000000..0bd9abf865a
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveExcludedResources.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues-result.xml
new file mode 100644
index 00000000000..524bf2a39af
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues-result.xml
@@ -0,0 +1,39 @@
+<dataset>
+ <metrics id="1" NAME="violations" VAL_TYPE="INT" DESCRIPTION="[null]" DIRECTION="1" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" best_value="0" optimized_best_value="true" hidden="false"/>
+ <metrics id="2" NAME="blocker_violations" VAL_TYPE="INT" DESCRIPTION="[null]" DIRECTION="1" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" best_value="0" optimized_best_value="true" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.foo" qualifier="PAC" name="org.foo"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="org.foo.Bar" id="12" scope="FIL" kee="group:artifact:org.foo.Bar" qualifier="CLA" name="Bar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <snapshots id="2" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="11" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false" path="1."
+ depth="1"/>
+
+ <snapshots id="3" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="12" scope="FIL" qualifier="CLA"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="2" STATUS="U" ISLAST="false" path="1.2."
+ depth="2"/>
+
+ <!-- violations on file -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="60" METRIC_ID="1" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues.xml
new file mode 100644
index 00000000000..dc2f999d901
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/doNotSaveOptimizedBestValues.xml
@@ -0,0 +1,24 @@
+<dataset>
+ <metrics id="1" NAME="violations" VAL_TYPE="INT" DESCRIPTION="[null]" DIRECTION="1" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" best_value="0" optimized_best_value="true" hidden="false" />
+ <metrics id="2" NAME="blocker_violations" VAL_TYPE="INT" DESCRIPTION="[null]" DIRECTION="1" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" best_value="0" optimized_best_value="true" hidden="false" />
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.foo" qualifier="PAC" name="org.foo"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="org.foo.Bar" id="12" scope="FIL" kee="group:artifact:org.foo.Bar" qualifier="CLA" name="Bar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency-result.xml
new file mode 100644
index 00000000000..39eca6144cc
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency-result.xml
@@ -0,0 +1,36 @@
+<dataset>
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.sonar.source" qualifier="PAC"
+ name="org.sonar.source"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="12" scope="DIR" kee="group:artifact:org.sonar.target" qualifier="PAC"
+ name="org.sonar.target"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <snapshots id="2" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="11" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false"
+ path="1."
+ depth="1"/>
+
+ <snapshots id="3" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="12" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false"
+ path="1."
+ depth="1"/>
+
+
+ <dependencies id="1" from_resource_id="11" from_snapshot_id="2" to_resource_id="12" to_snapshot_id="3"
+ parent_dependency_id="[null]" project_snapshot_id="1"
+ dep_usage="INHERITS" dep_weight="3" />
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency.xml
new file mode 100644
index 00000000000..a81a295f57e
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveDependency.xml
@@ -0,0 +1,22 @@
+<dataset>
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.sonar.source" qualifier="PAC"
+ name="org.sonar.source"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="12" scope="DIR" kee="group:artifact:org.sonar.target" qualifier="PAC"
+ name="org.sonar.target"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource-result.xml
new file mode 100644
index 00000000000..f7d7623ebde
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource-result.xml
@@ -0,0 +1,28 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="2" NAME="other" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.sonar" qualifier="PAC" name="org.sonar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+ <snapshots id="2" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="11" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false" path="1."
+ depth="1"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="200" METRIC_ID="1" SNAPSHOT_ID="2" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource.xml
new file mode 100644
index 00000000000..497f2f24ad0
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveMeasureOnExistingResource.xml
@@ -0,0 +1,18 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="2" NAME="other" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.sonar" qualifier="PAC" name="org.sonar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields-result.xml
new file mode 100644
index 00000000000..c3aed6707b9
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields-result.xml
@@ -0,0 +1,37 @@
+<dataset>
+ <metrics id="1" NAME="violations" VAL_TYPE="INT" DESCRIPTION="[null]" DIRECTION="1" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" best_value="0" optimized_best_value="true" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.foo" qualifier="PAC" name="org.foo"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="org.foo.Bar" id="12" scope="FIL" kee="group:artifact:org.foo.Bar" qualifier="CLA" name="Bar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <snapshots id="2" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="11" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false" path="1."
+ depth="1"/>
+
+ <snapshots id="3" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="12" scope="FIL" qualifier="CLA"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="2" STATUS="U" ISLAST="false" path="1.2."
+ depth="2"/>
+
+ <!-- violations with default value. It's saved because tendency is set -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="0" METRIC_ID="1" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="1" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields.xml
new file mode 100644
index 00000000000..ee4d8fd9fbb
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveOptimizedBestValuesIfOptionalFields.xml
@@ -0,0 +1,22 @@
+<dataset>
+ <metrics id="1" NAME="violations" VAL_TYPE="INT" DESCRIPTION="[null]" DIRECTION="1" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" best_value="0" optimized_best_value="true" hidden="false" />
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.foo" qualifier="PAC" name="org.foo"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="org.foo.Bar" id="12" scope="FIL" kee="group:artifact:org.foo.Bar" qualifier="CLA" name="Bar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure-result.xml
new file mode 100644
index 00000000000..31381039d29
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure-result.xml
@@ -0,0 +1,20 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="500" METRIC_ID="1" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure.xml
new file mode 100644
index 00000000000..0bd9abf865a
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveProjectMeasure.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree-result.xml
new file mode 100644
index 00000000000..a8d5bcb9ad6
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree-result.xml
@@ -0,0 +1,30 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" kee="group:artifact:org.foo" qualifier="PAC" name="org.foo"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="org.foo.Bar" id="12" scope="FIL" kee="group:artifact:org.foo.Bar" qualifier="CLA" name="Bar"
+ root_id="10"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <snapshots id="2" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="11" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false" path="1."
+ depth="1"/>
+
+ <snapshots id="3" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="12" scope="FIL" qualifier="CLA"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="2" STATUS="U" ISLAST="false" path="1.2."
+ depth="2"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree.xml
new file mode 100644
index 00000000000..2837836be38
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourceTree.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourcesBeforeBuildingDependencies.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourcesBeforeBuildingDependencies.xml
new file mode 100644
index 00000000000..9a7372deaa8
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveResourcesBeforeBuildingDependencies.xml
@@ -0,0 +1,11 @@
+<dataset>
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures-result.xml
new file mode 100644
index 00000000000..f0bb10d8e22
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures-result.xml
@@ -0,0 +1,26 @@
+<dataset>
+ <metrics id="1" NAME="violations" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]" rule_priority="3"
+ alert_text="[null]" id="1" VALUE="500" METRIC_ID="1" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="2" VALUE="200" METRIC_ID="1" SNAPSHOT_ID="1" RULES_CATEGORY_ID="3"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures.xml
new file mode 100644
index 00000000000..f5a8045402c
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/saveRuleMeasures.xml
@@ -0,0 +1,14 @@
+<dataset>
+ <metrics id="1" NAME="violations" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields-result.xml
new file mode 100644
index 00000000000..8c290640bd5
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields-result.xml
@@ -0,0 +1,22 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="DIR" qualifier="PAC" kee="group:artifact:org.foo" name="org.foo"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+ <snapshots id="2" created_at="2008-12-25 00:00:00.00" version="[null]" project_id="11" scope="DIR" qualifier="PAC"
+ root_project_id="10" root_snapshot_id="1" parent_snapshot_id="1" STATUS="U" ISLAST="false" path="1."
+ depth="1"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields.xml
new file mode 100644
index 00000000000..5c90f7296f9
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultSensorContextTest/updateExistingResourceFields.xml
@@ -0,0 +1,18 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="10" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="11" scope="FOO" qualifier="BAR" kee="group:artifact:org.foo" name="org.foo"
+ root_id="[null]"
+ description="[null]" enabled="false" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="10" scope="PRJ" qualifier="TRK"
+ root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path=""
+ depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/DefaultTimeMachineTest/loadMeasuresFromDate.xml b/sonar-batch/src/test/resources/org/sonar/batch/DefaultTimeMachineTest/loadMeasuresFromDate.xml
new file mode 100644
index 00000000000..eca84419a8d
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/DefaultTimeMachineTest/loadMeasuresFromDate.xml
@@ -0,0 +1,65 @@
+<dataset>
+ <metrics id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="2" NAME="coverage" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="1" scope="PRJ" kee="group:artifact" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+ <projects long_name="[null]" id="2" scope="DIR" kee="group:artifact:org.sonar" qualifier="PAC" name="org.sonar"
+ root_id="2"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+
+ <!-- Project : 3 snapshots -->
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path=""
+ depth="0"/>
+ <snapshots id="2" created_at="2009-02-25 13:58:00.00" version="2.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path=""
+ depth="0"/>
+ <snapshots id="3" created_at="2009-02-26 15:00:00.00" version="[null]" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="true"
+ path="" depth="0"/>
+
+ <!-- unprocessed snapshot -->
+ <snapshots id="4" created_at="2009-02-28 15:00:00.00" version="[null]" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="[null]" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path="" depth="0"/>
+
+ <!-- first snapshot measures -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="1" VALUE="200" METRIC_ID="1" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="2" VALUE="80" METRIC_ID="2" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <!-- second snapshot measures -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="3" VALUE="230" METRIC_ID="1" SNAPSHOT_ID="2" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="4" VALUE="85" METRIC_ID="2" SNAPSHOT_ID="2" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+
+ <!-- third snapshot measures : no coverage -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" id="5" VALUE="180" METRIC_ID="1" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast-result.xml
new file mode 100644
index 00000000000..a1b5dc5de93
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast-result.xml
@@ -0,0 +1,22 @@
+<dataset>
+ <!-- currently processing snapshots -->
+ <snapshots depth="[null]" id="5" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="6" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="5" root_project_id="[null]" root_snapshot_id="5" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="7" scope="DIR" qualifier="PAC" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="6" root_project_id="[null]" root_snapshot_id="5" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="8" scope="FIL" qualifier="CLA" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="7" root_project_id="[null]" root_snapshot_id="5" status="P" islast="true"
+ path="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast.xml b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast.xml
new file mode 100644
index 00000000000..700793aaeee
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/doNotFailIfNoPenultimateLast.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <!-- currently processing snapshots -->
+ <snapshots depth="[null]" id="5" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="U" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="6" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="5" root_project_id="[null]" root_snapshot_id="5" status="U" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="7" scope="DIR" qualifier="PAC" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="6" root_project_id="[null]" root_snapshot_id="5" status="U" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="8" scope="FIL" qualifier="CLA" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="7" root_project_id="[null]" root_snapshot_id="5" status="U" islast="false"
+ path="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot-result.xml
new file mode 100644
index 00000000000..1bab0601797
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot-result.xml
@@ -0,0 +1,173 @@
+<dataset>
+
+ <!-- last snapshot : is always last, and not purged -->
+ <snapshots depth="[null]" id="1" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="2" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="1" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="3" scope="DIR" qualifier="PAC" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="2" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="4" scope="FIL" qualifier="CLA" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="3" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="5" scope="FIL" qualifier="CLA" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="5"
+ parent_snapshot_id="3" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+
+ <!-- the snapshot to process : not set as last-->
+ <snapshots depth="[null]" id="6" scope="PRJ" qualifier="TRK" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="7" scope="PRJ" qualifier="TRK" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="6" root_project_id="[null]" root_snapshot_id="6" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="8" scope="DIR" qualifier="PAC" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="7" root_project_id="[null]" root_snapshot_id="6" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="9" scope="FIL" qualifier="CLA" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="8" root_project_id="[null]" root_snapshot_id="6" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="10" scope="FIL" qualifier="CLA" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="5"
+ parent_snapshot_id="9" root_project_id="[null]" root_snapshot_id="6" status="P" islast="false"
+ path="[null]"/>
+
+
+ <SNAPSHOT_SOURCES ID="1" SNAPSHOT_ID="4" DATA="source code of Class1"/>
+ <SNAPSHOT_SOURCES ID="2" SNAPSHOT_ID="5" DATA="source code of Class2"/>
+
+ <RULE_FAILURES ID="1" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg1"/>
+ <RULE_FAILURES ID="2" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg2"/>
+ <RULE_FAILURES ID="3" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg3"/>
+ <RULE_FAILURES ID="4" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg4"/>
+
+ <RULE_FAILURES ID="5" SNAPSHOT_ID="5" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg5"/>
+ <RULE_FAILURES ID="6" SNAPSHOT_ID="5" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg6"/>
+
+ <!-- measures at project level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="1" VALUE="10.0" METRIC_ID="1" SNAPSHOT_ID="1" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="2" VALUE="20.0" METRIC_ID="2" SNAPSHOT_ID="1" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="3" VALUE="30.0" METRIC_ID="3" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <!-- measures at module level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="4" VALUE="10.0" METRIC_ID="1" SNAPSHOT_ID="2" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="5" VALUE="20.0" METRIC_ID="2" SNAPSHOT_ID="2" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="6" VALUE="30.0" METRIC_ID="3" SNAPSHOT_ID="2" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <!-- measures at package level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="7" VALUE="40.0" METRIC_ID="1" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="8" VALUE="50.0" METRIC_ID="2" SNAPSHOT_ID="3" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="9" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="10" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <!-- measures at class level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="11" VALUE="40.0" METRIC_ID="1" SNAPSHOT_ID="4" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="12" VALUE="50.0" METRIC_ID="2" SNAPSHOT_ID="4" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="13" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="4" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="14" VALUE="40.0" METRIC_ID="1" SNAPSHOT_ID="5" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="15" VALUE="50.0" METRIC_ID="2" SNAPSHOT_ID="5" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="16" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="5" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot.xml
new file mode 100644
index 00000000000..c0914d54a78
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/lastSnapshotIsNotUpdatedWhenAnalyzingPastSnapshot.xml
@@ -0,0 +1,174 @@
+<dataset>
+
+ <!-- last snapshot -->
+ <snapshots depth="[null]" id="1" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="2" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="1" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="3" scope="DIR" qualifier="PAC" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="2" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="4" scope="FIL" qualifier="CLA" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="3" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="5" scope="FIL" qualifier="CLA" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="5"
+ parent_snapshot_id="3" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+
+ <!-- the snapshot to process -->
+ <snapshots depth="[null]" id="6" scope="PRJ" qualifier="TRK" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="[null]"
+ islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="7" scope="PRJ" qualifier="TRK" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="6" root_project_id="[null]" root_snapshot_id="6" status="[null]" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="8" scope="DIR" qualifier="PAC" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="7" root_project_id="[null]" root_snapshot_id="6" status="[null]" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="9" scope="FIL" qualifier="CLA" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="8" root_project_id="[null]" root_snapshot_id="6" status="[null]" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="10" scope="FIL" qualifier="CLA" created_at="2005-10-01 00:00:00.00" version="[null]"
+ project_id="5"
+ parent_snapshot_id="9" root_project_id="[null]" root_snapshot_id="6" status="[null]" islast="false"
+ path="[null]"/>
+
+
+ <SNAPSHOT_SOURCES ID="1" SNAPSHOT_ID="4" DATA="source code of Class1"/>
+ <SNAPSHOT_SOURCES ID="2" SNAPSHOT_ID="5" DATA="source code of Class2"/>
+
+ <RULE_FAILURES ID="1" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg1"/>
+ <RULE_FAILURES ID="2" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg2"/>
+ <RULE_FAILURES ID="3" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg3"/>
+ <RULE_FAILURES ID="4" SNAPSHOT_ID="4" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg4"/>
+
+ <RULE_FAILURES ID="5" SNAPSHOT_ID="5" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg5"/>
+ <RULE_FAILURES ID="6" SNAPSHOT_ID="5" RULE_ID="1" FAILURE_LEVEL="2" MESSAGE="msg6"/>
+
+ <!-- measures at project level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="1" VALUE="10.0" METRIC_ID="1" SNAPSHOT_ID="1" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="2" VALUE="20.0" METRIC_ID="2" SNAPSHOT_ID="1" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="3" VALUE="30.0" METRIC_ID="3" SNAPSHOT_ID="1" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <!-- measures at module level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="4" VALUE="10.0" METRIC_ID="1" SNAPSHOT_ID="2" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="5" VALUE="20.0" METRIC_ID="2" SNAPSHOT_ID="2" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="6" VALUE="30.0" METRIC_ID="3" SNAPSHOT_ID="2" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <!-- measures at package level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="7" VALUE="40.0" METRIC_ID="1" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="8" VALUE="50.0" METRIC_ID="2" SNAPSHOT_ID="3" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="9" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="10" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="3" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <!-- measures at class level -->
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="11" VALUE="40.0" METRIC_ID="1" SNAPSHOT_ID="4" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="12" VALUE="50.0" METRIC_ID="2" SNAPSHOT_ID="4" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="13" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="4" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="14" VALUE="40.0" METRIC_ID="1" SNAPSHOT_ID="5" RULES_CATEGORY_ID="1"
+ RULE_ID="1"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="15" VALUE="50.0" METRIC_ID="2" SNAPSHOT_ID="5" RULES_CATEGORY_ID="1"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+ <project_measures characteristic_id="[null]" url="[null]" diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"
+ rule_priority="[null]"
+ alert_text="[null]" ID="16" VALUE="60.0" METRIC_ID="3" SNAPSHOT_ID="5" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]"
+ text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/sharedFixture.xml b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/sharedFixture.xml
new file mode 100644
index 00000000000..00420a0362f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/sharedFixture.xml
@@ -0,0 +1,38 @@
+<dataset>
+ <rules_categories id="1" name="category one" description="[null]"/>
+ <rules id="1" name="foo" rules_category_id="1" plugin_config_key="checker/foo" plugin_rule_key="checkstyle.rule1"
+ plugin_name="maven-checkstyle-plugin" description="description" cardinality="SINGLE" parent_id="[null]"/>
+
+ <metrics id="1" name="ncloc" val_type="INT" description="[null]" domain="[null]"
+ short_name="" qualitative="false" user_managed="false" enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="2" name="foo" val_type="INT" description="[null]" domain="[null]"
+ short_name="" qualitative="false" user_managed="false" enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="3" name="bar" val_type="INT" description="[null]" domain="[null]"
+ short_name="" qualitative="false" user_managed="false" enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="4" name="baz" val_type="INT" description="[null]" domain="[null]"
+ short_name="" qualitative="false" user_managed="false" enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="mygroup:myartifact" name="[null]"
+ root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="2" scope="PRJ" qualifier="TRK" kee="mygroup:myartifact2" name="[null]" root_id="1"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="3" scope="DIR" qualifier="PAC" kee="mygroup:myartifact:my.package" name="[null]"
+ root_id="1"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="4" scope="FIL" qualifier="CLA" kee="mygroup:myartifact:my.package.Class1"
+ name="[null]" root_id="1"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="5" scope="FIL" qualifier="CLA" kee="mygroup:myartifact:my.package.Class2"
+ name="[null]" root_id="1"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot-result.xml
new file mode 100644
index 00000000000..815b71743a4
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot-result.xml
@@ -0,0 +1,55 @@
+<dataset>
+
+ <!-- previous last snapshots -->
+ <snapshots depth="[null]" id="1" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="2" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="1" root_project_id="[null]" root_snapshot_id="1" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="3" scope="DIR" qualifier="PAC" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="2" root_project_id="[null]" root_snapshot_id="1" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="4" scope="FIL" qualifier="CLA" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="3" root_project_id="[null]" root_snapshot_id="1" status="P" islast="false"
+ path="[null]"/>
+
+ <!-- already purged snapshot -->
+ <snapshots depth="[null]" id="9" scope="PRJ" qualifier="TRK" created_at="2008-10-02 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="10" scope="PRJ" qualifier="TRK" created_at="2008-10-02 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="9" root_project_id="[null]" root_snapshot_id="9" status="P" islast="false"
+ path="[null]"/>
+
+ <!-- currently processing snapshots -->
+ <snapshots depth="[null]" id="11" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="12" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="11" root_project_id="[null]" root_snapshot_id="11" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="13" scope="DIR" qualifier="PAC" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="12" root_project_id="[null]" root_snapshot_id="11" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="14" scope="FIL" qualifier="CLA" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="13" root_project_id="[null]" root_snapshot_id="11" status="P" islast="true"
+ path="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot.xml
new file mode 100644
index 00000000000..7e4130b75d9
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/FinalizeSnapshotsJobTest/shouldUnflagPenultimateLastSnapshot.xml
@@ -0,0 +1,57 @@
+<dataset>
+
+ <!-- previous last snapshots -->
+ <snapshots depth="[null]" id="1" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="2" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="1" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="3" scope="DIR" qualifier="PAC" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="2" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="4" scope="FIL" qualifier="CLA" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="3" root_project_id="[null]" root_snapshot_id="1" status="P" islast="true"
+ path="[null]"/>
+
+
+ <!-- already purged snapshot -->
+ <snapshots depth="[null]" id="9" scope="PRJ" qualifier="TRK" created_at="2008-10-02 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="10" scope="PRJ" qualifier="TRK" created_at="2008-10-02 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="9" root_project_id="[null]" root_snapshot_id="9" status="P" islast="false"
+ path="[null]"/>
+
+
+ <!-- currently processing snapshots -->
+ <snapshots depth="[null]" id="11" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="1"
+ parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="U" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="12" scope="PRJ" qualifier="TRK" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="2"
+ parent_snapshot_id="11" root_project_id="[null]" root_snapshot_id="11" status="U" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="13" scope="DIR" qualifier="PAC" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="3"
+ parent_snapshot_id="12" root_project_id="[null]" root_snapshot_id="11" status="U" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="14" scope="FIL" qualifier="CLA" created_at="2008-12-04 13:58:00.00" version="[null]"
+ project_id="4"
+ parent_snapshot_id="13" root_project_id="[null]" root_snapshot_id="11" status="U" islast="false"
+ path="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/MavenPluginsConfiguratorTest/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/MavenPluginsConfiguratorTest/pom.xml
new file mode 100644
index 00000000000..2f95da16602
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/MavenPluginsConfiguratorTest/pom.xml
@@ -0,0 +1,19 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>mygroup</groupId>
+ <artifactId>myartifact</artifactId>
+ <packaging>jar</packaging>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>2.2</version>
+ <configuration>
+ <outputFileFormat>html</outputFileFormat>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysis.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysis.xml
new file mode 100644
index 00000000000..0d69b6587a4
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysis.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <projects long_name="[null]" id="5" scope="PRJ" qualifier="TRK" kee="my:key"
+ name="My project" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <snapshots depth="[null]" id="30" scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="50" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysisIfNeverAnalysed.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysisIfNeverAnalysed.xml
new file mode 100644
index 00000000000..0eecd371324
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isLatestAnalysisIfNeverAnalysed.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <!-- other project -->
+ <projects long_name="[null]" id="5" scope="PRJ" qualifier="TRK" kee="other:key"
+ name="My project" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <snapshots depth="[null]" id="50" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isNotLatestAnalysis.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isNotLatestAnalysis.xml
new file mode 100644
index 00000000000..98d5825cc37
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectBuilderTest/isNotLatestAnalysis.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <projects long_name="[null]" id="5" scope="PRJ" qualifier="TRK" kee="my:key"
+ name="My project" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <snapshots depth="[null]" id="30" scope="PRJ" qualifier="TRK" created_at="2010-11-01 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="false"
+ path="[null]"/>
+
+ <snapshots depth="[null]" id="50" scope="PRJ" qualifier="TRK" created_at="2010-12-02 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="P" islast="true"
+ path="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/global-properties.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/global-properties.xml
new file mode 100644
index 00000000000..a0f67d7938d
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/global-properties.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <!-- another project -->
+ <projects long_name="[null]" id="3333" scope="PRJ" qualifier="TRK" kee="mygroup:anotherproject" name="[null]"
+ root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- global properties -->
+ <properties prop_key="key1" resource_id="[null]" text_value="value1"/>
+ <properties prop_key="key2" resource_id="[null]" text_value="value2"/>
+
+ <!-- another project properties -->
+ <properties prop_key="key1" resource_id="3333" text_value="overriden value1"/>
+ <properties prop_key="key3" resource_id="3333" text_value="value3"/>
+
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/modules-properties.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/modules-properties.xml
new file mode 100644
index 00000000000..899e5d2cc02
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/modules-properties.xml
@@ -0,0 +1,26 @@
+<dataset>
+
+ <projects long_name="[null]" id="100" scope="PRJ" qualifier="TRK" kee="mygroup:myproject" name="[null]"
+ root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="[null]" id="101" scope="PRJ" qualifier="BRC" kee="mygroup:mymodule" name="[null]"
+ root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- global properties -->
+ <properties prop_key="key1" resource_id="[null]" text_value="value_1"/>
+ <properties prop_key="key2" resource_id="[null]" text_value="value_2"/>
+
+ <!-- project properties -->
+ <properties prop_key="key1" resource_id="100" text_value="project_value_1"/>
+ <properties prop_key="key3" resource_id="100" text_value="project_value_3"/>
+
+ <!-- module properties -->
+ <properties prop_key="key3" resource_id="101" text_value="module_value_3"/>
+ <properties prop_key="key4" resource_id="101" text_value="module_value_4"/>
+
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/project-properties.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/project-properties.xml
new file mode 100644
index 00000000000..b8fa6538b72
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectConfigurationTest/project-properties.xml
@@ -0,0 +1,17 @@
+<dataset>
+
+ <projects long_name="[null]" id="100" scope="PRJ" qualifier="TRK" kee="mygroup:myproject" name="[null]"
+ root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- global properties -->
+ <properties prop_key="key1" resource_id="[null]" text_value="value1"/>
+ <properties prop_key="key2" resource_id="[null]" text_value="value2"/>
+
+ <!-- specific properties -->
+ <properties prop_key="key1" resource_id="100" text_value="overriden_value1"/>
+ <properties prop_key="key3" resource_id="100" text_value="value3"/>
+
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesBranch/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesBranch/pom.xml
new file mode 100644
index 00000000000..98ea71c1d09
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesBranch/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>project</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Project</name>
+ <properties>
+ <sonar.branch>BRANCH-1.X</sonar.branch>
+ </properties>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesDeprecatedBranch/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesDeprecatedBranch/pom.xml
new file mode 100644
index 00000000000..3fadbf40db1
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/keyIncludesDeprecatedBranch/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>project</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Project</name>
+ <properties>
+ <branch>BRANCH-1.X</branch>
+ </properties>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path1/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path1/pom.xml
new file mode 100644
index 00000000000..470f2d1f6e5
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path1/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module1</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path2/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path2/pom.xml
new file mode 100644
index 00000000000..88101678e0f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/path2/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module2</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/pom.xml
new file mode 100644
index 00000000000..afd92c0dbee
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameDifferentThanArtifactId/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <modules>
+ <module>path1</module>
+ <module>path2</module>
+ </modules>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module1/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module1/pom.xml
new file mode 100644
index 00000000000..470f2d1f6e5
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module1/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module1</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module2/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module2/pom.xml
new file mode 100644
index 00000000000..88101678e0f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/module2/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module2</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/pom.xml
new file mode 100644
index 00000000000..cc73a43ec08
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldEqualArtifactId/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <modules>
+ <module>module1</module>
+ <module>module2</module>
+ </modules>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path1/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path1/pom.xml
new file mode 100644
index 00000000000..470f2d1f6e5
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path1/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module1</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path2/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path2/pom.xml
new file mode 100644
index 00000000000..88101678e0f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/path2/pom.xml
@@ -0,0 +1,11 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>module2</artifactId>
+ <packaging>jar</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/pom.xml
new file mode 100644
index 00000000000..afd92c0dbee
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/moduleNameShouldNotEqualArtifactId/pom.xml
@@ -0,0 +1,12 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <modules>
+ <module>path1</module>
+ <module>path2</module>
+ </modules>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectHasNoModules/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectHasNoModules/pom.xml
new file mode 100644
index 00000000000..ffd40530c5d
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectHasNoModules/pom.xml
@@ -0,0 +1,8 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectWithoutModules/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectWithoutModules/pom.xml
new file mode 100644
index 00000000000..ffd40530c5d
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/ProjectTreeTest/singleProjectWithoutModules/pom.xml
@@ -0,0 +1,8 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.test</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/RemoteClassLoaderTest/foo.jar b/sonar-batch/src/test/resources/org/sonar/batch/RemoteClassLoaderTest/foo.jar
new file mode 100644
index 00000000000..c2bde4e5fff
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/RemoteClassLoaderTest/foo.jar
Binary files differ
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource-result.xml
new file mode 100644
index 00000000000..a8540058032
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource-result.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <projects long_name="My project" id="5" scope="PRJ" qualifier="TRK" kee="my:key"
+ name="My project" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+ <projects long_name="org.foo" id="6" scope="DIR" qualifier="PAC" kee="my:key:org.foo"
+ name="org.foo" root_id="5"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+
+
+ <snapshots depth="0" id="30" scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="U" islast="false"
+ path=""/>
+
+ <snapshots depth="1" id="31" scope="DIR" qualifier="PAC" created_at="2008-11-01 13:58:00.00" version="[null]"
+ project_id="6" parent_snapshot_id="30" root_project_id="5" root_snapshot_id="30" status="U" islast="false"
+ path="30."/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource.xml
new file mode 100644
index 00000000000..a248090db8c
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/createResource.xml
@@ -0,0 +1,12 @@
+<dataset>
+
+ <projects long_name="My project" id="5" scope="PRJ" qualifier="TRK" kee="my:key"
+ name="My project" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+ <snapshots depth="0" id="30" scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="U" islast="false"
+ path=""/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource-result.xml
new file mode 100644
index 00000000000..a8540058032
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource-result.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <projects long_name="My project" id="5" scope="PRJ" qualifier="TRK" kee="my:key"
+ name="My project" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+ <projects long_name="org.foo" id="6" scope="DIR" qualifier="PAC" kee="my:key:org.foo"
+ name="org.foo" root_id="5"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+
+
+ <snapshots depth="0" id="30" scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="U" islast="false"
+ path=""/>
+
+ <snapshots depth="1" id="31" scope="DIR" qualifier="PAC" created_at="2008-11-01 13:58:00.00" version="[null]"
+ project_id="6" parent_snapshot_id="30" root_project_id="5" root_snapshot_id="30" status="U" islast="false"
+ path="30."/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource.xml
new file mode 100644
index 00000000000..22b280f539d
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/DefaultPersisterTest/updateExistingResource.xml
@@ -0,0 +1,18 @@
+<dataset>
+
+ <projects long_name="My project" id="5" scope="PRJ" qualifier="TRK" kee="my:key"
+ name="My project" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+ <projects long_name="org.foo" id="6" scope="DIR" qualifier="PAC" kee="my:key:org.foo"
+ name="org.foo" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+
+ <snapshots depth="0" id="30" scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" version="[null]"
+ project_id="5" parent_snapshot_id="[null]" root_project_id="[null]" root_snapshot_id="[null]" status="U" islast="false"
+ path=""/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion-result.xml
new file mode 100644
index 00000000000..320b08ae0e2
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion-result.xml
@@ -0,0 +1,23 @@
+<dataset>
+
+ <projects long_name="my project" id="1" scope="PRJ" kee="my:project" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="Commons Lang" id="2" scope="PRJ" kee="commons-lang:commons-lang" qualifier="LIB" name="Commons Lang"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="[null]" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path="" depth="0"/>
+
+ <snapshots id="2" created_at="2010-05-18 17:00:00.00" version="1.1" project_id="2" scope="PRJ" qualifier="LIB"
+ root_project_id="2" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path="" depth="0"/>
+
+ <snapshots id="3" created_at="2010-05-18 17:00:00.00" version="1.2" project_id="2" scope="PRJ" qualifier="LIB"
+ root_project_id="2" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path="" depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion.xml
new file mode 100644
index 00000000000..ba9bcf884f6
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/addNewLibraryVersion.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <projects long_name="my project" id="1" scope="PRJ" kee="my:project" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="Commons Lang" id="2" scope="PRJ" kee="commons-lang:commons-lang" qualifier="LIB" name="Commons Lang"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="[null]" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path="" depth="0"/>
+
+ <snapshots id="2" created_at="2010-05-18 17:00:00.00" version="1.1" project_id="2" scope="PRJ" qualifier="LIB"
+ root_project_id="2" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path="" depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary-result.xml
new file mode 100644
index 00000000000..ba9bcf884f6
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary-result.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <projects long_name="my project" id="1" scope="PRJ" kee="my:project" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="Commons Lang" id="2" scope="PRJ" kee="commons-lang:commons-lang" qualifier="LIB" name="Commons Lang"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="[null]" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path="" depth="0"/>
+
+ <snapshots id="2" created_at="2010-05-18 17:00:00.00" version="1.1" project_id="2" scope="PRJ" qualifier="LIB"
+ root_project_id="2" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path="" depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary.xml
new file mode 100644
index 00000000000..6f57a77a8cd
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/createLibrary.xml
@@ -0,0 +1,11 @@
+<dataset>
+
+ <projects long_name="my project" id="1" scope="PRJ" kee="my:project" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path="" depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary-result.xml
new file mode 100644
index 00000000000..752dcc20f92
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary-result.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <projects long_name="my project" id="1" scope="PRJ" kee="my:project" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="Commons Lang" id="2" scope="PRJ" kee="commons-lang:commons-lang" qualifier="LIB" name="Commons Lang"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="[null]" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path="" depth="0"/>
+
+ <snapshots id="2" created_at="[null]" version="1.1" project_id="2" scope="PRJ" qualifier="LIB"
+ root_project_id="2" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path="" depth="0"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary.xml b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary.xml
new file mode 100644
index 00000000000..752dcc20f92
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/indexer/LibraryPersisterTest/reuseExistingLibrary.xml
@@ -0,0 +1,19 @@
+<dataset>
+
+ <projects long_name="my project" id="1" scope="PRJ" kee="my:project" qualifier="TRK" name="my project"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="Commons Lang" id="2" scope="PRJ" kee="commons-lang:commons-lang" qualifier="LIB" name="Commons Lang"
+ root_id="[null]"
+ description="[null]" enabled="true" profile_id="[null]" language="[null]" copy_resource_id="[null]"/>
+
+ <snapshots id="1" created_at="2008-12-25 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
+ root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="U" ISLAST="false"
+ path="" depth="0"/>
+
+ <snapshots id="2" created_at="[null]" version="1.1" project_id="2" scope="PRJ" qualifier="LIB"
+ root_project_id="2" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
+ path="" depth="0"/>
+
+</dataset> \ No newline at end of file