From 4603796ea1fbd7b9410c000bd738331a65dc4481 Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Fri, 5 Oct 2012 09:53:10 +0200 Subject: [PATCH] SONAR-3676 Create properties to define project links => even for non-Maven projects. The new properties are: - sonar.links.homepage - sonar.links.ci - sonar.links.issue - sonar.links.scm - sonar.links.scm_dev --- .../org/sonar/plugins/core/CorePlugin.java | 112 ++++++++++++++++++ .../core/sensors/ProjectLinksSensor.java | 55 ++++----- .../core/sensors/ProjectLinksSensorTest.java | 56 ++++----- .../sonar/batch/MavenProjectConverter.java | 61 ++++++++-- .../batch/MavenProjectConverterTest.java | 33 ++++++ .../projectWithLinks/pom.xml | 18 +++ .../projectWithLinksAndProperties/pom.xml | 27 +++++ .../java/org/sonar/api/CoreProperties.java | 25 ++++ 8 files changed, 314 insertions(+), 73 deletions(-) create mode 100644 sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinks/pom.xml create mode 100644 sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index c32b14de1c7..890a0d74e78 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -24,6 +24,7 @@ import org.sonar.api.CoreProperties; import org.sonar.api.Extension; import org.sonar.api.Properties; import org.sonar.api.Property; +import org.sonar.api.PropertyField; import org.sonar.api.PropertyType; import org.sonar.api.SonarPlugin; import org.sonar.api.checks.NoSonarFilter; @@ -126,6 +127,117 @@ import java.util.List; project = false, global = true, category = CoreProperties.CATEGORY_GENERAL), + @Property( + key = CoreProperties.LINKS_HOME_PAGE, + defaultValue = "", + name = "Project Home Page", + description = "HTTP URL of the home page of the project.", + project = false, + global = false, + category = CoreProperties.CATEGORY_GENERAL), + @Property( + key = CoreProperties.LINKS_CI, + defaultValue = "", + name = "CI server", + description = "HTTP URL of the continuous integration server.", + project = false, + global = false, + category = CoreProperties.CATEGORY_GENERAL), + @Property( + key = CoreProperties.LINKS_ISSUE_TRACKER, + defaultValue = "", + name = "Issue Tracker", + description = "HTTP URL of the issue tracker.", + project = false, + global = false, + category = CoreProperties.CATEGORY_GENERAL), + @Property( + key = CoreProperties.LINKS_SOURCES, + defaultValue = "", + name = "SCM server", + description = "HTTP URL of the server which hosts the sources of the project.", + project = false, + global = false, + category = CoreProperties.CATEGORY_GENERAL), + @Property( + key = CoreProperties.LINKS_SOURCES_DEV, + defaultValue = "", + name = "SCM connection for developers", + description = "HTTP URL used by developers to connect to the SCM server for the project.", + project = false, + global = false, + category = CoreProperties.CATEGORY_GENERAL), + @Property( + key = "sonar.test.jira.servers", + name = "Jira Servers", + description = "List of jira server definitions", + global = true, + project = true, + category = "DEV", + fields = { + @PropertyField( + key = "key", + name = "Key", + type = PropertyType.STRING, + indicativeSize = 10), + @PropertyField( + key = "url", + name = "Url", + description = "l'url du serveur jira", + type = PropertyType.STRING, + indicativeSize = 20), + @PropertyField( + key = "port", + name = "Port", + type = PropertyType.INTEGER, + indicativeSize = 5)}), + @Property( + key = "sonar.demo", + name = "Demo", + global = true, + project = true, + category = "DEV", + fields = { + @PropertyField( + key = "text", + name = "text", + type = PropertyType.TEXT), + @PropertyField( + key = "boolean", + name = "boolean", + type = PropertyType.BOOLEAN), + @PropertyField( + key = "float", + name = "float", + type = PropertyType.FLOAT), + @PropertyField( + key = "license", + name = "license", + type = PropertyType.LICENSE), + @PropertyField( + key = "metric", + name = "metric", + type = PropertyType.METRIC), + @PropertyField( + key = "password", + name = "password", + type = PropertyType.PASSWORD), + @PropertyField( + key = "regexp", + name = "regexp", + type = PropertyType.REGULAR_EXPRESSION), + @PropertyField( + key = "list", + name = "list", + type = PropertyType.SINGLE_SELECT_LIST, + options = {"AAA", "BBB"})}), + @Property( + key = "sonar.test.jira", + name = "Jira", + project = true, + category = "DEV", + type = PropertyType.PROPERTY_SET, + propertySetKey = "sonar.test.jira.servers"), @Property( key = CoreProperties.PROJECT_LANGUAGE_PROPERTY, defaultValue = Java.KEY, diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProjectLinksSensor.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProjectLinksSensor.java index a5aed475704..406b067ab28 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProjectLinksSensor.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProjectLinksSensor.java @@ -20,29 +20,24 @@ package org.sonar.plugins.core.sensors; import org.apache.commons.lang.StringUtils; -import org.apache.maven.model.CiManagement; -import org.apache.maven.model.IssueManagement; -import org.apache.maven.model.Scm; -import org.apache.maven.project.MavenProject; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.Sensor; import org.sonar.api.batch.SensorContext; -import org.sonar.api.batch.SupportedEnvironment; +import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; import org.sonar.api.resources.ProjectLink; +import org.sonar.core.i18n.I18nManager; -@SupportedEnvironment("maven") -public class ProjectLinksSensor implements Sensor { +import java.util.Locale; - public static final String KEY_HOME = "homepage"; - public static final String KEY_CONTINUOUS_INTEGRATION = "ci"; - public static final String KEY_ISSUE_TRACKER = "issue"; - public static final String KEY_SCM = "scm"; - public static final String KEY_SCM_DEVELOPER_CONNECTION = "scm_dev"; +public class ProjectLinksSensor implements Sensor { - private MavenProject pom; + private Settings settings; + private I18nManager i18nManager; - public ProjectLinksSensor(MavenProject pom) { - this.pom = pom; + public ProjectLinksSensor(Settings settings, I18nManager i18nManager) { + this.settings = settings; + this.i18nManager = i18nManager; } public boolean shouldExecuteOnProject(Project project) { @@ -50,26 +45,18 @@ public class ProjectLinksSensor implements Sensor { } public void analyse(Project project, SensorContext context) { - updateLink(context, KEY_HOME, "Home", pom.getUrl()); - - Scm scm = pom.getScm(); - if (scm == null) { - scm = new Scm(); - } - updateLink(context, KEY_SCM, "Sources", scm.getUrl()); - updateLink(context, KEY_SCM_DEVELOPER_CONNECTION, "Developer connection", scm.getDeveloperConnection()); - - CiManagement ci = pom.getCiManagement(); - if (ci == null) { - ci = new CiManagement(); - } - updateLink(context, KEY_CONTINUOUS_INTEGRATION, "Continuous integration", ci.getUrl()); + handleLink(context, CoreProperties.LINKS_HOME_PAGE); + handleLink(context, CoreProperties.LINKS_CI); + handleLink(context, CoreProperties.LINKS_ISSUE_TRACKER); + handleLink(context, CoreProperties.LINKS_SOURCES); + handleLink(context, CoreProperties.LINKS_SOURCES_DEV); + } - IssueManagement issues = pom.getIssueManagement(); - if (issues == null) { - issues = new IssueManagement(); - } - updateLink(context, KEY_ISSUE_TRACKER, "Issues", issues.getUrl()); + private void handleLink(SensorContext context, String linkProperty) { + String home = settings.getString(linkProperty); + String linkType = StringUtils.substringAfterLast(linkProperty, "."); + String name = i18nManager.message(Locale.getDefault(), "project_links." + linkType, linkProperty); + updateLink(context, linkType, name, home); } private void updateLink(SensorContext context, String key, String name, String url) { diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProjectLinksSensorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProjectLinksSensorTest.java index 0fd9f9505fb..f87147e55e2 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProjectLinksSensorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProjectLinksSensorTest.java @@ -20,64 +20,64 @@ package org.sonar.plugins.core.sensors; import org.apache.commons.lang.StringUtils; -import org.apache.maven.project.MavenProject; import org.junit.Test; import org.mockito.ArgumentMatcher; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.SensorContext; +import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; import org.sonar.api.resources.ProjectLink; -import org.sonar.api.test.MavenTestUtils; +import org.sonar.core.i18n.I18nManager; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import java.util.Locale; + +import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; public class ProjectLinksSensorTest { + @Test + public void testToString() { + assertThat(new ProjectLinksSensor(null, null).toString()).isEqualTo("ProjectLinksSensor"); + } + @Test public void shouldExecuteOnlyForLatestAnalysis() { - MavenProject pom = mock(MavenProject.class); Project project = mock(Project.class); when(project.isLatestAnalysis()).thenReturn(true).thenReturn(false); - assertThat(new ProjectLinksSensor(pom).shouldExecuteOnProject(project), is(true)); - assertThat(new ProjectLinksSensor(pom).shouldExecuteOnProject(project), is(false)); - verify(project, times(2)).isLatestAnalysis(); - verifyNoMoreInteractions(project); + assertThat(new ProjectLinksSensor(null, null).shouldExecuteOnProject(project)).isTrue(); + assertThat(new ProjectLinksSensor(null, null).shouldExecuteOnProject(project)).isFalse(); } @Test public void shouldSaveLinks() { - SensorContext context = mock(SensorContext.class); - MavenProject pom = MavenTestUtils.loadPom("/org/sonar/plugins/core/sensors/ProjectLinksSensorTest/shouldSaveLinks.xml"); + Settings settings = new Settings(); + settings.setProperty(CoreProperties.LINKS_HOME_PAGE, "http://home"); + I18nManager i18nManager = mock(I18nManager.class); + when(i18nManager.message(Locale.getDefault(), "project_links.homepage", CoreProperties.LINKS_HOME_PAGE)).thenReturn("HOME"); Project project = mock(Project.class); + SensorContext context = mock(SensorContext.class); - new ProjectLinksSensor(pom).analyse(project, context); + new ProjectLinksSensor(settings, i18nManager).analyse(project, context); - verify(context).saveLink(argThat(new MatchLink(ProjectLinksSensor.KEY_HOME, "Home", "http://sonar.codehaus.org"))); - verify(context).saveLink(argThat(new MatchLink(ProjectLinksSensor.KEY_ISSUE_TRACKER, "Issues", "http://jira.codehaus.org/browse/SONAR"))); - verify(context).saveLink(argThat(new MatchLink(ProjectLinksSensor.KEY_CONTINUOUS_INTEGRATION, "Continuous integration", "http://bamboo.ci.codehaus.org/browse/SONAR/"))); - verify(context).saveLink(argThat(new MatchLink(ProjectLinksSensor.KEY_SCM, "Sources", "http://svn.sonar.codehaus.org"))); - verify(context).saveLink(argThat(new MatchLink(ProjectLinksSensor.KEY_SCM_DEVELOPER_CONNECTION, "Developer connection", "scm:svn:https://svn.codehaus.org/sonar/trunk"))); + verify(context).saveLink(argThat(new MatchLink("homepage", "HOME", "http://home"))); } @Test - public void shouldDeleteMissingLinks() { - SensorContext context = mock(SensorContext.class); - MavenProject pom = MavenTestUtils.loadPom("/org/sonar/plugins/core/sensors/ProjectLinksSensorTest/shouldDeleteMissingLinks.xml"); + public void shouldDeleteLink() { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.LINKS_HOME_PAGE, ""); + I18nManager i18nManager = mock(I18nManager.class); + when(i18nManager.message(Locale.getDefault(), "project_links.homepage", CoreProperties.LINKS_HOME_PAGE)).thenReturn("HOME"); Project project = mock(Project.class); + SensorContext context = mock(SensorContext.class); - new ProjectLinksSensor(pom).analyse(project, context); + new ProjectLinksSensor(settings, i18nManager).analyse(project, context); - verify(context).deleteLink(ProjectLinksSensor.KEY_HOME); - verify(context).deleteLink(ProjectLinksSensor.KEY_ISSUE_TRACKER); - verify(context).deleteLink(ProjectLinksSensor.KEY_CONTINUOUS_INTEGRATION); - verify(context).deleteLink(ProjectLinksSensor.KEY_SCM); - verify(context).deleteLink(ProjectLinksSensor.KEY_SCM_DEVELOPER_CONNECTION); + verify(context).deleteLink("homepage"); } private class MatchLink extends ArgumentMatcher { diff --git a/sonar-batch/src/main/java/org/sonar/batch/MavenProjectConverter.java b/sonar-batch/src/main/java/org/sonar/batch/MavenProjectConverter.java index a05c3c5dc11..1c25beabf6a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/MavenProjectConverter.java +++ b/sonar-batch/src/main/java/org/sonar/batch/MavenProjectConverter.java @@ -19,21 +19,27 @@ */ package org.sonar.batch; -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; - +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; +import org.apache.maven.model.CiManagement; +import org.apache.maven.model.IssueManagement; +import org.apache.maven.model.Scm; import org.apache.maven.project.MavenProject; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.utils.SonarException; -import com.google.common.collect.Maps; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Properties; public final class MavenProjectConverter { private static final String UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE = "Unable to determine structure of project." + - " Probably you use Maven Advanced Reactor Options, which is not supported by Sonar and should not be used."; + " Probably you use Maven Advanced Reactor Options, which is not supported by Sonar and should not be used."; public static ProjectDefinition convert(List poms, MavenProject root) { Map paths = Maps.newHashMap(); // projects by canonical path to pom.xml @@ -78,24 +84,57 @@ public final class MavenProjectConverter { return rootProject; } - /** - * Visibility has been relaxed for tests. - */ + @VisibleForTesting static ProjectDefinition convert(MavenProject pom) { String key = new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString(); ProjectDefinition definition = ProjectDefinition.create(); // IMPORTANT NOTE : reference on properties from POM model must not be saved, instead they should be copied explicitly - see SONAR-2896 + Properties properties = pom.getModel().getProperties(); + convertMavenLinksToProperties(pom, properties); definition - .setProperties(pom.getModel().getProperties()) + .setProperties(properties) .setKey(key) .setVersion(pom.getVersion()) .setName(pom.getName()) .setDescription(pom.getDescription()) .addContainerExtension(pom); synchronizeFileSystem(pom, definition); + return definition; } + /** + * For SONAR-3676 + */ + private static void convertMavenLinksToProperties(MavenProject pom, Properties properties) { + setPropertyIfNotAlreadyExists(properties, CoreProperties.LINKS_HOME_PAGE, pom.getUrl()); + + Scm scm = pom.getScm(); + if (scm == null) { + scm = new Scm(); + } + setPropertyIfNotAlreadyExists(properties, CoreProperties.LINKS_SOURCES, scm.getUrl()); + setPropertyIfNotAlreadyExists(properties, CoreProperties.LINKS_SOURCES_DEV, scm.getDeveloperConnection()); + + CiManagement ci = pom.getCiManagement(); + if (ci == null) { + ci = new CiManagement(); + } + setPropertyIfNotAlreadyExists(properties, CoreProperties.LINKS_CI, ci.getUrl()); + + IssueManagement issues = pom.getIssueManagement(); + if (issues == null) { + issues = new IssueManagement(); + } + setPropertyIfNotAlreadyExists(properties, CoreProperties.LINKS_ISSUE_TRACKER, issues.getUrl()); + } + + private static void setPropertyIfNotAlreadyExists(Properties properties, String propertyKey, String propertyValue) { + if (StringUtils.isBlank(properties.getProperty(propertyKey))) { + properties.setProperty(propertyKey, StringUtils.defaultString(propertyValue)); + } + } + public static void synchronizeFileSystem(MavenProject pom, ProjectDefinition into) { into.setBaseDir(pom.getBasedir()); into.setWorkDir(new File(resolvePath(pom.getBuild().getDirectory(), pom.getBasedir()), "sonar")); diff --git a/sonar-batch/src/test/java/org/sonar/batch/MavenProjectConverterTest.java b/sonar-batch/src/test/java/org/sonar/batch/MavenProjectConverterTest.java index 38abbf99fd0..91c77da73fa 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/MavenProjectConverterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/MavenProjectConverterTest.java @@ -37,6 +37,7 @@ import java.net.URISyntaxException; import java.util.Arrays; import java.util.Properties; +import static org.fest.assertions.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertNull; @@ -150,6 +151,38 @@ public class MavenProjectConverterTest { assertThat(rootDef.getBaseDir(), is(rootDir)); } + @Test + public void shouldConvertLinksToProperties() throws Exception { + MavenProject pom = loadPom("/org/sonar/batch/MavenProjectConverterTest/projectWithLinks/pom.xml", true); + + ProjectDefinition rootDef = MavenProjectConverter.convert(Arrays.asList(pom), pom); + + Properties props = rootDef.getProperties(); + assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com"); + assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com"); + assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com"); + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com"); + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com"); + } + + @Test + public void shouldNotConvertLinksToPropertiesIfPropertyAlreadyDefined() throws Exception { + MavenProject pom = loadPom("/org/sonar/batch/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml", true); + + ProjectDefinition rootDef = MavenProjectConverter.convert(Arrays.asList(pom), pom); + + Properties props = rootDef.getProperties(); + + // Those properties have been fed by the POM elements , , ... + assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com"); + assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com"); + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com"); + + // ... but those ones have been overridden by in the POM + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com-OVERRIDEN-BY-PROPS"); + assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com-OVERRIDEN-BY-PROPS"); + } + private MavenProject loadPom(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))); diff --git a/sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinks/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinks/pom.xml new file mode 100644 index 00000000000..460e8967e5c --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinks/pom.xml @@ -0,0 +1,18 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + http://home.com + + http://ci.com + + + http://issues.com + + + http://sources.com + http://sources-dev.com + + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml new file mode 100644 index 00000000000..5b024e5c4a7 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + http://home.com + + http://ci.com + + + http://issues.com + + + http://sources.com + http://sources-dev.com + + + + + + http://home.com-OVERRIDEN-BY-PROPS + http://sources.com-OVERRIDEN-BY-PROPS + + + + \ No newline at end of file diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java index c6e91e815fc..c72cd3781ab 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java @@ -301,4 +301,29 @@ public interface CoreProperties { * @since 2.11 */ String SERVER_ID_IP_ADDRESS = "sonar.server_id.ip_address"; + + /** + * @since 3.3 + */ + String LINKS_HOME_PAGE = "sonar.links.homepage"; + + /** + * @since 3.3 + */ + String LINKS_CI = "sonar.links.ci"; + + /** + * @since 3.3 + */ + String LINKS_ISSUE_TRACKER = "sonar.links.issue"; + + /** + * @since 3.3 + */ + String LINKS_SOURCES = "sonar.links.scm"; + + /** + * @since 3.3 + */ + String LINKS_SOURCES_DEV = "sonar.links.scm_dev"; } -- 2.39.5