Skip to content

Commit

Permalink
feat: support running plugins from JAR in development mode
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Sep 11, 2023
1 parent 31675db commit fed1636
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 113 deletions.
36 changes: 36 additions & 0 deletions api/src/main/java/run/halo/app/plugin/BasePlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
}
27 changes: 27 additions & 0 deletions api/src/main/java/run/halo/app/plugin/PluginContext.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -184,11 +185,12 @@ Optional<Setting> 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)
Expand All @@ -215,6 +217,7 @@ boolean waitForSettingCreation(Plugin plugin) {
return false;
}

var runtimeMode = getRuntimeMode(pluginName);
Optional<Setting> settingOption = lookupPluginSetting(pluginName, settingName)
.map(setting -> {
// This annotation is added to prevent it from being deleted when stopped.
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -145,9 +120,8 @@ public ClassLoader loadPlugin(Path pluginPath,
getClass().getClassLoader(), ClassLoadingStrategy.APD);
pluginClassLoader.addFile(pluginPath.toFile());
return pluginClassLoader;

}
}, this::isNotDevelopment);
});
}
}

Expand All @@ -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()));
}
};

Expand All @@ -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();
}
Expand Down
Loading

0 comments on commit fed1636

Please sign in to comment.