From e5509bd63f114cb973f171097dbf44e1f0bc9df6 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 24 Aug 2021 16:33:05 +0200 Subject: [PATCH] load certain services from jars located in plugin dir --- .../org/cryptomator/common/Environment.java | 5 ++ .../cryptomator/common/PluginClassLoader.java | 66 +++++++++++++++++++ .../common/keychain/KeychainModule.java | 5 +- .../ui/launcher/UiLauncherModule.java | 13 ++-- 4 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/cryptomator/common/PluginClassLoader.java diff --git a/src/main/java/org/cryptomator/common/Environment.java b/src/main/java/org/cryptomator/common/Environment.java index bec4a2986..b42b1a356 100644 --- a/src/main/java/org/cryptomator/common/Environment.java +++ b/src/main/java/org/cryptomator/common/Environment.java @@ -36,6 +36,7 @@ public class Environment { LOG.debug("cryptomator.ipcSocketPath: {}", System.getProperty("cryptomator.ipcSocketPath")); LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath")); LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir")); + LOG.debug("cryptomator.pluginDir: {}", System.getProperty("cryptomator.pluginDir")); LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir")); LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength")); LOG.debug("cryptomator.appVersion: {}", System.getProperty("cryptomator.appVersion")); @@ -64,6 +65,10 @@ public class Environment { return getPath("cryptomator.logDir").map(this::replaceHomeDir); } + public Optional getPluginDir() { + return getPath("cryptomator.pluginDir").map(this::replaceHomeDir); + } + public Optional getMountPointsDir() { return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir); } diff --git a/src/main/java/org/cryptomator/common/PluginClassLoader.java b/src/main/java/org/cryptomator/common/PluginClassLoader.java new file mode 100644 index 000000000..16932923b --- /dev/null +++ b/src/main/java/org/cryptomator/common/PluginClassLoader.java @@ -0,0 +1,66 @@ +package org.cryptomator.common; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +@Singleton +public class PluginClassLoader extends URLClassLoader { + + private static final Logger LOG = LoggerFactory.getLogger(PluginClassLoader.class); + private static final String NAME = "PluginClassLoader"; + private static final String JAR_SUFFIX = ".jar"; + + @Inject + public PluginClassLoader(Environment env) { + super(NAME, env.getPluginDir().map(PluginClassLoader::findJars).orElse(new URL[0]), PluginClassLoader.class.getClassLoader()); + } + + private static URL[] findJars(Path path) { + if (!Files.isDirectory(path)) { + return new URL[0]; + } else { + try { + var visitor = new JarVisitor(); + Files.walkFileTree(path, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, visitor); + return visitor.urls.toArray(URL[]::new); + } catch (IOException e) { + LOG.warn("Failed to scan plugin dir " + path, e); + return new URL[0]; + } + } + } + + private static final class JarVisitor extends SimpleFileVisitor { + + private final List urls = new ArrayList<>(); + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (attrs.isRegularFile() && file.getFileName().toString().toLowerCase().endsWith(JAR_SUFFIX)) { + try { + urls.add(file.toUri().toURL()); + } catch (MalformedURLException e) { + LOG.warn("Failed to create URL for jar file {}", file); + } + } + return FileVisitResult.CONTINUE; + } + } + +} diff --git a/src/main/java/org/cryptomator/common/keychain/KeychainModule.java b/src/main/java/org/cryptomator/common/keychain/KeychainModule.java index 01a221ed7..6356c4966 100644 --- a/src/main/java/org/cryptomator/common/keychain/KeychainModule.java +++ b/src/main/java/org/cryptomator/common/keychain/KeychainModule.java @@ -2,6 +2,7 @@ package org.cryptomator.common.keychain; import dagger.Module; import dagger.Provides; +import org.cryptomator.common.PluginClassLoader; import org.cryptomator.common.settings.Settings; import org.cryptomator.integrations.keychain.KeychainAccessProvider; @@ -17,8 +18,8 @@ public class KeychainModule { @Provides @Singleton - static Set> provideAvailableKeychainAccessProviderFactories() { - return ServiceLoader.load(KeychainAccessProvider.class).stream().collect(Collectors.toUnmodifiableSet()); + static Set> provideAvailableKeychainAccessProviderFactories(PluginClassLoader classLoader) { + return ServiceLoader.load(KeychainAccessProvider.class, classLoader).stream().collect(Collectors.toUnmodifiableSet()); } @Provides diff --git a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java index f85659c53..c30efa30e 100644 --- a/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java +++ b/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java @@ -2,6 +2,7 @@ package org.cryptomator.ui.launcher; import dagger.Module; import dagger.Provides; +import org.cryptomator.common.PluginClassLoader; import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.tray.TrayIntegrationProvider; import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; @@ -33,21 +34,21 @@ public abstract class UiLauncherModule { @Provides @Singleton - static Optional provideAppearanceProvider() { - return ServiceLoader.load(UiAppearanceProvider.class).findFirst(); + static Optional provideAppearanceProvider(PluginClassLoader classLoader) { + return ServiceLoader.load(UiAppearanceProvider.class, classLoader).findFirst(); } @Provides @Singleton - static Optional provideAutostartProvider() { - return ServiceLoader.load(AutoStartProvider.class).findFirst(); + static Optional provideAutostartProvider(PluginClassLoader classLoader) { + return ServiceLoader.load(AutoStartProvider.class, classLoader).findFirst(); } @Provides @Singleton - static Optional provideTrayIntegrationProvider() { - return ServiceLoader.load(TrayIntegrationProvider.class).findFirst(); + static Optional provideTrayIntegrationProvider(PluginClassLoader classLoader) { + return ServiceLoader.load(TrayIntegrationProvider.class, classLoader).findFirst(); } @Provides