212 lines
7.0 KiB
Java
212 lines
7.0 KiB
Java
package run.halo.app.plugin;
|
|
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Stack;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.pf4j.CompoundPluginLoader;
|
|
import org.pf4j.CompoundPluginRepository;
|
|
import org.pf4j.DefaultPluginManager;
|
|
import org.pf4j.DefaultPluginRepository;
|
|
import org.pf4j.ExtensionFactory;
|
|
import org.pf4j.ExtensionFinder;
|
|
import org.pf4j.JarPluginLoader;
|
|
import org.pf4j.JarPluginRepository;
|
|
import org.pf4j.PluginDescriptor;
|
|
import org.pf4j.PluginDescriptorFinder;
|
|
import org.pf4j.PluginFactory;
|
|
import org.pf4j.PluginLoader;
|
|
import org.pf4j.PluginRepository;
|
|
import org.pf4j.PluginRuntimeException;
|
|
import org.pf4j.PluginStatusProvider;
|
|
import org.pf4j.PluginWrapper;
|
|
import org.springframework.context.ApplicationContext;
|
|
import org.springframework.data.util.Lazy;
|
|
import run.halo.app.infra.SystemVersionSupplier;
|
|
|
|
/**
|
|
* PluginManager to hold the main ApplicationContext.
|
|
* It provides methods for managing the plugin lifecycle.
|
|
*
|
|
* @author guqing
|
|
* @author johnniang
|
|
* @since 2.0.0
|
|
*/
|
|
@Slf4j
|
|
public class HaloPluginManager extends DefaultPluginManager implements SpringPluginManager {
|
|
|
|
private final ApplicationContext rootContext;
|
|
|
|
private final Lazy<ApplicationContext> sharedContext;
|
|
|
|
private final PluginProperties pluginProperties;
|
|
|
|
public HaloPluginManager(ApplicationContext rootContext,
|
|
PluginProperties pluginProperties,
|
|
SystemVersionSupplier systemVersionSupplier) {
|
|
this.pluginProperties = pluginProperties;
|
|
this.rootContext = rootContext;
|
|
// We have to initialize share context lazily because the root context has not refreshed
|
|
this.sharedContext = Lazy.of(() -> SharedApplicationContextFactory.create(rootContext));
|
|
super.runtimeMode = pluginProperties.getRuntimeMode();
|
|
|
|
setExactVersionAllowed(pluginProperties.isExactVersionAllowed());
|
|
setSystemVersion(systemVersionSupplier.get().getNormalVersion());
|
|
|
|
super.initialize();
|
|
}
|
|
|
|
@Override
|
|
protected void initialize() {
|
|
// Leave the implementation empty because the super#initialize eagerly initializes
|
|
// components before properties set.
|
|
}
|
|
|
|
@Override
|
|
protected ExtensionFactory createExtensionFactory() {
|
|
return new SpringExtensionFactory(this);
|
|
}
|
|
|
|
@Override
|
|
protected ExtensionFinder createExtensionFinder() {
|
|
var finder = new SpringComponentsFinder(this);
|
|
addPluginStateListener(finder);
|
|
return finder;
|
|
}
|
|
|
|
@Override
|
|
protected PluginFactory createPluginFactory() {
|
|
var contextFactory = new DefaultPluginApplicationContextFactory(this);
|
|
return new SpringPluginFactory(contextFactory);
|
|
}
|
|
|
|
@Override
|
|
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);
|
|
var pluginWrapper =
|
|
new HaloPluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
|
|
pluginWrapper.setPluginFactory(getPluginFactory());
|
|
return pluginWrapper;
|
|
}
|
|
|
|
@Override
|
|
protected PluginLoader createPluginLoader() {
|
|
var compoundLoader = new CompoundPluginLoader();
|
|
compoundLoader.add(new DevPluginLoader(this, this.pluginProperties), this::isDevelopment);
|
|
compoundLoader.add(new JarPluginLoader(this));
|
|
return compoundLoader;
|
|
}
|
|
|
|
@Override
|
|
protected PluginStatusProvider createPluginStatusProvider() {
|
|
if (PropertyPluginStatusProvider.isPropertySet(pluginProperties)) {
|
|
return new PropertyPluginStatusProvider(pluginProperties);
|
|
}
|
|
return super.createPluginStatusProvider();
|
|
}
|
|
|
|
@Override
|
|
protected PluginRepository createPluginRepository() {
|
|
var developmentPluginRepository =
|
|
new DefaultDevelopmentPluginRepository(getPluginsRoots());
|
|
developmentPluginRepository
|
|
.setFixedPaths(pluginProperties.getFixedPluginPath());
|
|
return new CompoundPluginRepository()
|
|
.add(developmentPluginRepository, this::isDevelopment)
|
|
.add(new JarPluginRepository(getPluginsRoots()))
|
|
.add(new DefaultPluginRepository(getPluginsRoots()));
|
|
}
|
|
|
|
@Override
|
|
protected List<Path> createPluginsRoot() {
|
|
var pluginsRoot = pluginProperties.getPluginsRoot();
|
|
if (StringUtils.isNotBlank(pluginsRoot)) {
|
|
return List.of(Paths.get(pluginsRoot));
|
|
}
|
|
return super.createPluginsRoot();
|
|
}
|
|
|
|
@Override
|
|
public void startPlugins() {
|
|
throw new UnsupportedOperationException(
|
|
"The operation of starting all plugins is not supported."
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public void stopPlugins() {
|
|
throw new UnsupportedOperationException(
|
|
"The operation of stopping all plugins is not supported."
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public ApplicationContext getRootContext() {
|
|
return rootContext;
|
|
}
|
|
|
|
@Override
|
|
public ApplicationContext getSharedContext() {
|
|
return sharedContext.get();
|
|
}
|
|
|
|
public boolean reloadPlugin(String pluginId, Path loadLocation) {
|
|
log.info("Reloading plugin {} from {}", pluginId, loadLocation);
|
|
|
|
var dependents = getDependents(pluginId);
|
|
dependents.forEach(plugin -> unloadPlugin(plugin.getPluginId(), false));
|
|
|
|
var unloaded = unloadPlugin(pluginId, false);
|
|
if (!unloaded) {
|
|
return false;
|
|
}
|
|
|
|
// load the root plugin
|
|
var loadedPluginId = loadPlugin(loadLocation);
|
|
|
|
if (!Objects.equals(pluginId, loadedPluginId)) {
|
|
throw new PluginRuntimeException("""
|
|
The plugin {} is reloaded successfully, \
|
|
but the plugin id is different from the original one.
|
|
""");
|
|
}
|
|
|
|
// load all dependents with reverse order
|
|
dependents.stream()
|
|
.map(PluginWrapper::getPluginPath)
|
|
.sorted(Comparator.reverseOrder())
|
|
.forEach(this::loadPlugin);
|
|
|
|
log.info("Reloaded plugin {} from {}", loadedPluginId, loadLocation);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public List<PluginWrapper> getDependents(String pluginId) {
|
|
var dependents = new ArrayList<PluginWrapper>();
|
|
var stack = new Stack<String>();
|
|
dependencyResolver.getDependents(pluginId).forEach(stack::push);
|
|
while (!stack.isEmpty()) {
|
|
var dependent = stack.pop();
|
|
var pluginWrapper = getPlugin(dependent);
|
|
if (pluginWrapper != null) {
|
|
dependents.add(pluginWrapper);
|
|
dependencyResolver.getDependents(dependent).forEach(stack::push);
|
|
}
|
|
}
|
|
return dependents;
|
|
}
|
|
}
|