From 1f04209be1769e40c730cf2701926611e2f85435 Mon Sep 17 00:00:00 2001 From: Decebal Suiu Date: Mon, 30 Jan 2023 20:36:47 +0200 Subject: [PATCH] Relax Plugin construction (remove dependency on PluginWrapper) (#512) --- .../java/org/pf4j/demo/api/DemoPlugin.java | 35 + .../java/org/pf4j/demo/api/PluginContext.java | 39 ++ .../app/src/main/java/org/pf4j/demo/Boot.java | 22 +- .../java/org/pf4j/demo/DemoPluginFactory.java | 40 ++ .../java/org/pf4j/demo/DemoPluginManager.java | 42 ++ .../org/pf4j/demo/welcome/WelcomePlugin.java | 15 +- .../java/org/pf4j/demo/hello/HelloPlugin.java | 10 +- .../java/org/pf4j/DefaultPluginFactory.java | 26 +- pf4j/src/main/java/org/pf4j/Plugin.java | 12 + .../org/pf4j/SecurePluginManagerWrapper.java | 613 +++++++++--------- .../org/pf4j/DefaultPluginFactoryTest.java | 17 + .../org/pf4j/test/AnotherFailTestPlugin.java | 2 +- .../java/org/pf4j/test/AnotherTestPlugin.java | 34 + 13 files changed, 561 insertions(+), 346 deletions(-) create mode 100644 demo/api/src/main/java/org/pf4j/demo/api/DemoPlugin.java create mode 100644 demo/api/src/main/java/org/pf4j/demo/api/PluginContext.java create mode 100644 demo/app/src/main/java/org/pf4j/demo/DemoPluginFactory.java create mode 100644 demo/app/src/main/java/org/pf4j/demo/DemoPluginManager.java create mode 100644 pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java diff --git a/demo/api/src/main/java/org/pf4j/demo/api/DemoPlugin.java b/demo/api/src/main/java/org/pf4j/demo/api/DemoPlugin.java new file mode 100644 index 0000000..a2f76fc --- /dev/null +++ b/demo/api/src/main/java/org/pf4j/demo/api/DemoPlugin.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.demo.api; + +import org.pf4j.Plugin; + +/** + * Base {@link Plugin} for all demo plugins. + * + * @author Decebal Suiu + */ +public abstract class DemoPlugin extends Plugin { + + protected final PluginContext context; + + protected DemoPlugin(PluginContext context) { + super(); + + this.context = context; + } + +} diff --git a/demo/api/src/main/java/org/pf4j/demo/api/PluginContext.java b/demo/api/src/main/java/org/pf4j/demo/api/PluginContext.java new file mode 100644 index 0000000..0a7506f --- /dev/null +++ b/demo/api/src/main/java/org/pf4j/demo/api/PluginContext.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.demo.api; + +import org.pf4j.RuntimeMode; + +/** + * An instance of this class is provided to plugins in their constructor. + * It's safe for plugins to keep a reference to the instance for later use. + * This class facilitates communication with application and plugin manager. + * + * @author Decebal Suiu + */ +public class PluginContext { + + private final RuntimeMode runtimeMode; + + public PluginContext(RuntimeMode runtimeMode) { + this.runtimeMode = runtimeMode; + } + + public RuntimeMode getRuntimeMode() { + return runtimeMode; + } + +} diff --git a/demo/app/src/main/java/org/pf4j/demo/Boot.java b/demo/app/src/main/java/org/pf4j/demo/Boot.java index 524009c..d101104 100644 --- a/demo/app/src/main/java/org/pf4j/demo/Boot.java +++ b/demo/app/src/main/java/org/pf4j/demo/Boot.java @@ -16,7 +16,6 @@ package org.pf4j.demo; import org.apache.commons.lang.StringUtils; -import org.pf4j.DefaultPluginManager; import org.pf4j.PluginManager; import org.pf4j.PluginWrapper; import org.pf4j.demo.api.Greeting; @@ -40,7 +39,7 @@ public class Boot { printLogo(); // create the plugin manager - PluginManager pluginManager = createPluginManager(); + PluginManager pluginManager = new DemoPluginManager(); // load the plugins pluginManager.loadPlugins(); @@ -129,23 +128,4 @@ public class Boot { log.info(StringUtils.repeat("#", 40)); } - private static PluginManager createPluginManager() { - return new DefaultPluginManager(); - - // use below plugin manager instance if you want to enable ServiceProviderExtensionFinder - /* - return new DefaultPluginManager() { - - @Override - protected ExtensionFinder createExtensionFinder() { - DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder(); - extensionFinder.addServiceProviderExtensionFinder(); // to activate "HowdyGreeting" extension - - return extensionFinder; - } - - }; - */ - } - } diff --git a/demo/app/src/main/java/org/pf4j/demo/DemoPluginFactory.java b/demo/app/src/main/java/org/pf4j/demo/DemoPluginFactory.java new file mode 100644 index 0000000..432dceb --- /dev/null +++ b/demo/app/src/main/java/org/pf4j/demo/DemoPluginFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.demo; + +import org.pf4j.DefaultPluginFactory; +import org.pf4j.Plugin; +import org.pf4j.PluginWrapper; +import org.pf4j.demo.api.PluginContext; + +import java.lang.reflect.Constructor; + +class DemoPluginFactory extends DefaultPluginFactory { + + @Override + protected Plugin createInstance(Class pluginClass, PluginWrapper pluginWrapper) { + PluginContext context = new PluginContext(pluginWrapper.getRuntimeMode()); + try { + Constructor constructor = pluginClass.getConstructor(PluginContext.class); + return (Plugin) constructor.newInstance(context); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + return null; + } + +} diff --git a/demo/app/src/main/java/org/pf4j/demo/DemoPluginManager.java b/demo/app/src/main/java/org/pf4j/demo/DemoPluginManager.java new file mode 100644 index 0000000..2571a3e --- /dev/null +++ b/demo/app/src/main/java/org/pf4j/demo/DemoPluginManager.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.demo; + +import org.pf4j.DefaultExtensionFinder; +import org.pf4j.DefaultPluginFactory; +import org.pf4j.DefaultPluginManager; +import org.pf4j.ExtensionFinder; +import org.pf4j.PluginFactory; + +class DemoPluginManager extends DefaultPluginManager { + + // Use below code if you want to enable ServiceProviderExtensionFinder + /* + @Override + protected ExtensionFinder createExtensionFinder() { + DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder(); + extensionFinder.addServiceProviderExtensionFinder(); // to activate "HowdyGreeting" extension + + return extensionFinder; + } + */ + + @Override + protected PluginFactory createPluginFactory() { + return new DemoPluginFactory(); + } + +} diff --git a/demo/plugins/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java b/demo/plugins/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java index a0cecab..3064902 100644 --- a/demo/plugins/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java +++ b/demo/plugins/plugin1/src/main/java/org/pf4j/demo/welcome/WelcomePlugin.java @@ -16,27 +16,26 @@ package org.pf4j.demo.welcome; import org.apache.commons.lang.StringUtils; - -import org.pf4j.PluginWrapper; +import org.pf4j.Extension; import org.pf4j.RuntimeMode; +import org.pf4j.demo.api.DemoPlugin; import org.pf4j.demo.api.Greeting; -import org.pf4j.Extension; -import org.pf4j.Plugin; +import org.pf4j.demo.api.PluginContext; /** * @author Decebal Suiu */ -public class WelcomePlugin extends Plugin { +public class WelcomePlugin extends DemoPlugin { - public WelcomePlugin(PluginWrapper wrapper) { - super(wrapper); + public WelcomePlugin(PluginContext context) { + super(context); } @Override public void start() { log.info("WelcomePlugin.start()"); // for testing the development mode - if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) { + if (RuntimeMode.DEVELOPMENT.equals(context.getRuntimeMode())) { log.info(StringUtils.upperCase("WelcomePlugin")); } } diff --git a/demo/plugins/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java b/demo/plugins/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java index 3b7c9ce..ed67878 100644 --- a/demo/plugins/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java +++ b/demo/plugins/plugin2/src/main/java/org/pf4j/demo/hello/HelloPlugin.java @@ -16,19 +16,19 @@ package org.pf4j.demo.hello; import org.pf4j.Extension; -import org.pf4j.Plugin; -import org.pf4j.PluginWrapper; +import org.pf4j.demo.api.DemoPlugin; import org.pf4j.demo.api.Greeting; +import org.pf4j.demo.api.PluginContext; /** * A very simple plugin. * * @author Decebal Suiu */ -public class HelloPlugin extends Plugin { +public class HelloPlugin extends DemoPlugin { - public HelloPlugin(PluginWrapper wrapper) { - super(wrapper); + public HelloPlugin(PluginContext context) { + super(context); } @Override diff --git a/pf4j/src/main/java/org/pf4j/DefaultPluginFactory.java b/pf4j/src/main/java/org/pf4j/DefaultPluginFactory.java index 2f55cab..310c791 100644 --- a/pf4j/src/main/java/org/pf4j/DefaultPluginFactory.java +++ b/pf4j/src/main/java/org/pf4j/DefaultPluginFactory.java @@ -23,18 +23,16 @@ import java.lang.reflect.Modifier; /** * The default implementation for {@link PluginFactory}. - * It uses {@link Class#newInstance()} method. + * It uses {@link Constructor#newInstance(Object...)} method. * * @author Decebal Suiu */ public class DefaultPluginFactory implements PluginFactory { - private static final Logger log = LoggerFactory.getLogger(DefaultPluginFactory.class); + protected static final Logger log = LoggerFactory.getLogger(DefaultPluginFactory.class); /** - * Creates a plugin instance. If an error occurs than that error is logged and the method returns null. - * @param pluginWrapper - * @return + * Creates a plugin instance. If an error occurs than that error is logged and the method returns {@code null}. */ @Override public Plugin create(final PluginWrapper pluginWrapper) { @@ -58,10 +56,26 @@ public class DefaultPluginFactory implements PluginFactory { return null; } - // create the plugin instance + return createInstance(pluginClass, pluginWrapper); + } + + protected Plugin createInstance(Class pluginClass, PluginWrapper pluginWrapper) { try { Constructor constructor = pluginClass.getConstructor(PluginWrapper.class); return (Plugin) constructor.newInstance(pluginWrapper); + } catch (NoSuchMethodException e) { + return createUsingNoParametersConstructor(pluginClass); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + return null; + } + + protected Plugin createUsingNoParametersConstructor(Class pluginClass) { + try { + Constructor constructor = pluginClass.getConstructor(); + return (Plugin) constructor.newInstance(); } catch (Exception e) { log.error(e.getMessage(), e); } diff --git a/pf4j/src/main/java/org/pf4j/Plugin.java b/pf4j/src/main/java/org/pf4j/Plugin.java index e3dd449..aaee012 100644 --- a/pf4j/src/main/java/org/pf4j/Plugin.java +++ b/pf4j/src/main/java/org/pf4j/Plugin.java @@ -33,14 +33,20 @@ public class Plugin { /** * Wrapper of the plugin. + * @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper}. + * See demo for more details. */ + @Deprecated protected PluginWrapper wrapper; /** * Constructor to be used by plugin manager for plugin instantiation. * Your plugins have to provide constructor with this exact signature to * be successfully loaded by manager. + * @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper}. + * See demo for more details. */ + @Deprecated public Plugin(final PluginWrapper wrapper) { if (wrapper == null) { throw new IllegalArgumentException("Wrapper cannot be null"); @@ -49,9 +55,15 @@ public class Plugin { this.wrapper = wrapper; } + public Plugin() { + } + /** * Retrieves the wrapper of this plug-in. + * @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper}. + * See demo for more details. */ + @Deprecated public final PluginWrapper getWrapper() { return wrapper; } diff --git a/pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java b/pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java index f55b1e6..ee2d681 100644 --- a/pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java +++ b/pf4j/src/main/java/org/pf4j/SecurePluginManagerWrapper.java @@ -1,305 +1,308 @@ -package org.pf4j; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Use this class to wrap the original plugin manager to prevent full access from within plugins. - * Override AbstractPluginManager.createPluginWrapper to use this class - * @author Wolfram Haussig - * - */ -public class SecurePluginManagerWrapper implements PluginManager { - - private static final String PLUGIN_PREFIX = "Plugin "; - /** - * the current plugin - */ - private String currentPluginId; - /** - * the original plugin manager - */ - private PluginManager original; - - /** - * The registered {@link PluginStateListener}s. - */ - protected List pluginStateListeners = new ArrayList<>(); - /** - * wrapper for pluginStateListeners - */ - private PluginStateListenerWrapper listenerWrapper = new PluginStateListenerWrapper(); - - /** - * constructor - * @param original the original plugin manager - * @param currentPlugin the current pluginId - */ - public SecurePluginManagerWrapper(PluginManager original, String currentPluginId) { - this.original = original; - this.currentPluginId = currentPluginId; - } - - @Override - public boolean isDevelopment() { - return original.isDevelopment(); - } - - @Override - public boolean isNotDevelopment() { - return original.isNotDevelopment(); - } - - @Override - public List getPlugins() { - return Arrays.asList(getPlugin(currentPluginId)); - } - - @Override - public List getPlugins(PluginState pluginState) { - return getPlugins().stream().filter(p -> p.getPluginState() == pluginState).collect(Collectors.toList()); - } - - @Override - public List getResolvedPlugins() { - return getPlugins().stream().filter(p -> p.getPluginState().ordinal() >= PluginState.RESOLVED.ordinal()).collect(Collectors.toList()); - } - - @Override - public List getUnresolvedPlugins() { - return Collections.emptyList(); - } - - @Override - public List getStartedPlugins() { - return getPlugins(PluginState.STARTED); - } - - @Override - public PluginWrapper getPlugin(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.getPlugin(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPlugin for foreign pluginId!"); - } - } - - @Override - public void loadPlugins() { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugins!"); - } - - @Override - public String loadPlugin(Path pluginPath) { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugin!"); - } - - @Override - public void startPlugins() { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugins!"); - } - - @Override - public PluginState startPlugin(String pluginId) { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugin!"); - } - - @Override - public void stopPlugins() { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugins!"); - } - - @Override - public PluginState stopPlugin(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.stopPlugin(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugin for foreign pluginId!"); - } - } - - @Override - public void unloadPlugins() { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugins!"); - } - - @Override - public boolean unloadPlugin(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.unloadPlugin(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugin for foreign pluginId!"); - } - } - - @Override - public boolean disablePlugin(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.disablePlugin(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute disablePlugin for foreign pluginId!"); - } - } - - @Override - public boolean enablePlugin(String pluginId) { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute enablePlugin!"); - } - - @Override - public boolean deletePlugin(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.deletePlugin(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute deletePlugin for foreign pluginId!"); - } - } - - @Override - public ClassLoader getPluginClassLoader(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.getPluginClassLoader(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginClassLoader for foreign pluginId!"); - } - } - - @Override - public List> getExtensionClasses(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.getExtensionClasses(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!"); - } - } - - @Override - public List> getExtensionClasses(Class type) { - return getExtensionClasses(type, currentPluginId); - } - - @Override - public List> getExtensionClasses(Class type, String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.getExtensionClasses(type, pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!"); - } - } - - @Override - public List getExtensions(Class type) { - return getExtensions(type, currentPluginId); - } - - @Override - public List getExtensions(Class type, String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.getExtensions(type, pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!"); - } - } - - @Override - public List getExtensions(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.getExtensions(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!"); - } - } - - @Override - public Set getExtensionClassNames(String pluginId) { - if (currentPluginId.equals(pluginId)) { - return original.getExtensionClassNames(pluginId); - } else { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClassNames for foreign pluginId!"); - } - } - - @Override - public ExtensionFactory getExtensionFactory() { - return original.getExtensionFactory(); - } - - @Override - public RuntimeMode getRuntimeMode() { - return original.getRuntimeMode(); - } - - @Override - public PluginWrapper whichPlugin(Class clazz) { - ClassLoader classLoader = clazz.getClassLoader(); - PluginWrapper plugin = getPlugin(currentPluginId); - if (plugin.getPluginClassLoader() == classLoader) { - return plugin; - } - return null; - } - - @Override - public void addPluginStateListener(PluginStateListener listener) { - if (pluginStateListeners.isEmpty()) { - this.original.addPluginStateListener(listenerWrapper); - } - pluginStateListeners.add(listener); - } - - @Override - public void removePluginStateListener(PluginStateListener listener) { - pluginStateListeners.remove(listener); - if (pluginStateListeners.isEmpty()) { - this.original.removePluginStateListener(listenerWrapper); - } - } - - @Override - public void setSystemVersion(String version) { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute setSystemVersion!"); - } - - @Override - public String getSystemVersion() { - return original.getSystemVersion(); - } - - @Override - public Path getPluginsRoot() { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoot!"); - } - - @Override - public List getPluginsRoots() { - throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoots!"); - } - - @Override - public VersionManager getVersionManager() { - return original.getVersionManager(); - } - - /** - * Wrapper for PluginStateListener events. will only propagate events if they match the current pluginId - * @author Wolfram Haussig - * - */ - private class PluginStateListenerWrapper implements PluginStateListener { - - @Override - public void pluginStateChanged(PluginStateEvent event) { - if (event.getPlugin().getPluginId().equals(currentPluginId)) { - for (PluginStateListener listener : pluginStateListeners) { - listener.pluginStateChanged(event); - } - } - } - - } -} +package org.pf4j; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Use this class to wrap the original plugin manager to prevent full access from within plugins. + * Override AbstractPluginManager.createPluginWrapper to use this class. + * @deprecated Use application custom {@code PluginContext} instead of {@code PluginWrapper} to communicate with {@link Plugin}. + * See demo for more details. + * + * @author Wolfram Haussig + */ +@Deprecated() +public class SecurePluginManagerWrapper implements PluginManager { + + private static final String PLUGIN_PREFIX = "Plugin "; + /** + * the current plugin + */ + private String currentPluginId; + /** + * the original plugin manager + */ + private PluginManager original; + + /** + * The registered {@link PluginStateListener}s. + */ + protected List pluginStateListeners = new ArrayList<>(); + /** + * wrapper for pluginStateListeners + */ + private PluginStateListenerWrapper listenerWrapper = new PluginStateListenerWrapper(); + + /** + * constructor + * @param original the original plugin manager + * @param currentPluginId the current pluginId + */ + public SecurePluginManagerWrapper(PluginManager original, String currentPluginId) { + this.original = original; + this.currentPluginId = currentPluginId; + } + + @Override + public boolean isDevelopment() { + return original.isDevelopment(); + } + + @Override + public boolean isNotDevelopment() { + return original.isNotDevelopment(); + } + + @Override + public List getPlugins() { + return Arrays.asList(getPlugin(currentPluginId)); + } + + @Override + public List getPlugins(PluginState pluginState) { + return getPlugins().stream().filter(p -> p.getPluginState() == pluginState).collect(Collectors.toList()); + } + + @Override + public List getResolvedPlugins() { + return getPlugins().stream().filter(p -> p.getPluginState().ordinal() >= PluginState.RESOLVED.ordinal()).collect(Collectors.toList()); + } + + @Override + public List getUnresolvedPlugins() { + return Collections.emptyList(); + } + + @Override + public List getStartedPlugins() { + return getPlugins(PluginState.STARTED); + } + + @Override + public PluginWrapper getPlugin(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.getPlugin(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPlugin for foreign pluginId!"); + } + } + + @Override + public void loadPlugins() { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugins!"); + } + + @Override + public String loadPlugin(Path pluginPath) { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute loadPlugin!"); + } + + @Override + public void startPlugins() { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugins!"); + } + + @Override + public PluginState startPlugin(String pluginId) { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute startPlugin!"); + } + + @Override + public void stopPlugins() { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugins!"); + } + + @Override + public PluginState stopPlugin(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.stopPlugin(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute stopPlugin for foreign pluginId!"); + } + } + + @Override + public void unloadPlugins() { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugins!"); + } + + @Override + public boolean unloadPlugin(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.unloadPlugin(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute unloadPlugin for foreign pluginId!"); + } + } + + @Override + public boolean disablePlugin(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.disablePlugin(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute disablePlugin for foreign pluginId!"); + } + } + + @Override + public boolean enablePlugin(String pluginId) { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute enablePlugin!"); + } + + @Override + public boolean deletePlugin(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.deletePlugin(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute deletePlugin for foreign pluginId!"); + } + } + + @Override + public ClassLoader getPluginClassLoader(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.getPluginClassLoader(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginClassLoader for foreign pluginId!"); + } + } + + @Override + public List> getExtensionClasses(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.getExtensionClasses(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!"); + } + } + + @Override + public List> getExtensionClasses(Class type) { + return getExtensionClasses(type, currentPluginId); + } + + @Override + public List> getExtensionClasses(Class type, String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.getExtensionClasses(type, pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClasses for foreign pluginId!"); + } + } + + @Override + public List getExtensions(Class type) { + return getExtensions(type, currentPluginId); + } + + @Override + public List getExtensions(Class type, String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.getExtensions(type, pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!"); + } + } + + @Override + public List getExtensions(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.getExtensions(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensions for foreign pluginId!"); + } + } + + @Override + public Set getExtensionClassNames(String pluginId) { + if (currentPluginId.equals(pluginId)) { + return original.getExtensionClassNames(pluginId); + } else { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getExtensionClassNames for foreign pluginId!"); + } + } + + @Override + public ExtensionFactory getExtensionFactory() { + return original.getExtensionFactory(); + } + + @Override + public RuntimeMode getRuntimeMode() { + return original.getRuntimeMode(); + } + + @Override + public PluginWrapper whichPlugin(Class clazz) { + ClassLoader classLoader = clazz.getClassLoader(); + PluginWrapper plugin = getPlugin(currentPluginId); + if (plugin.getPluginClassLoader() == classLoader) { + return plugin; + } + return null; + } + + @Override + public void addPluginStateListener(PluginStateListener listener) { + if (pluginStateListeners.isEmpty()) { + this.original.addPluginStateListener(listenerWrapper); + } + pluginStateListeners.add(listener); + } + + @Override + public void removePluginStateListener(PluginStateListener listener) { + pluginStateListeners.remove(listener); + if (pluginStateListeners.isEmpty()) { + this.original.removePluginStateListener(listenerWrapper); + } + } + + @Override + public void setSystemVersion(String version) { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute setSystemVersion!"); + } + + @Override + public String getSystemVersion() { + return original.getSystemVersion(); + } + + @Override + public Path getPluginsRoot() { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoot!"); + } + + @Override + public List getPluginsRoots() { + throw new IllegalAccessError(PLUGIN_PREFIX + currentPluginId + " tried to execute getPluginsRoots!"); + } + + @Override + public VersionManager getVersionManager() { + return original.getVersionManager(); + } + + /** + * Wrapper for PluginStateListener events. will only propagate events if they match the current pluginId + * @author Wolfram Haussig + * + */ + private class PluginStateListenerWrapper implements PluginStateListener { + + @Override + public void pluginStateChanged(PluginStateEvent event) { + if (event.getPlugin().getPluginId().equals(currentPluginId)) { + for (PluginStateListener listener : pluginStateListeners) { + listener.pluginStateChanged(event); + } + } + } + + } +} diff --git a/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java b/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java index 3107083..c453e38 100644 --- a/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java +++ b/pf4j/src/test/java/org/pf4j/DefaultPluginFactoryTest.java @@ -17,6 +17,7 @@ package org.pf4j; import org.junit.jupiter.api.Test; import org.pf4j.test.AnotherFailTestPlugin; +import org.pf4j.test.AnotherTestPlugin; import org.pf4j.test.FailTestPlugin; import org.pf4j.test.TestPlugin; @@ -48,6 +49,22 @@ public class DefaultPluginFactoryTest { assertThat(result, instanceOf(TestPlugin.class)); } + @Test + public void pluginConstructorNoParameters() { + PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class); + when(pluginDescriptor.getPluginClass()).thenReturn(AnotherTestPlugin.class.getName()); + + PluginWrapper pluginWrapper = mock(PluginWrapper.class); + when(pluginWrapper.getDescriptor()).thenReturn(pluginDescriptor); + when(pluginWrapper.getPluginClassLoader()).thenReturn(getClass().getClassLoader()); + + PluginFactory pluginFactory = new DefaultPluginFactory(); + + Plugin result = pluginFactory.create(pluginWrapper); + assertNotNull(result); + assertThat(result, instanceOf(AnotherTestPlugin.class)); + } + @Test public void testCreateFail() { PluginDescriptor pluginDescriptor = mock(PluginDescriptor.class); diff --git a/pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java b/pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java index d97b32c..79aa41b 100644 --- a/pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java +++ b/pf4j/src/test/java/org/pf4j/test/AnotherFailTestPlugin.java @@ -19,7 +19,7 @@ import org.pf4j.Plugin; /** * A wrong {@link org.pf4j.Plugin}. - * It's wrong because it doesn't contain a constructor with one parameter ({@link org.pf4j.PluginWrapper} as parameter type). + * It's wrong because it calls super constructor with {@code null} for ({@link org.pf4j.PluginWrapper} parameter). * * @author Mario Franco */ diff --git a/pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java b/pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java new file mode 100644 index 0000000..f2e10c1 --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/test/AnotherTestPlugin.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j.test; + +import org.pf4j.Plugin; + +/** + * A simple {@link Plugin}. + * + * In real applications you don't need to create a plugin like this if you are not interested in lifecycle events. + * {@code PF4J} will automatically create a plugin similar to this (empty / dummy) if no class plugin is specified. + * + * @author Decebal Suiu + */ +public class AnotherTestPlugin extends Plugin { + + public AnotherTestPlugin() { + super(); + } + +} -- 2.39.5