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 11:51:08 UTC

[jackrabbit-filevault] branch master updated: JCRVLT-567 provide standard validation context implementations (#176)

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

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git


The following commit(s) were added to refs/heads/master by this push:
     new ddcb4da  JCRVLT-567 provide standard validation context implementations (#176)
ddcb4da is described below

commit ddcb4da215e6beee51d27ad396b2421e6c09064d
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Thu Nov 4 12:50:38 2021 +0100

    JCRVLT-567 provide standard validation context implementations (#176)
---
 .../jackrabbit/vault/packaging/PackageId.java      |  30 +--
 .../jackrabbit/vault/packaging/package-info.java   |   2 +-
 .../vault/packaging/VersionRangeTest.java          |   5 +
 .../context/AbstractDependencyResolver.java        | 222 +++++++++++++++++++++
 .../context/ArchiveValidationContext.java          |  74 +++++++
 .../validation/context/DependencyResolver.java     |  40 ++++
 .../SubPackageInArchiveValidationContext.java      |  42 ++++
 .../vault/validation/context}/package-info.java    |   8 +-
 .../vault/validation/spi/ValidationContext.java    |  10 +-
 .../context/AbstractDependencyResolverTest.java    |  19 +-
 10 files changed, 427 insertions(+), 25 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/main/java/org/apache/jackrabbit/vault/packaging/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
index 1d4a580..87722e1 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("2.14.0")
+@Version("2.14.1")
 package org.apache.jackrabbit.vault.packaging;
 
 import org.osgi.annotation.versioning.Version;
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..d91c8ca
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolver.java
@@ -0,0 +1,222 @@
+/*
+ * 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;
+
+/**
+ * Abstract resolver supporting Maven repository dependency location URIs (starting with {@code maven}:).
+ * All package dependencies are mapped to Maven coordinates by this class and then resolved via {@link #resolvePackageInfo(MavenCoordinates)}.
+ * It comes with a cache so that the same package dependency is not resolved more than once.
+ * This class is not thread-safe.
+ */
+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 = new LinkedList<>(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;
+            // 1. try to get from cache
+            for (PackageInfo packageInfoFromCache : packageInfoCache) {
+                if (dependency.matches(packageInfoFromCache.getId())) {
+                    log.debug("Dependency is already resolved from project dependencies: {}", dependency);
+                    packageInfo = packageInfoFromCache;
+                }
+            }
+            // 2. try to resolve via provided dependency location URIs
+            if (packageInfo == null) {
+                for (Map.Entry<PackageId, URI> dependencyLocation : dependencyLocations.entrySet()) {
+                    if (dependency.matches(dependencyLocation.getKey())) {
+                        packageInfo = resolvePackageInfo(MavenCoordinates.parse(dependencyLocation.getValue()));
+                    }
+                }
+            }
+            // 3. try to apply some heuristics
+            if (packageInfo == null) {
+                packageInfo = resolvePackageInfo(dependency);
+            }
+            if (packageInfo != null) {
+                packageInfos.add(packageInfo);
+                if (packageInfoCache.contains(packageInfo)) {
+                    packageInfoCache.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;
+        }
+
+        public 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..3416804
--- /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 {@link ValidationContext} 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..fb3880a
--- /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;
+
+/** Resolve the main meta information of a package dependency. */
+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 dependencies 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 of {@code dependencies}).
+     * @throws IOException in case some error happened during resolving
+     */
+    public @NotNull Collection<PackageInfo> resolvePackageInfo(@NotNull Dependency[] dependencies, @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..ca14c5c
--- /dev/null
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/SubPackageInArchiveValidationContext.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Implements a {@link ValidationContext} based on a sub package given through an {@link Archive}.
+ */
+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-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/package-info.java
similarity index 81%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
copy to vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/package-info.java
index 1d4a580..0b7b1f4 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/context/package-info.java
@@ -14,8 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-@Version("2.14.0")
-package org.apache.jackrabbit.vault.packaging;
+/**
+ * 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-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolverTest.java
similarity index 52%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
copy to vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolverTest.java
index 1d4a580..ba39742 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/package-info.java
+++ b/vault-validation/src/test/java/org/apache/jackrabbit/vault/validation/context/AbstractDependencyResolverTest.java
@@ -14,8 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.jackrabbit.vault.validation.context;
 
-@Version("2.14.0")
-package org.apache.jackrabbit.vault.packaging;
+import java.net.URI;
+import java.net.URISyntaxException;
 
-import org.osgi.annotation.versioning.Version;
+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)));
+    }
+
+}