From 9c7f6daf75b4559ca5126b60d3ff240160cd694a Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 13 May 2017 08:37:22 -0400 Subject: [PATCH 1/4] updated groovy version to allow the use of newer APIs in the build scripts --- dependency-check-maven/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dependency-check-maven/pom.xml b/dependency-check-maven/pom.xml index ec672c570..8f03b9bb0 100644 --- a/dependency-check-maven/pom.xml +++ b/dependency-check-maven/pom.xml @@ -227,6 +227,13 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved. org.apache.maven.plugins maven-invoker-plugin 2.0.0 + + + org.codehaus.groovy + groovy-all + 2.4.11 + + From 523eed9319e8ef2e08f5098c5dd9367486687a90 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 13 May 2017 08:38:43 -0400 Subject: [PATCH 2/4] resolved issue #686 - reports are generated even if no dependencies were analyzed --- .../dependencycheck/maven/CheckMojo.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java index b566cc306..cb95628db 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java @@ -108,24 +108,23 @@ public class CheckMojo extends BaseDependencyCheckMojo { } exCol = ex; } - if (exCol == null || !exCol.isFatal()) { - try { - writeReports(engine, getProject(), getCorrectOutputDirectory()); - } catch (ReportException ex) { - if (this.isFailOnError()) { - if (exCol != null) { - exCol.addException(ex); - } else { - exCol = new ExceptionCollection("Unable to write the dependency-check report", ex); - } + } + if (exCol == null || !exCol.isFatal()) { + try { + writeReports(engine, getProject(), getCorrectOutputDirectory()); + } catch (ReportException ex) { + if (this.isFailOnError()) { + if (exCol != null) { + exCol.addException(ex); + } else { + exCol = new ExceptionCollection("Unable to write the dependency-check report", ex); } } - //writeDataFile(getProject(), null, engine.getDependencies()); - showSummary(getProject(), engine.getDependencies()); - checkForFailure(engine.getDependencies()); - if (exCol != null && this.isFailOnError()) { - throw new MojoExecutionException("One or more exceptions occurred during dependency-check analysis", exCol); - } + } + showSummary(getProject(), engine.getDependencies()); + checkForFailure(engine.getDependencies()); + if (exCol != null && this.isFailOnError()) { + throw new MojoExecutionException("One or more exceptions occurred during dependency-check analysis", exCol); } } engine.cleanup(); From 555b1dc1cc9a9e781e205e4c3f1252f56b612701 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sat, 13 May 2017 08:40:08 -0400 Subject: [PATCH 3/4] resolution for enhancement #729 --- .../it/729-system-scope/invoker.properties | 19 +++++++++++ .../src/it/729-system-scope/pom.xml | 34 +++++++++++++++++++ .../src/it/729-system-scope/postbuild.groovy | 30 ++++++++++++++++ .../src/it/729-system-scope/prebuild.groovy | 17 ++++++++++ .../maven/BaseDependencyCheckMojo.java | 12 ++++++- .../src/site/markdown/configuration.md | 3 +- 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 dependency-check-maven/src/it/729-system-scope/invoker.properties create mode 100644 dependency-check-maven/src/it/729-system-scope/pom.xml create mode 100644 dependency-check-maven/src/it/729-system-scope/postbuild.groovy create mode 100644 dependency-check-maven/src/it/729-system-scope/prebuild.groovy diff --git a/dependency-check-maven/src/it/729-system-scope/invoker.properties b/dependency-check-maven/src/it/729-system-scope/invoker.properties new file mode 100644 index 000000000..b41bc60f9 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/invoker.properties @@ -0,0 +1,19 @@ +# +# This file is part of dependency-check-maven. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (c) 2014 Jeremy Long. All Rights Reserved. +# + +invoker.goals = install ${project.groupId}:${project.artifactId}:${project.version}:check -DskipSystemScope=true -Dformat=JSON diff --git a/dependency-check-maven/src/it/729-system-scope/pom.xml b/dependency-check-maven/src/it/729-system-scope/pom.xml new file mode 100644 index 000000000..6f2d06950 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + org.owasp.test + test-system-scope + 1.0.0-SNAPSHOT + jar + + + system + com.sun + tools + 1.8 + ${java.home}/../lib/tools.jar + + + \ No newline at end of file diff --git a/dependency-check-maven/src/it/729-system-scope/postbuild.groovy b/dependency-check-maven/src/it/729-system-scope/postbuild.groovy new file mode 100644 index 000000000..335aaa589 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/postbuild.groovy @@ -0,0 +1,30 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2017 Jeremy Long. All Rights Reserved. + */ + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import java.nio.charset.Charset; +import groovy.json.JsonSlurper; + +def slurper = new JsonSlurper() +def json = slurper.parse(new File(basedir, "target/dependency-check-report.json"), "UTF-8") + +assert json instanceof Map +assert json.analysis.dependencies instanceof List +assert json.analysis.dependencies.size()==0 +return true; diff --git a/dependency-check-maven/src/it/729-system-scope/prebuild.groovy b/dependency-check-maven/src/it/729-system-scope/prebuild.groovy new file mode 100644 index 000000000..9eff4bb5c --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope/prebuild.groovy @@ -0,0 +1,17 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2014 Jeremy Long. All Rights Reserved. + */ diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 1a923a698..741e832a3 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -403,6 +403,13 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma @SuppressWarnings("CanBeFinal") @Parameter(property = "skipProvidedScope", defaultValue = "false", required = false) private boolean skipProvidedScope = false; + + /** + * Skip Analysis for Provided Scope Dependencies. + */ + @SuppressWarnings("CanBeFinal") + @Parameter(property = "skipSystemScope", defaultValue = "false", required = false) + private boolean skipSystemScope = false; /** * The data directory, hold DC SQL DB. */ @@ -631,10 +638,10 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma List nodes, ProjectBuildingRequest buildingRequest) { ExceptionCollection exCol = null; for (DependencyNode dependencyNode : nodes) { - exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); if (excludeFromScan(dependencyNode.getArtifact().getScope())) { continue; } + exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); try { final ArtifactCoordinate coordinate = TransferUtils.toArtifactCoordinate(dependencyNode.getArtifact()); final Artifact result = artifactResolver.resolveArtifact(buildingRequest, coordinate).getArtifact(); @@ -963,6 +970,9 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma if (skipProvidedScope && org.apache.maven.artifact.Artifact.SCOPE_PROVIDED.equals(scope)) { return true; } + if (skipSystemScope && org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(scope)) { + return true; + } return skipRuntimeScope && !org.apache.maven.artifact.Artifact.SCOPE_RUNTIME.equals(scope); } diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md index c43075f2e..9f1518976 100644 --- a/dependency-check-maven/src/site/markdown/configuration.md +++ b/dependency-check-maven/src/site/markdown/configuration.md @@ -23,9 +23,10 @@ format | The report format to be generated (HTML, XML, VULN name | The name of the report in the site. | dependency-check or dependency-check:aggregate outputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build. | 'target' skip | Skips the dependency-check analysis. | false -skipTestScope | Skip analysis for artifacts with Test Scope. | true skipProvidedScope | Skip analysis for artifacts with Provided Scope. | false skipRuntimeScope | Skip analysis for artifacts with Runtime Scope. | false +skipSystemScope | Skip analysis for artifacts with System Scope. | false +skipTestScope | Skip analysis for artifacts with Test Scope. | true suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html). |   hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html). |   enableExperimental | Enable the [experimental analyzers](../analyzers/index.html). If not enabled the experimental analyzers (see below) will not be loaded or used. | false From e218b8ad707f451fcc668ca6a4c947676405e2d3 Mon Sep 17 00:00:00 2001 From: Jeremy Long Date: Sun, 14 May 2017 07:45:55 -0400 Subject: [PATCH 4/4] added attempt to resolve system scoped dependency with test cases --- .../DependencyNotFoundException.java | 66 ++++++++++++++++++ .../invoker.properties | 19 ++++++ .../pom.xml | 0 .../postbuild.groovy | 30 +++++++++ .../729-system-scope-resolved/prebuild.groovy | 17 +++++ .../invoker.properties | 0 .../src/it/729-system-scope-skipped/pom.xml | 34 ++++++++++ .../postbuild.groovy | 0 .../prebuild.groovy | 0 .../maven/BaseDependencyCheckMojo.java | 67 +++++++++++++++++-- 10 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java create mode 100644 dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-resolved}/pom.xml (100%) create mode 100644 dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy create mode 100644 dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-skipped}/invoker.properties (100%) create mode 100644 dependency-check-maven/src/it/729-system-scope-skipped/pom.xml rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-skipped}/postbuild.groovy (100%) rename dependency-check-maven/src/it/{729-system-scope => 729-system-scope-skipped}/prebuild.groovy (100%) diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java new file mode 100644 index 000000000..3406b9900 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/exception/DependencyNotFoundException.java @@ -0,0 +1,66 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2017 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.exception; + +/** + * An exception used when a dependency could not be found. + * + * @author Jeremy Long + */ +public class DependencyNotFoundException extends Exception { + + /** + * The serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a new DependencyNotFoundException. + */ + public DependencyNotFoundException() { + super(); + } + + /** + * Creates a new DependencyNotFoundException. + * + * @param msg a message for the exception. + */ + public DependencyNotFoundException(String msg) { + super(msg); + } + + /** + * Creates a new DependencyNotFoundException. + * + * @param ex the cause of the exception. + */ + public DependencyNotFoundException(Throwable ex) { + super(ex); + } + + /** + * Creates a new DependencyNotFoundException. + * + * @param msg a message for the exception. + * @param ex the cause of the exception. + */ + public DependencyNotFoundException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties b/dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties new file mode 100644 index 000000000..135fb86f1 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-resolved/invoker.properties @@ -0,0 +1,19 @@ +# +# This file is part of dependency-check-maven. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (c) 2017 Jeremy Long. All Rights Reserved. +# + +invoker.goals = install ${project.groupId}:${project.artifactId}:${project.version}:check -e -Dformat=JSON diff --git a/dependency-check-maven/src/it/729-system-scope/pom.xml b/dependency-check-maven/src/it/729-system-scope-resolved/pom.xml similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/pom.xml rename to dependency-check-maven/src/it/729-system-scope-resolved/pom.xml diff --git a/dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy b/dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy new file mode 100644 index 000000000..c1d6476e4 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-resolved/postbuild.groovy @@ -0,0 +1,30 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2017 Jeremy Long. All Rights Reserved. + */ + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import java.nio.charset.Charset; +import groovy.json.JsonSlurper; + +def slurper = new JsonSlurper() +def json = slurper.parse(new File(basedir, "target/dependency-check-report.json"), "UTF-8") + +assert json instanceof Map +assert json.analysis.dependencies instanceof List +assert json.analysis.dependencies.size()==1 +return true; diff --git a/dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy b/dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy new file mode 100644 index 000000000..9ec3a0a91 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-resolved/prebuild.groovy @@ -0,0 +1,17 @@ +/* + * This file is part of dependency-check-maven. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2017 Jeremy Long. All Rights Reserved. + */ diff --git a/dependency-check-maven/src/it/729-system-scope/invoker.properties b/dependency-check-maven/src/it/729-system-scope-skipped/invoker.properties similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/invoker.properties rename to dependency-check-maven/src/it/729-system-scope-skipped/invoker.properties diff --git a/dependency-check-maven/src/it/729-system-scope-skipped/pom.xml b/dependency-check-maven/src/it/729-system-scope-skipped/pom.xml new file mode 100644 index 000000000..6f2d06950 --- /dev/null +++ b/dependency-check-maven/src/it/729-system-scope-skipped/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + org.owasp.test + test-system-scope + 1.0.0-SNAPSHOT + jar + + + system + com.sun + tools + 1.8 + ${java.home}/../lib/tools.jar + + + \ No newline at end of file diff --git a/dependency-check-maven/src/it/729-system-scope/postbuild.groovy b/dependency-check-maven/src/it/729-system-scope-skipped/postbuild.groovy similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/postbuild.groovy rename to dependency-check-maven/src/it/729-system-scope-skipped/postbuild.groovy diff --git a/dependency-check-maven/src/it/729-system-scope/prebuild.groovy b/dependency-check-maven/src/it/729-system-scope-skipped/prebuild.groovy similarity index 100% rename from dependency-check-maven/src/it/729-system-scope/prebuild.groovy rename to dependency-check-maven/src/it/729-system-scope-skipped/prebuild.groovy diff --git a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java index 741e832a3..3c1e89fd0 100644 --- a/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java +++ b/dependency-check-maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java @@ -55,6 +55,7 @@ import org.owasp.dependencycheck.dependency.Confidence; import org.owasp.dependencycheck.dependency.Dependency; import org.owasp.dependencycheck.dependency.Identifier; import org.owasp.dependencycheck.dependency.Vulnerability; +import org.owasp.dependencycheck.exception.DependencyNotFoundException; import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.reporting.ReportGenerator; @@ -643,16 +644,45 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma } exCol = collectDependencies(engine, project, dependencyNode.getChildren(), buildingRequest); try { - final ArtifactCoordinate coordinate = TransferUtils.toArtifactCoordinate(dependencyNode.getArtifact()); - final Artifact result = artifactResolver.resolveArtifact(buildingRequest, coordinate).getArtifact(); - if (result.isResolved() && result.getFile() != null) { - final List deps = engine.scan(result.getFile().getAbsoluteFile(), + boolean isResolved = false; + File artifactFile = null; + String artifactId = null; + String groupId = null; + String version = null; + if (org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dependencyNode.getArtifact().getScope())) { + for (org.apache.maven.model.Dependency d : project.getDependencies()) { + Artifact a = dependencyNode.getArtifact(); + if (d.getSystemPath() != null && artifactsMatch(d, a)) { + + artifactFile = new File(d.getSystemPath()); + isResolved = artifactFile.isFile(); + groupId = a.getGroupId(); + artifactId = a.getArtifactId(); + version = a.getVersion(); + break; + } + } + if (!isResolved) { + getLog().error("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString()); + exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString())); + } + } else { + final ArtifactCoordinate coordinate = TransferUtils.toArtifactCoordinate(dependencyNode.getArtifact()); + final Artifact result = artifactResolver.resolveArtifact(buildingRequest, coordinate).getArtifact(); + isResolved = result.isResolved(); + artifactFile = result.getFile(); + groupId = result.getGroupId(); + artifactId = result.getArtifactId(); + version = result.getVersion(); + } + if (isResolved && artifactFile != null) { + final List deps = engine.scan(artifactFile.getAbsoluteFile(), project.getName() + ":" + dependencyNode.getArtifact().getScope()); if (deps != null) { if (deps.size() == 1) { final Dependency d = deps.get(0); if (d != null) { - final MavenArtifact ma = new MavenArtifact(result.getGroupId(), result.getArtifactId(), result.getVersion()); + final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version); d.addAsEvidence("pom", ma, Confidence.HIGHEST); if (getLog().isDebugEnabled()) { getLog().debug(String.format("Adding project reference %s on dependency %s", @@ -690,6 +720,33 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma return exCol; } + /** + * Determines if the groupId, artifactId, and version of the Maven + * dependency and artifact match. + * + * @param d the Maven dependency + * @param a the Maven artifact + * @return true if the groupId, artifactId, and version match + */ + private static boolean artifactsMatch(org.apache.maven.model.Dependency d, Artifact a) { + return (isEqualOrNull(a.getArtifactId(), d.getArtifactId())) + && (isEqualOrNull(a.getGroupId(), d.getGroupId())) + && (isEqualOrNull(a.getVersion(), d.getVersion())); + } + + /** + * Compares two strings for equality; if both strings are null they are + * considered equal. + * + * @param left the first string to compare + * @param right the second string to compare + * @return true if the strings are equal or if they are both null; otherwise + * false. + */ + private static boolean isEqualOrNull(String left, String right) { + return (left != null && left.equals(right)) || (left == null && right == null); + } + /** * @return Returns a new ProjectBuildingRequest populated from the current * session and the current project remote repositories, used to resolve