You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2021/11/04 08:45:18 UTC

[jackrabbit-filevault] branch feature/JCRVLT-567-provide-standard-validationcontexts created (now 3d0477f)

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a change to branch feature/JCRVLT-567-provide-standard-validationcontexts
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git.


      at 3d0477f  JCRVLT-567 provide standard validation context implementations

This branch includes the following new commits:

     new 3d0477f  JCRVLT-567 provide standard validation context implementations

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[jackrabbit-filevault] 01/01: JCRVLT-567 provide standard validation context implementations

Posted by kw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/JCRVLT-567-provide-standard-validationcontexts
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git

commit 3d0477ff21b2439adab864602cc5a1a79998477f
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Mon Oct 25 20:29:33 2021 +0200

    JCRVLT-567 provide standard validation context implementations
---
 .../jackrabbit/vault/packaging/PackageId.java      |  30 +--
 .../vault/packaging/VersionRangeTest.java          |   5 +
 .../context/AbstractDependencyResolver.java        | 212 +++++++++++++++++++++
 .../context/ArchiveValidationContext.java          |  74 +++++++
 .../validation/context/DependencyResolver.java     |  40 ++++
 .../SubPackageInArchiveValidationContext.java      |  39 ++++
 .../vault/validation/context/package-info.java     |  23 +++
 .../vault/validation/spi/ValidationContext.java    |  10 +-
 .../context/AbstractDependencyResolverTest.java    |  34 ++++
 9 files changed, 449 insertions(+), 18 deletions(-)

diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageId.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageId.java
index 4afd3de..e51b414 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageId.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/PackageId.java
@@ -18,13 +18,16 @@
 package org.apache.jackrabbit.vault.packaging;
 
 import org.apache.jackrabbit.util.XMLChar;
