Skip to content

Optimized Plugin Loading for Improved Performance #1764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,128 +12,111 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.*;

@Slf4j
@RequiredArgsConstructor
@Component
public class PathBasedPluginLoader implements PluginLoader
{
public class PathBasedPluginLoader implements PluginLoader {
private final CommonConfig common;
private final ApplicationHome applicationHome;


// Cache for plugin JAR paths to avoid redundant filesystem scans
private static final Map<String, List<String>> cachedPluginJars = new HashMap<>();

@Override
public List<LowcoderPlugin> loadPlugins()
{
public List<LowcoderPlugin> loadPlugins() {
List<LowcoderPlugin> plugins = new ArrayList<>();


// Find plugin JARs using caching
List<String> pluginJars = findPluginsJars();
if (pluginJars.isEmpty())
{
if (pluginJars.isEmpty()) {
log.debug("No plugin JARs found.");
return plugins;
}

for (String pluginJar : pluginJars)
{
// Load plugins from JARs
pluginJars.parallelStream().forEach(pluginJar -> {
log.debug("Inspecting plugin jar candidate: {}", pluginJar);
List<LowcoderPlugin> loadedPlugins = loadPluginCandidates(pluginJar);
if (loadedPlugins.isEmpty())
{
if (loadedPlugins.isEmpty()) {
log.debug(" - no plugins found in the jar file");
} else {
synchronized (plugins) {
plugins.addAll(loadedPlugins);
}
}
else
{
for (LowcoderPlugin plugin : loadedPlugins)
{
plugins.add(plugin);
}
}
}

});

return plugins;
}

protected List<String> findPluginsJars()
{

protected List<String> findPluginsJars() {
String cacheKey = common.getPluginDirs().toString();

// Use cached JAR paths if available
if (cachedPluginJars.containsKey(cacheKey)) {
log.debug("Using cached plugin jar candidates for key: {}", cacheKey);
return cachedPluginJars.get(cacheKey);
}

List<String> candidates = new ArrayList<>();
if (CollectionUtils.isNotEmpty(common.getPluginDirs()))
{
for (String pluginDir : common.getPluginDirs())
{
if (CollectionUtils.isNotEmpty(common.getPluginDirs())) {
for (String pluginDir : common.getPluginDirs()) {
final Path pluginPath = getAbsoluteNormalizedPath(pluginDir);
if (pluginPath != null)
{
if (pluginPath != null) {
candidates.addAll(findPluginCandidates(pluginPath));
}
}
}


// Cache the results
cachedPluginJars.put(cacheKey, candidates);
return candidates;
}


protected List<String> findPluginCandidates(Path pluginsDir)
{
List<String> pluginCandidates = new ArrayList<>();
try
{
Files.walk(pluginsDir)
.filter(Files::isRegularFile)
.filter(path -> StringUtils.endsWithIgnoreCase(path.toAbsolutePath().toString(), ".jar"))
.forEach(path -> pluginCandidates.add(path.toString()));
}
catch(IOException cause)
{
protected List<String> findPluginCandidates(Path pluginsDir) {
try {
return Files.walk(pluginsDir)
.filter(Files::isRegularFile)
.filter(path -> StringUtils.endsWithIgnoreCase(path.toAbsolutePath().toString(), ".jar"))
.map(Path::toString)
.toList(); // Use Java 16+ `toList()` for better performance
} catch (IOException cause) {
log.error("Error walking plugin folder! - {}", cause.getMessage());
return Collections.emptyList();
}

return pluginCandidates;
}

protected List<LowcoderPlugin> loadPluginCandidates(String pluginJar)
{

protected List<LowcoderPlugin> loadPluginCandidates(String pluginJar) {
List<LowcoderPlugin> pluginCandidates = new ArrayList<>();

try
{
try {
Path pluginPath = Path.of(pluginJar);
PluginClassLoader pluginClassLoader = new PluginClassLoader(pluginPath.getFileName().toString(), pluginPath);

ServiceLoader<LowcoderPlugin> pluginServices = ServiceLoader.load(LowcoderPlugin.class, pluginClassLoader);
if (pluginServices != null )
{
Iterator<LowcoderPlugin> pluginIterator = pluginServices.iterator();
while(pluginIterator.hasNext())
{
LowcoderPlugin plugin = pluginIterator.next();
if (pluginServices != null) {
for (LowcoderPlugin plugin : pluginServices) {
log.debug(" - loaded plugin: {} - {}", plugin.pluginId(), plugin.description());
pluginCandidates.add(plugin);
}
}
}
catch(Throwable cause)
{
} catch (Throwable cause) {
log.warn("Error loading plugin!", cause);
}

return pluginCandidates;
}

private Path getAbsoluteNormalizedPath(String path)
{
if (StringUtils.isNotBlank(path))
{

private Path getAbsoluteNormalizedPath(String path) {
if (StringUtils.isNotBlank(path)) {
Path absPath = Path.of(path);
if (!absPath.isAbsolute())
{
if (!absPath.isAbsolute()) {
absPath = Path.of(applicationHome.getDir().getAbsolutePath(), absPath.toString());
}
return absPath.normalize().toAbsolutePath();
}

return null;
}
}
}
Loading