[java-identity-provider] branch main updated: IDP-2073 Consider enabling the installer to download new versions

Rod Widdowson rdw at steadingsoftware.com
Sat Jun 3 14:57:47 UTC 2023


This is an automated email from the git hooks/post-receive script.

rdw pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=608ef6e80d12d61c55352e56fc9bdcb327679b82

The following commit(s) were added to refs/heads/main by this push:
     new 608ef6e80 IDP-2073 Consider enabling the installer to download new versions
608ef6e80 is described below

commit 608ef6e80d12d61c55352e56fc9bdcb327679b82
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Sat Jun 3 15:54:37 2023 +0100

    IDP-2073 Consider enabling the installer to download new versions
    
    https://shibboleth.atlassian.net/browse/IDP-2073
    
    Major refactor of the Plugin handling
    
    1) Move some version handling things inside the IdP (prior
    to onward transfer to shib-profile).  This will allow the IdP
    to make enquries about plugins (and eventually itself).
    
    2) Collapse a lot of specific Plugin Installer helper code up into the
    IdP helper code.
---
 .../idp/plugin/InstallableComponentInfo.java       | 334 +++++++++++++++++
 ...rsion.java => InstallableComponentVersion.java} |  14 +-
 .../net/shibboleth/idp/plugin/PluginSupport.java   | 128 ++++++-
 .../net/shibboleth/idp/plugin/PluginVersion.java   | 158 +-------
 .../idp/plugin/PropertyDrivenIdPPlugin.java        |   4 +-
 .../shibboleth/idp/plugin/PluginVersionTest.java   |  26 +-
 .../shibboleth/idp/installer/InstallerSupport.java | 205 ++++++++++-
 .../shibboleth/idp/installer/impl/V5Install.java   |   6 +-
 .../idp/installer/plugin/impl/PluginInfo.java      | 247 ++-----------
 .../idp/installer/plugin/impl/PluginInstaller.java |  35 +-
 .../plugin/impl/PluginInstallerArguments.java      |  10 +-
 .../installer/plugin/impl/PluginInstallerCLI.java  |  56 +--
 .../plugin/impl/PluginInstallerSupport.java        | 397 ---------------------
 .../idp/installer/plugin/impl/PluginState.java     |  59 +--
 .../idp/installer/plugin/impl/BasePluginTest.java  |   2 +-
 .../idp/installer/plugin/impl/PluginCLITest.java   |   3 +-
 .../idp/installer/plugin/impl/PluginStateTest.java |  28 +-
 .../idp/installer/plugin/impl/RollbackTester.java  |   3 +-
 18 files changed, 799 insertions(+), 916 deletions(-)

diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/InstallableComponentInfo.java b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/InstallableComponentInfo.java
new file mode 100644
index 000000000..5fae44edf
--- /dev/null
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/InstallableComponentInfo.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.PluginSupport.SupportLevel;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Class which encapsulates the information about a given Installable Component as downloaded
+ * from  the appropriate location (as a Properties file).
+ * Different variants deal with the max and min supported versions (Plugins get them from a file
+ * Non plugins derive them from the version under consideration.
+ */
+public abstract class InstallableComponentInfo {
+
+    /** regexp for spaces. */
+    private static final Pattern SPACE_CONTAINING = Pattern.compile("\\s+");
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(InstallableComponentInfo.class);
+
+    /** The support information. */
+    @Nonnull private final Map<InstallableComponentVersion, InstallableComponentInfo.VersionInfo> versionInfo = new HashMap<>();
+
+    /** The Download information. */
+    @Nonnull private final Map<InstallableComponentVersion, Pair<URL,String>> downloadInfo = new HashMap<>();
+
+    /** The Id. */
+    @Nonnull private final String componentId;
+
+    /** Whether the information was sufficient. */
+    private boolean allInfoPresent = true;
+
+    /**
+     * Constructor.
+     *
+     * @param id the id we care about
+     * @param props all the properties.
+     */
+    public InstallableComponentInfo(@Nonnull final String id, @Nonnull final Properties props) {
+        componentId = id;
+        parse(props);
+    }
+
+    /** Did the property file have enough information?
+     * @return true if something was missing.
+     */
+    public boolean isInfoComplete() {
+        return allInfoPresent;
+    }
+
+    /** Get the base URL for this version.
+     * @param version which version
+     * @return the base URL
+     */
+    @Nullable public URL getUpdateURL(@Nonnull final InstallableComponentVersion version) {
+        final Pair<URL, String> p = downloadInfo.get(version);
+        if (p == null) {
+            return null;
+        }
+        return p.getFirst();
+    }
+
+    /** Get the base Name for this version.
+     * @param version which version
+     * @return the base name
+     */
+    @Nullable public String getUpdateBaseName(@Nonnull final InstallableComponentVersion version) {
+        final Pair<URL, String> p = downloadInfo.get(version);
+        if (p == null) {
+            return null;
+        }
+        return p.getSecond();
+    }
+
+    /** Get the raw version info for this component.
+     * @return the info.
+     */
+    @Nonnull public Map<InstallableComponentVersion, InstallableComponentInfo.VersionInfo> getAvailableVersions() {
+        return versionInfo;
+    }
+    
+    /** Get the Installable Component Id under consideration
+     * @return Returns the componentId.
+     */
+    @Nonnull protected String getComponentId() {
+        return componentId;
+    }
+
+    /** (try to) populate the information about this component.
+     * @param props what to load.
+     */
+    private void  parse(@Nonnull final Properties props) {
+        final String name = componentId + PluginSupport.AVAILABLE_VERSIONS_PROPERTY_SUFFIX;
+        final String availableVersions = StringSupport.trim(props.getProperty(name));
+        if (availableVersions == null) {
+            log.warn("Component {}: Could not find {} property.", componentId, name);
+            allInfoPresent = false;
+        } else {
+            handleAvailableVersions(props, availableVersions);
+        }
+    }
+
+    /** Given a version find out more.
+     * @param props the property files for this component we are looking at
+     * @param version the version in question.
+     */
+    private void handleAvailableVersion(@Nonnull final Properties props, @Nonnull final String version) {
+        final InstallableComponentVersion theVersion = new InstallableComponentVersion(version);
+        if (theVersion.getMajor() == 0 && theVersion.getMinor() == 0 && theVersion.getPatch() == 0) {
+            log.warn("Component {}: Improbable version {}", componentId, version);
+        }
+        if (versionInfo.containsKey(theVersion)) {
+            log.warn("Component {}: Duplicate version {}", componentId, version);
+        }
+
+        final InstallableComponentVersion maxVersionInfo = getMaxVersion(props, version);
+        if (maxVersionInfo == null) {
+            log.warn("Component {}, Version {}: Could not find max idp version.", componentId, version);
+            allInfoPresent = false;
+            return;
+        }
+
+        final InstallableComponentVersion minVersionInfo = getMinVersion(props, version);
+        if (minVersionInfo == null) {
+            log.warn("Component {}, Version {}: Could not find min idp version.", componentId, version);
+            allInfoPresent = false;
+            return;
+        }
+
+        final String supportLevelString = StringSupport.trimOrNull(
+                props.getProperty(componentId + PluginSupport.SUPPORT_LEVEL_INTERFIX + version));
+        PluginSupport.SupportLevel supportLevel;
+        if (supportLevelString == null) {
+            log.debug("Component {}, Version {}: Could not find support level for {}.", componentId, version);
+            supportLevel = SupportLevel.Unknown;
+        } else {
+            try {
+                supportLevel = Enum.valueOf(SupportLevel.class, supportLevelString);
+            } catch (final IllegalArgumentException e) {
+                log.warn("Component {}, Version {}: Invalid support level {}.", componentId, version, supportLevelString);
+                supportLevel = SupportLevel.Unknown;
+            }
+        }
+
+        log.debug("Component {}: MaxIdP {}, MinIdP {}, Support Level {}",
+                componentId, maxVersionInfo, minVersionInfo, supportLevel);
+        final InstallableComponentInfo.VersionInfo info;
+        info = new InstallableComponentInfo.VersionInfo(maxVersionInfo, minVersionInfo, supportLevel);
+        versionInfo.put(theVersion, info);
+        String downloadURL =  StringSupport.trimOrNull(
+                getDefaultedValue(props, PluginSupport.DOWNLOAD_URL_INTERFIX, version));
+        final String baseName =  StringSupport.trimOrNull(
+                getDefaultedValue(props, PluginSupport.BASE_NAME_INTERFIX, version));
+        if (baseName != null && downloadURL != null) {
+            try {
+                if (!downloadURL.endsWith("/")) {
+                    downloadURL += "/";
+                }
+                final URL url = new URL(downloadURL);
+                downloadInfo.put(theVersion, new Pair<>(url, baseName));
+                log.trace("Component {}, version {}: Added download URL {}  baseName {} for {}",
+                        componentId, theVersion, url, baseName);
+            } catch (final MalformedURLException e) {
+               log.warn("Component {}, version {}: Download URL '{}' could not be constructed",
+                       componentId, theVersion, downloadURL, e);
+            }
+        } else {
+            log.info("Component {}, version {}: no download information present", componentId, theVersion);
+        }
+    }
+    
+    /** Find the max supported version version we can be installed into.
+     * @param props the properties to look at
+     * @param version the component version we are enquiring about
+     * @return the version null if not found.
+     */
+    @Nullable abstract protected InstallableComponentVersion getMaxVersion(@Nonnull final Properties props, @Nonnull final String version);
+
+    /** Find the min supported version we can be installed into.
+     * @param props the properties to look at
+     * @param version the component version we are enquiring about
+     * @return the version null if not found.
+     */
+    @Nullable abstract protected InstallableComponentVersion getMinVersion(@Nonnull final Properties props, @Nonnull final String version);
+
+    /** Given a list of versions find out more.
+     * @param props the property files for the component we are looking at
+     * @param availableVersions a space delimited array of versions
+     */
+    private void handleAvailableVersions(@Nonnull final Properties props, @Nonnull final String availableVersions) {
+        final String[] versions = SPACE_CONTAINING.split(availableVersions, 0);
+
+        log.debug("Component {}: Available versions : {} ", componentId, availableVersions);
+        for (final String version:versions) {
+            assert version  != null;
+            log.debug("Component {}: Considering {}", componentId, version);
+            handleAvailableVersion(props, version);
+        }
+    }
+
+    /** Look up the key derived from the component, the interfix and the version, but if that
+     * fails look for a templated definition.
+     * @param props what to look in
+     * @param interfix the interface (between the ID and the version)
+     * @param version the version.
+     * @return the suitable value
+     */
+    @Nullable private String getDefaultedValue(@Nonnull final Properties props, @Nonnull final String interfix, @Nonnull final String version) {
+        String result = props.getProperty(componentId + interfix + version);
+        if (result != null) {
+            return result;
+        }
+        result = props.getProperty(componentId + interfix + PluginSupport.VERSION_PATTERN);
+        if (result == null) {
+            return result;
+        }
+        return result.replaceAll(PluginSupport.VERSION_PATTERN_REGEX, version);
+    }
+
+    /** Can the specified component be installed into this version?
+     * @param componentVersion the version if the component as a {@link InstallableComponentVersion}
+     * @param intsallIntoVersion the version if the IDP as a {@link InstallableComponentVersion}
+     * @return whether it is supported.
+     */
+    public boolean isSupportedWithIdPVersion(@Nonnull final InstallableComponentVersion componentVersion, @Nonnull final InstallableComponentVersion intsallIntoVersion) {
+        final InstallableComponentInfo.VersionInfo info = versionInfo.get(componentVersion);
+        if (info == null) {
+            log.error("Component {}: Unknown version {} supplied.", componentId, componentVersion);
+            log.debug("Component {}: Available {}", componentId, versionInfo.keySet());
+            return false;
+        }
+        return InstallableComponentInfo.isSupportedWithIdPVersion(info, intsallIntoVersion);
+    }
+
+    /** Can the specified component be installed into this version?
+     * Worker method for all 'isSupportedWith' classes.
+     * @param componentVersionInfo the version info to consider
+     * @param installIntoVersion the version as a {@link InstallableComponentVersion}
+     * @return whether it is supported.
+     */
+    public static boolean isSupportedWithIdPVersion(@Nonnull final VersionInfo componentVersionInfo,
+            @Nonnull final InstallableComponentVersion installIntoVersion) {
+        final int maxCompare = installIntoVersion.compareTo(componentVersionInfo.getMaxSupported()); 
+        if (maxCompare >= 0) {
+            // Exclusive:
+            // IdP (test against) Version is GREATER THAN OR EQUAL to our Max
+            return false;
+        }
+        final int minCompare = installIntoVersion.compareTo(componentVersionInfo.getMinSupported());
+        if (minCompare >= 0) {
+            // Inclusive:
+            // IdP (test against) version is GREATER THAN OR EQUAL to our Min
+            return true;
+        }
+        return false;
+    }
+
+    /** Encapsulation of the information about a given IdP version. */
+    public static class VersionInfo {
+
+        /** Maximum version - this version is NOT SUPPORTED. */
+        private final InstallableComponentVersion maxSupported;
+
+        /** Minimum version - this version IS supported. */ 
+        private final InstallableComponentVersion minSupported;
+
+        /** support level. */
+        private final SupportLevel supportLevel;
+
+        /**
+         * Constructor.
+         *
+         * @param max support level
+         * @param min support level
+         * @param support support level
+         */
+        public VersionInfo(final InstallableComponentVersion max, final InstallableComponentVersion min, final SupportLevel support) {
+            maxSupported = max;
+            minSupported = min;
+            supportLevel = support;
+        }
+
+        /** get Maximum version - this version is NOT SUPPORTED.
+         * @return Returns the maxSupported.
+         */
+        public InstallableComponentVersion getMaxSupported() {
+            return maxSupported;
+        }
+
+        /** get Minimum (IdP) version - this version IS supported.
+         * @return Returns the minSupported.
+         */
+        public InstallableComponentVersion getMinSupported() {
+            return minSupported;
+        }
+
+        /** get support level.
+         * @return Returns the supportLevel.
+         */
+        public SupportLevel getSupportLevel() {
+            return supportLevel;
+        }
+    }
+}
diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginVersion.java b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/InstallableComponentVersion.java
similarity index 89%
copy from idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginVersion.java
copy to idp-admin-api/src/main/java/net/shibboleth/idp/plugin/InstallableComponentVersion.java
index 2c0ae97fd..f0bd5d4bd 100644
--- a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginVersion.java
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/InstallableComponentVersion.java
@@ -24,7 +24,7 @@ import net.shibboleth.shared.primitive.StringSupport;
 /**
  * A version string (Major.minor.patch) as a handy class.
  */
-public final class PluginVersion implements Comparable<PluginVersion>{
+public class InstallableComponentVersion implements Comparable<InstallableComponentVersion>{
 
     /** arbitrary maximum (to help hashing and sanity). */
     private static final int MAX_VNO = 10000;
@@ -45,7 +45,7 @@ public final class PluginVersion implements Comparable<PluginVersion>{
      * @throws NumberFormatException if it doesn't fit a 1.2.3 format or if the values are
      * out of range
      */
-    public PluginVersion(final String version) throws NumberFormatException {
+    public InstallableComponentVersion(final String version) throws NumberFormatException {
         
         final String versionStr = StringSupport.trimOrNull(version);
         if (null == versionStr) {
@@ -70,7 +70,7 @@ public final class PluginVersion implements Comparable<PluginVersion>{
      * @param plugin what to get the version of.
      * @throws NumberFormatException if the values are out of range
      */
-    public PluginVersion(@Nonnull final IdPPlugin plugin) throws NumberFormatException {
+    public InstallableComponentVersion(@Nonnull final IdPPlugin plugin) throws NumberFormatException {
         this(plugin.getMajorVersion(), plugin.getMinorVersion(), plugin.getPatchVersion());
     }
 
@@ -82,7 +82,7 @@ public final class PluginVersion implements Comparable<PluginVersion>{
      * @param pat Patch Version
      * @throws NumberFormatException if the values are out of range
      */
-    public PluginVersion(final int maj, final int min, final int pat) throws NumberFormatException {
+    public InstallableComponentVersion(final int maj, final int min, final int pat) throws NumberFormatException {
         major = maj;
         if (maj < 0 || maj >= MAX_VNO) {
             throw new NumberFormatException("Improbable major version number : " + maj);
@@ -142,8 +142,8 @@ public final class PluginVersion implements Comparable<PluginVersion>{
 
     /** {@inheritDoc} */
     public boolean equals(final Object obj) {
-        if (obj instanceof PluginVersion) {
-            final PluginVersion other= (PluginVersion)obj;
+        if (obj instanceof InstallableComponentVersion) {
+            final InstallableComponentVersion other= (InstallableComponentVersion)obj;
             return other.major == major && other.minor == minor && other.patch == patch;
         }
         return false;
@@ -162,7 +162,7 @@ public final class PluginVersion implements Comparable<PluginVersion>{
      * negative integer, zero, or a positive integer as this object is less
      * than, equal to, or greater than the specified object.
      * {@inheritDoc} */
-    public int compareTo(final PluginVersion other) {
+    public int compareTo(final InstallableComponentVersion other) {
         if (major == other.major) {
             if (minor == other.minor) {
                 return patch - other.patch;
diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginSupport.java b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginSupport.java
index bc5a8daf6..4b1232f3b 100644
--- a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginSupport.java
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginSupport.java
@@ -17,12 +17,27 @@
 
 package net.shibboleth.idp.plugin;
 
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.opensaml.security.httpclient.HttpClientSecurityContextHandler;
+import org.opensaml.security.httpclient.HttpClientSecurityParameters;
 import org.slf4j.Logger;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
 
 import net.shibboleth.idp.Version;
+import net.shibboleth.idp.plugin.InstallableComponentInfo.VersionInfo;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.spring.httpclient.resource.HTTPResource;
 
 /** Useful methods for supporting plugins. 
  *
@@ -77,14 +92,119 @@ public final class PluginSupport {
     }
 
     /** Get parse IdP Version (with fallback for testing).
-     * @return a {@link PluginVersion} of the version.
+     * @return a {@link InstallableComponentVersion} of the version.
      */
-    public static PluginVersion getIdPVersion() {
+    public static InstallableComponentVersion getIdPVersion() {
         final String idpVersion = Version.getVersion();
         if (idpVersion!=null) {
-            return new PluginVersion(idpVersion);
+            return new InstallableComponentVersion(idpVersion);
         }
         log.error("Could not locate IdP Version, assuming 5.0.0");
-        return new PluginVersion(5,0,0);
+        return new InstallableComponentVersion(5,0,0);
+    }
+
+    /** Find the best update version  (plugin or IdP).
+     * @param installIntoVersion The IdP version to check.
+     * @param pluginVersion The Plugin version
+     * @param pluginInfo all about the plugin
+     * @return the best version (or null)
+     */
+    @Nullable static public InstallableComponentVersion getBestVersion(
+            @Nonnull final InstallableComponentVersion installIntoVersion,
+            @Nonnull final InstallableComponentVersion pluginVersion,
+            @Nonnull final InstallableComponentInfo pluginInfo) {
+        final List<InstallableComponentVersion> availableVersions = new ArrayList<>(pluginInfo.getAvailableVersions().keySet());
+        availableVersions.sort(null);
+        log.debug("Considering versions: {}", availableVersions);
+    
+        for (int i = availableVersions.size()-1; i >= 0; i--) {
+            final InstallableComponentVersion version = availableVersions.get(i);
+            if (version.compareTo(pluginVersion) <= 0) {
+                log.debug("Version {} is less than or the same as {}. All done", version, pluginVersion);
+                return null;
+            }
+            final VersionInfo versionInfo = pluginInfo.getAvailableVersions().get(version);
+            if (versionInfo.getSupportLevel() != SupportLevel.Current) {
+                log.debug("Version {} has support level {}, ignoring", version, versionInfo.getSupportLevel());
+                continue;
+            }
+            if (!pluginInfo.isSupportedWithIdPVersion(version, installIntoVersion)) {
+                log.debug("Version {} is not supported with idpVersion {}", version, installIntoVersion);
+                continue;
+            }
+            log.debug("Version {} is supported with idpVersion {}", version, installIntoVersion);
+            if (pluginInfo.getUpdateURL(version) == null || pluginInfo.getUpdateBaseName(version) == null) {
+                log.debug("Version {} is does not have update information", version);
+                continue;
+            }
+            return version;
+        }
+        return null;
+    }
+
+    /** Find the best update version (plugin or IdP).
+     * @param pluginVersion The Plugin version
+     * @param pluginInfo all about the plugin
+     * @return the best version (or null)
+     */
+    @Nullable static public InstallableComponentVersion getBestVersion(
+            @Nonnull final InstallableComponentVersion pluginVersion, @Nonnull final InstallableComponentInfo pluginInfo) {
+        return getBestVersion(getIdPVersion(), pluginVersion, pluginInfo);
+    }
+
+    /** Load the property file describing all the plugin we know about from a known location.
+     * @param updateURLs where to look
+     * @param client the http client to use
+     * @param securityParameters the HttpClientSecurityParameters, if any 
+     * @return the property files for the component.
+     */
+    @Nullable public static Properties loadPluginInfo(@Nonnull final List<URL> updateURLs, @Nonnull final HttpClient client,
+            @Nullable final HttpClientSecurityParameters securityParameters) {
+        final List<URL> urls;
+        final Properties props = new Properties();
+        try {
+            if (updateURLs.isEmpty()) {
+                urls = List.of(
+                        new URL("https://shibboleth.net/downloads/identity-provider/plugins/plugins.properties"),
+                        new URL("http://plugins.shibboleth.net/plugins.properties"));
+            } else {
+                urls = updateURLs;
+            }
+        } catch (final IOException e) {
+            log.error("Could not load update URLs", e);
+            return null;
+        }
+        for (final URL url: urls) {
+            final Resource propertyResource;
+            try {
+                if ("file".equals(url.getProtocol())) {
+                    final String path =url.getPath();
+                    assert path != null;
+                    propertyResource = new FileSystemResource(path);
+                } else if ("http".equals(url.getProtocol()) || "https".equals(url.getProtocol())) {
+                    final HTTPResource httpResource;
+                    propertyResource = httpResource = new HTTPResource(client , url);
+                    final HttpClientSecurityContextHandler handler = new HttpClientSecurityContextHandler();
+                    handler.setHttpClientSecurityParameters(securityParameters);
+                    handler.initialize();
+                    httpResource.setHttpClientContextHandler(handler);
+                } else {
+                    log.error("Only file and http[s] URLs are allowed");
+                    continue;
+                }
+                log.debug("Plugin Listing: Looking for update at {}", propertyResource.getDescription());
+                if (!propertyResource.exists()) {
+                    log.info("{} could not be located", propertyResource.getDescription());
+                    continue;
+                }
+                props.load(propertyResource.getInputStream());
+                return props;
+            } catch (final IOException | ComponentInitializationException e) {
+                log.error("Could not open Update URL {} :", url, e);
+                continue;
+            }
+        }
+        log.error("Could not locate any active update servers");
+        return null;
     }
 }
diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginVersion.java b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginVersion.java
index 2c0ae97fd..baa3f1644 100644
--- a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginVersion.java
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PluginVersion.java
@@ -19,165 +19,35 @@ package net.shibboleth.idp.plugin;
 
 import javax.annotation.Nonnull;
 
-import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.primitive.DeprecationSupport;
+import net.shibboleth.shared.primitive.DeprecationSupport.ObjectType;
 
 /**
- * A version string (Major.minor.patch) as a handy class.
+ * @deprecated class.
  */
-public final class PluginVersion implements Comparable<PluginVersion>{
+ at Deprecated(forRemoval = true, since = "5.0.0") public class PluginVersion extends InstallableComponentVersion {
 
-    /** arbitrary maximum (to help hashing and sanity). */
-    private static final int MAX_VNO = 10000;
-
-    /** Major version. */
-    private int major;
-    
-    /** Minor version. */
-    private int minor;
-    
-    /** Patch version. */
-    private int patch;
-    
-    /**
-     * Constructor.
-     *
-     * @param version what to build from 
-     * @throws NumberFormatException if it doesn't fit a 1.2.3 format or if the values are
-     * out of range
-     */
-    public PluginVersion(final String version) throws NumberFormatException {
-        
-        final String versionStr = StringSupport.trimOrNull(version);
-        if (null == versionStr) {
-            throw new NumberFormatException("Empty Version not allowed");
-        }
-        
-        final String[] components  = versionStr.split("\\.|\\+|-");
-        if (components.length >= 1) {
-            major = parseValue(components[0]);
-        }
-        if (components.length >= 2) {
-            minor = parseValue(components[1]);
-        }
-        if (components.length >= 3) {
-            patch = parseValue(components[2]);
-        }
-    }
-    
     /**
      * Constructor.
      *
      * @param plugin what to get the version of.
      * @throws NumberFormatException if the values are out of range
      */
-    public PluginVersion(@Nonnull final IdPPlugin plugin) throws NumberFormatException {
-        this(plugin.getMajorVersion(), plugin.getMinorVersion(), plugin.getPatchVersion());
+    public PluginVersion(@Nonnull IdPPlugin plugin) throws NumberFormatException {
+        super(plugin);
+        DeprecationSupport.warnOnce(ObjectType.CLASS, PluginVersion.class.toString(), null, InstallableComponentVersion.class.toString());
     }
 
+    
     /**
      * Constructor.
      *
-     * @param maj Major Version 
-     * @param min Minor Version 
-     * @param pat Patch Version
-     * @throws NumberFormatException if the values are out of range
-     */
-    public PluginVersion(final int maj, final int min, final int pat) throws NumberFormatException {
-        major = maj;
-        if (maj < 0 || maj >= MAX_VNO) {
-            throw new NumberFormatException("Improbable major version number : " + maj);
-        }
-        minor = min;
-        if (min < 0 || min >= MAX_VNO) {
-            throw new NumberFormatException("Improbable minor version number : " + min);
-        }
-        patch = pat;
-        if (pat < 0 || pat >= MAX_VNO) {
-            throw new NumberFormatException("Improbable patch version number : " + pat);
-        }
-    }
-
-    /** Get the major version.
-     * @return Returns the major version.
-     */
-    public int getMajor() {
-        return major;
-    }
-
-    /** Get the minor version.
-     * @return Returns the minor version.
-     */
-    public int getMinor() {
-        return minor;
-    }
-
-    /** Get the patch version.
-     * @return Returns the patch version.
-     */
-    public int getPatch() {
-        return patch;
-    }
-    
-    /** Is this version all zeros (usually as a result of a parsing issue.
-     * @return if all nulls */
-    public boolean isNull() {
-        return major == 0 && minor ==0 && patch == 0;
-    }
-    
-    /** Helper function for the constructor.
-     *
-     * Parse a string into an int with a range check.
-     * @param valueAsString what to parse
-     * @return the value as an int
-     * @throws NumberFormatException if {@link Integer#parseInt(String, int)} does
-     * or if the value is less than 0 or > {@link #MAX_VNO}.
+     * @param version what to build from 
+     * @throws NumberFormatException if it doesn't fit a 1.2.3 format or if the values are
+     * out of range
      */
-    private int parseValue(final String valueAsString) throws NumberFormatException{
-        final int value = Integer.parseInt(valueAsString);
-        if (value < 0 || value >= MAX_VNO) {
-            throw new NumberFormatException("Improbable version number : " + value);
-        }
-        return value;
-    }
-
-    /** {@inheritDoc} */
-    public boolean equals(final Object obj) {
-        if (obj instanceof PluginVersion) {
-            final PluginVersion other= (PluginVersion)obj;
-            return other.major == major && other.minor == minor && other.patch == patch;
-        }
-        return false;
-    }
-    
-    /** {@inheritDoc} */
-    public int hashCode() {
-        long l = major*MAX_VNO*MAX_VNO;
-        l += minor * MAX_VNO;
-        l += patch;
-        return Long.hashCode(l);
-    }
-
-    /**
-     * Compares this object with the specified object for order.  Returns a
-     * negative integer, zero, or a positive integer as this object is less
-     * than, equal to, or greater than the specified object.
-     * {@inheritDoc} */
-    public int compareTo(final PluginVersion other) {
-        if (major == other.major) {
-            if (minor == other.minor) {
-                return patch - other.patch;
-            }
-            return minor - other.minor;
-        }
-        return major - other.major;
-    }
-    
-    /** {@inheritDoc} */
-    public String toString() {
-        return new StringBuffer(8).append(Integer.toString(major))
-                .append('.')
-                .append(Integer.toString(minor))
-                .append('.')
-                .append(Integer.toString(patch)).toString();
+    public PluginVersion(final String version) throws NumberFormatException {
+        super(version);
+        DeprecationSupport.warnOnce(ObjectType.CLASS, PluginVersion.class.toString(), null, InstallableComponentVersion.class.toString());
     }
 }
diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PropertyDrivenIdPPlugin.java b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PropertyDrivenIdPPlugin.java
index e0d232ad4..abad86e09 100644
--- a/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PropertyDrivenIdPPlugin.java
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/plugin/PropertyDrivenIdPPlugin.java
@@ -77,7 +77,7 @@ public abstract class PropertyDrivenIdPPlugin extends AbstractIdPPlugin {
     @Nullable private String pluginId;
     
     /** Handles parsing of plugin version. */
-    @Nullable private PluginVersion pluginVersion;
+    @Nullable private InstallableComponentVersion pluginVersion;
     
     /** Plugin update URLs. */
     @Nonnull @NonnullElements private List<URL> updateURLs = CollectionSupport.emptyList();
@@ -144,7 +144,7 @@ public abstract class PropertyDrivenIdPPlugin extends AbstractIdPPlugin {
         }
         
         try {
-            pluginVersion = new PluginVersion(version);
+            pluginVersion = new InstallableComponentVersion(version);
         } catch (final NumberFormatException e) {
             throw new PluginException(e);
         }
diff --git a/idp-admin-api/src/test/java/net/shibboleth/idp/plugin/PluginVersionTest.java b/idp-admin-api/src/test/java/net/shibboleth/idp/plugin/PluginVersionTest.java
index 0cbd666f3..79066c8cf 100644
--- a/idp-admin-api/src/test/java/net/shibboleth/idp/plugin/PluginVersionTest.java
+++ b/idp-admin-api/src/test/java/net/shibboleth/idp/plugin/PluginVersionTest.java
@@ -24,14 +24,14 @@ import static org.testng.Assert.fail;
 import org.testng.annotations.Test;
 
 /**
- * Tests for {@link PluginVersion}
+ * Tests for {@link InstallableComponentVersion}
  */
 @SuppressWarnings("javadoc")
 public final class PluginVersionTest {
 
     private void failParse(final String what) {
         try {
-            new PluginVersion(what);
+            new InstallableComponentVersion(what);
             fail("Invalid version parsed OK");
         } catch (final NumberFormatException e) {
             return;
@@ -39,23 +39,23 @@ public final class PluginVersionTest {
     }
     
     @Test public void parseTest() {
-        PluginVersion ver = new PluginVersion("4.2.1");
+        InstallableComponentVersion ver = new InstallableComponentVersion("4.2.1");
         assertEquals(ver.getMajor(), 4);
         assertEquals(ver.getMinor(), 2);
         assertEquals(ver.getPatch(), 1);
 
-        ver = new PluginVersion("3.4");
+        ver = new InstallableComponentVersion("3.4");
         assertEquals(ver.getMajor(), 3);
         assertEquals(ver.getMinor(), 4);
         assertEquals(ver.getPatch(), 0);
         
-        ver = new PluginVersion("2");
+        ver = new InstallableComponentVersion("2");
         assertEquals(ver.getMajor(), 2);
         assertEquals(ver.getMinor(), 0);
         assertEquals(ver.getPatch(), 0);
 
         // Edge cases
-        ver = new PluginVersion("2.-.");
+        ver = new InstallableComponentVersion("2.-.");
         assertEquals(ver.getMajor(), 2);
         assertEquals(ver.getMinor(), 0);
         assertEquals(ver.getPatch(), 0);
@@ -72,19 +72,19 @@ public final class PluginVersionTest {
         failParse("10001.99.0");
 
         try {
-            new PluginVersion(1,2,-1);
+            new InstallableComponentVersion(1,2,-1);
             fail("Bad version not caught");
         } catch (NumberFormatException ex) {
             // OK
         }
         try {
-            new PluginVersion(10000,2,0);
+            new InstallableComponentVersion(10000,2,0);
             fail("Bad version not caught");
         } catch (NumberFormatException ex) {
             // OK
         }
         try {
-            new PluginVersion(1, 10000,2);
+            new InstallableComponentVersion(1, 10000,2);
             fail("Bad version not caught");
         } catch (NumberFormatException ex) {
             // OK
@@ -95,9 +95,9 @@ public final class PluginVersionTest {
         // check direction
         assertTrue(Integer.valueOf(-1).compareTo(Integer.valueOf(0)) < 0);
         
-        assertTrue(new PluginVersion("4.5.6").compareTo(new PluginVersion(4,5,6)) == 0); 
-        assertTrue(new PluginVersion(4,0,0).compareTo(new PluginVersion(3,9,9)) > 0); 
-        assertTrue(new PluginVersion(4,1,0).compareTo(new PluginVersion(4,2,1)) < 0);
-        assertTrue(new PluginVersion(4,1,0).compareTo(new PluginVersion(4,1,1)) < 0);
+        assertTrue(new InstallableComponentVersion("4.5.6").compareTo(new InstallableComponentVersion(4,5,6)) == 0); 
+        assertTrue(new InstallableComponentVersion(4,0,0).compareTo(new InstallableComponentVersion(3,9,9)) > 0); 
+        assertTrue(new InstallableComponentVersion(4,1,0).compareTo(new InstallableComponentVersion(4,2,1)) < 0);
+        assertTrue(new InstallableComponentVersion(4,1,0).compareTo(new InstallableComponentVersion(4,1,1)) < 0);
     }
 }
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/InstallerSupport.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/InstallerSupport.java
index e9b8f814e..5f5bb9021 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/InstallerSupport.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/InstallerSupport.java
@@ -17,11 +17,21 @@
 
 package net.shibboleth.idp.installer;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.List;
+import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
@@ -35,11 +45,18 @@ import org.apache.tools.ant.taskdefs.optional.unix.Chgrp;
 import org.apache.tools.ant.types.FileSet;
 import org.apache.tools.ant.types.selectors.PresentSelector;
 import org.apache.tools.ant.types.selectors.PresentSelector.FilePresence;
+import org.opensaml.security.httpclient.HttpClientSecurityContextHandler;
 import org.slf4j.Logger;
 
+import net.shibboleth.idp.installer.plugin.impl.LoggingVisitor;
+import net.shibboleth.shared.annotation.constraint.Live;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.spring.httpclient.resource.HTTPResource;
 
-/** General common names and helper functions for the Installer. 
+/** General common names and helper functions for the IdP & Plugin Installers.
  * This is not intended for general use.
  */
 public final class InstallerSupport {
@@ -270,7 +287,7 @@ public final class InstallerSupport {
      * @param where where
      * @throws BuildException if badness occurs
      */
-    public static void deleteTree(final Path where) throws BuildException {
+    public static void deleteTree(@Nullable final Path where) throws BuildException {
         deleteTree(where, null);
     }
 
@@ -279,8 +296,11 @@ public final class InstallerSupport {
      * @param excludes wildcards to exclude
      * @throws BuildException if badness occurs
      */
-    public static void deleteTree(final Path where, final String excludes) throws BuildException {
-        if (!Files.exists(where)) {
+    public static void deleteTree(@Nullable final Path where, @Nullable final String excludes) throws BuildException {
+        if (where == null) {
+            return;
+        }
+        if (where == null || !Files.exists(where)) {
             log.debug("Directory {} does not exist. Skipping delete.", where);
             return;
         }
@@ -307,7 +327,7 @@ public final class InstallerSupport {
         delete.setVerbose(!log.isDebugEnabled());
         delete.execute();
     }
-    
+
     /** Return a {@link Jar} task.
      * @param baseDir where from
      * @param destFile where to
@@ -320,4 +340,179 @@ public final class InstallerSupport {
         jarTask.setProject(InstallerSupport.ANT_PROJECT);
         return jarTask;
     }
+
+    /** Download helper method.
+     * @param baseResource where to go for the file
+     * @param handler HttpClientSecurityContextHandler to use
+     * @param downloadDirectory where to download to
+     * @param fileName the file name
+     * @throws IOException as required
+     */
+    public static void download(@Nonnull final HTTPResource baseResource,
+            @Nonnull final HttpClientSecurityContextHandler handler,
+            @Nonnull final Path downloadDirectory,
+            @Nonnull final String fileName) throws IOException {
+        final HTTPResource httpResource = baseResource.createRelative(fileName, handler);
+        final Path filePath = downloadDirectory.resolve(fileName);
+        log.info("Downloading from {}", httpResource.getDescription());
+        log.debug("Downloading to {}", filePath);
+        try (final OutputStream fileOut = new ProgressReportingOutputStream(new FileOutputStream(filePath.toFile()))) {
+            httpResource.getInputStream().transferTo(fileOut);
+        }
+    }
+
+    /** Return the canonical path.
+     * @param from the path we get given
+     * @return the canonicalized one
+     * @throws IOException  as from {@link File#getCanonicalFile()}
+     */
+    @SuppressWarnings("null")
+    @Nonnull
+    public static Path canonicalPath(@Nonnull final Path from) throws IOException {
+        return from.toFile().getCanonicalFile().toPath();
+    }
+
+    /** Rename Files into the provided tree.
+     * @param fromBase The root directory of the from files
+     * @param toBase The root directory to rename to
+     * @param fromFiles The list of files (inside fromBase) to rename
+     * @param renames All the work as it is done
+     * @throws IOException If any of the file operations fail
+     */
+    public static void renameToTree(@Nonnull final Path fromBase,
+            @Nonnull final Path toBase,
+            @Nonnull final List<Path> fromFiles,
+            @Nonnull @Live final List<Pair<Path, Path>> renames) throws IOException {
+        if (!Files.exists(toBase)) {
+            Files.createDirectories(toBase);
+        }
+        for (final Path path : fromFiles) {
+            if (!Files.exists(path)) {
+                log.info("File {} was not renamed away because it does not exist", path);
+                continue;
+            }
+            final Path relName = fromBase.relativize(path);
+            log.trace("Relative name {}", relName);
+            final Path to = toBase.resolve(relName);
+            Files.createDirectories(to.getParent());
+            Files.move(path,to);
+            renames.add(new Pair<>(path, to));
+        }
+    }
+
+    /** Traverse "from" looking to see if any of the files are already in "to".
+     * @param from source directory
+     * @param to target directory
+     * @return true if there was a match
+     * @throws BuildException if anything threw and {@link IOException}
+     */
+    public static boolean detectDuplicates(@Nonnull final Path from, @Nullable final Path to) throws BuildException {
+
+        if (to == null || !Files.exists(to)) {
+            return false;
+        }
+        final NameClashVisitor detector = new NameClashVisitor(from, to);
+        log.debug("Walking {}, looking for a name clash in {}", from, to);
+        try {
+            Files.walkFileTree(from, detector);
+        } catch (final IOException e) {
+            log.error("Failed during duplicate detection:", e);
+            throw new BuildException(e);
+        }
+        return detector.wasNameClash();
+    }
+
+    /** Copy a directory tree and keep a log of what has changed.
+     * @param from source directory
+     * @param to target directory
+     * @param pathsCopied the list of files copied up (including if there was a failure)
+     * @throws BuildException from the copy
+     */
+    public static void copyWithLogging(@Nullable final Path from, 
+            @Nonnull final Path to, @Nonnull @Live final List<Path> pathsCopied) throws BuildException {
+        if (from == null || !Files.exists(from)) {
+            return;
+        }
+        log.debug("Copying from {} to {}", from, to);
+        final LoggingVisitor visitor = new LoggingVisitor(from, to);
+        try {
+            Files.walkFileTree(from, visitor);
+        } catch (final IOException e) {
+            pathsCopied.addAll(visitor.getCopiedList());
+            log.error("Error copying files from {} to {}", from, to, e);
+            throw new BuildException(e);
+        }
+        pathsCopied.addAll(visitor.getCopiedList());
+    }
+
+    /**
+     * A @{link {@link FileVisitor} which detects (and logs) whether a copy would overwrite.
+     */
+    private static final class NameClashVisitor extends SimpleFileVisitor<Path> {
+        /** did we find a duplicate. */
+        private boolean nameClash;
+
+        /** Path we are traversing. */
+        private final Path from;
+
+        /** Path where we check for Duplicates. */
+        private final Path to;
+
+        /**
+         * Constructor.
+         *
+         * @param fromDir Path we are traversing
+         * @param toDir Path where we check for Duplicates
+         */
+        public NameClashVisitor(@Nonnull final Path fromDir, @Nonnull final Path toDir) {
+            from = fromDir;
+            to = toDir;
+        }
+
+        @Override
+        public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+            final Path relFile = from.relativize(file);
+            final Path toFile = to.resolve(relFile);
+            if (Files.exists(toFile)) {
+                nameClash = true;
+                log.warn("{} already exists", toFile);
+            }
+            return FileVisitResult.CONTINUE;
+        }
+
+        /** did we find a name clash?
+         * @return whether we found a name clash.
+         */
+        public boolean wasNameClash() {
+            return nameClash;
+        }
+    }
+
+    /** Predicate to ask the user if they want to install the trust store provided. */
+    public static class InstallerQuery implements Predicate<String> {
+
+        /** What to say. */
+        @Nonnull
+        private final String promptText;
+
+        /**
+         * Constructor.
+         * @param text What to say before the prompt information
+         */
+        public InstallerQuery(@Nonnull final String text) {
+            promptText = Constraint.isNotNull(text, "Text should not be null");
+        }
+
+        /** {@inheritDoc} */
+        public boolean test(final String keyString) {
+            if (System.console() == null) {
+                log.error("No Console Attached to installer");
+                return false;
+            }
+            System.console().printf("%s:\n%s [yN] ", promptText, keyString);
+            System.console().flush();
+            final String result  = StringSupport.trimOrNull(System.console().readLine());
+            return result != null && "y".equalsIgnoreCase(result.substring(0, 1));
+        }
+    }
 }
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/V5Install.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/V5Install.java
index 231efbcb7..d63d0e043 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/V5Install.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/V5Install.java
@@ -59,7 +59,7 @@ import net.shibboleth.idp.module.IdPModule;
 import net.shibboleth.idp.module.ModuleContext;
 import net.shibboleth.idp.module.ModuleException;
 import net.shibboleth.idp.plugin.IdPPlugin;
-import net.shibboleth.idp.plugin.PluginVersion;
+import net.shibboleth.idp.plugin.InstallableComponentVersion;
 import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
@@ -136,10 +136,10 @@ public class V5Install {
      */
     protected void checkPreConditions() throws BuildException {
         final String versionAsString = Version.getVersion();
-        final PluginVersion idpVersion = new PluginVersion(versionAsString!=null?versionAsString:"5.0.0");
+        final InstallableComponentVersion idpVersion = new InstallableComponentVersion(versionAsString!=null?versionAsString:"5.0.0");
         for (final IdPPlugin plugin: ServiceLoader.load(IdPPlugin.class, currentState.getInstalledPluginsLoader())) {
             final String pluginId = plugin.getPluginId();
-            final PluginVersion pluginVersion = new PluginVersion(plugin);
+            final InstallableComponentVersion pluginVersion = new InstallableComponentVersion(plugin);
             try {
                 log.debug("Considering Plugin {}, version {}", pluginId,  pluginVersion);
                 final PluginState state = new PluginState(plugin, CollectionSupport.emptyList());
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInfo.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInfo.java
index 11df4cfad..7613e0677 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInfo.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInfo.java
@@ -17,255 +17,52 @@
 
 package net.shibboleth.idp.installer.plugin.impl;
 
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Properties;
-import java.util.regex.Pattern;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.installer.plugin.impl.PluginState.VersionInfo;
+import net.shibboleth.idp.plugin.InstallableComponentInfo;
+import net.shibboleth.idp.plugin.InstallableComponentVersion;
 import net.shibboleth.idp.plugin.PluginSupport;
-import net.shibboleth.idp.plugin.PluginSupport.SupportLevel;
-import net.shibboleth.idp.plugin.PluginVersion;
-import net.shibboleth.shared.collection.Pair;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
- * Class which encapsulates the information about a given plugin as downloaded
- * fro  the plugin URL (or file).
+ * Information about a Plugin
  */
-public class PluginInfo {
-
-    /** regexp for spaces. */
-    private static final Pattern SPACE_CONTAINING = Pattern.compile("\\s+");
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(PluginInfo.class);
-
-    /** The support information. */
-    @Nonnull private final Map<PluginVersion, VersionInfo> versionInfo = new HashMap<>();
-
-    /** The Download information. */
-    @Nonnull private final Map<PluginVersion, Pair<URL,String>> downloadInfo = new HashMap<>();
-
-    /** The pluginId. */
-    @Nonnull private final String pluginId;
-
-    /** Whether the information was sufficient. */
-    private boolean allInfoPresent = true;
+public class PluginInfo extends InstallableComponentInfo {
 
     /**
      * Constructor.
      *
-     * @param id the id we care about
-     * @param props all the properties.
-     */
-    public PluginInfo(final String id, @Nonnull final Properties props) {
-        pluginId = Constraint.isNotNull(StringSupport.trimOrNull(id), "pluginID must be non-null");
-        parse(props);
-    }
-
-    /** Did the property file have enough information?
-     * @return true if something was missing.
-     */
-    public boolean isInfoComplete() {
-        return allInfoPresent;
-    }
-
-    /** Get the base URL for this version.
-     * @param version which version
-     * @return the base URL
-     */
-    @Nullable public URL getUpdateURL(final PluginVersion version) {
-        final Pair<URL, String> p = downloadInfo.get(version);
-        if (p == null) {
-            return null;
-        }
-        return p.getFirst();
-    }
-
-    /** Get the base Name for this version.
-     * @param version which version
-     * @return the base name
-     */
-    @Nullable public String getUpdateBaseName(final PluginVersion version) {
-        final Pair<URL, String> p = downloadInfo.get(version);
-        if (p == null) {
-            return null;
-        }
-        return p.getSecond();
-    }
-
-    /** Get the raw version imfo for this plugin.
-     * @return the info.
+     * @param id the plugin Id to ask about.
+     * @param props the properties file to load from
      */
-    public Map<PluginVersion, VersionInfo> getAvailableVersions() {
-        return versionInfo;
+    public PluginInfo(@Nonnull String id, @Nonnull Properties props) {
+        super(id, props);
     }
 
-    /** (try to) populate the information about this plugin.
-     * @param props what to load.
-     */
-    private void  parse(@Nonnull final Properties props) {
-        final String name = pluginId + PluginSupport.AVAILABLE_VERSIONS_PROPERTY_SUFFIX;
-        final String availableVersions = StringSupport.trim(props.getProperty(name));
-        if (availableVersions == null) {
-            log.warn("Plugin {}: Could not find {} property.", pluginId, name);
-            allInfoPresent = false;
-        } else {
-            handleAvailableVersions(props, availableVersions);
-        }
-    }
-
-    /** Given a version find out more.
-     * @param props the property files for this plugin we are looking at
-     * @param version the version in question.
-     */
-    // Checkstyle: CyclomaticComplexity OFF
-    private void handleAvailableVersion(final Properties props, final String version) {
-        final PluginVersion theVersion = new PluginVersion(version);
-        if (theVersion.getMajor() == 0 && theVersion.getMinor() == 0 && theVersion.getPatch() == 0) {
-            log.warn("Plugin {}: Improbable version {}", pluginId, version);
-        }
-        if (versionInfo.containsKey(theVersion)) {
-            log.warn("Plugin {}: Duplicate version {}", pluginId, version);
-        }
-
+    /** {@inheritDoc} */
+    @Override
+    protected @Nullable InstallableComponentVersion getMaxVersion(@Nonnull Properties props, @Nonnull String version) {
         final String maxVersionInfo = StringSupport.trimOrNull(
-                props.getProperty(pluginId + PluginSupport.MAX_IDP_VERSION_INTERFIX + version));
+                props.getProperty(getComponentId()  + PluginSupport.MAX_IDP_VERSION_INTERFIX + version));
         if (maxVersionInfo == null) {
-            log.warn("Plugin {}, Version {}: Could not find max idp version.", pluginId, version);
-            allInfoPresent = false;
-            return;
+            return null;
         }
+        return new InstallableComponentVersion(maxVersionInfo);
+    }
 
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    protected InstallableComponentVersion getMinVersion(@Nonnull Properties props, @Nonnull String version) {
         final String minVersionInfo = StringSupport.trimOrNull(
-                props.getProperty(pluginId + PluginSupport.MIN_IDP_VERSION_INTERFIX + version));
+                props.getProperty(getComponentId() + PluginSupport.MIN_IDP_VERSION_INTERFIX + version));
         if (minVersionInfo == null) {
-            log.warn("Plugin {}, Version {}: Could not find min idp version.", pluginId, version);
-            allInfoPresent = false;
-            return;
-        }
-
-        final String supportLevelString = StringSupport.trimOrNull(
-                props.getProperty(pluginId + PluginSupport.SUPPORT_LEVEL_INTERFIX + version));
-        PluginSupport.SupportLevel supportLevel;
-        if (supportLevelString == null) {
-            log.debug("Plugin {}, Version {}: Could not find support level for {}.", pluginId, version);
-            supportLevel = SupportLevel.Unknown;
-        } else {
-            try {
-                supportLevel = Enum.valueOf(SupportLevel.class, supportLevelString);
-            } catch (final IllegalArgumentException e) {
-                log.warn("Plugin {}, Version {}: Invalid support level {}.", pluginId, version, supportLevelString);
-                supportLevel = SupportLevel.Unknown;
-            }
-        }
-
-        log.debug("Plugin {}: MaxIdP {}, MinIdP {}, Support Level {}",
-                pluginId, maxVersionInfo, minVersionInfo, supportLevel);
-        final VersionInfo info;
-        info = new VersionInfo(new PluginVersion(maxVersionInfo), new PluginVersion(minVersionInfo), supportLevel);
-        versionInfo.put(theVersion, info);
-        String downloadURL =  StringSupport.trimOrNull(
-                getDefaultedValue(props, PluginSupport.DOWNLOAD_URL_INTERFIX, version));
-        final String baseName =  StringSupport.trimOrNull(
-                getDefaultedValue(props, PluginSupport.BASE_NAME_INTERFIX, version));
-        if (baseName != null && downloadURL != null) {
-            try {
-                if (!downloadURL.endsWith("/")) {
-                    downloadURL += "/";
-                }
-                final URL url = new URL(downloadURL);
-                downloadInfo.put(theVersion, new Pair<>(url, baseName));
-                log.trace("Plugin {}, version {}: Added download URL {}  baseName {} for {}",
-                        pluginId, theVersion, url, baseName);
-            } catch (final MalformedURLException e) {
-               log.warn("Plugin {}, version {}: Download URL '{}' could not be constructed",
-                       pluginId, theVersion, downloadURL, e);
-            }
-        } else {
-            log.info("Plugin {}, version {}: no download information present", pluginId, theVersion);
-        }
-    }
-    // Checkstyle: CyclomaticComplexity ON
-
-    /** Given a list of versions find out more.
-     * @param props the property files for this plugin we are looking at
-     * @param availableVersions a space delimited array of versions
-     */
-    private void handleAvailableVersions(final Properties props, final String availableVersions) {
-        final String[] versions = SPACE_CONTAINING.split(availableVersions, 0);
-
-        log.debug("Plugin {}: Available versions : {} ", pluginId, availableVersions);
-        for (final String version:versions) {
-            log.debug("Plugin {}: Considering {}", pluginId, version);
-            handleAvailableVersion(props, version);
-        }
-    }
-
-    /** Look up the key derived from the pluginId, the interfix and the version, but if that
-     * fails look for a templated definition.
-     * @param props what to look in
-     * @param interfix the interface (between the ID and the version)
-     * @param version the version.
-     * @return the suitable value
-     */
-    @Nullable private String getDefaultedValue(final Properties props, final String interfix, final String version) {
-        String result = props.getProperty(pluginId + interfix + version);
-        if (result != null) {
-            return result;
-        }
-        result = props.getProperty(pluginId + interfix + PluginSupport.VERSION_PATTERN);
-        if (result == null) {
-            return result;
-        }
-        return result.replaceAll(PluginSupport.VERSION_PATTERN_REGEX, version);
-    }
-
-    /** Is the specified plugin supported with this IdP version.
-     * @param pluginVersion the version if the plugin as a {@link PluginVersion}
-     * @param idPVersion the version if the IDP as a {@link PluginVersion}
-     * @return whether it is supported.
-     */
-    public boolean isSupportedWithIdPVersion(final PluginVersion pluginVersion, final PluginVersion idPVersion) {
-        final VersionInfo info = versionInfo.get(pluginVersion);
-        if (info == null) {
-            log.error("Plugin {}: Unknown version {} supplied.", pluginId, pluginVersion);
-            log.debug("Plugin {}: Available {}", pluginId, versionInfo.keySet());
-            return false;
+            return null;
         }
-        return PluginInfo.isSupportedWithIdPVersion(info, idPVersion);
+        return new InstallableComponentVersion(minVersionInfo);
     }
 
-    /** Is the specified plugin supported with this IdP version.
-     * Worker method for all 'isSupportedWith' classes.
-     * @param pluginVersionInfo the version info to consider
-     * @param idPVersion the version as a {@link PluginVersion}
-     * @return whether it is supported.
-     */
-    public static boolean isSupportedWithIdPVersion(final PluginState.VersionInfo pluginVersionInfo,
-            final PluginVersion idPVersion) {
-        final int maxCompare = idPVersion.compareTo(pluginVersionInfo.getMaxSupported()); 
-        if (maxCompare >= 0) {
-            // Exclusive:
-            // IdP (test against) Version is GREATER THAN OR EQUAL to our Max
-            return false;
-        }
-        final int minCompare = idPVersion.compareTo(pluginVersionInfo.getMinSupported());
-        if (minCompare >= 0) {
-            // Inclusive:
-            // IdP (test against) version is GREATER THAN OR EQUAL to our Min
-            return true;
-        }
-        return false;
-    }
 }
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
index 520785f09..3865dd9ac 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstaller.java
@@ -75,7 +75,7 @@ import net.shibboleth.idp.module.IdPModule.ResourceResult;
 import net.shibboleth.idp.module.ModuleContext;
 import net.shibboleth.idp.module.ModuleException;
 import net.shibboleth.idp.plugin.IdPPlugin;
-import net.shibboleth.idp.plugin.PluginVersion;
+import net.shibboleth.idp.plugin.InstallableComponentVersion;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -296,8 +296,8 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
             } catch (final ComponentInitializationException e) {
                throw new BuildException(e);
             }
-            final PluginVersion pluginVersion = new PluginVersion(getDescription());
-            final PluginVersion idpVersion = getIdPVersion();
+            final InstallableComponentVersion pluginVersion = new InstallableComponentVersion(getDescription());
+            final InstallableComponentVersion idpVersion = getIdPVersion();
             if (!state.getPluginInfo().isSupportedWithIdPVersion(pluginVersion, idpVersion)) {
                 LOG.error("Plugin {} version {} is not supported with IdP Version {}",
                         pluginId, pluginVersion, idpVersion);
@@ -513,10 +513,11 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
      */
     private void installNew(final RollbackPluginInstall rollBack) throws BuildException {
         final Path from = distribution.resolve("webapp");
-        if (PluginInstallerSupport.detectDuplicates(from, getPluginsWebapp())) {
+        assert from != null;
+        if (InstallerSupport.detectDuplicates(from, getPluginsWebapp())) {
             throw new BuildException("Install would overwrite files");
         }
-        PluginInstallerSupport.copyWithLogging(from, getPluginsWebapp(), rollBack.getFilesCopied());
+        InstallerSupport.copyWithLogging(from, getPluginsWebapp(), rollBack.getFilesCopied());
 
         String moduleId = null;
         try {
@@ -546,7 +547,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
                 final Path rollbackDir = workspacePath.resolve("rollback");
                 assert rollbackDir != null;
                 LOG.debug("Uninstalling version {} of {}", oldVersion, pluginId);
-                PluginInstallerSupport.renameToTree(getPluginsWebapp(),
+                InstallerSupport.renameToTree(getPluginsWebapp(),
                         rollbackDir,
                         getInstalledContents(),
                         rollback.getFilesRenamedAway());
@@ -565,7 +566,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
         try {
             Files.createDirectories(pluginsContents);
             final Properties props = new Properties(1+copiedFiles.size());
-            props.setProperty(PLUGIN_VERSION_PROPERTY, new PluginVersion(getDescription()).toString());
+            props.setProperty(PLUGIN_VERSION_PROPERTY, new InstallableComponentVersion(getDescription()).toString());
             props.setProperty(PLUGIN_RELATIVE_PATHS_PROPERTY, "true");
             int count = 1;
             for (final Path p: copiedFiles) {
@@ -679,8 +680,8 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
             handler.setHttpClientSecurityParameters(securityParams);
             handler.initialize();
             baseResource.setHttpClientContextHandler(handler);
-            PluginInstallerSupport.download(baseResource, handler, dir,  fileName);
-            PluginInstallerSupport.download(baseResource, handler, dir,  fileName + ".asc");
+            InstallerSupport.download(baseResource, handler, dir,  fileName);
+            InstallerSupport.download(baseResource, handler, dir,  fileName + ".asc");
         } catch (final IOException | ComponentInitializationException e) {
             LOG.error("Error in download", e);
             throw new BuildException(e);
@@ -787,7 +788,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
                 }
                 final Path next = contents.next();
                 assert next != null;
-                distribution = PluginInstallerSupport.canonicalPath(next);
+                distribution = InstallerSupport.canonicalPath(next);
                 if (contents.hasNext()) {
                     LOG.error("Too many packages in distributions {}", fullName);
                     throw new BuildException("Too many packages in distributions");
@@ -913,7 +914,7 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
             throw new ComponentInitializationException("idp.home property must be set");
         }
         try {
-            idpHome = myIdpHome = PluginInstallerSupport.canonicalPath(myIdpHome);
+            idpHome = myIdpHome = InstallerSupport.canonicalPath(myIdpHome);
         } catch (final IOException e) {
             LOG.error("Could not canonicalize idp home", e);
             throw new ComponentInitializationException(e);
@@ -1050,24 +1051,24 @@ public final class PluginInstaller extends AbstractInitializableComponent implem
     public void close() {
         closeSilently(installedPluginsLoader);
         closeSilently(installingPluginLoader);
-        PluginInstallerSupport.deleteTree(downloadDirectory);
-        PluginInstallerSupport.deleteTree(unpackDirectory);
-        PluginInstallerSupport.deleteTree(workspacePath);
+        InstallerSupport.deleteTree(downloadDirectory);
+        InstallerSupport.deleteTree(unpackDirectory);
+        InstallerSupport.deleteTree(workspacePath);
         InstallerSupport.setReadOnly(distPath, true);
     }
     
     /** Return a version we can use in a test proof manner.
      * @return the IdP version or a fixed value
      */
-    @Nonnull protected static PluginVersion getIdPVersion() {
+    @Nonnull protected static InstallableComponentVersion getIdPVersion() {
         final String version  = Version.getVersion();
 
         if (version == null) {
             LOG.error("Could not determine IdP Version. Assuming 4.2.0");
             LOG.error("You should never see this outside a test environment");
-            return new PluginVersion(4,2,0);
+            return new InstallableComponentVersion(4,2,0);
         } 
-        return new PluginVersion(version);
+        return new InstallableComponentVersion(version);
     }
 }
 
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerArguments.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerArguments.java
index 665d7e1d4..3fb49d5c8 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerArguments.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerArguments.java
@@ -31,7 +31,7 @@ import org.slf4j.Logger;
 import com.beust.jcommander.Parameter;
 
 import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLineArguments;
-import net.shibboleth.idp.plugin.PluginVersion;
+import net.shibboleth.idp.plugin.InstallableComponentVersion;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
@@ -108,8 +108,8 @@ public class PluginInstallerArguments extends AbstractIdPHomeAwareCommandLineArg
     @Parameter(names= {"--noRebuild", "--no-rebuild"})
     private boolean noRebuild;
 
-    /** The {@link #forceUpdateVersion} as a {@link PluginVersion}. */
-    @Nullable private PluginVersion updateVersion;
+    /** The {@link #forceUpdateVersion} as a {@link InstallableComponentVersion}. */
+    @Nullable private InstallableComponentVersion updateVersion;
 
     /** Decomposed input - name. */
     @Nullable private String inputName;
@@ -260,7 +260,7 @@ public class PluginInstallerArguments extends AbstractIdPHomeAwareCommandLineArg
     /** Return the version to update to or null.
      * @return the version or null
      */
-    @Nullable public PluginVersion getUpdateVersion() {
+    @Nullable public InstallableComponentVersion getUpdateVersion() {
         return updateVersion;
     }
 
@@ -318,7 +318,7 @@ public class PluginInstallerArguments extends AbstractIdPHomeAwareCommandLineArg
             pluginId = updatePluginId;
             operation = OperationType.UPDATE;
             if (forceUpdateVersion != null) {
-                updateVersion = new PluginVersion(forceUpdateVersion);
+                updateVersion = new InstallableComponentVersion(forceUpdateVersion);
             }
         } else if (uninstallId != null) {
             pluginId = uninstallId;
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerCLI.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerCLI.java
index 9f5f7eddf..951c71560 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerCLI.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerCLI.java
@@ -46,9 +46,11 @@ import org.springframework.core.io.Resource;
 
 import net.shibboleth.idp.Version;
 import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLine;
-import net.shibboleth.idp.installer.plugin.impl.PluginState.VersionInfo;
+import net.shibboleth.idp.installer.InstallerSupport;
 import net.shibboleth.idp.plugin.IdPPlugin;
-import net.shibboleth.idp.plugin.PluginVersion;
+import net.shibboleth.idp.plugin.InstallableComponentInfo;
+import net.shibboleth.idp.plugin.InstallableComponentVersion;
+import net.shibboleth.idp.plugin.PluginSupport;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.cli.AbstractCommandLine;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -216,7 +218,7 @@ public final class PluginInstallerCLI extends AbstractIdPHomeAwareCommandLine<Pl
         assert idpHome != null;
         inst.setIdpHome(idpHome);
         if (!args.isUnattended()) {
-            inst.setAcceptKey(new PluginInstallerSupport.InstallerQuery("Accept this key"));
+            inst.setAcceptKey(new InstallerSupport.InstallerQuery("Accept this key"));
         }
         inst.setTrustore(args.getTruststore());
         final HttpClient client = getHttpClient();
@@ -261,19 +263,20 @@ public final class PluginInstallerCLI extends AbstractIdPHomeAwareCommandLine<Pl
             log.error("Could not interrogate plugin {}", plugin.getPluginId(), e);
             return;
         }
-        final Map<PluginVersion, VersionInfo> versionMap = state.getPluginInfo().getAvailableVersions();
-        final List<PluginVersion> versionList = new ArrayList<>(versionMap.keySet());
+        final Map<InstallableComponentVersion, InstallableComponentInfo.VersionInfo> versionMap = state.getPluginInfo().getAvailableVersions();
+        final List<InstallableComponentVersion> versionList = new ArrayList<>(versionMap.keySet());
         versionList.sort(null);
         outOrLog("\tVersions ");
-        for (final PluginVersion version:versionList) {
+        for (final InstallableComponentVersion version:versionList) {
             final String downLoadDetails;
+            assert version != null;
             if (state.getPluginInfo().getUpdateBaseName(version) == null ||
                 state.getPluginInfo().getUpdateURL(version)==null ) {
                 downLoadDetails = " - No download available";
             } else {
                 downLoadDetails = "";
             }
-            final VersionInfo info = versionMap.get(version);
+            final InstallableComponentInfo.VersionInfo info = versionMap.get(version);
             outOrLog(String.format("\t%s:\tMin=%s\tMax=%s\tSupport level: %s%s",
                     version,
                     info.getMinSupported(),
@@ -367,7 +370,7 @@ public final class PluginInstallerCLI extends AbstractIdPHomeAwareCommandLine<Pl
             log.error("Plugin {} found, but no contents listed", pluginId);
             return;
         }
-        final String installedVersion = new PluginVersion(thePlugin).toString();
+        final String installedVersion = new InstallableComponentVersion(thePlugin).toString();
         if (!fromContentsVersion.equals(installedVersion)) {
             log.error("Installed version of Plugin {} ({}) does not match contents ({})", 
                     pluginId, installedVersion, fromContentsVersion);
@@ -386,41 +389,44 @@ public final class PluginInstallerCLI extends AbstractIdPHomeAwareCommandLine<Pl
      * @return whether it worked
      */
     private int doListAvailable() {
-        final Properties props = PluginInstallerSupport.loadPluginInfo(updateURLs, this);
+        final HttpClient client = getHttpClient();
+        assert client != null;
+        final Properties props = PluginSupport.loadPluginInfo(updateURLs, client, getHttpClientSecurityParameters());
         if (props == null) {
             return RC_IO;
         }
 
-        final Map<String, PluginInfo> plugins = new HashMap<>();
+        final Map<String, InstallableComponentInfo> plugins = new HashMap<>();
         final Enumeration<Object> en = props.keys();
         while (en.hasMoreElements()) {
             final String key = (String)en.nextElement();
             if (key.endsWith(".versions")) {
                 final String pluginId = key.substring(0, key.length()-9);
-                final PluginInfo info = new PluginInfo(pluginId, props);
+                assert pluginId != null;
+                final InstallableComponentInfo info = new PluginInfo(pluginId, props);
                 if (info.isInfoComplete()) {
                     plugins.put(pluginId, info);
                 }
             }
         }
 
-        for (final Entry<String, PluginInfo> entry: plugins.entrySet()) {
-            final PluginVersion nullVersion = new PluginVersion(0, 0, 0);
+        for (final Entry<String, InstallableComponentInfo> entry: plugins.entrySet()) {
+            final InstallableComponentVersion nullVersion = new InstallableComponentVersion(0, 0, 0);
             final String key = entry.getKey();
-            final PluginInfo value = entry.getValue();
+            final InstallableComponentInfo value = entry.getValue();
             assert key!=null && value !=null;
             assert installer != null;
             final IdPPlugin existingPlugin = installer.getInstalledPlugin(key);
             if (existingPlugin == null) {
-                final PluginVersion version = PluginInstallerSupport.getBestVersion(nullVersion, value);
+                final InstallableComponentVersion version = PluginSupport.getBestVersion(nullVersion, value);
                 if (version == null) {
                     log.debug("Plugin {} has no version available", entry.getKey());
                 } else {
                     outOrLog(String.format("Plugin %s: version %s available for install", entry.getKey(), version));
                 }
             } else {
-                final PluginVersion existingVersion = new PluginVersion(existingPlugin);
-                final PluginVersion version = PluginInstallerSupport.getBestVersion(existingVersion, value);
+                final InstallableComponentVersion existingVersion = new InstallableComponentVersion(existingPlugin);
+                final InstallableComponentVersion version = PluginSupport.getBestVersion(existingVersion, value);
                 if (version == null) {
                     outOrLog(String.format("Plugin %s: Installed version %s: No update available",
                             entry.getKey(),
@@ -451,17 +457,19 @@ public final class PluginInstallerCLI extends AbstractIdPHomeAwareCommandLine<Pl
             log.error("Plugin {} is already installed", pluginId);
             return RC_INIT;
         }
-        final Properties props = PluginInstallerSupport.loadPluginInfo(updateURLs, this);
+        final HttpClient client = getHttpClient();
+        assert client != null;
+        final Properties props = PluginSupport.loadPluginInfo(updateURLs, client, getHttpClientSecurityParameters());
         if (props == null) {
             log.error("AutoInstall not possible");
             return RC_INIT;
         }
-        final PluginInfo info = new PluginInfo(pluginId, props);
+        final InstallableComponentInfo info = new PluginInfo(pluginId, props);
         if (!info.isInfoComplete()) {
             log.error("Plugin {}: Information not found", pluginId);
             return RC_INIT;
         }
-        final PluginVersion versionToInstall = PluginInstallerSupport.getBestVersion(new PluginVersion(0,0,0), info);
+        final InstallableComponentVersion versionToInstall = PluginSupport.getBestVersion(new InstallableComponentVersion(0,0,0), info);
         if (versionToInstall == null) {
             log.error("Plugin {}: No version available to install", pluginId);
             return RC_INIT;
@@ -480,7 +488,7 @@ public final class PluginInstallerCLI extends AbstractIdPHomeAwareCommandLine<Pl
      * @param checkVersion are we checking the version. 
      */
     private void doUpdate(@Nonnull final String pluginId, 
-            @Nullable final PluginVersion pluginVersion,
+            @Nullable final InstallableComponentVersion pluginVersion,
             final boolean checkVersion) {
         
         final PluginInstaller inst = installer;
@@ -503,16 +511,16 @@ public final class PluginInstallerCLI extends AbstractIdPHomeAwareCommandLine<Pl
             log.error("Could not interrogate plugin {}", plugin.getPluginId(), e);
             return;
         }
-        final PluginVersion installVersion;
+        final InstallableComponentVersion installVersion;
         if (pluginVersion == null) {
-            installVersion = PluginInstallerSupport.getBestVersion(new PluginVersion(plugin), state.getPluginInfo());
+            installVersion = PluginSupport.getBestVersion(new InstallableComponentVersion(plugin), state.getPluginInfo());
             if (installVersion == null) {
                 log.info("No suitable update version available");
                 return;
             }
         } else {
             installVersion = pluginVersion;
-            final Map<PluginVersion, VersionInfo> versions = state.getPluginInfo().getAvailableVersions();
+            final Map<InstallableComponentVersion, InstallableComponentInfo.VersionInfo> versions = state.getPluginInfo().getAvailableVersions();
             if (!versions.containsKey(installVersion)) {
                 log.error("Specified version {} could not be found. Available versions: {}",
                         installVersion, versions.keySet());
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerSupport.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerSupport.java
deleted file mode 100644
index f3fc533f7..000000000
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginInstallerSupport.java
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID 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 net.shibboleth.idp.installer.plugin.impl;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.URL;
-import java.nio.file.FileVisitResult;
-import java.nio.file.FileVisitor;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-import java.util.function.Predicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.apache.hc.client5.http.classic.HttpClient;
-import org.apache.tools.ant.BuildException;
-import org.opensaml.security.httpclient.HttpClientSecurityContextHandler;
-import org.slf4j.Logger;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.core.io.Resource;
-
-import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLine;
-import net.shibboleth.idp.installer.InstallerSupport;
-import net.shibboleth.idp.installer.ProgressReportingOutputStream;
-import net.shibboleth.idp.installer.plugin.impl.PluginState.VersionInfo;
-import net.shibboleth.idp.plugin.PluginSupport.SupportLevel;
-import net.shibboleth.idp.plugin.PluginVersion;
-import net.shibboleth.shared.annotation.constraint.Live;
-import net.shibboleth.shared.collection.Pair;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.shared.spring.httpclient.resource.HTTPResource;
-
-/**
- * Support for copying files during plugin manipulation.
- */
-public final class PluginInstallerSupport {
-    
-    /** Class logger. */
-    @Nonnull
-    private static final Logger LOG = LoggerFactory.getLogger(PluginInstallerSupport.class);
-
-    /** Constructor. */
-    private PluginInstallerSupport() {
-    }
-
-    /** Return the canonical path.
-     * @param from the path we get given
-     * @return the canonicalized one
-     * @throws IOException  as from {@link File#getCanonicalFile()}
-     */
-    @SuppressWarnings("null")
-    @Nonnull static Path canonicalPath(@Nonnull final Path from) throws IOException {
-        return from.toFile().getCanonicalFile().toPath();
-    }
-
-    /** Delete a directory tree. 
-     * @param directory what to delete
-     */
-    public static void deleteTree(@Nullable final Path directory) {
-        if (directory == null || !Files.exists(directory)) {
-            return;
-        }
-        LOG.debug("Deleting directory {}", directory);
-        InstallerSupport.setReadOnly(directory, false);
-        try {
-            Files.walkFileTree(directory, new DeletingVisitor());
-        } catch (final IOException e) {
-            LOG.error("Couldn't delete {}", directory, e);
-        }
-    }
-    
-    /** Traverse "from" looking to see if any of the files are already in "to".
-     * @param from source directory
-     * @param to target directory
-     * @return true if there was a match 
-     * @throws BuildException if anything threw and {@link IOException}
-     */
-    public static boolean detectDuplicates(final Path from, final Path to) throws BuildException {
-        
-        if (to == null || !Files.exists(to)) {
-            return false;
-        }
-        final NameClashVisitor detector = new NameClashVisitor(from, to);
-        LOG.debug("Walking {}, looking for a name clash in {}", from, to);
-        try {
-            Files.walkFileTree(from, detector);
-        } catch (final IOException e) {
-            LOG.error("Failed during duplicate detection:", e);
-            throw new BuildException(e);
-        }        
-        return detector.wasNameClash();
-    }
-    
-    /** Copy a directory tree and keep a log of what has changed.
-     * @param from source directory
-     * @param to target directory
-     * @param pathsCopied the list of files copied up (including if there was a failure)
-     * @throws BuildException from the copy
-     */
-    public static void copyWithLogging(final Path from, 
-            final Path to, @Live final List<Path> pathsCopied) throws BuildException {
-        if (from == null || !Files.exists(from)) {
-            return;
-        }
-        LOG.debug("Copying from {} to {}", from, to);
-        final LoggingVisitor visitor = new LoggingVisitor(from, to);
-        try {
-            Files.walkFileTree(from, visitor);
-        } catch (final IOException e) {
-            pathsCopied.addAll(visitor.getCopiedList());
-            LOG.error("Error copying files from {} to {}", from, to, e);
-            throw new BuildException(e);
-        }
-        pathsCopied.addAll(visitor.getCopiedList());
-    }
-    
-    /** Rename Files into the provided tree.
-     * @param fromBase The root directory of the from files
-     * @param toBase The root directory to rename to
-     * @param fromFiles The list of files (inside fromBase) to rename
-     * @param renames All the work as it is done
-     * @throws IOException If any of the file operations fail
-     */
-    public static void renameToTree(@Nonnull final Path fromBase,
-            @Nonnull final Path toBase,
-            @Nonnull final List<Path> fromFiles,
-            @Nonnull @Live final List<Pair<Path, Path>> renames) throws IOException {
-        if (!Files.exists(toBase)) {
-            Files.createDirectories(toBase);
-        }
-        for (final Path path : fromFiles) {
-            if (!Files.exists(path)) {
-                LOG.info("File {} was not renamed away because it does not exist", path);
-                continue;
-            }
-            final Path relName = fromBase.relativize(path);
-            LOG.trace("Relative name {}", relName);
-            final Path to = toBase.resolve(relName);
-            Files.createDirectories(to.getParent());
-            Files.move(path,to);
-            renames.add(new Pair<>(path, to));
-        }
-    }
-
-    /**
-     * A @{link {@link FileVisitor} which detects (and logs) whether a copy would overwrite.
-     */
-    private static final class NameClashVisitor extends SimpleFileVisitor<Path> {
-        /** did we find a duplicate. */
-        private boolean nameClash;
-
-        /** Path we are traversing. */
-        private final Path from;
-        
-        /** Path where we check for Duplicates. */
-        private final Path to;
-        /**
-         * Constructor.
-         *
-         * @param fromDir Path we are traversing
-         * @param toDir Path where we check for Duplicates
-         */
-        public NameClashVisitor(final Path fromDir, final Path toDir) {
-            from = fromDir;
-            to = toDir;
-        }
-        
-        @Override
-        public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
-            final Path relFile = from.relativize(file);
-            final Path toFile = to.resolve(relFile);
-            if (Files.exists(toFile)) {
-                nameClash = true;
-                LOG.warn("{} already exists", toFile);
-            }
-            return FileVisitResult.CONTINUE;
-        }
-        
-        /** did we find a name clash?
-         * @return whether we found a name clash.
-         */
-        public boolean wasNameClash() {
-            return nameClash;
-        }
-    }
-    
-    /**
-     * A @{link {@link FileVisitor} which deletes files.
-     */
-    private static final class DeletingVisitor extends SimpleFileVisitor<Path> {
-        @Override
-        public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
-            try {
-                Files.delete(file);
-            } catch (final IOException e) {
-                LOG.error("Could not delete {}", file.toAbsolutePath(), e);
-                file.toFile().deleteOnExit();
-                // and carry on
-            }
-            return FileVisitResult.CONTINUE;
-        }
-        @Override
-        public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
-            if (exc != null) {
-                throw exc;
-            }
-            try {
-                Files.delete(dir);
-            } catch (final IOException e) {
-                LOG.error("Could not delete {}", dir.toAbsolutePath(), e);
-                dir.toFile().deleteOnExit();
-                // and carry on
-            }
-            return FileVisitResult.CONTINUE;
-        }
-    }
-
-    /** Find the best update version (plugin or IdP).
-     * @param pluginVersion The Plugin version
-     * @param pluginInfo all about the plugin
-     * @return the best version (or null)
-     */
-    @Nullable static public PluginVersion getBestVersion(@Nonnull final PluginVersion pluginVersion, @Nonnull final PluginInfo pluginInfo) {
-        return getBestVersion(PluginInstaller.getIdPVersion(), pluginVersion, pluginInfo);
-    }
-
-   /** Find the best update version  (plugin or IdP).
-     * @param idPVersion The IdP version to check.
-     * @param pluginVersion The Plugin version
-     * @param pluginInfo all about the plugin
-     * @return the best version (or null)
-     */
-    @Nullable static public PluginVersion getBestVersion(@Nonnull final PluginVersion idPVersion,
-            @Nonnull final PluginVersion pluginVersion, @Nonnull final PluginInfo pluginInfo) {
-
-        final List<PluginVersion> availableVersions = new ArrayList<>(pluginInfo.getAvailableVersions().keySet());
-        availableVersions.sort(null);
-        LOG.debug("Considering versions: {}", availableVersions);
-
-        for (int i = availableVersions.size()-1; i >= 0; i--) {
-            final PluginVersion version = availableVersions.get(i);
-            if (version.compareTo(pluginVersion) <= 0) {
-                LOG.debug("Version {} is less than or the same as {}. All done", version, pluginVersion);
-                return null;
-            }
-            final VersionInfo versionInfo = pluginInfo.getAvailableVersions().get(version);
-            if (versionInfo.getSupportLevel() != SupportLevel.Current) {
-                LOG.debug("Version {} has support level {}, ignoring", version, versionInfo.getSupportLevel());
-                continue;
-            }
-            if (!pluginInfo.isSupportedWithIdPVersion(version, idPVersion)) {
-                LOG.debug("Version {} is not supported with idpVersion {}", version, idPVersion);
-                continue;
-            }
-            LOG.debug("Version {} is supported with idpVersion {}", version, idPVersion);
-            if (pluginInfo.getUpdateURL(version) == null || pluginInfo.getUpdateBaseName(version) == null) {
-                LOG.debug("Version {} is does not have update information", version);
-                continue;
-            }
-            return version;
-        }
-        return null;
-    }
-
-    /** Load the property file describing all the plugin we know about from a known location.
-     * @param updateURLs where to look
-     * @param commandLine the programming calling us.
-     * @return the property files plugins.
-     */
-    @Nullable public static Properties loadPluginInfo(@Nonnull final List<URL> updateURLs,
-            @Nonnull final AbstractIdPHomeAwareCommandLine<?> commandLine) {
-        final List<URL> urls;
-        final Properties props = new Properties();
-        try {
-            if (updateURLs.isEmpty()) {
-                urls = List.of(
-                        new URL("https://shibboleth.net/downloads/identity-provider/plugins/plugins.properties"),
-                        new URL("http://plugins.shibboleth.net/plugins.properties"));
-            } else {
-                urls = updateURLs;
-            }
-        } catch (final IOException e) {
-            LOG.error("Could not load update URLs", e);
-            return null;
-        }
-        for (final URL url: urls) {
-            final Resource propertyResource;
-            try {
-                if ("file".equals(url.getProtocol())) {
-                    final String path =url.getPath();
-                    assert path != null;
-                    propertyResource = new FileSystemResource(path);
-                } else if ("http".equals(url.getProtocol()) || "https".equals(url.getProtocol())) {
-                    final HttpClient client = commandLine.getHttpClient();
-                    assert client != null;
-                    final HTTPResource httpResource;
-                    propertyResource = httpResource = new HTTPResource(client , url);
-                    final HttpClientSecurityContextHandler handler = new HttpClientSecurityContextHandler();
-                    handler.setHttpClientSecurityParameters(commandLine.getHttpClientSecurityParameters());
-                    handler.initialize();
-                    httpResource.setHttpClientContextHandler(handler);
-                } else {
-                    LOG.error("Only file and http[s] URLs are allowed");
-                    continue;
-                }
-                LOG.debug("Plugin Listing: Looking for update at {}", propertyResource.getDescription());
-                if (!propertyResource.exists()) {
-                    LOG.info("{} could not be located", propertyResource.getDescription());
-                    continue;
-                }
-                props.load(propertyResource.getInputStream());
-                return props;
-            } catch (final IOException | ComponentInitializationException e) {
-                LOG.error("Could not open Update URL {} :", url, e);
-                continue;
-            }
-        }
-        LOG.error("Could not locate any active update servers");
-        return null;
-    }
-
-    /** Download helper method.
-     * @param baseResource where to go for the file
-     * @param handler HttpClientSecurityContextHandler to use
-     * @param downloadDirectory where to download to
-     * @param fileName the file name
-     * @throws IOException as required
-     */
-    public static void download(@Nonnull final HTTPResource baseResource,
-            @Nonnull final HttpClientSecurityContextHandler handler,
-            @Nonnull final Path downloadDirectory,
-            @Nonnull final String fileName) throws IOException {
-        final HTTPResource httpResource = baseResource.createRelative(fileName, handler);
-        final Path filePath = downloadDirectory.resolve(fileName);
-        LOG.info("Downloading from {}", httpResource.getDescription());
-        LOG.debug("Downloading to {}", filePath);
-        try (final OutputStream fileOut = new ProgressReportingOutputStream(new FileOutputStream(filePath.toFile()))) {
-            httpResource.getInputStream().transferTo(fileOut);
-        }
-    }
-
-/** Predicate to ask the user if they want to install the trust store provided. */
-    public static class InstallerQuery implements Predicate<String> {
-
-        /** What to say. */
-        @Nonnull
-        private final String promptText;
-
-        /**
-         * Constructor.
-         * @param text What to say before the prompt information
-         */
-        public InstallerQuery(@Nonnull final String text) {
-            promptText = Constraint.isNotNull(text, "Text should not be null");
-        }
-
-        /** {@inheritDoc} */
-        public boolean test(final String keyString) {
-            if (System.console() == null) {
-                LOG.error("No Console Attached to installer");
-                return false;
-            }
-            System.console().printf("%s:\n%s [yN] ", promptText, keyString);
-            System.console().flush();
-            final String result  = StringSupport.trimOrNull(System.console().readLine());
-            return result != null && "y".equalsIgnoreCase(result.substring(0, 1));
-        }
-    }
-}
-
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginState.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginState.java
index 93e960562..a4d94df2a 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginState.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/plugin/impl/PluginState.java
@@ -33,8 +33,8 @@ import org.springframework.core.io.FileSystemResource;
 import org.springframework.core.io.Resource;
 
 import net.shibboleth.idp.plugin.IdPPlugin;
-import net.shibboleth.idp.plugin.PluginSupport.SupportLevel;
-import net.shibboleth.idp.plugin.PluginVersion;
+import net.shibboleth.idp.plugin.InstallableComponentInfo;
+import net.shibboleth.idp.plugin.InstallableComponentVersion;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.component.AbstractInitializableComponent;
 import net.shibboleth.shared.component.ComponentInitializationException;
@@ -53,10 +53,10 @@ public class PluginState extends AbstractInitializableComponent {
     @Nonnull private final IdPPlugin plugin;
 
     /** My Plugin Info. */
-    @NonnullAfterInit private PluginInfo myPluginInfo;
+    @NonnullAfterInit private InstallableComponentInfo myPluginInfo;
 
     /** The version of this plugin. */
-    @Nonnull private final PluginVersion myPluginVersion;
+    @Nonnull private final InstallableComponentVersion myPluginVersion;
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(PluginState.class);
@@ -79,13 +79,13 @@ public class PluginState extends AbstractInitializableComponent {
     public PluginState(@Nonnull final IdPPlugin description, final List<URL> updateOverrides) {
         updateOverrideURLs = Constraint.isNotNull(updateOverrides, "updated Locations must not be null");
         plugin = Constraint.isNotNull(description, "Plugin must not be null");
-        myPluginVersion = new PluginVersion(plugin);
+        myPluginVersion = new InstallableComponentVersion(plugin);
     }
     
     /** get our PluginInfo.
      * @return our PluginInfo.
      */
-    @Nonnull public PluginInfo getPluginInfo() {
+    @Nonnull public InstallableComponentInfo getPluginInfo() {
         checkComponentActive();
         assert myPluginInfo!=null;
         return myPluginInfo;
@@ -212,51 +212,4 @@ public class PluginState extends AbstractInitializableComponent {
         }
     }
     // CheckStyle: CyclomaticComplexity ON
-
-    /** Encapsulation of the information about a given IdP version. */
-    public static class VersionInfo {
-        
-        /** Maximum version - this version is NOT SUPPORTED. */ 
-        private final PluginVersion maxSupported; 
-
-        /** Minimum version - this version IS supported. */ 
-        private final PluginVersion minSupported; 
-        
-        /** support level. */
-        private final SupportLevel supportLevel;
-
-        /**
-         * Constructor.
-         *
-         * @param max support level
-         * @param min support level
-         * @param support support level
-         */
-        VersionInfo(final PluginVersion max, final PluginVersion min, final SupportLevel support) {
-            maxSupported = max;
-            minSupported = min;
-            supportLevel = support;
-        }
-
-        /** get Maximum version - this version is NOT SUPPORTED.
-         * @return Returns the maxSupported.
-         */
-        public PluginVersion getMaxSupported() {
-            return maxSupported;
-        }
-
-        /** get Minimum (IdP) version - this version IS supported.
-         * @return Returns the minSupported.
-         */
-        public PluginVersion getMinSupported() {
-            return minSupported;
-        }
-
-        /** get support level.
-         * @return Returns the supportLevel.
-         */
-        public SupportLevel getSupportLevel() {
-            return supportLevel;
-        }
-    }
 }
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/BasePluginTest.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/BasePluginTest.java
index 268f20a18..e4ad9959d 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/BasePluginTest.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/BasePluginTest.java
@@ -71,7 +71,7 @@ public class BasePluginTest {
             return;
         }
         InstallerSupport.setReadOnly(idpHome, false);
-        PluginInstallerSupport.deleteTree(idpHome);
+        InstallerSupport.deleteTree(idpHome);
     }
 
     protected Path getIdpHome() {
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginCLITest.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginCLITest.java
index 0958ddeb3..a83f3f867 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginCLITest.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginCLITest.java
@@ -32,6 +32,7 @@ import org.springframework.core.io.Resource;
 import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.Test;
 
+import net.shibboleth.idp.installer.InstallerSupport;
 import net.shibboleth.idp.installer.ProgressReportingOutputStream;
 import net.shibboleth.shared.cli.AbstractCommandLine;
 import net.shibboleth.shared.httpclient.HttpClientBuilder;
@@ -156,7 +157,7 @@ public class PluginCLITest extends BasePluginTest {
                     AbstractCommandLine.RC_OK);
         } finally {
             if (unpack != null) {
-                PluginInstallerSupport.deleteTree(unpack);
+                InstallerSupport.deleteTree(unpack);
             }
         }
     }
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginStateTest.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginStateTest.java
index 021f01569..1d46ed9a9 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginStateTest.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/PluginStateTest.java
@@ -31,7 +31,7 @@ import javax.annotation.Nonnull;
 import org.testng.annotations.Test;
 
 import net.shibboleth.idp.plugin.IdPPlugin;
-import net.shibboleth.idp.plugin.PluginVersion;
+import net.shibboleth.idp.plugin.InstallableComponentVersion;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
@@ -41,8 +41,8 @@ import net.shibboleth.shared.component.ComponentInitializationException;
 @SuppressWarnings("javadoc")
 public class PluginStateTest {
     
-    private boolean testSupportState(final PluginVersion pluginVersion, final PluginState state, final String IdpVersion) {
-        final PluginVersion idPVersion = new PluginVersion(IdpVersion);
+    private boolean testSupportState(@Nonnull final InstallableComponentVersion pluginVersion, final PluginState state, final String IdpVersion) {
+        final InstallableComponentVersion idPVersion = new InstallableComponentVersion(IdpVersion);
         return state.getPluginInfo().isSupportedWithIdPVersion(pluginVersion, idPVersion);
     }
 
@@ -55,27 +55,27 @@ public class PluginStateTest {
         
         state.initialize();
 
-        final PluginVersion pluginVersion = new PluginVersion(simple.getMajorVersion(), simple.getMinorVersion(), simple.getPatchVersion());
+        final InstallableComponentVersion pluginVersion = new InstallableComponentVersion(simple.getMajorVersion(), simple.getMinorVersion(), simple.getPatchVersion());
         
-        assertEquals(pluginVersion, new PluginVersion("1.2.3"));
+        assertEquals(pluginVersion, new InstallableComponentVersion("1.2.3"));
         assertEquals(state.getPluginInfo().getAvailableVersions().size(), 3);
-        assertTrue(state.getPluginInfo().getAvailableVersions().containsKey(new PluginVersion(1, 2, 3)));
-        assertTrue(state.getPluginInfo().getAvailableVersions().containsKey(new PluginVersion(1, 2, 4)));
-        assertTrue(state.getPluginInfo().getAvailableVersions().containsKey(new PluginVersion(2,0,0)));
-        assertFalse(state.getPluginInfo().getAvailableVersions().containsKey(new PluginVersion(3, 2, 3)));
+        assertTrue(state.getPluginInfo().getAvailableVersions().containsKey(new InstallableComponentVersion(1, 2, 3)));
+        assertTrue(state.getPluginInfo().getAvailableVersions().containsKey(new InstallableComponentVersion(1, 2, 4)));
+        assertTrue(state.getPluginInfo().getAvailableVersions().containsKey(new InstallableComponentVersion(2,0,0)));
+        assertFalse(state.getPluginInfo().getAvailableVersions().containsKey(new InstallableComponentVersion(3, 2, 3)));
 
         assertTrue(testSupportState(pluginVersion, state, "4.1.0"));
         assertTrue(testSupportState(pluginVersion, state, "4.2.0"));
         assertTrue(testSupportState(pluginVersion, state, "4.99.9"));
         assertFalse(testSupportState(pluginVersion, state, "5.0.0"));
 
-        final PluginVersion v124 = new PluginVersion(1,2,3);        
+        final InstallableComponentVersion v124 = new InstallableComponentVersion(1,2,3);        
         assertTrue(testSupportState(v124, state,"4.1.0"));
         assertTrue(testSupportState(v124, state, "4.99.9"));
         assertFalse(testSupportState(v124, state, "5.0.0"));
         assertFalse(testSupportState(v124, state, "4.0.0"));
 
-        final PluginVersion v2 = new PluginVersion(2,0,0);
+        final InstallableComponentVersion v2 = new InstallableComponentVersion(2,0,0);
         assertTrue(testSupportState(v2, state, "4.99.1"));
         assertTrue(testSupportState(v2, state, "4.99.999"));
         assertFalse(testSupportState(v2, state, "4.99.0"));
@@ -91,9 +91,9 @@ public class PluginStateTest {
         final TestPlugin tp = new TestPlugin();
         final PluginState state = new PluginState(tp, tp.getUpdateURLs());
         state.initialize();
-        final PluginVersion v123 = new PluginVersion(1,2,3);
-        final PluginVersion v124 = new PluginVersion(1,2,4);
-        final PluginVersion v2 = new PluginVersion(2,0,0);
+        final InstallableComponentVersion v123 = new InstallableComponentVersion(1,2,3);
+        final InstallableComponentVersion v124 = new InstallableComponentVersion(1,2,4);
+        final InstallableComponentVersion v2 = new InstallableComponentVersion(2,0,0);
 
         assertEquals(state.getPluginInfo().getUpdateURL(v123), new URL("https://example.org/plugins/"));
         assertEquals(state.getPluginInfo().getUpdateURL(v124), new URL("https://example.org/plugins4/"));
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java
index c4461faef..3fcb6c9bf 100644
--- a/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java
+++ b/idp-installer/src/test/java/net/shibboleth/idp/installer/plugin/impl/RollbackTester.java
@@ -29,6 +29,7 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import net.shibboleth.idp.installer.InstallerSupport;
 import net.shibboleth.idp.module.IdPModule;
 import net.shibboleth.idp.module.ModuleContext;
 import net.shibboleth.idp.module.ModuleException;
@@ -45,7 +46,7 @@ public class RollbackTester {
     }
     
     @AfterClass public void teardown() {
-        PluginInstallerSupport.deleteTree(parent);
+        InstallerSupport.deleteTree(parent);
     }
     
     @Test public void rollbackTest() throws IOException, ModuleException {

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list