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/10/14 07:23:40 UTC

[jackrabbit-filevault-package-maven-plugin] branch master updated: JCRVLT-564 fix default value for validator jackrabbit-packagetype (#62)

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-package-maven-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new 84e3d89  JCRVLT-564 fix default value for validator jackrabbit-packagetype (#62)
84e3d89 is described below

commit 84e3d8947d3bb268fb119017d708354fb94e3782
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Thu Oct 14 09:23:34 2021 +0200

    JCRVLT-564 fix default value for validator jackrabbit-packagetype (#62)
    
    merge validator settings for the same id correctly (from most specific
    to most generic key)
---
 .../maven/packaging/AbstractValidateMojo.java      | 118 +++++++++++-----
 .../maven/packaging/ValidatePackageMojo.java       |   8 +-
 .../maven/packaging/ValidatorSettings.java         |  56 ++++----
 .../maven/packaging/ValidatorSettingsKey.java      | 155 +++++++++++++++++++++
 .../maven/packaging/AbstractValidateMojoTest.java  |  17 ++-
 .../maven/packaging/ValidatorSettingsKeyTest.java  |  62 +++++++++
 .../maven/packaging/ValidatorSettingsTest.java     |  44 ++++++
 7 files changed, 391 insertions(+), 69 deletions(-)

diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java
index be332fd..7de12e7 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojo.java
@@ -18,12 +18,16 @@ package org.apache.jackrabbit.filevault.maven.packaging;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -40,6 +44,7 @@ import org.apache.jackrabbit.vault.validation.ValidationExecutorFactory;
 import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
 import org.apache.jackrabbit.vault.validation.spi.impl.AdvancedFilterValidatorFactory;
 import org.apache.jackrabbit.vault.validation.spi.impl.DependencyValidatorFactory;
+import org.apache.jackrabbit.vault.validation.spi.impl.PackageTypeValidatorFactory;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.DefaultArtifact;
 import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
@@ -54,6 +59,7 @@ import org.apache.maven.plugins.annotations.Component;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.repository.RepositorySystem;
+import org.osgi.framework.Version;
 import org.sonatype.plexus.build.incremental.BuildContext;
 
 /**
@@ -68,7 +74,7 @@ public abstract class AbstractValidateMojo extends AbstractMojo {
     /** All validator settings in a map. The keys are the validator ids (optionally suffixed by {@code :<package group>:<package name>} to be restricted to certain packages).
      * You can use {@code *} as wildcard value for {@code package group}.
      * Alternatively you can use the suffix {@code :subpackages} to influence the settings for all sub packages only!
-     * The values are a complex object of type ValdidatorSettings.
+     * The values are a complex object of type {@link ValidatorSettings}.
      * An example configuration looks like
      * <pre>
      *  &lt;jackrabbit-filter&gt;
@@ -77,6 +83,17 @@ public abstract class AbstractValidateMojo extends AbstractMojo {
      *      &lt;/options&gt;
      *  &lt;/jackrabbit-filter&gt;
      * </pre>
+     * 
+     * Each validator settings consists of the fields {@code isDisabled}, {@code defaultSeverity} and {@code options}.
+     * 
+     * As potentially multiple map entries may affect the same validator id (due to different suffixes) the settings for a single validator id are merged in the order from more specific to more generic keys:
+     * <ol>
+     * <li>settings for a specific groupId and artifactId</li>
+     * <li>settings for any groupId (*) and a specific artifactId</li>
+     * <li>settings for subpackages</li>
+     * <li>settings without restrictions</li>
+     * </ol>
+     * Merging will only overwrite non-existing fields, i.e. same-named options from more specific keys will overwrite those from more generic keys (for the same validator id).
      */
     @Parameter
     private Map<String, ValidatorSettings> validatorsSettings;
@@ -182,6 +199,9 @@ public abstract class AbstractValidateMojo extends AbstractMojo {
      * Artificial Maven artifact which indicates that it should not be considered for further lookup!
      */
     public static final Artifact IGNORE_ARTIFACT = new DefaultArtifact("ignore", "ignore", "1.0", "", "", "", null);
+    
+    private static Version fileVaultValidationBundleVersion = null;
+    private final static Version VERSION_3_5_4 = Version.parseVersion("3.5.4");
 
     protected String getProjectRelativeFilePath(Path file) {
         return "'" + project.getBasedir().toPath().relativize(file).toString() + "'";
@@ -219,6 +239,11 @@ public abstract class AbstractValidateMojo extends AbstractMojo {
             return;
         }
         translateLegacyParametersToValidatorParameters();
+        try {
+            fixWrongDefaultForValidatorParameters();
+        } catch (IOException e) {
+            getLog().error("Could not fix the default values of validators because retrieving the FileVault validation bundle version failed: " + e.getMessage(), e);
+        }
         final Collection<PackageInfo> resolvedDependencies = new LinkedList<>();
         
         // repository structure only defines valid roots
@@ -301,7 +326,8 @@ public abstract class AbstractValidateMojo extends AbstractMojo {
             }
 
             // do no fully disable but emit violations with level DEBUG
-            ValidatorSettings dependencyValidatorSettings = new ValidatorSettings(ValidationMessageSeverity.DEBUG);
+            ValidatorSettings dependencyValidatorSettings = new ValidatorSettings();
+            dependencyValidatorSettings.setDefaultSeverity(ValidationMessageSeverity.DEBUG);
             validatorsSettings.put(DependencyValidatorFactory.ID, dependencyValidatorSettings);
 
             ValidatorSettings filterValidatorSettings = validatorsSettings.containsKey(AdvancedFilterValidatorFactory.ID) ? validatorsSettings.get(AdvancedFilterValidatorFactory.ID) : new ValidatorSettings();
@@ -313,51 +339,69 @@ public abstract class AbstractValidateMojo extends AbstractMojo {
         }
     }
 
+    // https://issues.apache.org/jira/browse/JCRVLT-564
+    // TODO: remove once depending on FileVault 3.5.5 or newer
+    private void fixWrongDefaultForValidatorParameters() throws IOException {
+        if (getFileVaultValidationBundleVersion().equals(VERSION_3_5_4)) {
+            if (validatorsSettings == null) {
+                validatorsSettings = new HashMap<>();
+            }
+            ValidatorSettings packageTypeValidatorSettings = validatorsSettings.get("jackrabbit-packagetype");
+            if (packageTypeValidatorSettings == null) {
+                packageTypeValidatorSettings = new ValidatorSettings();
+                validatorsSettings.put("jackrabbit-packagetype", packageTypeValidatorSettings);
+            }
+            if (!packageTypeValidatorSettings.getOptions().containsKey(PackageTypeValidatorFactory.OPTION_JCR_INSTALLER_ADDITIONAL_FILE_NODE_PATH_REGEX)) {
+                packageTypeValidatorSettings.addOption(PackageTypeValidatorFactory.OPTION_JCR_INSTALLER_ADDITIONAL_FILE_NODE_PATH_REGEX, ".*\\.(config|cfg|cfg\\.json|jar)");
+                getLog().info("Overriding wrong default value for validator option '" + PackageTypeValidatorFactory.OPTION_JCR_INSTALLER_ADDITIONAL_FILE_NODE_PATH_REGEX + "' (see https://issues.apache.org/jira/browse/JCRVLT-564)");
+            }
+        }
+    }
+
+    static synchronized Version getFileVaultValidationBundleVersion() throws IOException {
+        if (fileVaultValidationBundleVersion == null) {
+            URL url = AbstractValidateMojo.class.getClassLoader().getResource("org/apache/jackrabbit/vault/validation/ValidationExecutor.class");
+            if (url == null) {
+                throw new IllegalStateException("This classloader does not see the ValidationExecutor class from FileVault Validation");
+            }
+            URLConnection connection =  url.openConnection();
+            if (connection instanceof JarURLConnection) {
+                fileVaultValidationBundleVersion = Version.parseVersion(((JarURLConnection)connection).getMainAttributes().getValue("Bundle-Version"));
+            } else {
+                fileVaultValidationBundleVersion = Version.emptyVersion;
+            }
+        }
+        return fileVaultValidationBundleVersion;
+    }
+
     public abstract void doExecute(ValidationHelper validationHelper) throws MojoExecutionException, MojoFailureException;
 
-    protected Map<String, ValidatorSettings> getValidatorSettingsForPackage(PackageId packageId, boolean isSubPackage) {
-        return getValidatorSettingsForPackage(getLog(), validatorsSettings, packageId, isSubPackage);
+    protected Map<String, ValidatorSettings> getValidatorSettingsForPackage(PackageId packageId, boolean isSubPackage) throws MojoFailureException {
+        try {
+            return getValidatorSettingsForPackage(getLog(), validatorsSettings, packageId, isSubPackage);
+        }
+        catch (IllegalArgumentException e) {
+            throw new MojoFailureException("Invalid value for 'validatorsSettings': " + e.getMessage(), e);
+        }
     }
 
-    static Map<String, ValidatorSettings> getValidatorSettingsForPackage(Log log, Map<String, ValidatorSettings> validatorsSettings, PackageId packageId, boolean isSubPackage) {
+    protected static Map<String, ValidatorSettings> getValidatorSettingsForPackage(Log log, Map<String, ValidatorSettings> validatorsSettings, PackageId packageId, boolean isSubPackage) {
         Map<String, ValidatorSettings> validatorSettingsById = new HashMap<>();
         if (validatorsSettings == null) {
             return validatorSettingsById;
         }
-        for (Map.Entry<String, ValidatorSettings> validatorSettingByIdAndPackage : validatorsSettings.entrySet()) {
-            // does this setting belong to this package?
-            boolean shouldAdd = false;
-            String[] parts = validatorSettingByIdAndPackage.getKey().split(":", 3);
-            final String validatorId = parts[0];
-            
-            if (parts.length == 2) {
-                if (parts[1].equals("subpackage")) {
-                    shouldAdd = isSubPackage;
+        // from most specific to least specific
+        List<ValidatorSettingsKey> sortedKeys = validatorsSettings.keySet().stream().map(ValidatorSettingsKey::fromString).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
+        
+        for (ValidatorSettingsKey key : sortedKeys) {
+            if (key.matchesForPackage(packageId, isSubPackage)) {
+                ValidatorSettings settings = validatorSettingsById.get(key.getValidatorId());
+                if (settings != null) {
+                    settings = settings.merge(validatorsSettings.get(key.getKey()));
                 } else {
-                    log.warn("Invalid validatorSettings key '" + validatorSettingByIdAndPackage.getKey() +"'" );
-                    continue;
+                    settings = validatorsSettings.get(key.getKey());
                 }
-            }
-            // does it contain a package id filter?
-            else if (parts.length == 3) {
-                String group = parts[1];
-                String name = parts[2];
-                if (!"*".equals(group)) {
-                    if (!group.equals(packageId.getGroup())) {
-                        log.debug("Not applying validator settings with id '" + validatorSettingByIdAndPackage.getKey() +"' as it does not match the package " + packageId);
-                        continue;
-                    }
-                }
-                if (!name.equals(packageId.getName())) {
-                    log.debug("Not applying validator settings with id '" + validatorSettingByIdAndPackage.getKey() +"' as it does not match the package " + packageId);
-                    continue;
-                }
-                shouldAdd = true;
-            } else {
-                shouldAdd = true;
-            }
-            if (shouldAdd) {
-                validatorSettingsById.put(validatorId, validatorSettingByIdAndPackage.getValue());
+                validatorSettingsById.put(key.getValidatorId(), settings);
             }
         }
         return validatorSettingsById;
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java
index ea495ee..707227f 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatePackageMojo.java
@@ -121,7 +121,7 @@ public class ValidatePackageMojo extends AbstractValidateMojo {
         validationHelper.failBuildInCaseOfViolations(failOnValidationWarnings);
     }
 
-    private void validatePackage(ValidationHelper validationHelper, Path file) throws MojoExecutionException {
+    private void validatePackage(ValidationHelper validationHelper, Path file) throws MojoExecutionException, MojoFailureException {
         getLog().info("Start validating package " + getProjectRelativeFilePath(file) + "...");
 
         // open file to extract the meta data for the validation context
@@ -144,13 +144,13 @@ public class ValidatePackageMojo extends AbstractValidateMojo {
     }
 
     private void validateArchive(ValidationHelper validationHelper, Archive archive, Path path, ArchiveValidationContextImpl context,
-            ValidationExecutor executor) throws IOException, SAXException, ParserConfigurationException {
+            ValidationExecutor executor) throws IOException, SAXException, ParserConfigurationException, MojoFailureException {
         validateEntry(validationHelper, archive, archive.getRoot(), Paths.get(""), path, context, executor);
         validationHelper.printMessages(executor.done(), getLog(), buildContext, path);
     }
 
     private void validateEntry(ValidationHelper validationHelper, Archive archive, Archive.Entry entry, Path entryPath, Path packagePath, ArchiveValidationContextImpl context,
-            ValidationExecutor executor) throws IOException, SAXException, ParserConfigurationException {
+            ValidationExecutor executor) throws IOException, SAXException, ParserConfigurationException, MojoFailureException {
         // sort children to make sure that .content.xml comes first!
         List<Archive.Entry> sortedEntryList = new ArrayList<>(entry.getChildren());
         sortedEntryList.sort(Comparator.comparing(Archive.Entry::getName, new DotContentXmlFirstComparator()));
@@ -168,7 +168,7 @@ public class ValidatePackageMojo extends AbstractValidateMojo {
     }
 
     private void validateInputStream(ValidationHelper validationHelper, @Nullable InputStream inputStream, Path entryPath, Path packagePath, ArchiveValidationContextImpl context,
-            ValidationExecutor executor) throws IOException, SAXException, ParserConfigurationException {
+            ValidationExecutor executor) throws IOException, SAXException, ParserConfigurationException, MojoFailureException {
         Collection<ValidationViolation> messages = new LinkedList<>();
         if (entryPath.startsWith(Constants.META_INF)) {
             messages.addAll(executor.validateMetaInf(inputStream, Paths.get(Constants.META_INF).relativize(entryPath), packagePath.resolve(Constants.META_INF)));
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettings.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettings.java
index e7c32cc..c5c959d 100644
--- a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettings.java
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettings.java
@@ -18,25 +18,37 @@ package org.apache.jackrabbit.filevault.maven.packaging;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import org.apache.jackrabbit.vault.validation.spi.ValidationMessageSeverity;
 
+/**
+ * Mutable implementation of org.apache.jackrabbit.vault.validation.spi.ValidatorSettings. Used as mojo parameter type.
+ */
 public class ValidatorSettings implements org.apache.jackrabbit.vault.validation.spi.ValidatorSettings {
 
-    private final boolean isDisabled;
+    private Boolean isDisabled;
     
     private ValidationMessageSeverity defaultSeverity;
 
     private final Map<String, String> options;
     
     public ValidatorSettings() {
-        isDisabled = false;
         options = new HashMap<>();
     }
-    
-    public ValidatorSettings(ValidationMessageSeverity defaultSeverity) {
-        this();
-        this.defaultSeverity = defaultSeverity;
+
+    public ValidatorSettings merge(ValidatorSettings otherSettings) {
+        // fields of current object take precedence (if not null)
+        ValidatorSettings mergedSettings = new ValidatorSettings();
+        mergedSettings.isDisabled = isDisabled != null ? isDisabled : otherSettings.isDisabled;
+        mergedSettings.defaultSeverity = defaultSeverity != null ? defaultSeverity : otherSettings.defaultSeverity;
+        mergedSettings.options.putAll(options);
+        for (Map.Entry<String, String> entry : otherSettings.getOptions().entrySet()) {
+            if (!options.containsKey(entry.getKey())) {
+                mergedSettings.addOption(entry.getKey(), entry.getValue());
+            }
+        }
+        return mergedSettings;
     }
 
     public ValidatorSettings setDefaultSeverity(String defaultSeverity) {
@@ -46,6 +58,10 @@ public class ValidatorSettings implements org.apache.jackrabbit.vault.validation
         return this;
     }
 
+    public void setDefaultSeverity(ValidationMessageSeverity defaultSeverity) {
+        this.defaultSeverity = defaultSeverity;
+    }
+
     protected ValidatorSettings addOption(String key, String value) {
         options.put(key, value);
         return this;
@@ -63,24 +79,23 @@ public class ValidatorSettings implements org.apache.jackrabbit.vault.validation
 
     @Override
     public boolean isDisabled() {
-        return isDisabled;
+        return (isDisabled != null) && isDisabled.booleanValue();
+    }
+
+    public void setIsDisabled(boolean isDisabled) {
+        this.isDisabled = isDisabled;
     }
 
     @Override
     public String toString() {
-        return "ValidatorSettings [isDisabled=" + isDisabled + ", "
+        return "ValidatorSettings [" + (isDisabled != null ? "isDisabled=" + isDisabled + ", " : "")
                 + (defaultSeverity != null ? "defaultSeverity=" + defaultSeverity + ", " : "")
                 + (options != null ? "options=" + options : "") + "]";
     }
 
     @Override
     public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((defaultSeverity == null) ? 0 : defaultSeverity.hashCode());
-        result = prime * result + (isDisabled ? 1231 : 1237);
-        result = prime * result + ((options == null) ? 0 : options.hashCode());
-        return result;
+        return Objects.hash(defaultSeverity, isDisabled, options);
     }
 
     @Override
@@ -92,16 +107,9 @@ public class ValidatorSettings implements org.apache.jackrabbit.vault.validation
         if (getClass() != obj.getClass())
             return false;
         ValidatorSettings other = (ValidatorSettings) obj;
-        if (defaultSeverity != other.defaultSeverity)
-            return false;
-        if (isDisabled != other.isDisabled)
-            return false;
-        if (options == null) {
-            if (other.options != null)
-                return false;
-        } else if (!options.equals(other.options))
-            return false;
-        return true;
+        return defaultSeverity == other.defaultSeverity && Objects.equals(isDisabled, other.isDisabled)
+                && Objects.equals(options, other.options);
     }
 
+    
 }
diff --git a/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsKey.java b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsKey.java
new file mode 100644
index 0000000..9a18f6f
--- /dev/null
+++ b/src/main/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsKey.java
@@ -0,0 +1,155 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import java.util.Objects;
+
+import org.apache.jackrabbit.vault.packaging.PackageId;
+
+/** 
+ * The parsed key as used on {@link AbstractValidateMojo#validatorsSettings}.
+ * Implements a comparator which sorts the more generic settings first and the more specific ones later.
+ * The exact order is
+ * <ol>
+ * <li>settings without restrictions</li>
+ * <li>settings for subpackages</li>
+ * <li>settings for any groupId (*) and a specific artifactId</li>
+ * <li>settings for a specific groupId and artifactId</li>
+ * </ol>
+ * 
+ */
+public class ValidatorSettingsKey implements Comparable<ValidatorSettingsKey>{
+
+    private static final int LESS_THAN = -1;
+    private static final int GREATER_THEN = 1;
+    private static final String WILDCARD_FILTER_VALUE = "*";
+    final String key;
+    final String validatorId;
+    final boolean isForSubPackagesOnly;
+    final String packageNameFilter;
+    final String packageGroupFilter;
+
+    public static ValidatorSettingsKey fromString(String key) {
+        String[] parts = key.split(":", 3);
+        final String validatorId = parts[0];
+        boolean isForSubPackagesOnly = false;
+        String packageGroupFilter = null;
+        String packageNameFilter = null;
+        if (parts.length == 2) {
+            if (parts[1].equals("subpackage")) {
+                isForSubPackagesOnly = true;
+            } else {
+                throw new IllegalArgumentException("Invalid validatorSettings key '" + key +"'" );
+            }
+        }
+        // does it contain a package id filter?
+        else if (parts.length == 3) {
+            packageGroupFilter = parts[1];
+            packageNameFilter = parts[2];
+        }
+        return new ValidatorSettingsKey(key, validatorId, isForSubPackagesOnly, packageGroupFilter, packageNameFilter);
+    }
+ 
+    ValidatorSettingsKey(String key, String validatorId, boolean isForSubPackagesOnly, String packageGroupFilter, String packageNameFilter) {
+        super();
+        this.key = key;
+        this.validatorId = validatorId;
+        this.isForSubPackagesOnly = isForSubPackagesOnly;
+        this.packageGroupFilter = packageGroupFilter;
+        this.packageNameFilter = packageNameFilter;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public String getValidatorId() {
+        return validatorId;
+    }
+
+    public boolean matchesForPackage(PackageId packageId, boolean isSubPackage) {
+        if (!isSubPackage && isForSubPackagesOnly) {
+            return false;
+        }
+        if (packageNameFilter != null) {
+            if (!packageNameFilter.equals(packageId.getName())) {
+                return false;
+            }
+        }
+        if (packageGroupFilter != null && !WILDCARD_FILTER_VALUE.equals(packageGroupFilter)) {
+            if (!packageGroupFilter.equals(packageId.getGroup())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int compareTo(ValidatorSettingsKey o) {
+        if (packageNameFilter != null && o.packageNameFilter != null) {
+            if (WILDCARD_FILTER_VALUE.equals(packageGroupFilter)) {
+                if (WILDCARD_FILTER_VALUE.equals(o.packageGroupFilter)) {
+                    return packageNameFilter.compareTo(o.packageNameFilter);
+                } else {
+                    return LESS_THAN;
+                }
+            } else {
+                if (WILDCARD_FILTER_VALUE.equals(o.packageGroupFilter)) {
+                    return GREATER_THEN;
+                } else {
+                    return packageNameFilter.compareTo(o.packageNameFilter);
+                }
+            }
+        } else if (packageNameFilter == null && o.packageNameFilter != null) {
+            return LESS_THAN;
+        } else if (packageNameFilter != null && o.packageNameFilter == null) {
+            return GREATER_THEN;
+        } else if (!isForSubPackagesOnly && o.isForSubPackagesOnly) {
+            return LESS_THAN;
+        } else if (isForSubPackagesOnly && !o.isForSubPackagesOnly) {
+            return GREATER_THEN;
+        }
+        return validatorId.compareTo(o.validatorId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(isForSubPackagesOnly, key, packageGroupFilter, packageNameFilter, validatorId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ValidatorSettingsKey other = (ValidatorSettingsKey) obj;
+        return isForSubPackagesOnly == other.isForSubPackagesOnly && Objects.equals(key, other.key)
+                && Objects.equals(packageGroupFilter, other.packageGroupFilter)
+                && Objects.equals(packageNameFilter, other.packageNameFilter) && Objects.equals(validatorId, other.validatorId);
+    }
+
+    @Override
+    public String toString() {
+        return "ValidatorSettingsKey [" + (key != null ? "key=" + key + ", " : "")
+                + (validatorId != null ? "validatorId=" + validatorId + ", " : "") + "isForSubPackagesOnly=" + isForSubPackagesOnly + ", "
+                + (packageNameFilter != null ? "packageNameFilter=" + packageNameFilter + ", " : "")
+                + (packageGroupFilter != null ? "packageGroupFilter=" + packageGroupFilter : "") + "]";
+    }
+}
diff --git a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojoTest.java b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojoTest.java
index 2c11fb9..f2be2eb 100644
--- a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojoTest.java
+++ b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/AbstractValidateMojoTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.filevault.maven.packaging;
 
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -28,7 +29,10 @@ import org.apache.maven.artifact.InvalidArtifactRTException;
 import org.apache.maven.plugin.logging.SystemStreamLog;
 import org.hamcrest.MatcherAssert;
 import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Test;
+import org.osgi.framework.Version;
+
 
 public class AbstractValidateMojoTest {
 
@@ -66,15 +70,15 @@ public class AbstractValidateMojoTest {
         validatorsSettings.put("id1", new ValidatorSettings().addOption("id1", "foo"));
         validatorsSettings.put("id2:mygroup:myname", new ValidatorSettings().addOption("id2", "foo"));
         validatorsSettings.put("id3:subpackage", new ValidatorSettings().addOption("id3", "foo"));
-        validatorsSettings.put("id4:invalid", new ValidatorSettings().addOption("id4", "foo"));
         validatorsSettings.put("id5:othergroup:myname", new ValidatorSettings().addOption("id5", "foo"));
         validatorsSettings.put("id6:mygroup:myothername", new ValidatorSettings().addOption("id3", "foo"));
-        validatorsSettings.put("id7:", new ValidatorSettings().addOption("id7", "foo"));
-        
+        validatorsSettings.put("id1:*:myname", new ValidatorSettings().addOption("id2", "bar"));
+        validatorsSettings.put("id3", new ValidatorSettings().addOption("id3", "bar"));
         Map<String, ValidatorSettings> actualValidatorSettings = AbstractValidateMojo.getValidatorSettingsForPackage(new SystemStreamLog(), validatorsSettings, PackageId.fromString("mygroup:myname:1.0.0"), false);
         Map<String, ValidatorSettings> expectedValidatorSettings = new HashMap<>();
-        expectedValidatorSettings.put("id1", new ValidatorSettings().addOption("id1", "foo"));
+        expectedValidatorSettings.put("id1", new ValidatorSettings().addOption("id1", "foo").addOption("id2", "bar"));
         expectedValidatorSettings.put("id2", new ValidatorSettings().addOption("id2", "foo"));
+        expectedValidatorSettings.put("id3", new ValidatorSettings().addOption("id3", "bar"));
         MatcherAssert.assertThat(actualValidatorSettings, Matchers.equalTo(expectedValidatorSettings));
         
         actualValidatorSettings = AbstractValidateMojo.getValidatorSettingsForPackage(new SystemStreamLog(), validatorsSettings, PackageId.fromString("mygroup:myname:1.0.0"), true);
@@ -92,4 +96,9 @@ public class AbstractValidateMojoTest {
         list.sort(new AbstractValidateMojo.DotContentXmlFirstComparator());
         MatcherAssert.assertThat(list, Matchers.contains(".content.xml", ".content.xml", "someEntryA", "someEntryB"));
     }
+    
+    @Test
+    public void testGetFileVaultValidatorVersion() throws IOException {
+        Assert.assertTrue(AbstractValidateMojo.getFileVaultValidationBundleVersion().compareTo(Version.parseVersion("3.5.4")) >= 0);
+    }
 }
diff --git a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsKeyTest.java b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsKeyTest.java
new file mode 100644
index 0000000..3880d03
--- /dev/null
+++ b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsKeyTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ValidatorSettingsKeyTest {
+
+    @Test
+    public void testFromString() {
+        Assert.assertEquals(new ValidatorSettingsKey("id1", "id1", false, null, null), ValidatorSettingsKey.fromString("id1"));
+        Assert.assertEquals(new ValidatorSettingsKey("id2:subpackage", "id2", true, null, null), ValidatorSettingsKey.fromString("id2:subpackage"));
+        Assert.assertThrows(IllegalArgumentException.class, () -> {ValidatorSettingsKey.fromString("id1:invalid");} );
+        Assert.assertEquals(new ValidatorSettingsKey("id3:othergroup:myname", "id3", false, "othergroup", "myname"),  ValidatorSettingsKey.fromString("id3:othergroup:myname"));
+        Assert.assertEquals(new ValidatorSettingsKey("id4:*:othername", "id4", false, "*", "othername"),  ValidatorSettingsKey.fromString("id4:*:othername"));
+    }
+
+    @Test
+    public void testComparator() {
+        SortedSet<ValidatorSettingsKey> keys = new TreeSet<>();
+        
+        ValidatorSettingsKey key1 = new ValidatorSettingsKey("id1", "id1", false, null, null);
+        ValidatorSettingsKey key2 = new ValidatorSettingsKey("id1:subpackage", "id1", true, null, null);
+        ValidatorSettingsKey key3 = new ValidatorSettingsKey("id1:*:mypackage", "id1", false, "*", "mypackage");
+        ValidatorSettingsKey key4 = new ValidatorSettingsKey("id1:mygroup:mypackage", "id1", false, "mygroup", "mypackage");
+        ValidatorSettingsKey key5 = new ValidatorSettingsKey("id1:mygroup:otherpackage", "id1", false, "mygroup", "otherpackage");
+        Assert.assertEquals(-1, Integer.signum(key1.compareTo(key2)));
+        Assert.assertEquals(1, Integer.signum(key2.compareTo(key1)));
+        Assert.assertEquals(-1, Integer.signum(key2.compareTo(key3)));
+        Assert.assertEquals(1, Integer.signum(key3.compareTo(key2)));
+        Assert.assertEquals(-1, Integer.signum(key3.compareTo(key4)));
+        Assert.assertEquals(1, Integer.signum(key4.compareTo(key3)));
+        Assert.assertEquals(-1, Integer.signum(key4.compareTo(key5)));
+        Assert.assertEquals(1, Integer.signum(key5.compareTo(key4)));
+        Assert.assertEquals(0, Integer.signum(key5.compareTo(key5)));
+        keys.add(key5);
+        keys.add(key4);
+        keys.add(key3);
+        keys.add(key2);
+        keys.add(key1);
+        Assert.assertEquals("id1,id1:subpackage,id1:*:mypackage,id1:mygroup:mypackage,id1:mygroup:otherpackage", keys.stream().map(ValidatorSettingsKey::getKey).collect(Collectors.joining(",")));
+    }
+}
diff --git a/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsTest.java b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsTest.java
new file mode 100644
index 0000000..654e48f
--- /dev/null
+++ b/src/test/java/org/apache/jackrabbit/filevault/maven/packaging/ValidatorSettingsTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.filevault.maven.packaging;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ValidatorSettingsTest {
+
+    @Test
+    public void testMerge() {
+        ValidatorSettings settings1 = new ValidatorSettings();
+        settings1.setIsDisabled(true);
+        settings1.addOption("option1", "from1");
+        settings1.addOption("option3", "from1");
+        ValidatorSettings settings2 = new ValidatorSettings();
+        settings2.setIsDisabled(false);
+        settings2.addOption("option1", "from2");
+        settings2.addOption("option2", "from2");
+        settings2.addOption("option3", "from2");
+        settings2.setDefaultSeverity("WARN");
+        ValidatorSettings mergedSettings = new ValidatorSettings();
+        mergedSettings.setIsDisabled(true);
+        mergedSettings.addOption("option1", "from1");
+        mergedSettings.addOption("option2", "from2");
+        mergedSettings.addOption("option3", "from1");
+        mergedSettings.setDefaultSeverity("WARN");
+        Assert.assertEquals(mergedSettings, settings1.merge(settings2));
+    }
+}