]> source.dussan.org Git - pf4j.git/commitdiff
Async POC
authorDecebal Suiu <decebal.suiu@gmail.com>
Thu, 18 Apr 2019 22:13:38 +0000 (01:13 +0300)
committerDecebal Suiu <decebal.suiu@gmail.com>
Thu, 18 Apr 2019 22:13:38 +0000 (01:13 +0300)
demo/app/src/main/java/org/pf4j/demo/Boot.java
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
pf4j/src/main/java/org/pf4j/AsyncPluginManager.java [new file with mode: 0644]
pf4j/src/main/java/org/pf4j/DefaultAsyncPluginManager.java [new file with mode: 0644]

index 562f8f88f359a3da9d8904024b7be70426964fe0..c4ddc3f7f0367b0800723a398702639172f7f027 100644 (file)
@@ -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<Void> 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<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
index 38335887103c94ed609bf7ffa36df9b97488ddef..a1ea5797bf7dc6375ed46dd785e8f64697bf115e 100644 (file)
@@ -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 (file)
index 0000000..5f3c12b
--- /dev/null
@@ -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<Void> loadPluginsAsync();
+
+    CompletableFuture<Void> 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 (file)
index 0000000..5673f0a
--- /dev/null
@@ -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<Void> 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<Path> 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<Void> 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<Void> startPluginsAsync() {
+        /*
+        // chain start plugins one after another
+        CompletableFuture<Void> 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<PluginWrapper> loadPluginFromPathAsync(Path pluginPath) {
+        return CompletableFuture.supplyAsync(() -> {
+            try {
+                return loadPluginFromPath(pluginPath);
+            } catch (PluginException e) {
+                log.error(e.getMessage(), e);
+                return null;
+            }
+        });
+    }
+
+    protected CompletableFuture<Void> 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);
+                }
+            }
+        });
+    }
+
+}