diff --git a/src/main/java/run/halo/app/plugin/DefaultDevelopmentPluginRepository.java b/src/main/java/run/halo/app/plugin/DefaultDevelopmentPluginRepository.java new file mode 100644 index 000000000..dac831c51 --- /dev/null +++ b/src/main/java/run/halo/app/plugin/DefaultDevelopmentPluginRepository.java @@ -0,0 +1,42 @@ +package run.halo.app.plugin; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import org.pf4j.DevelopmentPluginRepository; +import org.springframework.util.CollectionUtils; + +/** + * @author guqing + * @since 2.0.0 + */ +public class DefaultDevelopmentPluginRepository extends DevelopmentPluginRepository { + private final List fixedPaths = new ArrayList<>(); + + public DefaultDevelopmentPluginRepository(Path... pluginsRoots) { + super(pluginsRoots); + } + + public DefaultDevelopmentPluginRepository(List pluginsRoots) { + super(pluginsRoots); + } + + public void addFixedPath(Path path) { + fixedPaths.add(path); + } + + public void setFixedPaths(List paths) { + if (CollectionUtils.isEmpty(paths)) { + return; + } + fixedPaths.clear(); + fixedPaths.addAll(paths); + } + + @Override + public List getPluginPaths() { + List paths = new ArrayList<>(fixedPaths); + paths.addAll(super.getPluginPaths()); + return paths; + } +} diff --git a/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java b/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java index ed73f88a5..ea708fd09 100644 --- a/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java +++ b/src/main/java/run/halo/app/plugin/PluginAutoConfiguration.java @@ -6,12 +6,16 @@ import java.nio.file.Path; import lombok.extern.slf4j.Slf4j; import org.pf4j.ClassLoadingStrategy; import org.pf4j.CompoundPluginLoader; +import org.pf4j.CompoundPluginRepository; +import org.pf4j.DefaultPluginRepository; import org.pf4j.DevelopmentPluginLoader; import org.pf4j.JarPluginLoader; +import org.pf4j.JarPluginRepository; import org.pf4j.PluginClassLoader; import org.pf4j.PluginDescriptor; import org.pf4j.PluginLoader; import org.pf4j.PluginManager; +import org.pf4j.PluginRepository; import org.pf4j.PluginStatusProvider; import org.pf4j.RuntimeMode; import org.springframework.beans.factory.annotation.Qualifier; @@ -33,6 +37,7 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolver; public class PluginAutoConfiguration { private final PluginProperties pluginProperties; + @Qualifier("webFluxContentTypeResolver") private final RequestedContentTypeResolver requestedContentTypeResolver; @@ -126,11 +131,23 @@ public class PluginAutoConfiguration { } 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()), this::isNotDevelopment) + .add(new DefaultPluginRepository(getPluginsRoots()), + this::isNotDevelopment); + } }; pluginManager.setExactVersionAllowed(pluginProperties.isExactVersionAllowed()); pluginManager.setSystemVersion(pluginProperties.getSystemVersion()); - return pluginManager; } } diff --git a/src/main/java/run/halo/app/plugin/PluginDevelopmentInitializer.java b/src/main/java/run/halo/app/plugin/PluginDevelopmentInitializer.java new file mode 100644 index 000000000..493014659 --- /dev/null +++ b/src/main/java/run/halo/app/plugin/PluginDevelopmentInitializer.java @@ -0,0 +1,55 @@ +package run.halo.app.plugin; + +import java.nio.file.Path; +import org.pf4j.PluginWrapper; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import run.halo.app.core.extension.Plugin; +import run.halo.app.extension.ExtensionClient; + +/** + * @author guqing + * @since 2.0.0 + */ +@Component +public class PluginDevelopmentInitializer implements ApplicationListener { + + private final HaloPluginManager haloPluginManager; + + private final PluginProperties pluginProperties; + + private final ExtensionClient extensionClient; + + public PluginDevelopmentInitializer(HaloPluginManager haloPluginManager, + PluginProperties pluginProperties, ExtensionClient extensionClient) { + this.haloPluginManager = haloPluginManager; + this.pluginProperties = pluginProperties; + this.extensionClient = extensionClient; + } + + @Override + public void onApplicationEvent(@NonNull ApplicationReadyEvent event) { + if (!haloPluginManager.isDevelopment()) { + return; + } + createFixedPluginIfNecessary(haloPluginManager); + } + + private void createFixedPluginIfNecessary(HaloPluginManager pluginManager) { + for (Path path : pluginProperties.getFixedPluginPath()) { + String pluginId = pluginManager.loadPlugin(path); + PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId); + if (pluginWrapper == null) { + continue; + } + Plugin plugin = new YamlPluginFinder().find(pluginWrapper.getPluginPath()); + extensionClient.fetch(Plugin.class, plugin.getMetadata().getName()) + .ifPresentOrElse(persistent -> { + plugin.getMetadata().setVersion(persistent.getMetadata().getVersion()); + extensionClient.update(plugin); + }, () -> extensionClient.create(plugin)); + } + } +} diff --git a/src/main/java/run/halo/app/plugin/PluginProperties.java b/src/main/java/run/halo/app/plugin/PluginProperties.java index 597c79469..f621a577c 100644 --- a/src/main/java/run/halo/app/plugin/PluginProperties.java +++ b/src/main/java/run/halo/app/plugin/PluginProperties.java @@ -1,5 +1,6 @@ package run.halo.app.plugin; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import lombok.Data; @@ -22,6 +23,12 @@ public class PluginProperties { */ private boolean autoStartPlugin = true; + /** + * The default plugin path is obtained through file scanning. + * In the development mode, you can specify the plugin path as the project directory. + */ + private List fixedPluginPath = new ArrayList<>(); + /** * Plugins disabled by default. */ diff --git a/src/main/java/run/halo/app/plugin/PluginRequestMappingHandlerMapping.java b/src/main/java/run/halo/app/plugin/PluginRequestMappingHandlerMapping.java index 42137140d..91b362315 100644 --- a/src/main/java/run/halo/app/plugin/PluginRequestMappingHandlerMapping.java +++ b/src/main/java/run/halo/app/plugin/PluginRequestMappingHandlerMapping.java @@ -35,6 +35,13 @@ public class PluginRequestMappingHandlerMapping extends RequestMappingHandlerMap private final MultiValueMap pluginMappingInfo = new LinkedMultiValueMap<>(); + @Override + protected void initHandlerMethods() { + // Parent method will scan beans in the ApplicationContext + // detect and register handler methods. + // but this is superfluous for this class. + } + /** * Register handler methods according to the plugin id and the controller(annotated * {@link Controller}) bean. diff --git a/src/main/java/run/halo/app/plugin/SettingFetcher.java b/src/main/java/run/halo/app/plugin/SettingFetcher.java index fbfbf663c..630837774 100644 --- a/src/main/java/run/halo/app/plugin/SettingFetcher.java +++ b/src/main/java/run/halo/app/plugin/SettingFetcher.java @@ -6,11 +6,9 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import java.util.Collection; import java.util.Map; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; import run.halo.app.core.extension.Plugin; import run.halo.app.extension.ConfigMap; import run.halo.app.extension.ExtensionClient; @@ -19,17 +17,12 @@ import run.halo.app.infra.utils.JsonUtils; /** *

A value fetcher for pPlugin form configuration.

- *

The method of obtaining values is lazy,only when it is used for the first time can the real - * query value be obtained from {@link ConfigMap}.

- *

It is thread safe.

* * @author guqing * @since 2.0.0 */ public class SettingFetcher { - private final AtomicReference> valueRef = new AtomicReference<>(null); - private final ExtensionClient extensionClient; private final String pluginName; @@ -40,13 +33,13 @@ public class SettingFetcher { this.pluginName = pluginName; } - @Nullable - public T getGroupForObject(String group, Class clazz) { - return convertValue(getInternal(group), clazz); + @NonNull + public Optional fetch(String group, Class clazz) { + return Optional.ofNullable(convertValue(getInternal(group), clazz)); } @NonNull - public JsonNode getGroup(String group) { + public JsonNode get(String group) { return getInternal(group); } @@ -57,13 +50,11 @@ public class SettingFetcher { */ @NonNull public Map getValues() { - Map values = - valueRef.updateAndGet(m -> m != null ? m : getValuesInternal()); - return Map.copyOf(values); + return Map.copyOf(getValuesInternal()); } private JsonNode getInternal(String group) { - return Optional.ofNullable(getValues().get(group)) + return Optional.ofNullable(getValuesInternal().get(group)) .orElse(JsonNodeFactory.instance.missingNode()); } diff --git a/src/test/java/run/halo/app/plugin/SettingFetcherTest.java b/src/test/java/run/halo/app/plugin/SettingFetcherTest.java index f6287f1a8..b50f1fed3 100644 --- a/src/test/java/run/halo/app/plugin/SettingFetcherTest.java +++ b/src/test/java/run/halo/app/plugin/SettingFetcherTest.java @@ -64,29 +64,28 @@ class SettingFetcherTest { // The extensionClient will only be called once Map callAgain = settingFetcher.getValues(); assertThat(callAgain).isNotNull(); - verify(extensionClient, times(1)).fetch(eq(ConfigMap.class), any()); } @Test void getGroupForObject() throws JSONException { - Sns sns = settingFetcher.getGroupForObject("sns", Sns.class); - assertThat(sns).isNotNull(); - JSONAssert.assertEquals(getSns(), JsonUtils.objectToJson(sns), true); + Optional sns = settingFetcher.fetch("sns", Sns.class); + assertThat(sns.isEmpty()).isFalse(); + JSONAssert.assertEquals(getSns(), JsonUtils.objectToJson(sns.get()), true); - Sns missing = settingFetcher.getGroupForObject("sns1", Sns.class); - assertThat(missing).isNull(); + Optional missing = settingFetcher.fetch("sns1", Sns.class); + assertThat(missing.isEmpty()).isTrue(); } @Test void getGroup() { - JsonNode jsonNode = settingFetcher.getGroup("basic"); + JsonNode jsonNode = settingFetcher.get("basic"); assertThat(jsonNode).isNotNull(); assertThat(jsonNode.isObject()).isTrue(); assertThat(jsonNode.get("color").asText()).isEqualTo("red"); assertThat(jsonNode.get("width").asInt()).isEqualTo(100); // missing key will return empty json node - JsonNode emptyNode = settingFetcher.getGroup("basic1"); + JsonNode emptyNode = settingFetcher.get("basic1"); assertThat(emptyNode.isEmpty()).isTrue(); }