[java-shib-profile] branch main updated: IDP-2121 Future Proofing the Module Plugin infrastructure for Future SP use

Rod Widdowson rdw at steadingsoftware.com
Thu Jun 8 14:04:12 UTC 2023


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

rdw pushed a commit to branch main
in repository java-shib-profile.

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-profile.git;a=commit;h=d33ce8b112042c88b09cef9369ee9c78c0ecf3cd

The following commit(s) were added to refs/heads/main by this push:
     new d33ce8b  IDP-2121 Future Proofing the Module Plugin infrastructure for Future SP use
d33ce8b is described below

commit d33ce8b112042c88b09cef9369ee9c78c0ecf3cd
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Thu Jun 8 15:03:02 2023 +0100

    IDP-2121 Future Proofing the Module Plugin infrastructure for Future SP use
    
    https://shibboleth.atlassian.net/browse/IDP-2121
    
    Introduce net.shibboleth.profile.installablecomponent
    
    And move the worder classes from their temporary home in
    idp...plugin
---
 .../InstallableComponentInfo.java                  | 334 +++++++++++++++++++++
 .../InstallableComponentSupport.java               | 175 +++++++++++
 .../InstallableComponentVersion.java               | 185 ++++++++++++
 .../profile/installablecomponent/package-info.java |  24 ++
 4 files changed, 718 insertions(+)

diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentInfo.java b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentInfo.java
new file mode 100644
index 0000000..f01ef01
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/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.profile.installablecomponent;
+
+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.profile.installablecomponent.InstallableComponentSupport.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 + InstallableComponentSupport.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 + InstallableComponentSupport.SUPPORT_LEVEL_INTERFIX + version));
+        InstallableComponentSupport.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, InstallableComponentSupport.DOWNLOAD_URL_INTERFIX, version));
+        final String baseName =  StringSupport.trimOrNull(
+                getDefaultedValue(props, InstallableComponentSupport.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 + InstallableComponentSupport.VERSION_PATTERN);
+        if (result == null) {
+            return result;
+        }
+        return result.replaceAll(InstallableComponentSupport.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/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentSupport.java b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentSupport.java
new file mode 100644
index 0000000..ec78956
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentSupport.java
@@ -0,0 +1,175 @@
+/*
+ * 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.profile.installablecomponent;
+
+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.profile.installablecomponent.InstallableComponentInfo.VersionInfo;
+import net.shibboleth.profile.plugin.Plugin;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.spring.httpclient.resource.HTTPResource;
+
+/** Useful methods for supporting Installable Components.
+ *
+ */
+public final class InstallableComponentSupport {
+    
+    /** Property Name suffix for available versions inside {@link Plugin#getUpdateURLs()}. */
+    @Nonnull public static final String AVAILABLE_VERSIONS_PROPERTY_SUFFIX = ".versions";
+
+    /** Property Name for Download directory {@link Plugin#getUpdateURLs()}. */
+    @Nonnull public static final String DOWNLOAD_URL_INTERFIX = ".downloadURL.";
+
+    /** Property Name for download name {@link Plugin#getUpdateURLs()}. */
+    @Nonnull public static final String BASE_NAME_INTERFIX = ".baseName.";
+
+    /** Property Name for max supported IdP version inside inside {@link Plugin#getUpdateURLs()}. */
+    @Nonnull public static final String MAX_IDP_VERSION_INTERFIX = ".idpVersionMax.";
+
+    /** Property Name for minimum supported IdP version inside inside {@link Plugin#getUpdateURLs()}. */
+    @Nonnull public static final String MIN_IDP_VERSION_INTERFIX = ".idpVersionMin.";
+
+    /** Property Name for support level inside inside {@link Plugin#getUpdateURLs()}. */
+    @Nonnull public static final String SUPPORT_LEVEL_INTERFIX = ".supportLevel.";
+
+    /** Used for specifying templated keynames. */
+    @Nonnull public static final String VERSION_PATTERN = "%{version}";
+
+    /** Used for specifying templated results. */
+    @Nonnull public static final String VERSION_PATTERN_REGEX = "\\%\\{version\\}";
+
+    /** Value for support level pointed to by {@link #SUPPORT_LEVEL_INTERFIX}.*/
+    public static enum SupportLevel {
+        /** The current release. */
+        Current,
+        /** Still working but a new version is available. */
+        OutOfDate,
+        /** Out of Support. */
+        Unsupported,
+        /** Security alerts against this plugin. */
+        Secadv,
+        /** Withdrawn. */
+        Withdrawn,
+        /** Nothing published. */
+        Unknown
+    }
+
+    /** Class logger. */
+    @Nonnull private static Logger log = LoggerFactory.getLogger(InstallableComponentSupport.class);
+    
+    /** Constructor. */
+    private InstallableComponentSupport() {
+    }
+
+    /** 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 Application Version {}", version, installIntoVersion);
+                continue;
+            }
+            log.debug("Version {} is supported with Application Version {}", 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;
+    }
+
+    /** 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 loadInfo(@Nonnull final List<URL> updateURLs, @Nonnull final HttpClient client,
+            @Nullable final HttpClientSecurityParameters securityParameters) {
+        final Properties props = new Properties();
+        for (final URL url: updateURLs) {
+            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/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentVersion.java b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentVersion.java
new file mode 100644
index 0000000..67ef39f
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/InstallableComponentVersion.java
@@ -0,0 +1,185 @@
+/*
+ * 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.profile.installablecomponent;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.profile.plugin.Plugin;
+import net.shibboleth.profile.module.Module;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A version string (Major.minor.patch) as a handy class.
+ */
+public class InstallableComponentVersion implements Comparable<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 InstallableComponentVersion(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 InstallableComponentVersion(@Nonnull final Plugin<? extends Module> plugin) throws NumberFormatException {
+        this(plugin.getMajorVersion(), plugin.getMinorVersion(), plugin.getPatchVersion());
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param maj Major Version 
+     * @param min Minor Version 
+     * @param pat Patch Version
+     * @throws NumberFormatException if the values are out of range
+     */
+    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);
+        }
+        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}.
+     */
+    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 InstallableComponentVersion) {
+            final InstallableComponentVersion other= (InstallableComponentVersion)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 InstallableComponentVersion 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();
+    }
+}
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/package-info.java b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/package-info.java
new file mode 100644
index 0000000..a0242b5
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/installablecomponent/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for "Installable Components".  Currently The IdP and IdP plugins.
+ */
+ at NonnullElements
+package net.shibboleth.profile.installablecomponent;
+
+import net.shibboleth.shared.annotation.constraint.NonnullElements;

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


More information about the commits mailing list