From a436cc54501939a8946ae3e1d5bfd462b345ab86 Mon Sep 17 00:00:00 2001 From: Decebal Suiu Date: Fri, 19 Apr 2019 01:13:38 +0300 Subject: [PATCH] Async POC --- .../app/src/main/java/org/pf4j/demo/Boot.java | 19 ++- .../java/org/pf4j/AbstractPluginManager.java | 4 + .../java/org/pf4j/AsyncPluginManager.java | 31 +++++ .../org/pf4j/DefaultAsyncPluginManager.java | 123 ++++++++++++++++++ 4 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 pf4j/src/main/java/org/pf4j/AsyncPluginManager.java create mode 100644 pf4j/src/main/java/org/pf4j/DefaultAsyncPluginManager.java diff --git a/demo/app/src/main/java/org/pf4j/demo/Boot.java b/demo/app/src/main/java/org/pf4j/demo/Boot.java index 562f8f8..c4ddc3f 100644 --- a/demo/app/src/main/java/org/pf4j/demo/Boot.java +++ b/demo/app/src/main/java/org/pf4j/demo/Boot.java @@ -16,6 +16,8 @@ package org.pf4j.demo; import org.apache.commons.lang.StringUtils; +import org.pf4j.AsyncPluginManager; +import org.pf4j.DefaultAsyncPluginManager; import org.pf4j.DefaultExtensionFinder; import org.pf4j.DefaultPluginManager; import org.pf4j.ExtensionFinder; @@ -25,6 +27,7 @@ import org.pf4j.demo.api.Greeting; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; /** * A boot class that start the demo. @@ -33,12 +36,13 @@ import java.util.Set; */ public class Boot { - public static void main(String[] args) { + public static void main(String[] args) throws Exception { // print logo printLogo(); // create the plugin manager - final PluginManager pluginManager = new DefaultPluginManager() { + final AsyncPluginManager pluginManager = new DefaultAsyncPluginManager() { +// final PluginManager pluginManager = new DefaultPluginManager() { protected ExtensionFinder createExtensionFinder() { DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder(); @@ -50,13 +54,20 @@ public class Boot { }; // load the plugins - pluginManager.loadPlugins(); +// pluginManager.loadPlugins(); + CompletableFuture feature = pluginManager.loadPluginsAsync(); + feature.thenRun(() -> System.out.println("Plugins loaded")); // enable a disabled plugin // pluginManager.enablePlugin("welcome-plugin"); // start (active/resolved) the plugins - pluginManager.startPlugins(); +// pluginManager.startPlugins(); + feature.thenCompose(v -> pluginManager.startPluginsAsync()); + feature.thenRun(() -> System.out.println("Plugins started")); + + // block and wait for the future to complete (not the best approach in real applications) + feature.get(); // retrieves the extensions for Greeting extension point List greetings = pluginManager.getExtensions(Greeting.class); diff --git a/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java b/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java index 3833588..a1ea579 100644 --- a/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java +++ b/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java @@ -696,6 +696,10 @@ public abstract class AbstractPluginManager implements PluginManager { return pluginClassLoaders; } + protected PluginRepository getPluginRepository() { + return pluginRepository; + } + protected void initialize() { plugins = new HashMap<>(); pluginClassLoaders = new HashMap<>(); diff --git a/pf4j/src/main/java/org/pf4j/AsyncPluginManager.java b/pf4j/src/main/java/org/pf4j/AsyncPluginManager.java new file mode 100644 index 0000000..5f3c12b --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/AsyncPluginManager.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j; + +import java.util.concurrent.CompletableFuture; + +/** + * A {@link PluginManager} that supports asynchronous operation with the methods ending in {@code Async}. + * + * @author Decebal Suiu + */ +public interface AsyncPluginManager extends PluginManager { + + CompletableFuture loadPluginsAsync(); + + CompletableFuture startPluginsAsync(); + +} diff --git a/pf4j/src/main/java/org/pf4j/DefaultAsyncPluginManager.java b/pf4j/src/main/java/org/pf4j/DefaultAsyncPluginManager.java new file mode 100644 index 0000000..5673f0a --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/DefaultAsyncPluginManager.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * It's an extension of {@link DefaultPluginManager} that supports asynchronous methods (@{AsyncPluginManager}). + * + * @author Decebal Suiu + */ +public class DefaultAsyncPluginManager extends DefaultPluginManager implements AsyncPluginManager { + + private static final Logger log = LoggerFactory.getLogger(DefaultAsyncPluginManager.class); + + @Override + public CompletableFuture loadPluginsAsync() { + Path pluginsRoot = getPluginsRoot(); + PluginRepository pluginRepository = getPluginRepository(); + + log.debug("Lookup plugins in '{}'", pluginsRoot); + // check for plugins root + if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot)) { + log.warn("No '{}' root", pluginsRoot); + return CompletableFuture.completedFuture(null); + } + + // get all plugins paths from repository + List pluginsPaths = pluginRepository.getPluginsPaths(); + + // check for no plugins + if (pluginsPaths.isEmpty()) { + log.info("No plugins"); + return CompletableFuture.completedFuture(null); + } + + log.debug("Found {} possible plugins: {}", pluginsPaths.size(), pluginsPaths); + + // load plugins from plugin paths + CompletableFuture feature = CompletableFuture.allOf(pluginsPaths.stream() + .map(this::loadPluginFromPathAsync) + .filter(Objects::nonNull) + .toArray(CompletableFuture[]::new)); + + // resolve plugins + feature.thenRun(() -> { + try { + resolvePlugins(); + } catch (PluginException e) { + log.error(e.getMessage(), e); + } + }); + + return feature; + } + + @Override + public CompletableFuture startPluginsAsync() { + /* + // chain start plugins one after another + CompletableFuture feature = CompletableFuture.completedFuture(null); + for (PluginWrapper pluginWrapper : getResolvedPlugins()) { + feature = feature.thenCompose(v -> startPluginAsync(pluginWrapper)); + } + + return feature; + */ + + return CompletableFuture.allOf(getResolvedPlugins().stream() + .map(this::startPluginAsync) + .toArray(CompletableFuture[]::new)); + } + + protected CompletableFuture loadPluginFromPathAsync(Path pluginPath) { + return CompletableFuture.supplyAsync(() -> { + try { + return loadPluginFromPath(pluginPath); + } catch (PluginException e) { + log.error(e.getMessage(), e); + return null; + } + }); + } + + protected CompletableFuture startPluginAsync(PluginWrapper pluginWrapper) { + return CompletableFuture.runAsync(() -> { + PluginState pluginState = pluginWrapper.getPluginState(); + if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) { + try { + log.info("Start plugin '{}'", getPluginLabel(pluginWrapper.getDescriptor())); + pluginWrapper.getPlugin().start(); + pluginWrapper.setPluginState(PluginState.STARTED); + getStartedPlugins().add(pluginWrapper); + + firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + }); + } + +} -- 2.39.5