+import org.jetbrains.annotations.NotNull;
 import org.apache.jackrabbit.util.Text;
 
 /**
  * {@code PackageId} provides the basic metrics for identifying a package.
- * A package id consists of a group id, a name and a version.
- * the group is a relative path, eg: "company/project/subgroup", the name and the version
+ * A package id consists of a group, a name and an optional version.
+ * The group is a relative path, eg: "company/project/subgroup", the name and the version
  * can be of any format.
+ * <p>
+ * The string representation is {@code <group>:<name>[:<version>]}.
  */
 public class PackageId implements Comparable<PackageId> {
 
@@ -223,7 +226,7 @@ public class PackageId implements Comparable<PackageId> {
     }
 
     /**
-     * Returns a package id from a id string. if the given id is null or an
+     * Returns a package id from an id string in the format {@code <group>:<name>[:<version>]}. If the given id is null or an
      * empty string, {@code null} is returned.
      * @param str the string
      * @return the package id
@@ -243,9 +246,10 @@ public class PackageId implements Comparable<PackageId> {
     }
 
     /**
-     * Returns an array of package id from strings
+     * Returns an array of package id from strings.
      * @param str the strings
      * @return the array of package ids
+     * @see #fromString(String)
      */
     public static PackageId[] fromString(String ... str) {
         PackageId[] ret = new PackageId[str.length];
@@ -256,7 +260,7 @@ public class PackageId implements Comparable<PackageId> {
     }
 
     /**
-     * Creates a comma separated list of id strings.
+     * Creates a comma separated list of id strings in the format {@code <group>:<name>[:<version>]}.
      * @param packs the ids
      * @return the string
      */
@@ -306,8 +310,8 @@ public class PackageId implements Comparable<PackageId> {
     }
 
     /**
-     * Returns the group id of this package
-     * @return the group id;
+     * Returns the group of this package
+     * @return the group.
      * @since 2.2
      */
     public String getGroup() {
@@ -315,10 +319,10 @@ public class PackageId implements Comparable<PackageId> {
     }
 
     /**
-     * Returns the name of this package which is the last segment of the path.
+     * Returns the name of this package (usually this is the last segment of the path).
      * @return the name of this package.
      */
-    public String getName() {
+    public @NotNull String getName() {
         return name;
     }
 
@@ -327,7 +331,7 @@ public class PackageId implements Comparable<PackageId> {
      * @return the version of this package
      * @since 2.0
      */
-    public String getVersionString() {
+    public @NotNull String getVersionString() {
         return version.toString();
     }
 
@@ -347,15 +351,15 @@ public class PackageId implements Comparable<PackageId> {
     }
 
     /**
-     * Returns the version of this package or {@code null} if n/a.
+     * Returns the version of this package or {@code Version.EMPTY} if not set.
      * @return the version of this package
      */
-    public Version getVersion() {
+    public @NotNull Version getVersion() {
         return version;
     }
 
     /**
-     * Returns a string representation of this id
+     * Returns a string representation of this id in the format {@code <group>:<name>[:<version>]}.
      */
     @Override
     public String toString() {
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/VersionRangeTest.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/VersionRangeTest.java
index 89b5447..981237a 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/VersionRangeTest.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/VersionRangeTest.java
@@ -100,6 +100,11 @@ public class VersionRangeTest extends TestCase {
         assertFalse("(1.0,2.0) excludes 2.1", vr.isInRange(v21));
     }
 
+    public void testRangeWithEmptyVersionParameter() {
+        VersionRange vr = new VersionRange(v1, true, v2, true);
+        assertFalse("[1.0,2.0] excludes empty version", vr.isInRange(Version.EMPTY));
+    }
+
     public void testRangeSnapshots() {
         VersionRange vr = VersionRange.fromString("[1.0,2.0)");
         assertTrue("[1.0,2.0) includes 1.0-SNAPSHOT", vr.isInRange(v1s));
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolver.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolver.java
new file mode 100644
index 0000000..c42e94f
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolver.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.jackrabbit.vault.validation.context;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.PackageInfo;
+import org.apache.jackrabbit.vault.packaging.VersionRange;
+import org.apache.maven.artifact.Artifact;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractDependencyResolver implements DependencyResolver {
+
+    /**
+     * the default logger
+     */
+    private static final Logger log = LoggerFactory.getLogger(AbstractDependencyResolver.class);
+
+    public static final String MAVEN_REPOSITORY_SCHEME = "maven";
+    
+    private final Collection<PackageInfo> packageInfoCache;
+
+    protected AbstractDependencyResolver(Collection<PackageInfo> packageInfoCache) {
+        this.packageInfoCache = packageInfoCache;
+    }
+
+    @Override
+    public Collection<PackageInfo> resolvePackageInfo(Dependency[] dependencies, Map<PackageId, URI> dependencyLocations) throws IOException {
+        List<PackageInfo> packageInfos = new LinkedList<>();
+        // resolve dependencies
+        for (Dependency dependency : dependencies) {
+            PackageInfo packageInfo = null;
+            // is it already resolved?
+            for (PackageInfo knownPackageInfo : packageInfoCache) {
+                if (dependency.matches(knownPackageInfo.getId())) {
+                    log.debug("Dependency is already resolved from project dependencies: {}", dependency);
+                    packageInfo = knownPackageInfo;
+                }
+            }
+            if (packageInfo == null) {
+                for (Map.Entry<PackageId, URI> dependencyLocation : dependencyLocations.entrySet()) {
+                    if (dependency.matches(dependencyLocation.getKey())) {
+                        packageInfo = resolvePackageInfo(MavenCoordinates.parse(dependencyLocation.getValue()));
+                    }
+                }
+            }
+            if (packageInfo == null) {
+                packageInfo = resolvePackageInfo(dependency);
+            }
+            if (packageInfo != null) {
+                packageInfos.add(packageInfo);
+            }
+        }
+        return packageInfos;
+    }
+
+    /**
+     * Use some heuristics to map the package dependency to Maven coordinates and try to resolve them then via {@link #resolvePackageInfo(MavenCoordinates)}.
+     * @param dependency
+     * @return the resolved package info or {@code null}
+     * @throws IOException
+     */
+    private @Nullable PackageInfo resolvePackageInfo(Dependency dependency) throws IOException {
+        // resolving a version range is not supported with Maven API, but only with lower level Aether API (requires Maven 3.5 or newer)
+        // https://github.com/eclipse/aether-demo/blob/master/aether-demo-snippets/src/main/java/org/eclipse/aether/examples/FindAvailableVersions.java
+        // therefore do an best effort resolve instead
+
+        final String groupId = dependency.getGroup();
+        final String artifactId = dependency.getName();
+        PackageInfo info = null;
+        if (dependency.getRange().isLowInclusive()) {
+            info = resolvePackageInfo(new MavenCoordinates(groupId, artifactId, dependency.getRange().getLow().toString()));
+        }
+        if (info == null && dependency.getRange().isHighInclusive()) {
+            info = resolvePackageInfo(new MavenCoordinates(groupId, artifactId, dependency.getRange().getHigh().toString()));
+        }
+        if (info == null && VersionRange.INFINITE.equals(dependency.getRange())) {
+            info = resolvePackageInfo(new MavenCoordinates(groupId, artifactId, Artifact.LATEST_VERSION));
+        }
+        if (info == null) {
+            return null;
+        }
+        return info;
+    }
+
+    public abstract @Nullable PackageInfo resolvePackageInfo(MavenCoordinates mavenCoordinates) throws IOException;
+
+    public static final class MavenCoordinates {
+        @NotNull private final String groupId;
+        @NotNull private final String artifactId;
+        @Nullable private final String version;
+        @NotNull private final String packaging;
+        @Nullable private final String classifier;
+
+        private static final String DEFAULT_PACKAGING = "zip";
+
+        public MavenCoordinates(String groupId, String artifactId, String version) {
+            this(groupId, artifactId, version, DEFAULT_PACKAGING, null);
+        }
+
+        public MavenCoordinates(String groupId, String artifactId, String version, String packaging, String classifier) {
+            super();
+            this.groupId = groupId;
+            this.artifactId = artifactId;
+            this.version = version;
+            this.packaging = packaging;
+            this.classifier = classifier;
+        }
+
+        static MavenCoordinates parse(URI uri) {
+            if (!MAVEN_REPOSITORY_SCHEME.equals(uri.getScheme())) {
+                return null;
+            }
+            if (!uri.isOpaque()) {
+                throw new IllegalArgumentException("Only opaque Maven URIs are supported");
+            }
+            // support groupId, artifactId, packaging and classifier (format like https://maven.apache.org/plugins/maven-dependency-plugin/get-mojo.html#artifact)
+            // extract group id and artifact id
+            String[] parts = uri.getSchemeSpecificPart().split(":");
+            if (parts.length < 2) {
+                throw new IllegalArgumentException("At least group id and artifact id need to be given separated by ':'");
+            }
+            String groupId = parts[0];
+            String artifactId = parts[1];
+            String version = null;
+            if (parts.length > 2) {
+                version = parts[2];
+            }
+            String packaging = DEFAULT_PACKAGING;
+            if (parts.length > 3) {
+                packaging = parts[3];
+            }
+            String classifier = null;
+            if (parts.length > 4) {
+                classifier = parts[4];
+            }
+            return new MavenCoordinates(groupId, artifactId, version, packaging, classifier);
+        }
+
+        public @NotNull String getGroupId() {
+            return groupId;
+        }
+
+        public @NotNull String getArtifactId() {
+            return artifactId;
+        }
+
+        public @Nullable String getVersion() {
+            return version;
+        }
+
+        public @NotNull String getPackaging() {
+            return packaging;
+        }
+
+        public @Nullable String getClassifier() {
+            return classifier;
+        }
+
+        @Override
+        public String toString() {
+            return "MavenCoordinates [" + (groupId != null ? "groupId=" + groupId + ", " : "")
+                    + (artifactId != null ? "artifactId=" + artifactId + ", " : "") + (version != null ? "version=" + version + ", " : "")
+                    + (packaging != null ? "packaging=" + packaging + ", " : "") + (classifier != null ? "classifier=" + classifier : "")
+                    + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(artifactId, classifier, groupId, packaging, version);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            MavenCoordinates other = (MavenCoordinates) obj;
+            return Objects.equals(artifactId, other.artifactId) && Objects.equals(classifier, other.classifier)
+                    && Objects.equals(groupId, other.groupId) && Objects.equals(packaging, other.packaging)
+                    && Objects.equals(version, other.version);
+        }
+    }
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/ArchiveValidationContext.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/ArchiveValidationContext.java
new file mode 100644
index 0000000..5675fb1
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/ArchiveValidationContext.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.jackrabbit.vault.validation.context;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.packaging.PackageInfo;
+import org.apache.jackrabbit.vault.packaging.PackageProperties;
+import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
+
+
+/**
+ * Implements a validation context based on a given {@link Archive}.
+ */
+public class ArchiveValidationContext implements ValidationContext {
+
+    private final WorkspaceFilter filter;
+    private final PackageProperties properties;
+    private final Path archivePath;
+    private final Collection<PackageInfo> resolvedDependencies;
+
+    public ArchiveValidationContext(Archive archive, Path archivePath, DependencyResolver resolver) throws IOException {
+        this.archivePath = archivePath;
+        properties = archive.getMetaInf().getPackageProperties();
+        this.filter = archive.getMetaInf().getFilter();
+        if (filter == null) {
+            throw new IllegalStateException("Archive '" + archivePath + "' does not contain a filter.xml.");
+        }
+        this.resolvedDependencies = resolver.resolvePackageInfo(getProperties().getDependencies(), getProperties().getDependenciesLocations());
+    }
+
+    @Override
+    public PackageProperties getProperties() {
+        return properties;
+    }
+
+    @Override
+    public WorkspaceFilter getFilter() {
+        return filter;
+    }
+
+    @Override
+    public ValidationContext getContainerValidationContext() {
+        return null;
+    }
+
+    public Path getPackageRootPath() {
+        return archivePath;
+    }
+
+    @Override
+    public Collection<PackageInfo> getDependenciesPackageInfo() {
+        return this.resolvedDependencies;
+    }
+
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/DependencyResolver.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/DependencyResolver.java
new file mode 100644
index 0000000..220a0d5
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/DependencyResolver.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.jackrabbit.vault.validation.context;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.jackrabbit.vault.packaging.Dependency;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.PackageInfo;
+import org.jetbrains.annotations.NotNull;
+
+/** Allows to resolves the main meta information of a package dependency from a repository. */
+public interface DependencyResolver {
+
+    /**
+     * Resolves multiple package dependencies taking into account a map from package id to URI (given via <a href="http://jackrabbit.apache.org/filevault/properties.html">package properties {@code dependencies-locations}</a>).
+     * @param packageDependencies the dependencies to resolve
+     * @param dependencyLocations a map of package ids to URIs
+     * @return the list of {@link PackageInfo} for all resolved dependencies (as this is only a best effort implementation the list being returned might be smaller than the array size given in {@code packageDependencies}).
+     * @throws IOException
+     */
+    public @NotNull Collection<PackageInfo> resolvePackageInfo(@NotNull Dependency[] packageDependencies, @NotNull Map<PackageId, URI> dependencyLocations) throws IOException;
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/SubPackageInArchiveValidationContext.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/SubPackageInArchiveValidationContext.java
new file mode 100644
index 0000000..399498f
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/SubPackageInArchiveValidationContext.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.jackrabbit.vault.validation.context;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.validation.spi.ValidationContext;
+
+public class SubPackageInArchiveValidationContext extends ArchiveValidationContext {
+
+    private final ValidationContext containerPackageContext;
+
+    public SubPackageInArchiveValidationContext(ArchiveValidationContext containerPackageContext, Archive archive, Path archivePath, DependencyResolver resolver) throws IOException {
+        super(archive, archivePath, resolver);
+        this.containerPackageContext = containerPackageContext;
+    }
+
+    @Override
+    public ValidationContext getContainerValidationContext() {
+        return containerPackageContext;
+    }
+
+}
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/package-info.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/package-info.java
new file mode 100644
index 0000000..0b7b1f4
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/**
+ * Provides common implementations of {@link org.apache.jackrabbit.vault.validation.spi.ValidationContext}
+ */
+@Version("1.0.0")
+package org.apache.jackrabbit.vault.validation.context;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/ValidationContext.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/ValidationContext.java
index 0b09161..f4e2c54 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/ValidationContext.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/ValidationContext.java
@@ -27,8 +27,8 @@ import org.jetbrains.annotations.Nullable;
 import org.osgi.annotation.versioning.ProviderType;
 
 /**
- * The validation context encapsulates information about the package for which the validation is  being triggered.
- *
+ * The validation context encapsulates information about the package for which the validation is triggered.
+ * This class is used from both validators (SPI) and validation API but for historical reasons is located in (wrong) package {@code org.apache.jackrabbit.vault.validation.spi}.
  */
 @ProviderType
 public interface ValidationContext {
@@ -50,13 +50,13 @@ public interface ValidationContext {
      * @return the validation context of the container in case this is the context of a sub package otherwise {@code null}.
      */
     @Nullable ValidationContext getContainerValidationContext();
-    
+
     /**
      * Returns the root path of the package.
      * @return either the path to the ZIP file or a directory containing an exploded package.
      */
     @NotNull Path getPackageRootPath();
-    
+
     /**
      * PackageInfo for all resolved package dependencies.
      * In contrast to {@link PackageProperties#getDependencies()} the resolved dependencies also
@@ -64,7 +64,7 @@ public interface ValidationContext {
      * @return the package info of all resolved package dependencies (i.e. the ones for which an artifact was found).
      */
     @NotNull Collection<PackageInfo> getDependenciesPackageInfo();
-    
+
     /**
      * 
      * @return {@code true} in case the validation is incremental (i.e. does not cover all files in a package). This should relax some validations.
diff --git a/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolverTest.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolverTest.java
new file mode 100644
index 0000000..ba39742
--- /dev/null
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolverTest.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.jackrabbit.vault.validation.context;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.jackrabbit.vault.validation.context.AbstractDependencyResolver.MavenCoordinates;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AbstractDependencyResolverTest {
+
+    @Test
+    public void testUriToMavenCoordinates() throws URISyntaxException {
+        Assert.assertEquals(new MavenCoordinates("group1", "name1", null), MavenCoordinates.parse(new URI("maven", "group1:name1", null)));
+        Assert.assertEquals(new MavenCoordinates("group1", "name1", "1.0.0", "test", "myclassifier"), MavenCoordinates.parse(new URI("maven", "group1:name1:1.0.0:test:myclassifier", null)));
+    }
+
+}