diff --git a/api/src/main/java/run/halo/app/plugin/BasePlugin.java b/api/src/main/java/run/halo/app/plugin/BasePlugin.java
index f513514fec..b6f07b519a 100644
--- a/api/src/main/java/run/halo/app/plugin/BasePlugin.java
+++ b/api/src/main/java/run/halo/app/plugin/BasePlugin.java
@@ -3,6 +3,8 @@
import lombok.extern.slf4j.Slf4j;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
/**
* This class will be extended by all plugins and serve as the common class between a plugin and
@@ -14,12 +16,46 @@
@Slf4j
public class BasePlugin extends Plugin {
+ protected PluginContext context;
+
@Deprecated
public BasePlugin(PluginWrapper wrapper) {
super(wrapper);
log.info("Initialized plugin {}", wrapper.getPluginId());
}
+ /**
+ * Constructor a plugin with the given plugin context.
+ * TODO Mark {@link PluginContext} as final to prevent modification.
+ *
+ * @param pluginContext plugin context must not be null.
+ */
+ public BasePlugin(PluginContext pluginContext) {
+ this.context = pluginContext;
+ }
+
+ /**
+ * use {@link #BasePlugin(PluginContext)} instead of.
+ *
+ * @deprecated since 2.10.0
+ */
public BasePlugin() {
}
+
+ /**
+ * Compatible with old constructors, if the plugin is not use
+ * {@link #BasePlugin(PluginContext)} constructor then base plugin factory will use this
+ * method to set plugin context.
+ *
+ * @param context plugin context must not be null.
+ */
+ void setContext(PluginContext context) {
+ Assert.notNull(context, "Plugin context must not be null");
+ this.context = context;
+ }
+
+ @NonNull
+ public PluginContext getContext() {
+ return context;
+ }
}
diff --git a/api/src/main/java/run/halo/app/plugin/PluginContext.java b/api/src/main/java/run/halo/app/plugin/PluginContext.java
new file mode 100644
index 0000000000..01bae2f967
--- /dev/null
+++ b/api/src/main/java/run/halo/app/plugin/PluginContext.java
@@ -0,0 +1,27 @@
+package run.halo.app.plugin;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.pf4j.RuntimeMode;
+
+/**
+ *
This class will provide a context for the plugin, which will be used to store some
+ * information about the plugin.
+ * 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.
+ * Pf4j recommends that you use a custom PluginContext instead of PluginWrapper.
+ * Use application custom PluginContext instead of PluginWrapper
+ *
+ * @author guqing
+ * @since 2.10.0
+ */
+@Getter
+@RequiredArgsConstructor
+public class PluginContext {
+ private final String name;
+
+ private final String version;
+
+ private final RuntimeMode runtimeMode;
+}
diff --git a/application/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java b/application/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java
index 2e2f8161e8..ca7e9441b4 100644
--- a/application/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java
+++ b/application/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java
@@ -60,6 +60,7 @@
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.infra.utils.YamlUnstructuredLoader;
import run.halo.app.plugin.HaloPluginManager;
+import run.halo.app.plugin.HaloPluginWrapper;
import run.halo.app.plugin.PluginConst;
import run.halo.app.plugin.PluginExtensionLoaderUtils;
import run.halo.app.plugin.PluginStartingError;
@@ -184,11 +185,12 @@ Optional lookupPluginSetting(String name, String settingName) {
Assert.notNull(name, "Plugin name must not be null");
Assert.notNull(settingName, "Setting name must not be null");
PluginWrapper pluginWrapper = getPluginWrapper(name);
+ var runtimeMode = getRuntimeMode(name);
var resourceLoader =
new DefaultResourceLoader(pluginWrapper.getPluginClassLoader());
return PluginExtensionLoaderUtils.lookupExtensions(pluginWrapper.getPluginPath(),
- pluginWrapper.getRuntimeMode())
+ runtimeMode)
.stream()
.map(resourceLoader::getResource)
.filter(Resource::exists)
@@ -215,6 +217,7 @@ boolean waitForSettingCreation(Plugin plugin) {
return false;
}
+ var runtimeMode = getRuntimeMode(pluginName);
Optional settingOption = lookupPluginSetting(pluginName, settingName)
.map(setting -> {
// This annotation is added to prevent it from being deleted when stopped.
@@ -804,11 +807,19 @@ static String initialReverseProxyName(String pluginName) {
}
private boolean isDevelopmentMode(String name) {
- PluginWrapper pluginWrapper = haloPluginManager.getPlugin(name);
- RuntimeMode runtimeMode = haloPluginManager.getRuntimeMode();
- if (pluginWrapper != null) {
- runtimeMode = pluginWrapper.getRuntimeMode();
+ return RuntimeMode.DEVELOPMENT.equals(getRuntimeMode(name));
+ }
+
+ private RuntimeMode getRuntimeMode(String name) {
+ var pluginWrapper = haloPluginManager.getPlugin(name);
+ if (pluginWrapper == null) {
+ return haloPluginManager.getRuntimeMode();
+ }
+ if (pluginWrapper instanceof HaloPluginWrapper haloPluginWrapper) {
+ return haloPluginWrapper.getRuntimeMode();
}
- return RuntimeMode.DEVELOPMENT.equals(runtimeMode);
+ return Files.isDirectory(pluginWrapper.getPluginPath())
+ ? RuntimeMode.DEVELOPMENT
+ : RuntimeMode.DEPLOYMENT;
}
}
diff --git a/application/src/main/java/run/halo/app/plugin/BasePluginFactory.java b/application/src/main/java/run/halo/app/plugin/BasePluginFactory.java
index c769ab8e29..211cdee0d7 100644
--- a/application/src/main/java/run/halo/app/plugin/BasePluginFactory.java
+++ b/application/src/main/java/run/halo/app/plugin/BasePluginFactory.java
@@ -23,13 +23,17 @@ public Plugin create(PluginWrapper pluginWrapper) {
return getPluginContext(pluginWrapper)
.map(context -> {
try {
- return context.getBean(BasePlugin.class);
+ var basePlugin = context.getBean(BasePlugin.class);
+ var pluginContext = context.getBean(PluginContext.class);
+ basePlugin.setContext(pluginContext);
+ return basePlugin;
} catch (NoSuchBeanDefinitionException e) {
log.info(
"No bean named 'basePlugin' found in the context create default instance");
DefaultListableBeanFactory beanFactory =
context.getDefaultListableBeanFactory();
- BasePlugin pluginInstance = new BasePlugin();
+ var pluginContext = beanFactory.getBean(PluginContext.class);
+ BasePlugin pluginInstance = new BasePlugin(pluginContext);
beanFactory.registerSingleton(Plugin.class.getName(), pluginInstance);
return pluginInstance;
}
diff --git a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java
index 7abcb1d5f4..41a61a86e7 100644
--- a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java
+++ b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java
@@ -108,6 +108,17 @@ protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new YamlPluginDescriptorFinder();
}
+ @Override
+ protected PluginWrapper createPluginWrapper(PluginDescriptor pluginDescriptor, Path pluginPath,
+ ClassLoader pluginClassLoader) {
+ // create the plugin wrapper
+ log.debug("Creating wrapper for plugin '{}'", pluginPath);
+ HaloPluginWrapper pluginWrapper =
+ new HaloPluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
+ pluginWrapper.setPluginFactory(getPluginFactory());
+ return pluginWrapper;
+ }
+
@Override
protected void firePluginStateEvent(PluginStateEvent event) {
rootApplicationContext.publishEvent(
diff --git a/application/src/main/java/run/halo/app/plugin/HaloPluginWrapper.java b/application/src/main/java/run/halo/app/plugin/HaloPluginWrapper.java
new file mode 100644
index 0000000000..f784400ab4
--- /dev/null
+++ b/application/src/main/java/run/halo/app/plugin/HaloPluginWrapper.java
@@ -0,0 +1,34 @@
+package run.halo.app.plugin;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginManager;
+import org.pf4j.PluginWrapper;
+import org.pf4j.RuntimeMode;
+
+/**
+ * A wrapper over plugin instance for Halo.
+ *
+ * @author guqing
+ * @since 2.10.0
+ */
+public class HaloPluginWrapper extends PluginWrapper {
+
+ private final RuntimeMode runtimeMode;
+
+ /**
+ * Creates a new plugin wrapper to manage the specified plugin.
+ */
+ public HaloPluginWrapper(PluginManager pluginManager, PluginDescriptor descriptor,
+ Path pluginPath, ClassLoader pluginClassLoader) {
+ super(pluginManager, descriptor, pluginPath, pluginClassLoader);
+ this.runtimeMode = Files.isDirectory(pluginPath)
+ ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
+ }
+
+ @Override
+ public RuntimeMode getRuntimeMode() {
+ return runtimeMode;
+ }
+}
diff --git a/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java b/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java
index d437b61981..45c9d367f6 100644
--- a/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java
+++ b/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java
@@ -9,6 +9,7 @@
import java.util.Set;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
+import org.pf4j.PluginRuntimeException;
import org.pf4j.PluginWrapper;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.env.PropertySourceLoader;
@@ -88,6 +89,8 @@ private PluginApplicationContext createPluginApplicationContext(String pluginId)
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
stopWatch.stop();
+ beanFactory.registerSingleton("pluginContext", createPluginContext(plugin));
+ // TODO deprecated
beanFactory.registerSingleton("pluginWrapper", haloPluginManager.getPlugin(pluginId));
populateSettingFetcher(pluginId, beanFactory);
@@ -131,6 +134,15 @@ private void initApplicationContext(String pluginId) {
stopWatch.getTotalTimeMillis(), stopWatch.prettyPrint());
}
+ PluginContext createPluginContext(PluginWrapper pluginWrapper) {
+ if (pluginWrapper instanceof HaloPluginWrapper haloPluginWrapper) {
+ return new PluginContext(haloPluginWrapper.getPluginId(),
+ pluginWrapper.getDescriptor().getVersion(),
+ haloPluginWrapper.getRuntimeMode());
+ }
+ throw new PluginRuntimeException("PluginWrapper must be instance of HaloPluginWrapper");
+ }
+
private void populateSettingFetcher(String pluginName,
DefaultListableBeanFactory listableBeanFactory) {
ReactiveExtensionClient extensionClient =
diff --git a/application/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java b/application/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java
index 40d4df246f..bd74031438 100644
--- a/application/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java
+++ b/application/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java
@@ -5,6 +5,7 @@
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import lombok.extern.slf4j.Slf4j;
@@ -109,33 +110,7 @@ protected PluginLoader createPluginLoader() {
}
} else {
return new CompoundPluginLoader()
- .add(new DevelopmentPluginLoader(this) {
-
- @Override
- protected PluginClassLoader createPluginClassLoader(Path pluginPath,
- PluginDescriptor pluginDescriptor) {
- return new PluginClassLoader(pluginManager, pluginDescriptor,
- getClass().getClassLoader(), ClassLoadingStrategy.APD);
- }
-
- @Override
- public ClassLoader loadPlugin(Path pluginPath,
- PluginDescriptor pluginDescriptor) {
- if (pluginProperties.getClassesDirectories() != null) {
- for (String classesDirectory :
- pluginProperties.getClassesDirectories()) {
- pluginClasspath.addClassesDirectories(classesDirectory);
- }
- }
- if (pluginProperties.getLibDirectories() != null) {
- for (String libDirectory :
- pluginProperties.getLibDirectories()) {
- pluginClasspath.addJarsDirectories(libDirectory);
- }
- }
- return super.loadPlugin(pluginPath, pluginDescriptor);
- }
- }, this::isDevelopment)
+ .add(createDevelopmentPluginLoader(this), this::isDevelopment)
.add(new JarPluginLoader(this) {
@Override
public ClassLoader loadPlugin(Path pluginPath,
@@ -145,9 +120,8 @@ public ClassLoader loadPlugin(Path pluginPath,
getClass().getClassLoader(), ClassLoadingStrategy.APD);
pluginClassLoader.addFile(pluginPath.toFile());
return pluginClassLoader;
-
}
- }, this::isNotDevelopment);
+ });
}
}
@@ -167,9 +141,8 @@ protected PluginRepository createPluginRepository() {
.setFixedPaths(pluginProperties.getFixedPluginPath());
return new CompoundPluginRepository()
.add(developmentPluginRepository, this::isDevelopment)
- .add(new JarPluginRepository(getPluginsRoots()), this::isNotDevelopment)
- .add(new DefaultPluginRepository(getPluginsRoots()),
- this::isNotDevelopment);
+ .add(new JarPluginRepository(getPluginsRoots()))
+ .add(new DefaultPluginRepository(getPluginsRoots()));
}
};
@@ -181,6 +154,41 @@ protected PluginRepository createPluginRepository() {
return pluginManager;
}
+ DevelopmentPluginLoader createDevelopmentPluginLoader(PluginManager pluginManager) {
+ return new DevelopmentPluginLoader(pluginManager) {
+ @Override
+ protected PluginClassLoader createPluginClassLoader(Path pluginPath,
+ PluginDescriptor pluginDescriptor) {
+ return new PluginClassLoader(pluginManager, pluginDescriptor,
+ getClass().getClassLoader(), ClassLoadingStrategy.APD);
+ }
+
+ @Override
+ public ClassLoader loadPlugin(Path pluginPath,
+ PluginDescriptor pluginDescriptor) {
+ if (pluginProperties.getClassesDirectories() != null) {
+ for (String classesDirectory :
+ pluginProperties.getClassesDirectories()) {
+ pluginClasspath.addClassesDirectories(classesDirectory);
+ }
+ }
+ if (pluginProperties.getLibDirectories() != null) {
+ for (String libDirectory :
+ pluginProperties.getLibDirectories()) {
+ pluginClasspath.addJarsDirectories(libDirectory);
+ }
+ }
+ return super.loadPlugin(pluginPath, pluginDescriptor);
+ }
+
+ @Override
+ public boolean isApplicable(Path pluginPath) {
+ return Files.exists(pluginPath)
+ && Files.isDirectory(pluginPath);
+ }
+ };
+ }
+
String getSystemVersion() {
return systemVersionSupplier.get().getNormalVersion();
}
diff --git a/application/src/main/java/run/halo/app/plugin/SpringExtensionFactory.java b/application/src/main/java/run/halo/app/plugin/SpringExtensionFactory.java
index 4bf0c5b22f..6477f48115 100644
--- a/application/src/main/java/run/halo/app/plugin/SpringExtensionFactory.java
+++ b/application/src/main/java/run/halo/app/plugin/SpringExtensionFactory.java
@@ -6,11 +6,12 @@
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.Extension;
import org.pf4j.ExtensionFactory;
-import org.pf4j.Plugin;
import org.pf4j.PluginManager;
+import org.pf4j.PluginRuntimeException;
import org.pf4j.PluginWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
@@ -52,46 +53,18 @@
* @since 2.0.0
*/
@Slf4j
+@RequiredArgsConstructor
public class SpringExtensionFactory implements ExtensionFactory {
- public static final boolean AUTOWIRE_BY_DEFAULT = true;
-
/**
* The plugin manager is used for retrieving a plugin from a given extension class and as a
* fallback supplier of an application context.
*/
protected final PluginManager pluginManager;
- /**
- * Indicates if springs autowiring possibilities should be used.
- */
- protected final boolean autowire;
-
- public SpringExtensionFactory(PluginManager pluginManager) {
- this(pluginManager, AUTOWIRE_BY_DEFAULT);
- }
-
- public SpringExtensionFactory(final PluginManager pluginManager, final boolean autowire) {
- this.pluginManager = pluginManager;
- this.autowire = autowire;
- if (!autowire) {
- log.warn(
- "Autowiring is disabled although the only reason for existence of this special "
- + "factory is"
- +
- " supporting spring and its application context.");
- }
- }
-
@Override
@Nullable
public T create(Class extensionClass) {
- if (!this.autowire) {
- log.warn("Create instance of '" + nameOf(extensionClass)
- + "' without using springs possibilities as"
- + " autowiring is disabled.");
- return createWithoutSpring(extensionClass);
- }
Optional contextOptional =
getPluginApplicationContextBy(extensionClass);
if (contextOptional.isPresent()) {
@@ -154,52 +127,38 @@ private Object[] nullParameters(final Constructor> constructor) {
protected Optional getPluginApplicationContextBy(
final Class extensionClass) {
- final Plugin plugin = Optional.ofNullable(this.pluginManager.whichPlugin(extensionClass))
+ return Optional.ofNullable(this.pluginManager.whichPlugin(extensionClass))
.map(PluginWrapper::getPlugin)
- .orElse(null);
-
- final PluginApplicationContext applicationContext;
-
- if (plugin instanceof BasePlugin) {
- log.debug(
- " Extension class ' " + nameOf(extensionClass) + "' belongs to halo-plugin '"
- + nameOf(plugin)
- + "' and will be autowired by using its application context.");
- applicationContext = ExtensionContextRegistry.getInstance()
- .getByPluginId(plugin.getWrapper().getPluginId());
- return Optional.of(applicationContext);
- } else if (this.pluginManager instanceof HaloPluginManager && plugin != null) {
- log.debug(" Extension class ' " + nameOf(extensionClass)
- + "' belongs to a non halo-plugin (or main application)"
- + " '" + nameOf(plugin)
- + ", but the used Halo plugin-manager is a spring-plugin-manager. Therefore"
- + " the extension class will be autowired by using the managers application "
- + "contexts");
- String pluginId = plugin.getWrapper().getPluginId();
- applicationContext = ((HaloPluginManager) this.pluginManager)
- .getPluginApplicationContext(pluginId);
- } else {
- log.warn(" No application contexts can be used for instantiating extension class '"
- + nameOf(extensionClass) + "'."
- + " This extension neither belongs to a halo-plugin (id: '" + nameOf(plugin)
- + "') nor is the used"
- + " plugin manager a spring-plugin-manager (used manager: '"
- + nameOf(this.pluginManager.getClass()) + "')."
- + " At perspective of PF4J this seems highly uncommon in combination with a factory"
- + " which only reason for existence"
- + " is using spring (and its application context) and should at least be reviewed. "
- + "In fact no autowiring can be"
- + " applied although autowire flag was set to 'true'. Instantiating will fallback "
- + "to standard Java reflection.");
- applicationContext = null;
- }
-
- return Optional.ofNullable(applicationContext);
+ .map(plugin -> {
+ if (plugin instanceof BasePlugin basePlugin) {
+ return basePlugin;
+ }
+ throw new PluginRuntimeException(
+ "The plugin must be an instance of BasePlugin");
+ })
+ .map(plugin -> {
+ var pluginName = plugin.getContext().getName();
+ if (this.pluginManager instanceof HaloPluginManager haloPluginManager) {
+ log.debug(" Extension class ' " + nameOf(extensionClass)
+ + "' belongs to a non halo-plugin (or main application)"
+ + " '" + nameOf(plugin)
+ + ", but the used Halo plugin-manager is a spring-plugin-manager. Therefore"
+ + " the extension class will be autowired by using the managers "
+ + "application "
+ + "contexts");
+ return haloPluginManager.getPluginApplicationContext(pluginName);
+ }
+ log.debug(
+ " Extension class ' " + nameOf(extensionClass) + "' belongs to halo-plugin '"
+ + nameOf(plugin)
+ + "' and will be autowired by using its application context.");
+ return ExtensionContextRegistry.getInstance().getByPluginId(pluginName);
+ });
}
- private String nameOf(final Plugin plugin) {
+ private String nameOf(final BasePlugin plugin) {
return Objects.nonNull(plugin)
- ? plugin.getWrapper().getPluginId()
+ ? plugin.getContext().getName()
: "system";
}
diff --git a/application/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java b/application/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java
index 00228014f1..02b9ffc017 100644
--- a/application/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java
+++ b/application/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java
@@ -51,6 +51,7 @@
import run.halo.app.infra.utils.FileUtils;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.plugin.HaloPluginManager;
+import run.halo.app.plugin.HaloPluginWrapper;
import run.halo.app.plugin.PluginConst;
import run.halo.app.plugin.PluginStartingError;
@@ -70,7 +71,7 @@ class PluginReconcilerTest {
ExtensionClient extensionClient;
@Mock
- PluginWrapper pluginWrapper;
+ HaloPluginWrapper pluginWrapper;
@Mock
ApplicationEventPublisher eventPublisher;