Skip to content

Commit

Permalink
OF-2798: Plugin Admin console should list load problems
Browse files Browse the repository at this point in the history
When a plugin fails to load correctly, the admin console page that shows installed plugins should clearly indicate this.

Notably, this change will show a warning when a plugin was installed, but failed to execute its database installation or update script.
  • Loading branch information
guusdk committed Feb 17, 2024
1 parent d83b5ec commit 552254e
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 25 deletions.
5 changes: 5 additions & 0 deletions i18n/src/main/resources/openfire_i18n.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3256,6 +3256,11 @@ plugin.admin.update = Update
plugin.admin.updating = Updating
plugin.admin.failed.minserverversion=The version of the plugin that is installed requires Openfire {0} or later versions!
plugin.admin.failed.priortoserverversion=The version of the plugin that is installed is no longer compatible with Openfire {0} and later versions!
plugin.admin.failed.invalidJar=The JAR file that was provided is not an Openfire plugin.
plugin.admin.failed.minJavaVersion=The plugin requires Java specification version {0}. Openfire is currently running in Java {0}.
plugin.admin.failed.missingParent=The plugin requires another plugin, named {0}, that currently is not installed.
plugin.admin.failed.databaseScript=A plugin database install or update script failed. Review the logs for additional details.
plugin.admin.failed.unknown=An exception occurred while loading plugin. Review the logs for additional details.

# System Admin Console access
system.admin.console.access.title=Admin Console Access
Expand Down
19 changes: 12 additions & 7 deletions i18n/src/main/resources/openfire_i18n_nl.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2994,17 +2994,22 @@ plugin.admin.description=Omschrijving
plugin.admin.version=Versie
plugin.admin.author=Auteur
plugin.admin.restart=Opnieuw starten
plugin.admin.no_plugin=Er zijn geen plug-ins ge�nstalleerd.
plugin.admin.no_plugin=Er zijn geen plug-ins geïnstalleerd.
plugin.admin.confirm=Plug-in verwijderen?
plugin.admin.download=Download
plugin.admin.update-desc=New update is now available.
plugin.admin.update-desc=Nieuwe versie beschikbaar.
plugin.admin.update.complete = Update Completed
plugin.admin.version.available = Version {0} Available
plugin.admin.changelog = Change Log
plugin.admin.version.available = Versie {0} Beschikbaar
plugin.admin.changelog = Veranderingslogboek
plugin.admin.update = Update
plugin.admin.updating = Updating
plugin.admin.failed.minserverversion=De versie van de geinstalleerde plugin vereist Openfire versie {0} of later!
plugin.admin.failed.priortoserverversion=De versie van de plugin die is geinstalleerd is niet meer compatibel met Openfire {0} en nieuwere versies!
plugin.admin.updating = Aan het updaten
plugin.admin.failed.minserverversion=De versie van de geïnstalleerde plug-in vereist Openfire versie {0} of later!
plugin.admin.failed.priortoserverversion=De versie van de plug-in die is geïnstalleerd is niet meer compatibel met Openfire {0} en nieuwere versies!
plugin.admin.failed.invalidJar=Het geïnstalleerde JAR bestand is geen Openfire plug-in.
plugin.admin.failed.minJavaVersion=De plug-in vereist Java versie {0}. Openfire draait momenteel in Java {0}.
plugin.admin.failed.missingParent=De plug-in vereist een andere plugin, genaamd {0}, die op dit moment niet geïnstalleerd is.
plugin.admin.failed.databaseScript=Een plug-in database-installatie of -update script heeft gefaald. Raadpleeg de logs van de server voor details.
plugin.admin.failed.unknown=Een fout is opgetreden tijdens het laden van de plug-in. Raadpleeg de logs van de server voor details.

# System Admin Console access
system.admin.console.access.title=Beheerconsole Toegang
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2008 Jive Software, 2017-2023 Ignite Realtime Foundation. All rights reserved.
* Copyright (C) 2004-2008 Jive Software, 2017-2024 Ignite Realtime Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -111,6 +111,9 @@ public class PluginManager
@GuardedBy("this")
private final Map<String, Integer> failureToLoadCount = new HashMap<>();

@GuardedBy("this")
private final Map<String, String> lastLoadWarnings = new HashMap<>();

private final PluginMonitor pluginMonitor;
private boolean executed = false;

Expand Down Expand Up @@ -162,6 +165,7 @@ public synchronized void shutdown()
classloaders.clear();
childPluginMap.clear();
failureToLoadCount.clear();
lastLoadWarnings.clear();
}

/**
Expand Down Expand Up @@ -458,6 +462,9 @@ public boolean isExecuted()
*/
synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
{
// Clean up any warnings that were recorded during a previous attempt to load the plugin.
lastLoadWarnings.remove(canonicalName);

final PluginMetadata metadata = PluginMetadata.getInstance( pluginDir );
pluginMetadata.put( canonicalName, metadata );

Expand All @@ -467,9 +474,10 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
return false;
}

