mirror of https://github.com/halo-dev/halo
feat: support running plugins from JAR in development mode (#4589)
#### What type of PR is this? /kind feature /milestone 2.10.x /area core #### What this PR does / why we need it: 支持在开发模式下通过 JAR 运行插件 *从此版本开始 BasePlugin 的子类建议使用 BasePlugin(PluginContext context) 构造函数,而不要使用之前的 BasePlugin(PluginWrapper wrapper) 构造函数。BasePlugin(PluginWrapper wrapper) 构造函数将计划在后续版本移除* ,当移除构造函数后不再将 PluginWrapper 暴露给插件使用,它只应该在 halo core 使用。 how to test it? 1. 测试开发模式下配置的 `halo.plugin.fixed-plugin-path` 插件是否正确运行 2. 测试开发模式下通过 JAR 包安装插件是否正确运行 3. 测试生产模式下是否能通过项目目录的方式运行插件,期望是生产模式不可以运行开发模式的插件 4. 测试开发模式和生产模式的插件卸载功能是否正确 #### Which issue(s) this PR fixes: Fixes #2908 #### Does this PR introduce a user-facing change? ```release-note 支持在开发模式下通过 JAR 运行插件 ```pull/4668/head
parent
470b0de70d
commit
1f0cfc18e3
|
@ -3,6 +3,8 @@ package run.halo.app.plugin;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.Plugin;
|
import org.pf4j.Plugin;
|
||||||
import org.pf4j.PluginWrapper;
|
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
|
* This class will be extended by all plugins and serve as the common class between a plugin and
|
||||||
|
@ -14,12 +16,46 @@ import org.pf4j.PluginWrapper;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BasePlugin extends Plugin {
|
public class BasePlugin extends Plugin {
|
||||||
|
|
||||||
|
protected PluginContext context;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public BasePlugin(PluginWrapper wrapper) {
|
public BasePlugin(PluginWrapper wrapper) {
|
||||||
super(wrapper);
|
super(wrapper);
|
||||||
log.info("Initialized plugin {}", wrapper.getPluginId());
|
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() {
|
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.
|
||||||
|
*/
|
||||||
|
final void setContext(PluginContext context) {
|
||||||
|
Assert.notNull(context, "Plugin context must not be null");
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public PluginContext getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package run.halo.app.plugin;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.pf4j.RuntimeMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This class will provide a context for the plugin, which will be used to store some
|
||||||
|
* information about the plugin.</p>
|
||||||
|
* <p>An instance of this class is provided to plugins in their constructor.</p>
|
||||||
|
* <p>It's safe for plugins to keep a reference to the instance for later use.</p>
|
||||||
|
* <p>This class facilitates communication with application and plugin manager.</p>
|
||||||
|
* <p>Pf4j recommends that you use a custom PluginContext instead of PluginWrapper.</p>
|
||||||
|
* <a href="https://github.com/pf4j/pf4j/blob/e4d7c7b9ea0c9a32179c3e33da1403228838944f/pf4j/src/main/java/org/pf4j/Plugin.java#L46">Use application custom PluginContext instead of PluginWrapper</a>
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.10.0
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PluginContext {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
private final RuntimeMode runtimeMode;
|
||||||
|
}
|
|
@ -60,6 +60,7 @@ import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.infra.utils.PathUtils;
|
import run.halo.app.infra.utils.PathUtils;
|
||||||
import run.halo.app.infra.utils.YamlUnstructuredLoader;
|
import run.halo.app.infra.utils.YamlUnstructuredLoader;
|
||||||
import run.halo.app.plugin.HaloPluginManager;
|
import run.halo.app.plugin.HaloPluginManager;
|
||||||
|
import run.halo.app.plugin.HaloPluginWrapper;
|
||||||
import run.halo.app.plugin.PluginConst;
|
import run.halo.app.plugin.PluginConst;
|
||||||
import run.halo.app.plugin.PluginExtensionLoaderUtils;
|
import run.halo.app.plugin.PluginExtensionLoaderUtils;
|
||||||
import run.halo.app.plugin.PluginStartingError;
|
import run.halo.app.plugin.PluginStartingError;
|
||||||
|
@ -184,11 +185,12 @@ public class PluginReconciler implements Reconciler<Request> {
|
||||||
Assert.notNull(name, "Plugin name must not be null");
|
Assert.notNull(name, "Plugin name must not be null");
|
||||||
Assert.notNull(settingName, "Setting name must not be null");
|
Assert.notNull(settingName, "Setting name must not be null");
|
||||||
PluginWrapper pluginWrapper = getPluginWrapper(name);
|
PluginWrapper pluginWrapper = getPluginWrapper(name);
|
||||||
|
var runtimeMode = getRuntimeMode(name);
|
||||||
|
|
||||||
var resourceLoader =
|
var resourceLoader =
|
||||||
new DefaultResourceLoader(pluginWrapper.getPluginClassLoader());
|
new DefaultResourceLoader(pluginWrapper.getPluginClassLoader());
|
||||||
return PluginExtensionLoaderUtils.lookupExtensions(pluginWrapper.getPluginPath(),
|
return PluginExtensionLoaderUtils.lookupExtensions(pluginWrapper.getPluginPath(),
|
||||||
pluginWrapper.getRuntimeMode())
|
runtimeMode)
|
||||||
.stream()
|
.stream()
|
||||||
.map(resourceLoader::getResource)
|
.map(resourceLoader::getResource)
|
||||||
.filter(Resource::exists)
|
.filter(Resource::exists)
|
||||||
|
@ -215,6 +217,7 @@ public class PluginReconciler implements Reconciler<Request> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var runtimeMode = getRuntimeMode(pluginName);
|
||||||
Optional<Setting> settingOption = lookupPluginSetting(pluginName, settingName)
|
Optional<Setting> settingOption = lookupPluginSetting(pluginName, settingName)
|
||||||
.map(setting -> {
|
.map(setting -> {
|
||||||
// This annotation is added to prevent it from being deleted when stopped.
|
// This annotation is added to prevent it from being deleted when stopped.
|
||||||
|
@ -802,11 +805,19 @@ public class PluginReconciler implements Reconciler<Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isDevelopmentMode(String name) {
|
private boolean isDevelopmentMode(String name) {
|
||||||
PluginWrapper pluginWrapper = haloPluginManager.getPlugin(name);
|
return RuntimeMode.DEVELOPMENT.equals(getRuntimeMode(name));
|
||||||
RuntimeMode runtimeMode = haloPluginManager.getRuntimeMode();
|
}
|
||||||
if (pluginWrapper != null) {
|
|
||||||
runtimeMode = pluginWrapper.getRuntimeMode();
|
private RuntimeMode getRuntimeMode(String name) {
|
||||||
|
var pluginWrapper = haloPluginManager.getPlugin(name);
|
||||||
|
if (pluginWrapper == null) {
|
||||||
|
return haloPluginManager.getRuntimeMode();
|
||||||
}
|
}
|
||||||
return RuntimeMode.DEVELOPMENT.equals(runtimeMode);
|
if (pluginWrapper instanceof HaloPluginWrapper haloPluginWrapper) {
|
||||||
|
return haloPluginWrapper.getRuntimeMode();
|
||||||
|
}
|
||||||
|
return Files.isDirectory(pluginWrapper.getPluginPath())
|
||||||
|
? RuntimeMode.DEVELOPMENT
|
||||||
|
: RuntimeMode.DEPLOYMENT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,17 @@ public class BasePluginFactory implements PluginFactory {
|
||||||
return getPluginContext(pluginWrapper)
|
return getPluginContext(pluginWrapper)
|
||||||
.map(context -> {
|
.map(context -> {
|
||||||
try {
|
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) {
|
} catch (NoSuchBeanDefinitionException e) {
|
||||||
log.info(
|
log.info(
|
||||||
"No bean named 'basePlugin' found in the context create default instance");
|
"No bean named 'basePlugin' found in the context create default instance");
|
||||||
DefaultListableBeanFactory beanFactory =
|
DefaultListableBeanFactory beanFactory =
|
||||||
context.getDefaultListableBeanFactory();
|
context.getDefaultListableBeanFactory();
|
||||||
BasePlugin pluginInstance = new BasePlugin();
|
var pluginContext = beanFactory.getBean(PluginContext.class);
|
||||||
|
BasePlugin pluginInstance = new BasePlugin(pluginContext);
|
||||||
beanFactory.registerSingleton(Plugin.class.getName(), pluginInstance);
|
beanFactory.registerSingleton(Plugin.class.getName(), pluginInstance);
|
||||||
return pluginInstance;
|
return pluginInstance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,17 @@ public class HaloPluginManager extends DefaultPluginManager
|
||||||
return new YamlPluginDescriptorFinder();
|
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
|
@Override
|
||||||
protected void firePluginStateEvent(PluginStateEvent event) {
|
protected void firePluginStateEvent(PluginStateEvent event) {
|
||||||
rootApplicationContext.publishEvent(
|
rootApplicationContext.publishEvent(
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.pf4j.PluginRuntimeException;
|
||||||
import org.pf4j.PluginWrapper;
|
import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
import org.springframework.boot.env.PropertySourceLoader;
|
import org.springframework.boot.env.PropertySourceLoader;
|
||||||
|
@ -88,6 +89,8 @@ public class PluginApplicationInitializer {
|
||||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
|
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
|
||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
|
|
||||||
|
beanFactory.registerSingleton("pluginContext", createPluginContext(plugin));
|
||||||
|
// TODO deprecated
|
||||||
beanFactory.registerSingleton("pluginWrapper", haloPluginManager.getPlugin(pluginId));
|
beanFactory.registerSingleton("pluginWrapper", haloPluginManager.getPlugin(pluginId));
|
||||||
|
|
||||||
populateSettingFetcher(pluginId, beanFactory);
|
populateSettingFetcher(pluginId, beanFactory);
|
||||||
|
@ -131,6 +134,15 @@ public class PluginApplicationInitializer {
|
||||||
stopWatch.getTotalTimeMillis(), stopWatch.prettyPrint());
|
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,
|
private void populateSettingFetcher(String pluginName,
|
||||||
DefaultListableBeanFactory listableBeanFactory) {
|
DefaultListableBeanFactory listableBeanFactory) {
|
||||||
ReactiveExtensionClient extensionClient =
|
ReactiveExtensionClient extensionClient =
|
||||||
|
|
|
@ -5,6 +5,7 @@ import static run.halo.app.plugin.resources.BundleResourceUtils.getJsBundleResou
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -109,33 +110,7 @@ public class PluginAutoConfiguration {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return new CompoundPluginLoader()
|
return new CompoundPluginLoader()
|
||||||
.add(new DevelopmentPluginLoader(this) {
|
.add(createDevelopmentPluginLoader(this), this::isDevelopment)
|
||||||
|
|
||||||
@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(new JarPluginLoader(this) {
|
.add(new JarPluginLoader(this) {
|
||||||
@Override
|
@Override
|
||||||
public ClassLoader loadPlugin(Path pluginPath,
|
public ClassLoader loadPlugin(Path pluginPath,
|
||||||
|
@ -145,9 +120,8 @@ public class PluginAutoConfiguration {
|
||||||
getClass().getClassLoader(), ClassLoadingStrategy.APD);
|
getClass().getClassLoader(), ClassLoadingStrategy.APD);
|
||||||
pluginClassLoader.addFile(pluginPath.toFile());
|
pluginClassLoader.addFile(pluginPath.toFile());
|
||||||
return pluginClassLoader;
|
return pluginClassLoader;
|
||||||
|
|
||||||
}
|
}
|
||||||
}, this::isNotDevelopment);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,9 +141,8 @@ public class PluginAutoConfiguration {
|
||||||
.setFixedPaths(pluginProperties.getFixedPluginPath());
|
.setFixedPaths(pluginProperties.getFixedPluginPath());
|
||||||
return new CompoundPluginRepository()
|
return new CompoundPluginRepository()
|
||||||
.add(developmentPluginRepository, this::isDevelopment)
|
.add(developmentPluginRepository, this::isDevelopment)
|
||||||
.add(new JarPluginRepository(getPluginsRoots()), this::isNotDevelopment)
|
.add(new JarPluginRepository(getPluginsRoots()))
|
||||||
.add(new DefaultPluginRepository(getPluginsRoots()),
|
.add(new DefaultPluginRepository(getPluginsRoots()));
|
||||||
this::isNotDevelopment);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -181,6 +154,41 @@ public class PluginAutoConfiguration {
|
||||||
return pluginManager;
|
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() {
|
String getSystemVersion() {
|
||||||
return systemVersionSupplier.get().getNormalVersion();
|
return systemVersionSupplier.get().getNormalVersion();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,12 @@ import java.util.Comparator;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.Extension;
|
import org.pf4j.Extension;
|
||||||
import org.pf4j.ExtensionFactory;
|
import org.pf4j.ExtensionFactory;
|
||||||
import org.pf4j.Plugin;
|
|
||||||
import org.pf4j.PluginManager;
|
import org.pf4j.PluginManager;
|
||||||
|
import org.pf4j.PluginRuntimeException;
|
||||||
import org.pf4j.PluginWrapper;
|
import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||||
|
@ -52,46 +53,18 @@ import org.springframework.lang.Nullable;
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SpringExtensionFactory implements ExtensionFactory {
|
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
|
* The plugin manager is used for retrieving a plugin from a given extension class and as a
|
||||||
* fallback supplier of an application context.
|
* fallback supplier of an application context.
|
||||||
*/
|
*/
|
||||||
protected final PluginManager pluginManager;
|
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
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public <T> T create(Class<T> extensionClass) {
|
public <T> T create(Class<T> extensionClass) {
|
||||||
if (!this.autowire) {
|
|
||||||
log.warn("Create instance of '" + nameOf(extensionClass)
|
|
||||||
+ "' without using springs possibilities as"
|
|
||||||
+ " autowiring is disabled.");
|
|
||||||
return createWithoutSpring(extensionClass);
|
|
||||||
}
|
|
||||||
Optional<PluginApplicationContext> contextOptional =
|
Optional<PluginApplicationContext> contextOptional =
|
||||||
getPluginApplicationContextBy(extensionClass);
|
getPluginApplicationContextBy(extensionClass);
|
||||||
if (contextOptional.isPresent()) {
|
if (contextOptional.isPresent()) {
|
||||||
|
@ -154,52 +127,38 @@ public class SpringExtensionFactory implements ExtensionFactory {
|
||||||
|
|
||||||
protected <T> Optional<PluginApplicationContext> getPluginApplicationContextBy(
|
protected <T> Optional<PluginApplicationContext> getPluginApplicationContextBy(
|
||||||
final Class<T> extensionClass) {
|
final Class<T> extensionClass) {
|
||||||
final Plugin plugin = Optional.ofNullable(this.pluginManager.whichPlugin(extensionClass))
|
return Optional.ofNullable(this.pluginManager.whichPlugin(extensionClass))
|
||||||
.map(PluginWrapper::getPlugin)
|
.map(PluginWrapper::getPlugin)
|
||||||
.orElse(null);
|
.map(plugin -> {
|
||||||
|
if (plugin instanceof BasePlugin basePlugin) {
|
||||||
final PluginApplicationContext applicationContext;
|
return basePlugin;
|
||||||
|
}
|
||||||
if (plugin instanceof BasePlugin) {
|
throw new PluginRuntimeException(
|
||||||
log.debug(
|
"The plugin must be an instance of BasePlugin");
|
||||||
" Extension class ' " + nameOf(extensionClass) + "' belongs to halo-plugin '"
|
})
|
||||||
+ nameOf(plugin)
|
.map(plugin -> {
|
||||||
+ "' and will be autowired by using its application context.");
|
var pluginName = plugin.getContext().getName();
|
||||||
applicationContext = ExtensionContextRegistry.getInstance()
|
if (this.pluginManager instanceof HaloPluginManager haloPluginManager) {
|
||||||
.getByPluginId(plugin.getWrapper().getPluginId());
|
log.debug(" Extension class ' " + nameOf(extensionClass)
|
||||||
return Optional.of(applicationContext);
|
+ "' belongs to a non halo-plugin (or main application)"
|
||||||
} else if (this.pluginManager instanceof HaloPluginManager && plugin != null) {
|
+ " '" + nameOf(plugin)
|
||||||
log.debug(" Extension class ' " + nameOf(extensionClass)
|
+ ", but the used Halo plugin-manager is a spring-plugin-manager. Therefore"
|
||||||
+ "' belongs to a non halo-plugin (or main application)"
|
+ " the extension class will be autowired by using the managers "
|
||||||
+ " '" + nameOf(plugin)
|
+ "application "
|
||||||
+ ", but the used Halo plugin-manager is a spring-plugin-manager. Therefore"
|
+ "contexts");
|
||||||
+ " the extension class will be autowired by using the managers application "
|
return haloPluginManager.getPluginApplicationContext(pluginName);
|
||||||
+ "contexts");
|
}
|
||||||
String pluginId = plugin.getWrapper().getPluginId();
|
log.debug(
|
||||||
applicationContext = ((HaloPluginManager) this.pluginManager)
|
" Extension class ' " + nameOf(extensionClass) + "' belongs to halo-plugin '"
|
||||||
.getPluginApplicationContext(pluginId);
|
+ nameOf(plugin)
|
||||||
} else {
|
+ "' and will be autowired by using its application context.");
|
||||||
log.warn(" No application contexts can be used for instantiating extension class '"
|
return ExtensionContextRegistry.getInstance().getByPluginId(pluginName);
|
||||||
+ 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String nameOf(final Plugin plugin) {
|
private String nameOf(final BasePlugin plugin) {
|
||||||
return Objects.nonNull(plugin)
|
return Objects.nonNull(plugin)
|
||||||
? plugin.getWrapper().getPluginId()
|
? plugin.getContext().getName()
|
||||||
: "system";
|
: "system";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ import run.halo.app.extension.controller.Reconciler;
|
||||||
import run.halo.app.infra.utils.FileUtils;
|
import run.halo.app.infra.utils.FileUtils;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.plugin.HaloPluginManager;
|
import run.halo.app.plugin.HaloPluginManager;
|
||||||
|
import run.halo.app.plugin.HaloPluginWrapper;
|
||||||
import run.halo.app.plugin.PluginConst;
|
import run.halo.app.plugin.PluginConst;
|
||||||
import run.halo.app.plugin.PluginStartingError;
|
import run.halo.app.plugin.PluginStartingError;
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ class PluginReconcilerTest {
|
||||||
ExtensionClient extensionClient;
|
ExtensionClient extensionClient;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
PluginWrapper pluginWrapper;
|
HaloPluginWrapper pluginWrapper;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
ApplicationEventPublisher eventPublisher;
|
ApplicationEventPublisher eventPublisher;
|
||||||
|
|
Loading…
Reference in New Issue