diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2014-09-08 22:12:13 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2014-09-08 22:12:13 +0200 |
commit | cc3cabc3e7aa9c1467d2994ef78ae38c16f2e0dd (patch) | |
tree | 0a7a08c7e184fa2e5488ee06536a2e5cad67551c /plugins | |
parent | b8a4470a8cbcfc430fcde54614f7cf31708e48d9 (diff) | |
download | sonarqube-cc3cabc3e7aa9c1467d2994ef78ae38c16f2e0dd.tar.gz sonarqube-cc3cabc3e7aa9c1467d2994ef78ae38c16f2e0dd.zip |
SONAR-5604 Design plugin should no more rely on Maven APIs
Diffstat (limited to 'plugins')
3 files changed, 242 insertions, 26 deletions
diff --git a/plugins/sonar-design-plugin/pom.xml b/plugins/sonar-design-plugin/pom.xml index d7aef6c0223..f04d435231f 100644 --- a/plugins/sonar-design-plugin/pom.xml +++ b/plugins/sonar-design-plugin/pom.xml @@ -28,13 +28,21 @@ <artifactId>maven-dependency-tree</artifactId> <scope>provided</scope> </dependency> - + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </dependency> + <!-- unit tests --> <dependency> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar-testing-harness</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + </dependency> </dependencies> <build> diff --git a/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java b/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java index 088b3d182b7..9afa425a0e4 100644 --- a/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java +++ b/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java @@ -19,6 +19,15 @@ */ package org.sonar.plugins.design.batch; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.metadata.ArtifactMetadataSource; @@ -34,28 +43,42 @@ import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeV import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor; import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor; import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor; -import org.sonar.api.batch.SupportedEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.batch.Sensor; import org.sonar.api.batch.SensorContext; import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.SupportedEnvironment; +import org.sonar.api.config.Settings; import org.sonar.api.design.Dependency; import org.sonar.api.resources.Library; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.utils.SonarException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + @SupportedEnvironment("maven") public class MavenDependenciesSensor implements Sensor { + private static final String SONAR_MAVEN_PROJECT_DEPENDENCY = "sonar.maven.projectDependencies"; + + private static final Logger LOG = LoggerFactory.getLogger(MavenDependenciesSensor.class); + private ArtifactRepository localRepository; private ArtifactFactory artifactFactory; private ArtifactMetadataSource artifactMetadataSource; private ArtifactCollector artifactCollector; private DependencyTreeBuilder treeBuilder; private SonarIndex index; + private Settings settings; - public MavenDependenciesSensor(ArtifactRepository localRepository, ArtifactFactory artifactFactory, ArtifactMetadataSource artifactMetadataSource, - ArtifactCollector artifactCollector, DependencyTreeBuilder treeBuilder, SonarIndex index) { + public MavenDependenciesSensor(Settings settings, ArtifactRepository localRepository, ArtifactFactory artifactFactory, ArtifactMetadataSource artifactMetadataSource, + ArtifactCollector artifactCollector, DependencyTreeBuilder treeBuilder, SonarIndex index) { + this.settings = settings; this.localRepository = localRepository; this.artifactFactory = artifactFactory; this.artifactMetadataSource = artifactMetadataSource; @@ -64,42 +87,153 @@ public class MavenDependenciesSensor implements Sensor { this.treeBuilder = treeBuilder; } + /** + * Used with SQ Maven plugin 2.5+ + */ + public MavenDependenciesSensor(Settings settings, SonarIndex index) { + this.settings = settings; + this.index = index; + } + public boolean shouldExecuteOnProject(Project project) { return true; } - public void analyse(final Project project, final SensorContext context) { - try { - DependencyNode root = treeBuilder.buildDependencyTree(project.getPom(), localRepository, artifactFactory, artifactMetadataSource, null, artifactCollector); + private static class InputDependency { + + private final String key; + + private final String version; + + private String scope; + + List<InputDependency> dependencies = new ArrayList<InputDependency>(); + + public InputDependency(String key, String version) { + this.key = key; + this.version = version; + } + + public String key() { + return key; + } + + public String version() { + return version; + } + + public String scope() { + return scope; + } + + public InputDependency setScope(String scope) { + this.scope = scope; + return this; + } + + public List<InputDependency> dependencies() { + return dependencies; + } + } + + private static class DependencyDeserializer implements JsonDeserializer<InputDependency> { - DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor(new DependencyNodeVisitor() { - public boolean visit(DependencyNode node) { - return true; + @Override + public InputDependency deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + + JsonObject dep = json.getAsJsonObject(); + String key = dep.get("k").getAsString(); + String version = dep.get("v").getAsString(); + InputDependency result = new InputDependency(key, version); + result.setScope(dep.get("s").getAsString()); + JsonElement subDeps = dep.get("d"); + if (subDeps != null) { + JsonArray arrayOfSubDeps = subDeps.getAsJsonArray(); + for (JsonElement e : arrayOfSubDeps) { + result.dependencies().add(deserialize(e, typeOfT, context)); } + } + return result; + } + + } + + public void analyse(final Project project, final SensorContext context) { + if (settings.hasKey(SONAR_MAVEN_PROJECT_DEPENDENCY)) { + LOG.debug("Using dependency provided by property " + SONAR_MAVEN_PROJECT_DEPENDENCY); + String depsAsJson = settings.getString(SONAR_MAVEN_PROJECT_DEPENDENCY); + Collection<InputDependency> deps; + try { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(InputDependency.class, new DependencyDeserializer()); + Gson gson = gsonBuilder.create(); - public boolean endVisit(DependencyNode node) { - if (node.getParent() != null && node.getParent() != node) { - saveDependency(node, context); + Type collectionType = new TypeToken<Collection<InputDependency>>() { + }.getType(); + deps = gson.fromJson(depsAsJson, collectionType); + saveDependencies(project, deps, context); + } catch (Exception e) { + throw new IllegalStateException("Unable to deserialize dependency information: " + depsAsJson, e); + } + } + else if (treeBuilder != null) { + LOG.warn("Computation of Maven dependencies by SonarQube is deprecated. Please update the version of SonarQube Maven plugin to 2.5+"); + try { + DependencyNode root = treeBuilder.buildDependencyTree(project.getPom(), localRepository, artifactFactory, artifactMetadataSource, null, artifactCollector); + + DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor(new DependencyNodeVisitor() { + public boolean visit(DependencyNode node) { + return true; } - return true; - } - }); - // mode verbose OFF : do not show the same lib many times - DependencyNodeFilter filter = StateDependencyNodeFilter.INCLUDED; + public boolean endVisit(DependencyNode node) { + if (node.getParent() != null && node.getParent() != node) { + saveDependency(node, context); + } + return true; + } + }); + + // mode verbose OFF : do not show the same lib many times + DependencyNodeFilter filter = StateDependencyNodeFilter.INCLUDED; + + CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor(); + DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter); + root.accept(firstPassVisitor); - CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor(); - DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter); - root.accept(firstPassVisitor); + DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes()); + visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter); - DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes()); - visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter); + root.accept(visitor); + + } catch (DependencyTreeBuilderException e) { + throw new SonarException("Can not load the graph of dependencies of the project " + project.getKey(), e); + } + } + } - root.accept(visitor); + private void saveDependencies(Resource from, Collection<InputDependency> deps, SensorContext context) { + for (InputDependency inputDep : deps) { + Resource to = toResource(inputDep, context); + Dependency dependency = new Dependency(from, to); + dependency.setUsage(inputDep.scope()); + dependency.setWeight(1); + context.saveDependency(dependency); + if (!inputDep.dependencies().isEmpty()) { + saveDependencies(to, inputDep.dependencies(), context); + } + } + } - } catch (DependencyTreeBuilderException e) { - throw new SonarException("Can not load the graph of dependencies of the project " + project.getKey(), e); + private Resource toResource(InputDependency dependency, SensorContext context) { + Project project = new Project(dependency.key()); + Resource result = context.getResource(project); + if (result == null || !((Project) result).getAnalysisVersion().equals(dependency.version())) { + Library lib = new Library(project.getKey(), dependency.version()); + context.saveResource(lib); + result = context.getResource(lib); } + return result; } protected void saveDependency(DependencyNode node, SensorContext context) { diff --git a/plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java b/plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java new file mode 100644 index 00000000000..a0adb1a03fb --- /dev/null +++ b/plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java @@ -0,0 +1,74 @@ +package org.sonar.plugins.design.batch; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.config.Settings; +import org.sonar.api.design.Dependency; +import org.sonar.api.resources.Library; +import org.sonar.api.resources.Project; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class MavenDependenciesSensorTest { + + private MavenDependenciesSensor sensor; + private Settings settings; + private SonarIndex sonarIndex; + private SensorContext sensorContext; + + @Before + public void prepare() { + settings = new Settings(); + sonarIndex = mock(SonarIndex.class); + sensor = new MavenDependenciesSensor(settings, sonarIndex); + sensorContext = mock(SensorContext.class); + when(sensorContext.getResource(any(Library.class))).thenAnswer(new Answer<Library>() { + public Library answer(InvocationOnMock invocation) throws Throwable { + return (invocation.getArguments()[0] instanceof Library) ? (Library) invocation.getArguments()[0] : null; + } + }); + + } + + @Test + public void testDependenciesProvidedAsProperties() { + settings + .setProperty( + "sonar.maven.projectDependencies", + "[{\"k\":\"antlr:antlr\",\"v\":\"2.7.2\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-beanutils:commons-beanutils\",\"v\":\"1.7.0\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-chain:commons-chain\",\"v\":\"1.1\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-digester:commons-digester\",\"v\":\"1.8\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-fileupload:commons-fileupload\",\"v\":\"1.1.1\",\"s\":\"compile\",\"d\":[{\"k\":\"commons-io:commons-io\",\"v\":\"1.1\",\"s\":\"compile\",\"d\":[]}]}," + + "{\"k\":\"commons-logging:commons-logging\",\"v\":\"1.0.4\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-validator:commons-validator\",\"v\":\"1.3.1\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"javax.servlet:servlet-api\",\"v\":\"2.3\",\"s\":\"provided\",\"d\":[]}," + + "{\"k\":\"junit:junit\",\"v\":\"3.8.1\",\"s\":\"test\",\"d\":[]}," + + "{\"k\":\"oro:oro\",\"v\":\"2.0.8\",\"s\":\"compile\",\"d\":[]}]"); + + Project project = new Project("foo"); + sensor.analyse(project, sensorContext); + + Library antlr = new Library("antlr:antlr", "2.7.2"); + verify(sensorContext).saveResource(eq(antlr)); + Library commonsFU = new Library("commons-fileupload:commons-fileupload", "1.1.1"); + verify(sensorContext).saveResource(eq(commonsFU)); + Library commonsIo = new Library("commons-io:commons-io", "1.1"); + verify(sensorContext).saveResource(eq(commonsIo)); + Library junit = new Library("junit:junit", "3.8.1"); + verify(sensorContext).saveResource(eq(junit)); + + verify(sensorContext).saveDependency(new Dependency(project, antlr).setUsage("compile").setWeight(1)); + verify(sensorContext).saveDependency(new Dependency(commonsFU, commonsIo).setUsage("compile").setWeight(1)); + verify(sensorContext).saveDependency(new Dependency(project, junit).setUsage("test").setWeight(1)); + } + +} |