if ( failureToLoadCount.containsKey( canonicalName ) && failureToLoadCount.get( canonicalName ) > JiveGlobals.getIntProperty( "plugins.loading.retries", 5 ) )
final Integer loadFailures = failureToLoadCount.get(canonicalName);
if (loadFailures != null && loadFailures > JiveGlobals.getIntProperty("plugins.loading.retries", 5))
{
Log.debug( "The unloaded file for plugin '{}' is silently ignored, as it has failed to load repeatedly.", canonicalName );
Log.debug("The unloaded file for plugin '{}' is silently ignored, as it has failed to load repeatedly.", canonicalName);
return false;
}

Expand All @@ -481,6 +489,7 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
{
Log.warn( "Plugin '{}' could not be loaded: no plugin.xml file found.", canonicalName );
failureToLoadCount.put( canonicalName, Integer.MAX_VALUE ); // Don't retry - this cannot be recovered from.
lastLoadWarnings.put(canonicalName, LocaleUtils.getLocalizedString("plugin.admin.failed.invalidJar"));
return false;
}

Expand All @@ -493,6 +502,7 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
if (metadata.getMinServerVersion().isNewerThan(currentServerVersion.ignoringReleaseStatus())) {
Log.warn( "Ignoring plugin '{}': requires server version {}. Current server version is {}.", canonicalName, metadata.getMinServerVersion(), currentServerVersion );
failureToLoadCount.put( canonicalName, Integer.MAX_VALUE ); // Don't retry - this cannot be recovered from.
lastLoadWarnings.put(canonicalName, LocaleUtils.getLocalizedString("plugin.admin.failed.minserverversion", List.of(metadata.getMinServerVersion().toString())));
return false;
}
}
Expand All @@ -506,6 +516,7 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
{
Log.warn( "Ignoring plugin '{}': compatible with server versions up to but excluding {}. Current server version is {}.", canonicalName, metadata.getPriorToServerVersion(), currentServerVersion );
failureToLoadCount.put( canonicalName, Integer.MAX_VALUE ); // Don't retry - this cannot be recovered from.
lastLoadWarnings.put(canonicalName, LocaleUtils.getLocalizedString("plugin.admin.failed.priortoserverversion", List.of(metadata.getPriorToServerVersion().toString())));
return false;
}
}
Expand All @@ -518,6 +529,7 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
{
Log.warn( "Ignoring plugin '{}': requires Java specification version {}. Openfire is currently running in Java {}.", canonicalName, metadata.getMinJavaVersion(), System.getProperty( "java.specification.version" ) );
failureToLoadCount.put( canonicalName, Integer.MAX_VALUE ); // Don't retry - this cannot be recovered from.
lastLoadWarnings.put(canonicalName, LocaleUtils.getLocalizedString("plugin.admin.failed.minJavaVersion", List.of(metadata.getMinJavaVersion().toString(), runtimeVersion.getVersionString())));
return false;
}
}
Expand Down Expand Up @@ -553,6 +565,7 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
count = 0;
}
failureToLoadCount.put( canonicalName, ++count );
lastLoadWarnings.put(canonicalName, LocaleUtils.getLocalizedString("plugin.admin.failed.missingParent", List.of(parentCanonicalName)));
return false;
}
pluginLoader = classloaders.get( parentPlugin );
Expand Down Expand Up @@ -612,6 +625,8 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
{
// The schema was not there and auto-upgrade failed.
Log.error( "Error while loading plugin '{}': {}", canonicalName, LocaleUtils.getLocalizedString( "upgrade.database.failure" ) );
lastLoadWarnings.put(canonicalName, LocaleUtils.getLocalizedString("plugin.admin.failed.databaseScript"));
// Does not prevent the plugin from being loaded, as many database script errors are benign.
}

// Load any JSP's defined by the plugin.
Expand Down Expand Up @@ -696,6 +711,8 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
} else {
Log.info( "Successfully loaded plugin '{}'.", canonicalName);
}

failureToLoadCount.remove(canonicalName);
return true;
}
catch ( Throwable e )
Expand All @@ -706,6 +723,7 @@ synchronized boolean loadPlugin( String canonicalName, Path pluginDir )
count = 0;
}
failureToLoadCount.put( canonicalName, ++count );
lastLoadWarnings.put(canonicalName, LocaleUtils.getLocalizedString("plugin.admin.failed.unknown"));
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
Expand Down Expand Up @@ -823,6 +841,7 @@ synchronized void unloadPlugin( String canonicalName )
Log.debug( "Unloading plugin '{}'...", canonicalName );

