pf4j: add new missing dependency error handling

This commit is contained in:
ThatGamerBlue
2021-04-19 08:45:52 +01:00
parent d1b588d819
commit c50f250a13
3 changed files with 165 additions and 7 deletions

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2021, ThatGamerBlue <thatgamerblue@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins;
import com.google.common.collect.Multimap;
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.pf4j.DependencyResolver;
@Value
@EqualsAndHashCode(callSuper = true)
public class MissingDependenciesException extends DependencyResolver.DependenciesNotFoundException
{
Multimap<String, String> reverseDependencyMap;
public MissingDependenciesException(List<String> dependencies, Multimap<String, String> reverseDependencyMap)
{
super(dependencies);
this.reverseDependencyMap = reverseDependencyMap;
}
}

View File

@@ -1,5 +1,7 @@
package net.runelite.client.plugins;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.openosrs.client.OpenOSRS;
import java.io.Closeable;
import java.io.IOException;
@@ -25,6 +27,8 @@ import org.pf4j.JarPluginLoader;
import org.pf4j.JarPluginRepository;
import org.pf4j.ManifestPluginDescriptorFinder;
import org.pf4j.PluginAlreadyLoadedException;
import org.pf4j.PluginDependency;
import org.pf4j.PluginDescriptor;
import org.pf4j.PluginDescriptorFinder;
import org.pf4j.PluginLoader;
import org.pf4j.PluginRepository;
@@ -37,6 +41,8 @@ import org.pf4j.RuntimeMode;
@Slf4j
class OPRSExternalPf4jPluginManager extends DefaultPluginManager
{
private final Set<String> disabledPlugins = new HashSet<>();
public OPRSExternalPf4jPluginManager()
{
super(OpenOSRS.EXTERNALPLUGIN_DIR.toPath());
@@ -186,15 +192,26 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager
{
// retrieves the plugins descriptors
List<org.pf4j.PluginDescriptor> descriptors = new ArrayList<>();
Multimap<String, String> reverseDepMap = MultimapBuilder.hashKeys().hashSetValues().build();
for (PluginWrapper plugin : plugins.values())
{
descriptors.add(plugin.getDescriptor());
for (PluginDependency dependency : plugin.getDescriptor().getDependencies())
{
reverseDepMap.put(dependency.getPluginId(), plugin.getPluginId());
}
}
// retrieves the plugins descriptors from the resolvedPlugins list. This allows to load plugins that have already loaded dependencies.
for (PluginWrapper plugin : resolvedPlugins)
{
descriptors.add(plugin.getDescriptor());
for (PluginDependency dependency : plugin.getDescriptor().getDependencies())
{
reverseDepMap.put(dependency.getPluginId(), plugin.getPluginId());
}
}
DependencyResolver.Result result = dependencyResolver.resolve(descriptors);
@@ -207,7 +224,7 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager
List<String> notFoundDependencies = result.getNotFoundDependencies();
if (!notFoundDependencies.isEmpty())
{
throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies);
throw new MissingDependenciesException(notFoundDependencies, reverseDepMap);
}
List<DependencyResolver.WrongDependencyVersion> wrongVersionDependencies = result.getWrongVersionDependencies();
@@ -366,6 +383,92 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager
return pluginRepository.deletePluginPath(pluginPath);
}
@Override
protected PluginWrapper loadPluginFromPath(Path pluginPath)
{
// Test for plugin path duplication
String pluginId = idForPath(pluginPath);
if (pluginId != null)
{
throw new PluginAlreadyLoadedException(pluginId, pluginPath);
}
// Retrieve and validate the plugin descriptor
PluginDescriptorFinder pluginDescriptorFinder = getPluginDescriptorFinder();
log.debug("Use '{}' to find plugins descriptors", pluginDescriptorFinder);
log.debug("Finding plugin descriptor for plugin '{}'", pluginPath);
PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginPath);
validatePluginDescriptor(pluginDescriptor);
// OPRS START - don't load plugins that failed dependency resolution
if (disabledPlugins.contains(pluginDescriptor.getPluginId()))
{
log.debug("Skipping loading {}, was previously disabled.", pluginDescriptor.getPluginId());
return null;
}
// OPRS END
// Check there are no loaded plugins with the retrieved id
pluginId = pluginDescriptor.getPluginId();
if (plugins.containsKey(pluginId))
{
PluginWrapper loadedPlugin = getPlugin(pluginId);
throw new PluginRuntimeException("There is an already loaded plugin ({}) "
+ "with the same id ({}) as the plugin at path '{}'. Simultaneous loading "
+ "of plugins with the same PluginId is not currently supported.\n"
+ "As a workaround you may include PluginVersion and PluginProvider "
+ "in PluginId.",
loadedPlugin, pluginId, pluginPath);
}
log.debug("Found descriptor {}", pluginDescriptor);
String pluginClassName = pluginDescriptor.getPluginClass();
log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath);
// load plugin
log.debug("Loading plugin '{}'", pluginPath);
ClassLoader pluginClassLoader = getPluginLoader().loadPlugin(pluginPath, pluginDescriptor);
log.debug("Loaded plugin '{}' with class loader '{}'", pluginPath, pluginClassLoader);
// create the plugin wrapper
log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
pluginWrapper.setPluginFactory(getPluginFactory());
// test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId()))
{
log.info("Plugin '{}' is disabled", pluginPath);
pluginWrapper.setPluginState(PluginState.DISABLED);
}
// validate the plugin
if (!isPluginValid(pluginWrapper))
{
log.warn("Plugin '{}' is invalid and it will be disabled", pluginPath);
pluginWrapper.setPluginState(PluginState.DISABLED);
}
log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
pluginId = pluginDescriptor.getPluginId();
// add plugin to the list with plugins
plugins.put(pluginId, pluginWrapper);
getUnresolvedPlugins().add(pluginWrapper);
// add plugin class loader to the list with class loaders
getPluginClassLoaders().put(pluginId, pluginClassLoader);
return pluginWrapper;
}
void disableLoading(String pluginId)
{
unloadPlugin(pluginId);
disabledPlugins.add(pluginId);
}
private boolean isPluginEligibleForLoading(Path path)
{
return path.toFile().getName().endsWith(".jar");

View File

@@ -25,6 +25,7 @@
package net.runelite.client.plugins;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.Graphs;
import com.google.common.graph.MutableGraph;
@@ -205,15 +206,25 @@ public class OPRSExternalPluginManager
}
catch (Exception ex)
{
if (ex instanceof DependencyResolver.DependenciesNotFoundException)
if (ex instanceof MissingDependenciesException)
{
List<String> deps = ((DependencyResolver.DependenciesNotFoundException) ex).getDependencies();
List<String> deps = ((MissingDependenciesException) ex).getDependencies();
Multimap<String, String> reverseDepMap = ((MissingDependenciesException) ex).getReverseDependencyMap();
log.error("The following dependencies are missing: {}", deps);
for (String dep : deps)
for (String dependency : deps)
{
updateManager.installPlugin(dep, null);
Collection<String> dependentPlugins = reverseDepMap.get(dependency);
log.error("Dependency {} is missing, but is required by {}, attempting install.", dependency, dependentPlugins);
try
{
updateManager.installPlugin(dependency, null);
}
catch (PluginRuntimeException ex2)
{
log.error("Dependency {} is missing and couldn't be installed. Disabling loading of {} as they depend on it.", dependency, dependentPlugins);
dependentPlugins.forEach(s -> ((OPRSExternalPf4jPluginManager) externalPluginManager).disableLoading(s));
}
}
startExternalPluginManager();