From eba6d3dc6b83e31e1c98df23a527abed09886ad3 Mon Sep 17 00:00:00 2001 From: James Moger Date: Sat, 12 Apr 2014 12:10:56 -0400 Subject: [PATCH 1/2] Add an optional requires manifest value As integrators of pf4j evovle their extension APIs it will become a requirement to specify a minimum system version for loading plugins. Loading & starting a newer plugin on an older system could result in runtime failures due to method signature changes or other class differences. This change adds a manifest attribute to specify a 'requires' version which is a minmum system version. It also introduces a method to specify the system version of the plugin manager and logic to disable plugins on load if the system version is too old. This works for both loadPlugins() and loadPlugin(). --- .../fortsoft/pf4j/DefaultPluginManager.java | 37 +++++++++++++++++++ .../pf4j/ManifestPluginDescriptorFinder.java | 5 +++ .../ro/fortsoft/pf4j/PluginDescriptor.java | 13 +++++++ .../java/ro/fortsoft/pf4j/PluginManager.java | 16 ++++++++ .../java/ro/fortsoft/pf4j/PluginVersion.java | 21 ++++++++--- 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java index c44787e..970ae2d 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java @@ -89,6 +89,11 @@ public class DefaultPluginManager implements PluginManager { */ private RuntimeMode runtimeMode; + /** + * The system version used for comparisons to the plugin requires attribute. + */ + private PluginVersion systemVersion = PluginVersion.DEFAULT; + /** * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). */ @@ -110,6 +115,16 @@ public class DefaultPluginManager implements PluginManager { initialize(); } + @Override + public void setSystemVersion(PluginVersion version) { + systemVersion = version; + } + + @Override + public PluginVersion getSystemVersion() { + return systemVersion; + } + @Override public List getPlugins() { return new ArrayList(plugins.values()); @@ -461,6 +476,16 @@ public class DefaultPluginManager implements PluginManager { return true; } + PluginVersion requires = pluginWrapper.getDescriptor().getRequires(); + PluginVersion system = getSystemVersion(); + if (!system.isDefault() && !system.atLeast(requires)) { + log.warn(String.format("Failed to enable plugin `{}:{}` because it requires a minimum system version of %s", + pluginWrapper.getPluginId(), + pluginWrapper.getDescriptor().getVersion(), + requires)); + return false; + } + try { if (disabledPlugins.remove(pluginId)) { FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt")); @@ -724,6 +749,18 @@ public class DefaultPluginManager implements PluginManager { pluginWrapper.setPluginState(PluginState.DISABLED); } + // optionally enforce minimum system version requirements + PluginVersion requires = pluginWrapper.getDescriptor().getRequires(); + PluginVersion system = getSystemVersion(); + if (!system.isDefault() && !system.atLeast(requires)) { + log.warn(String.format("Disabling plugin '%s:%s' because it requires a minimum system version of %s", + pluginWrapper.getPluginId(), + pluginWrapper.getDescriptor().getVersion(), + requires)); + pluginWrapper.setPluginState(PluginState.DISABLED); + disabledPlugins.add(pluginWrapper.getPluginId()); + } + log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath); String pluginId = pluginDescriptor.getPluginId(); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java index 2de42f5..7f802d1 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java @@ -103,6 +103,11 @@ public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder { String dependencies = attrs.getValue("Plugin-Dependencies"); pluginDescriptor.setDependencies(dependencies); + String requires = attrs.getValue("Plugin-Requires"); + if (StringUtils.isNotEmpty(requires)) { + pluginDescriptor.setRequires(PluginVersion.createVersion(requires)); + } + return pluginDescriptor; } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java index cb91f7d..aa05b52 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java @@ -28,10 +28,12 @@ public class PluginDescriptor { private String pluginDescription; private String pluginClass; private PluginVersion version; + private PluginVersion requires; private String provider; private List dependencies; public PluginDescriptor() { + requires = PluginVersion.DEFAULT; dependencies = new ArrayList(); } @@ -63,6 +65,13 @@ public class PluginDescriptor { return version; } + /** + * Returns the requires of this plugin. + */ + public PluginVersion getRequires() { + return requires; + } + /** * Returns the provider name of this plugin. */ @@ -106,6 +115,10 @@ public class PluginDescriptor { this.provider = provider; } + void setRequires(PluginVersion requires) { + this.requires = requires; + } + void setDependencies(String dependencies) { if (dependencies != null) { dependencies = dependencies.trim(); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java index daf6fbf..81abe7c 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java @@ -141,4 +141,20 @@ public interface PluginManager { public void removePluginStateListener(PluginStateListener listener); + /** + * Set the system version. This is used to compare against the plugin + * requires attribute. The default system version is 0.0.0 which + * disables all version checking. + * + * @default 0.0.0 + * @param version + */ + public void setSystemVersion(PluginVersion version); + + /** + * Returns the system version. + * + * * @return the system version + */ + public PluginVersion getSystemVersion(); } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java index 6ffd284..7cfc94b 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java @@ -1,22 +1,22 @@ /* * Copyright 2012 Decebal Suiu - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * the License. You may obtain a copy of the License in the LICENSE file, or 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 ro.fortsoft.pf4j; -import ro.fortsoft.pf4j.util.StringUtils; - import java.util.regex.Matcher; import java.util.regex.Pattern; +import ro.fortsoft.pf4j.util.StringUtils; + /** * Represents the version of a Plugin and allows versions to be compared. * Version following semantic defined by Semantic Versioning document. @@ -33,6 +33,8 @@ import java.util.regex.Pattern; */ public class PluginVersion implements Comparable { + public static final PluginVersion DEFAULT = new PluginVersion(0, 0, 0); + private static final String FORMAT = "(\\d+)\\.(\\d+)(?:\\.)?(\\d*)(\\.|-|\\+)?([0-9A-Za-z-.]*)?"; private static final Pattern PATTERN = Pattern.compile(FORMAT); @@ -95,6 +97,7 @@ public class PluginVersion implements Comparable { return qualifier; } + @Override public String toString() { StringBuffer sb = new StringBuffer(50); sb.append(major); @@ -135,6 +138,14 @@ public class PluginVersion implements Comparable { return 0; } + public boolean isDefault() { + return compareTo(DEFAULT) == 0; + } + + public boolean atLeast(PluginVersion v) { + return compareTo(v) <= 0; + } + // for test only public static void main(String[] args) { PluginVersion v = PluginVersion.createVersion("1.2.3-SNAPSHOT"); From 316109a55835f677629ad70efcb854a6d4cfe7c2 Mon Sep 17 00:00:00 2001 From: James Moger Date: Mon, 14 Apr 2014 08:49:59 -0400 Subject: [PATCH 2/2] Move requires logic to isPluginValid(PluginWrapper), rename default version to ZERO --- .../fortsoft/pf4j/DefaultPluginManager.java | 49 ++++++++++--------- .../ro/fortsoft/pf4j/PluginDescriptor.java | 2 +- .../java/ro/fortsoft/pf4j/PluginVersion.java | 10 ++-- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java index 970ae2d..286b6ea 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java @@ -92,7 +92,7 @@ public class DefaultPluginManager implements PluginManager { /** * The system version used for comparisons to the plugin requires attribute. */ - private PluginVersion systemVersion = PluginVersion.DEFAULT; + private PluginVersion systemVersion = PluginVersion.ZERO; /** * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). @@ -469,23 +469,20 @@ public class DefaultPluginManager implements PluginManager { } PluginWrapper pluginWrapper = getPlugin(pluginId); + if (!isPluginValid(pluginWrapper)) { + log.warn("Plugin '{}:{}' can not be enabled", + pluginWrapper.getPluginId(), + pluginWrapper.getDescriptor().getVersion()); + return false; + } + PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); PluginState pluginState = pluginWrapper.getPluginState(); if (PluginState.DISABLED != pluginState) { - log.debug("Plugin plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); + log.debug("Plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); return true; } - PluginVersion requires = pluginWrapper.getDescriptor().getRequires(); - PluginVersion system = getSystemVersion(); - if (!system.isDefault() && !system.atLeast(requires)) { - log.warn(String.format("Failed to enable plugin `{}:{}` because it requires a minimum system version of %s", - pluginWrapper.getPluginId(), - pluginWrapper.getDescriptor().getVersion(), - requires)); - return false; - } - try { if (disabledPlugins.remove(pluginId)) { FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt")); @@ -659,6 +656,20 @@ public class DefaultPluginManager implements PluginManager { return !enabledPlugins.contains(pluginId); } + protected boolean isPluginValid(PluginWrapper pluginWrapper) { + PluginVersion requires = pluginWrapper.getDescriptor().getRequires(); + PluginVersion system = getSystemVersion(); + if (system.isZero() || system.atLeast(requires)) { + return true; + } + + log.warn(String.format("Plugin '%s:%s' requires a minimum system version of %s", + pluginWrapper.getPluginId(), + pluginWrapper.getDescriptor().getVersion(), + requires)); + return false; + } + protected FileFilter createHiddenPluginFilter() { return new HiddenFilter(); } @@ -749,16 +760,10 @@ public class DefaultPluginManager implements PluginManager { pluginWrapper.setPluginState(PluginState.DISABLED); } - // optionally enforce minimum system version requirements - PluginVersion requires = pluginWrapper.getDescriptor().getRequires(); - PluginVersion system = getSystemVersion(); - if (!system.isDefault() && !system.atLeast(requires)) { - log.warn(String.format("Disabling plugin '%s:%s' because it requires a minimum system version of %s", - pluginWrapper.getPluginId(), - pluginWrapper.getDescriptor().getVersion(), - requires)); - pluginWrapper.setPluginState(PluginState.DISABLED); - disabledPlugins.add(pluginWrapper.getPluginId()); + // validate the plugin + if (!isPluginValid(pluginWrapper)) { + log.info("Plugin '{}' is disabled", pluginPath); + pluginWrapper.setPluginState(PluginState.DISABLED); } log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java index aa05b52..ae07fbb 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptor.java @@ -33,7 +33,7 @@ public class PluginDescriptor { private List dependencies; public PluginDescriptor() { - requires = PluginVersion.DEFAULT; + requires = PluginVersion.ZERO; dependencies = new ArrayList(); } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java index 7cfc94b..c5d57fd 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginVersion.java @@ -33,7 +33,7 @@ import ro.fortsoft.pf4j.util.StringUtils; */ public class PluginVersion implements Comparable { - public static final PluginVersion DEFAULT = new PluginVersion(0, 0, 0); + public static final PluginVersion ZERO = new PluginVersion(0, 0, 0); private static final String FORMAT = "(\\d+)\\.(\\d+)(?:\\.)?(\\d*)(\\.|-|\\+)?([0-9A-Za-z-.]*)?"; private static final Pattern PATTERN = Pattern.compile(FORMAT); @@ -138,14 +138,18 @@ public class PluginVersion implements Comparable { return 0; } - public boolean isDefault() { - return compareTo(DEFAULT) == 0; + public boolean isZero() { + return compareTo(ZERO) == 0; } public boolean atLeast(PluginVersion v) { return compareTo(v) <= 0; } + public boolean exceeds(PluginVersion v) { + return compareTo(v) > 0; + } + // for test only public static void main(String[] args) { PluginVersion v = PluginVersion.createVersion("1.2.3-SNAPSHOT");