failureToLoadCount.remove( canonicalName );
lastLoadWarnings.remove(canonicalName);

Plugin plugin = pluginsLoaded.get( canonicalName );
if ( plugin != null )
Expand Down Expand Up @@ -962,6 +981,18 @@ else if ( plugin != null )
}
}

/**
* Returns a human-readable, localized message related to a failure while trying to load a plugin. When the last
* time that this plugin was loaded was successful (or when a plugin of this name was never attempted to be loaded
* at all), this method returns null.
*
* @param canonicalPluginName The canonical name of a plugin
* @return An optional human-readable, localized failure message.
*/
public String getLoadWarning(final String canonicalPluginName) {
return lastLoadWarnings.get(canonicalPluginName);
}

/**
* Loads a class from the classloader of a plugin.
*
Expand Down
34 changes: 19 additions & 15 deletions xmppserver/src/main/webapp/plugin-admin.jsp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<%@ page contentType="text/html; charset=UTF-8" %>
<%--
- Copyright (C) 2005-2008 Jive Software, 2017-2023 Ignite Realtime Foundation. All rights reserved.
- Copyright (C) 2005-2008 Jive Software, 2017-2024 Ignite Realtime Foundation. All rights reserved.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -252,6 +252,7 @@
tr.regular td,
tr.unsupported td,
tr.warning td,
tr.update td {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
Expand All @@ -277,6 +278,12 @@ tr.unsupported td {
border-color: #CB2B18;
}
tr.warning td {
font-size: 8pt;
background: #fbe5cb;
border-color: #cb7718;
}
tr.singleline > td {
border-top-width: 0;
border-right-width: 0;
Expand Down Expand Up @@ -392,15 +399,19 @@ tr.lowerhalf > td:last-child {
<c:set var="canonicalName" value="${entry.key}"/>
<c:set var="plugin" value="${entry.value}"/>
<c:if test="${canonicalName != 'admin'}">
<c:set var="minServerVersionFail" value="${not empty plugin.minServerVersion and plugin.minServerVersion.isNewerThan(serverVersion.ignoringReleaseStatus())}"/>
<c:set var="priorToServerVersionFail" value="${not empty plugin.priorToServerVersion and not plugin.priorToServerVersion.isNewerThan( serverVersion )}"/>
<c:set var="unsupported" value="${ minServerVersionFail or priorToServerVersionFail }"/>
<c:set var="loadWarning" value="${pluginManager.getLoadWarning(canonicalName)}"/>
<c:set var="unsupported" value="${not empty loadWarning and !pluginManager.isLoaded(canonicalName)}"/>
<c:set var="loadedWithWarning" value="${not empty loadWarning and pluginManager.isLoaded(canonicalName)}"/>
<c:set var="update" value="${updateManager.getPluginUpdate( plugin.name, plugin.version) }"/>
<c:choose>
<c:when test="${unsupported}">
<c:set var="colorClass" value="unsupported"/>
<c:set var="shapeClass" value="upperhalf"/>
</c:when>
<c:when test="${loadedWithWarning}">
<c:set var="colorClass" value="warning"/>
<c:set var="shapeClass" value="upperhalf"/>
</c:when>
<c:when test="${not empty update}">
<c:set var="colorClass" value="update"/>
<c:set var="shapeClass" value="upperhalf"/>
Expand Down Expand Up @@ -464,23 +475,16 @@ tr.lowerhalf > td:last-child {
</td>
</tr>

<c:if test="${unsupported}">
<c:if test="${unsupported or loadedWithWarning}">
<!-- When the plugin is unsupported, but *also* has an update, make sure that there's no bottom border -->
<c:set var="overrideStyle" value="${ not empty update ? 'border-bottom-width: 0' : ''}"/>

<tr class="${colorClass} lowerhalf">
<td style="${overrideStyle}">&nbsp;</td>
<td style="${overrideStyle}" colspan="6" nowrap>
<span class="small-label">
<c:if test="${minServerVersionFail}">
<fmt:message key="plugin.admin.failed.minserverversion">
<fmt:param value="${plugin.minServerVersion}"/>
</fmt:message>
</c:if>
<c:if test="${priorToServerVersionFail}">
<fmt:message key="plugin.admin.failed.priortoserverversion">
<fmt:param value="${plugin.priorToServerVersion}"/>
</fmt:message>
<c:if test="${not empty loadWarning}">
<c:out value="${loadWarning}"/>
</c:if>
</span>
</td>
Expand All @@ -489,7 +493,7 @@ tr.lowerhalf > td:last-child {

<tr><td></td></tr>

<!-- End of update section -->
<!-- End of unsupported section -->
</c:if>

<c:if test="${not empty update}">
Expand Down

0 comments on commit 552254e

Please sign in to comment.