Commit fee25623 authored by Guus der Kinderen's avatar Guus der Kinderen

OF-1353: Adding maxServerVersion (and more)

This commit started with the intention of adding a maxServerVersion attribute for plugins, but resulted in a partial rewrite. Changes:
- Removed the License enumeration - now, a simple string is used.
- Split off the admin console page that shows the content of readme and changelog files to a dedicated page, instead of plugin-admin.jsp
- Better define the difference between the plugin (human readable) name ("Avatar Resizer") and its canonical name ("avatarresizer")
- The admin console now shows plugins that have been downloaded/installed, but cannot be loaded. Where possible, an update is suggested.
- Support for maxServerVersion - plugins can now define a version of Openfire in which they will no longer load.
parent f7010088
......@@ -2644,6 +2644,8 @@ plugin.admin.version.available = Version {0} Available
plugin.admin.changelog = Change Log
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.maxserverversion=The version of the plugin that is installed only works with Openfire {0} or earlier versions!
# System Email
......@@ -2918,6 +2920,8 @@ plugin.available.installation.success = plugin installed successfully.
plugin.available.commercial_plugins = Commercial Plugins
plugin.available.outdated = The list of plugins below requires a newer version of the server.
plugin.available.outdated.update = Update the server now.
plugin.available.deprecated=These plugins that are installed are compatible with older versions of the server only.
plugin.available.autoupdate = Available plugins list auto-updated on
plugin.available.autoupdate.on = Auto update is turned on.
plugin.available.autoupdate.off = Auto update is off.
......
/*
* Copyright 2016 IgniteRealtime.org
*
* 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.jivesoftware.openfire.container;
/**
* An enumeration for license agreement types.
*/
public enum License
{
/**
* Distributed using a commercial license.
*/
commercial,
/**
* Distributed using the GNU Public License (GPL).
*/
gpl,
/**
* Distributed using the Apache license.
*/
apache,
/**
* For internal use at an organization only and is not re-distributed.
*/
internal,
/**
* Distributed under another license agreement not covered by one of the other choices. The license agreement
* should be detailed in a Readme or License file that accompanies the code.
*/
other
}
......@@ -40,12 +40,22 @@ import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Loads and manages plugins. The <tt>plugins</tt> directory is monitored for any
* new plugins, and they are dynamically loaded.
* Manages plugins.
*
* <p>An instance of this class can be obtained using:</p>
* The <tt>plugins</tt> directory is monitored for any new plugins, and they are dynamically loaded.
*
* <tt>XMPPServer.getInstance().getPluginManager()</tt>
* An instance of this class can be obtained using: <tt>XMPPServer.getInstance().getPluginManager()</tt>
*
* These states are defined for plugin management:
* <ul>
* <li><em>installed</em> - the plugin archive file is present in the <tt>plugins</tt> directory.</li>
* <li><em>extracted</em> - the plugin archive file has been extracted.</li>
* <li><em>loaded</em> - the plugin has (successfully) been initialized.</li>
* </ul>
*
* Note that an <em>installed</em> plugin is not per definition an <em>extracted</em> plugin, and an extracted
* plugin is not per definition a <em>loaded</em> plugin. A plugin that's extracted might, for instance, fail to
* load, due to restrictions imposed by its <tt>minServerVersion</tt> definition.
*
* @author Matt Tucker
* @see Plugin
......@@ -56,9 +66,31 @@ public class PluginManager
private static final Logger Log = LoggerFactory.getLogger( PluginManager.class );
private final Path pluginDirectory;
private final Map<String, Plugin> plugins = new TreeMap<>( String.CASE_INSENSITIVE_ORDER );
/**
* Plugins that are loaded, mapped by their canonical name.
*/
private final Map<String, Plugin> pluginsLoaded = new TreeMap<>( String.CASE_INSENSITIVE_ORDER );
/**
* The plugin classloader for each loaded plugin.
*/
private final Map<Plugin, PluginClassLoader> classloaders = new HashMap<>();
private final Map<Plugin, Path> pluginDirs = new HashMap<>();
/**
* The directory in which a plugin is extracted, mapped by canonical name. This collection contains loaded plugins,
* as well as extracted (but not loaded) plugins.
*
* Note that typically these directories are subdirectories of <tt>plugins</tt>, but a 'dev-plugin' could live
* elsewhere.
*/
private final Map<String, Path> pluginDirs = new HashMap<>();
/**
* Plugin metadata for all extracted plugins, mapped by canonical name.
*/
private final Map<String, PluginMetadata> pluginMetadata = new TreeMap<>( String.CASE_INSENSITIVE_ORDER );
private final Map<Plugin, PluginDevEnvironment> pluginDevelopment = new HashMap<>();
private final Map<Plugin, List<String>> parentPluginMap = new HashMap<>();
private final Map<Plugin, String> childPluginMap = new HashMap<>();
......@@ -93,13 +125,13 @@ public class PluginManager
*/
public synchronized void shutdown()
{
Log.info( "Shutting down. Unloading all installed plugins..." );
Log.info( "Shutting down. Unloading all loaded plugins..." );
// Stop the plugin monitoring service.
pluginMonitor.stop();
// Shutdown all installed plugins.
for ( Map.Entry<String, Plugin> plugin : plugins.entrySet() )
// Shutdown all loaded plugins.
for ( Map.Entry<String, Plugin> plugin : pluginsLoaded.entrySet() )
{
try
{
......@@ -111,8 +143,9 @@ public class PluginManager
Log.error( "An exception occurred while trying to unload plugin '{}':", plugin.getKey(), e );
}
}
plugins.clear();
pluginsLoaded.clear();
pluginDirs.clear();
pluginMetadata.clear();
classloaders.clear();
pluginDevelopment.clear();
childPluginMap.clear();
......@@ -176,37 +209,131 @@ public class PluginManager
}
/**
* Returns true if the specified filename, that belongs to a plugin, exists.
* Returns true if the plugin by the specified name is installed. Specifically, this checks if the plugin
* archive file is present in the <tt>plugins</tt> directory.
*
* @param pluginFilename the filename of the plugin to create or update.
* @return true if the specified filename, that belongs to a plugin, exists.
* Note that an <em>installed</em> plugin is not per definition an <em>extracted</em> plugin, and an extracted
* plugin is not per definition a <em>loaded</em> plugin. A plugin that's extracted might, for instance, fail to
* load, due to restrictions imposed by its <tt>minServerVersion</tt> definition.
*
* @param canonicalName the canonical filename of the plugin (cannot be null).
* @return true if the plugin is installed, otherwise false.
*/
public boolean isPluginDownloaded( String pluginFilename )
public boolean isInstalled( final String canonicalName )
{
return Files.exists( pluginDirectory.resolve( pluginFilename ) );
final DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>()
{
@Override
public boolean accept( Path entry ) throws IOException
{
final String name = entry.getFileName().toString();
return Files.exists( entry ) && !Files.isDirectory( entry ) &&
( name.equalsIgnoreCase( canonicalName + ".jar" ) || name.equalsIgnoreCase( canonicalName + ".war" ) );
}
};
try ( final DirectoryStream<Path> paths = Files.newDirectoryStream( pluginDirectory, filter ) )
{
return paths.iterator().hasNext();
}
catch ( IOException e )
{
Log.error( "Unable to determine if plugin '{}' is installed.", canonicalName, e );
// return the next best guess
return pluginsLoaded.containsKey( canonicalName );
}
}
/**
* Returns true if the plugin by the specified name is extracted. Specifically, this checks if the <tt>plugins</tt>
* directory contains a subdirectory that matches the canonical name of the plugin.
*
* Note that an <em>installed</em> plugin is not per definition an <em>extracted</em> plugin, and an extracted
* plugin is not per definition a <em>loaded</em> plugin. A plugin that's extracted might, for instance, fail to
* load, due to restrictions imposed by its <tt>minServerVersion</tt> definition.
*
* @param canonicalName the canonical filename of the plugin (cannot be null).
* @return true if the plugin is extracted, otherwise false.
*/
public boolean isExtracted( final String canonicalName )
{
return pluginMetadata.containsKey( canonicalName );
}
/**
* Returns a Collection of all installed plugins.
* Returns true if the plugin by the specified name is loaded. Specifically, this checks if an instance was created
* for the plugin class file.
*
* @return a Collection of all installed plugins.
* Note that an <em>installed</em> plugin is not per definition an <em>extracted</em> plugin, and an extracted
* plugin is not per definition a <em>loaded</em> plugin. A plugin that's extracted might, for instance, fail to
* load, due to restrictions imposed by its <tt>minServerVersion</tt> definition.
*
* @param canonicalName the canonical filename of the plugin (cannot be null).
* @return true if the plugin is extracted, otherwise false.
*/
public boolean isLoaded( final String canonicalName )
{
return pluginsLoaded.containsKey( canonicalName );
}
/**
* Returns metadata for all extracted plugins, mapped by their canonical name.
*
* The collection is alphabeticially sorted, by plugin name.
*
* Note that an <em>installed</em> plugin is not per definition an <em>extracted</em> plugin, and an extracted
* plugin is not per definition a <em>loaded</em> plugin. A plugin that's extracted might, for instance, fail to
* load, due to restrictions imposed by its <tt>minServerVersion</tt> definition.
*
* @return A collection of metadata (possibly empty, never null).
*/
public Map<String, PluginMetadata> getMetadataExtractedPlugins()
{
return Collections.unmodifiableMap( this.pluginMetadata );
}
/**
* Returns a Collection of all loaded plugins.
*
* The returned collection will not include plugins that have been downloaded, but not loaded.
*
* @return a Collection of all loaded plugins.
*/
public Collection<Plugin> getPlugins()
{
return Collections.unmodifiableCollection( Arrays.asList( plugins.values().toArray( new Plugin[ plugins.size() ] ) ) );
return Collections.unmodifiableCollection( Arrays.asList( pluginsLoaded.values().toArray( new Plugin[ pluginsLoaded.size() ] ) ) );
}
/**
* Returns the canonical name for a loaded plugin.
*
* @param plugin A plugin (cannot be null).
* @return The canonical name for the plugin (never null).
*/
public String getCanonicalName( Plugin plugin )
{
// TODO consider using a bimap for a more efficient lookup.
for ( Map.Entry<String, Plugin> entry : pluginsLoaded.entrySet() )
{
if ( entry.getValue().equals( plugin ) )
{
return entry.getKey();
}
}
return null;
}
/**
* Returns a plugin by name or <tt>null</tt> if a plugin with that name does not
* exist. The name is the name of the directory that the plugin is in such as
* "broadcast".
* Returns an loaded plugin by its canonical name or <tt>null</tt> if a plugin with that name does not exist. The
* canonical name is the lowercase-name of the plugin archive, without the file extension. For example: "broadcast".
*
* @param name the name of the plugin.
* @param canonicalName the name of the plugin.
* @return the plugin.
*/
public Plugin getPlugin( String name )
public Plugin getPlugin( String canonicalName )
{
return plugins.get( name );
return pluginsLoaded.get( canonicalName.toLowerCase() );
}
/**
......@@ -226,7 +353,12 @@ public class PluginManager
*/
public Path getPluginPath( Plugin plugin )
{
return pluginDirs.get( plugin );
final String canonicalName = getCanonicalName( plugin );
if ( canonicalName != null )
{
return pluginDirs.get( canonicalName );
}
return null;
}
/**
......@@ -246,56 +378,65 @@ public class PluginManager
*
* @param pluginDir the plugin directory.
*/
boolean loadPlugin( Path pluginDir )
boolean loadPlugin( String canonicalName, Path pluginDir )
{
final PluginMetadata metadata = PluginMetadata.getInstance( pluginDir );
pluginMetadata.put( canonicalName, metadata );
// Only load the admin plugin during setup mode.
final String pluginName = pluginDir.getFileName().toString();
if ( XMPPServer.getInstance().isSetupMode() && !( pluginName.equals( "admin" ) ) )
if ( XMPPServer.getInstance().isSetupMode() && !( canonicalName.equals( "admin" ) ) )
{
return false;
}
if ( failureToLoadCount.containsKey( pluginName ) && failureToLoadCount.get( pluginName ) > JiveGlobals.getIntProperty( "plugins.loading.retries", 5 ) )
if ( failureToLoadCount.containsKey( canonicalName ) && failureToLoadCount.get( canonicalName ) > JiveGlobals.getIntProperty( "plugins.loading.retries", 5 ) )
{
Log.debug( "The unloaded file for plugin '{}' is silently ignored, as it has failed to load repeatedly.", pluginName );
Log.debug( "The unloaded file for plugin '{}' is silently ignored, as it has failed to load repeatedly.", canonicalName );
return false;
}
Log.debug( "Loading plugin '{}'...", pluginName );
Log.debug( "Loading plugin '{}'...", canonicalName );
try
{
final Path pluginConfig = pluginDir.resolve( "plugin.xml" );
if ( !Files.exists( pluginConfig ) )
{
Log.warn( "Plugin '{}' could not be loaded: no plugin.xml file found.", pluginName );
failureToLoadCount.put( pluginName, Integer.MAX_VALUE ); // Don't retry - this cannot be recovered from.
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.
return false;
}
final SAXReader saxReader = new SAXReader();
saxReader.setEncoding( "UTF-8" );
final Document pluginXML = saxReader.read( pluginConfig.toFile() );
final Version currentServerVersion = XMPPServer.getInstance().getServerInfo().getVersion();
// See if the plugin specifies a version of Openfire required to run.
final Element minServerVersion = (Element) pluginXML.selectSingleNode( "/plugin/minServerVersion" );
if ( minServerVersion != null )
// See if the plugin specifies a minimum version of Openfire required to run.
if ( metadata.getMinServerVersion() != null )
{
final Version requiredVersion = new Version( minServerVersion.getTextTrim() );
final Version currentVersion = XMPPServer.getInstance().getServerInfo().getVersion();
// OF-1338: Ignore release status when comparing minimum server version requirement.
final Version compareVersion = new Version( currentVersion.getMajor(), currentVersion.getMinor(), currentVersion.getMicro(), null, -1 );
if ( requiredVersion.isNewerThan( compareVersion ) )
final Version compareVersion = new Version( currentServerVersion.getMajor(), currentServerVersion.getMinor(), currentServerVersion.getMicro(), null, -1 );
if ( metadata.getMinServerVersion().isNewerThan( compareVersion ) )
{
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.
return false;
}
}
// See if the plugin specifies a maximum version of Openfire required to run.
if ( metadata.getMaxServerVersion() != null )
{
// OF-1338: Ignore release status when comparing maximum server version requirement.
final Version compareVersion = new Version( currentServerVersion.getMajor(), currentServerVersion.getMinor(), currentServerVersion.getMicro(), null, -1 );
if ( compareVersion.isNewerThan( metadata.getMaxServerVersion() ) )
{
Log.warn( "Ignoring plugin '{}': requires server version {}. Current server version is {}.", pluginName, requiredVersion, currentVersion );
failureToLoadCount.put( pluginName, Integer.MAX_VALUE ); // Don't retry - this cannot be recovered from.
Log.warn( "Ignoring plugin '{}': compatible with server versions up to and including {}. Current server version is {}.", canonicalName, metadata.getMaxServerVersion(), currentServerVersion );
failureToLoadCount.put( canonicalName, Integer.MAX_VALUE ); // Don't retry - this cannot be recovered from.
return false;
}
}
// Properties to be used to load external resources. When set, plugin is considered to run in DEV mode.
final String devModeClassesDir = System.getProperty( pluginName + ".classes" );
final String devModewebRoot = System.getProperty( pluginName + ".webRoot" );
final String devModeClassesDir = System.getProperty( canonicalName + ".classes" );
final String devModewebRoot = System.getProperty( canonicalName + ".webRoot" );
final boolean devMode = devModewebRoot != null || devModeClassesDir != null;
final PluginDevEnvironment dev = ( devMode ? configurePluginDevEnvironment( pluginDir, devModeClassesDir, devModewebRoot ) : null );
......@@ -306,13 +447,14 @@ public class PluginManager
// loader so that the plugins can interact.
String parentPluginName = null;
Plugin parentPlugin = null;
final Element parentPluginNode = (Element) pluginXML.selectSingleNode( "/plugin/parentPlugin" );
if ( parentPluginNode != null )
final String parentCanonicalName = PluginMetadataHelper.getParentPlugin( pluginDir );
if ( parentCanonicalName != null )
{
// The name of the parent plugin as specified in plugin.xml might have incorrect casing. Lookup the correct name.
for ( final Map.Entry<String, Plugin> entry : plugins.entrySet() )
for ( final Map.Entry<String, Plugin> entry : pluginsLoaded.entrySet() )
{
if ( entry.getKey().equalsIgnoreCase( parentPluginNode.getTextTrim() ) )
if ( entry.getKey().equalsIgnoreCase( parentCanonicalName ) )
{
parentPluginName = entry.getKey();
parentPlugin = entry.getValue();
......@@ -323,12 +465,12 @@ public class PluginManager
// See if the parent is loaded.
if ( parentPlugin == null )
{
Log.info( "Unable to load plugin '{}': parent plugin '{}' has not been loaded.", pluginName, parentPluginNode.getTextTrim() );
Integer count = failureToLoadCount.get( pluginName );
Log.info( "Unable to load plugin '{}': parent plugin '{}' has not been loaded.", canonicalName, parentCanonicalName );
Integer count = failureToLoadCount.get( canonicalName );
if ( count == null ) {
count = 0;
}
failureToLoadCount.put( pluginName, ++count );
failureToLoadCount.put( canonicalName, ++count );
return false;
}
pluginLoader = classloaders.get( parentPlugin );
......@@ -349,13 +491,17 @@ public class PluginManager
}
// Instantiate the plugin!
final SAXReader saxReader = new SAXReader();
saxReader.setEncoding( "UTF-8" );
final Document pluginXML = saxReader.read( pluginConfig.toFile() );
final String className = pluginXML.selectSingleNode( "/plugin/class" ).getText().trim();
final Plugin plugin = (Plugin) pluginLoader.loadClass( className ).newInstance();
// Bookkeeping!
classloaders.put( plugin, pluginLoader );
plugins.put( pluginName, plugin );
pluginDirs.put( plugin, pluginDir );
pluginsLoaded.put( canonicalName, plugin );
pluginDirs.put( canonicalName, pluginDir );
if ( dev != null )
{
pluginDevelopment.put( plugin, dev );
......@@ -370,7 +516,7 @@ public class PluginManager
childrenPlugins = new ArrayList<>();
parentPluginMap.put( parentPlugin, childrenPlugins );
}
childrenPlugins.add( pluginName );
childrenPlugins.add( canonicalName );
// Also register child to parent relationship.
childPluginMap.put( plugin, parentPluginName );
......@@ -380,7 +526,7 @@ public class PluginManager
if ( !DbConnectionManager.getSchemaManager().checkPluginSchema( plugin ) )
{
// The schema was not there and auto-upgrade failed.
Log.error( "Error while loading plugin '{}': {}", pluginName, LocaleUtils.getLocalizedString( "upgrade.database.failure" ) );
Log.error( "Error while loading plugin '{}': {}", canonicalName, LocaleUtils.getLocalizedString( "upgrade.database.failure" ) );
}
// Load any JSP's defined by the plugin.
......@@ -398,13 +544,13 @@ public class PluginManager
}
// Configure caches of the plugin
configureCaches( pluginDir, pluginName );
configureCaches( pluginDir, canonicalName );
// Initialze the plugin.
final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader( pluginLoader );
plugin.initializePlugin( this, pluginDir.toFile() );
Log.debug( "Initialized plugin '{}'.", pluginName );
Log.debug( "Initialized plugin '{}'.", canonicalName );
Thread.currentThread().setContextClassLoader( oldLoader );
// If there a <adminconsole> section defined, register it.
......@@ -415,21 +561,21 @@ public class PluginManager
if ( appName != null )
{
// Set the plugin name so that the proper i18n String can be loaded.
appName.addAttribute( "plugin", pluginName );
appName.addAttribute( "plugin", canonicalName );
}
// If global images are specified, override their URL.
Element imageEl = (Element) adminElement.selectSingleNode( "/plugin/adminconsole/global/logo-image" );
if ( imageEl != null )
{
imageEl.setText( "plugins/" + pluginName + "/" + imageEl.getText() );
imageEl.addAttribute( "plugin", pluginName ); // Set the plugin name so that the proper i18n String can be loaded.
imageEl.setText( "plugins/" + canonicalName + "/" + imageEl.getText() );
imageEl.addAttribute( "plugin", canonicalName ); // Set the plugin name so that the proper i18n String can be loaded.
}
imageEl = (Element) adminElement.selectSingleNode( "/plugin/adminconsole/global/login-image" );
if ( imageEl != null )
{
imageEl.setText( "plugins/" + pluginName + "/" + imageEl.getText() );
imageEl.addAttribute( "plugin", pluginName ); // Set the plugin name so that the proper i18n String can be loaded.
imageEl.setText( "plugins/" + canonicalName + "/" + imageEl.getText() );
imageEl.addAttribute( "plugin", canonicalName ); // Set the plugin name so that the proper i18n String can be loaded.
}
// Modify all the URL's in the XML so that they are passed through the plugin servlet correctly.
......@@ -437,7 +583,7 @@ public class PluginManager
for ( final Object url : urls )
{
final Attribute attr = (Attribute) url;
attr.setValue( "plugins/" + pluginName + "/" + attr.getValue() );
attr.setValue( "plugins/" + canonicalName + "/" + attr.getValue() );
}
// In order to internationalize the names and descriptions in the model, we add a "plugin" attribute to
......@@ -452,25 +598,25 @@ public class PluginManager
// Make sure there's a name or description. Otherwise, no need to i18n settings.
if ( element.attribute( "name" ) != null || element.attribute( "value" ) != null )
{
element.addAttribute( "plugin", pluginName );
element.addAttribute( "plugin", canonicalName );
}
}
}
AdminConsole.addModel( pluginName, adminElement );
AdminConsole.addModel( canonicalName, adminElement );
}
firePluginCreatedEvent( pluginName, plugin );
Log.info( "Successfully loaded plugin '{}'.", pluginName );
firePluginCreatedEvent( canonicalName, plugin );
Log.info( "Successfully loaded plugin '{}'.", canonicalName );
return true;
}
catch ( Throwable e )
{
Log.error( "An exception occurred while loading plugin '{}':", pluginName, e );
Integer count = failureToLoadCount.get( pluginName );
Log.error( "An exception occurred while loading plugin '{}':", canonicalName, e );
Integer count = failureToLoadCount.get( canonicalName );
if ( count == null ) {
count = 0;
}
failureToLoadCount.put( pluginName, ++count );
failureToLoadCount.put( canonicalName, ++count );
return false;
}
}
......@@ -624,15 +770,15 @@ public class PluginManager
*
* This method is called automatically when a plugin's JAR file is deleted.
*
* @param pluginName the name of the plugin to unload.
* @param canonicalName the canonical name of the plugin to unload.
*/
void unloadPlugin( String pluginName )
void unloadPlugin( String canonicalName )
{
Log.debug( "Unloading plugin '{}'...", pluginName );
Log.debug( "Unloading plugin '{}'...", canonicalName );
failureToLoadCount.remove( pluginName );
failureToLoadCount.remove( canonicalName );
Plugin plugin = plugins.get( pluginName );
Plugin plugin = pluginsLoaded.get( canonicalName );
if ( plugin != null )
{
// Remove from dev mode if it exists.
......@@ -645,19 +791,19 @@ public class PluginManager
for ( String childPlugin : childPlugins )
{
Log.debug( "Unloading child plugin: '{}'.", childPlugin );
childPluginMap.remove( plugins.get( childPlugin ) );
childPluginMap.remove( pluginsLoaded.get( childPlugin ) );
unloadPlugin( childPlugin );
}
parentPluginMap.remove( plugin );
}
Path webXML = pluginDirectory.resolve( pluginName ).resolve( "web" ).resolve( "WEB-INF" ).resolve( "web.xml" );
Path webXML = pluginDirectory.resolve( canonicalName ).resolve( "web" ).resolve( "WEB-INF" ).resolve( "web.xml" );
if ( Files.exists( webXML ) )
{
AdminConsole.removeModel( pluginName );
AdminConsole.removeModel( canonicalName );
PluginServlet.unregisterServlets( webXML.toFile() );
}
Path customWebXML = pluginDirectory.resolve( pluginName ).resolve( "web" ).resolve( "WEB-INF" ).resolve( "web-custom.xml" );
Path customWebXML = pluginDirectory.resolve( canonicalName ).resolve( "web" ).resolve( "WEB-INF" ).resolve( "web-custom.xml" );
if ( Files.exists( customWebXML ) )
{
PluginServlet.unregisterServlets( customWebXML.toFile() );
......@@ -671,11 +817,11 @@ public class PluginManager
try
{
plugin.destroyPlugin();
Log.debug( "Destroyed plugin '{}'.", pluginName );
Log.debug( "Destroyed plugin '{}'.", canonicalName );
}
catch ( Exception e )
{
Log.error( "An exception occurred while unloading plugin '{}':", pluginName, e );
Log.error( "An exception occurred while unloading plugin '{}':", canonicalName, e );
}
}
......@@ -683,9 +829,10 @@ public class PluginManager
// If plugin still fails to be removed then we will add references back
// Anyway, for a few seconds admins may not see the plugin in the admin console
// and in a subsequent refresh it will appear if failed to be removed
plugins.remove( pluginName );
Path pluginFile = pluginDirs.remove( plugin );
pluginsLoaded.remove( canonicalName );
Path pluginFile = pluginDirs.remove( canonicalName );
PluginClassLoader pluginLoader = classloaders.remove( plugin );
PluginMetadata metadata = pluginMetadata.remove( canonicalName );
// try to close the cached jar files from the plugin class loader
if ( pluginLoader != null )
......@@ -694,13 +841,13 @@ public class PluginManager
}
else
{
Log.warn( "No plugin loader found for '{}'.", pluginName );
Log.warn( "No plugin loader found for '{}'.", canonicalName );
}
// Try to remove the folder where the plugin was exploded. If this works then
// the plugin was successfully removed. Otherwise, some objects created by the
// plugin are still in memory.
Path dir = pluginDirectory.resolve( pluginName );
Path dir = pluginDirectory.resolve( canonicalName );
// Give the plugin 2 seconds to unload.
try
{
......@@ -710,7 +857,7 @@ public class PluginManager
int count = 0;
while ( !deleteDir( dir ) && count++ < 5 )
{
Log.warn( "Error unloading plugin '{}'. Will attempt again momentarily.", pluginName );
Log.warn( "Error unloading plugin '{}'. Will attempt again momentarily.", canonicalName );
Thread.sleep( 8000 );
// Ask the system to clean up references.
System.gc();
......@@ -718,23 +865,23 @@ public class PluginManager
}
catch ( InterruptedException e )
{
Log.debug( "Stopped waiting for plugin '{}' to be fully unloaded.", pluginName, e );
Log.debug( "Stopped waiting for plugin '{}' to be fully unloaded.", canonicalName, e );
}
if ( plugin != null && Files.notExists( dir ) )
{
// Unregister plugin caches
PluginCacheRegistry.getInstance().unregisterCaches( pluginName );
PluginCacheRegistry.getInstance().unregisterCaches( canonicalName );
// See if this is a child plugin. If it is, we should unload
// the parent plugin as well.
if ( childPluginMap.containsKey( plugin ) )
{
String parentPluginName = childPluginMap.get( plugin );
Plugin parentPlugin = plugins.get( parentPluginName );
Plugin parentPlugin = pluginsLoaded.get( parentPluginName );
List<String> childrenPlugins = parentPluginMap.get( parentPlugin );
childrenPlugins.remove( pluginName );
childrenPlugins.remove( canonicalName );
childPluginMap.remove( plugin );
// When the parent plugin implements PluginListener, its pluginDestroyed() method
......@@ -745,19 +892,20 @@ public class PluginManager
{
PluginListener listener;
listener = (PluginListener) parentPlugin;
listener.pluginDestroyed( pluginName, plugin );
listener.pluginDestroyed( canonicalName, plugin );
}
unloadPlugin( parentPluginName );
}
firePluginDestroyedEvent( pluginName, plugin );
Log.info( "Successfully unloaded plugin '{}'.", pluginName );
firePluginDestroyedEvent( canonicalName, plugin );
Log.info( "Successfully unloaded plugin '{}'.", canonicalName );
}
else if ( plugin != null )
{
Log.info( "Restore references since we failed to remove the plugin '{}'.", pluginName );
plugins.put( pluginName, plugin );
pluginDirs.put( plugin, pluginFile );
Log.info( "Restore references since we failed to remove the plugin '{}'.", canonicalName );
pluginsLoaded.put( canonicalName, plugin );
pluginDirs.put( canonicalName, pluginFile );
classloaders.put( plugin, pluginLoader );
pluginMetadata.put( canonicalName, metadata );
}
}
......@@ -824,7 +972,7 @@ public class PluginManager
@Deprecated
public String getVersion( Plugin plugin )
{
return PluginMetadataHelper.getVersion( plugin );
return PluginMetadataHelper.getVersion( plugin ).getVersionString();
}
/**
......@@ -833,7 +981,7 @@ public class PluginManager
@Deprecated
public String getMinServerVersion( Plugin plugin )
{
return PluginMetadataHelper.getMinServerVersion( plugin );
return PluginMetadataHelper.getMinServerVersion( plugin ).getVersionString();
}
/**
......@@ -858,7 +1006,7 @@ public class PluginManager
* @deprecated Moved to {@link PluginMetadataHelper#getLicense(Plugin)}.
*/
@Deprecated
public License getLicense( Plugin plugin )
public String getLicense( Plugin plugin )
{
return PluginMetadataHelper.getLicense( plugin );
}
......
/*
* Copyright (C) 2017 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.
* 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.jivesoftware.openfire.container;
import org.jivesoftware.util.Version;
import java.net.URL;
import java.nio.file.Path;
/**
* A bean-like representation of the metadata of a plugin.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class PluginMetadata
{
/**
* Human readable name of the plugin.
*/
private final String name;
/**
* Canonical name of the plugin.
*/
private final String canonicalName;
/**
* Description of the plugin as specified in plugin.xml.
*/
private final String description;
/**
* The version of the plugin.
*/
private final Version version;
/**
* Author of the plugin as specified in plugin.xml.
*/
private final String author;
/**
* Icon's location of the plugin.
*/
private final URL icon;
/**
* Changelog location of the latest version of the plugin.
*/
private final URL changelog;
/**
* ReadMe location of the latest version of the plugin.
*/
private final URL readme;
/**
* Type of license of the plugin.
*/
private final String license;
/**
* Minimum server version required by this plugin as specified in plugin.xml.
*/
private final Version minServerVersion;
/**
* Maximum server version required by this plugin as specified in plugin.xml.
*/
private final Version maxServerVersion;
/**
* Constructs a metadata object based on a plugin.
*
* The plugin must be installed in Openfire.
*
* @param pluginDir the path of the plugin directory (cannot be null)
* @return Metadata for the plugin (never null).
*/
public static PluginMetadata getInstance( Path pluginDir )
{
return new PluginMetadata(
PluginMetadataHelper.getName( pluginDir ),
PluginMetadataHelper.getCanonicalName( pluginDir ),
PluginMetadataHelper.getDescription( pluginDir ),
PluginMetadataHelper.getVersion( pluginDir ),
PluginMetadataHelper.getAuthor( pluginDir ),
PluginMetadataHelper.getIcon( pluginDir ),
PluginMetadataHelper.getChangelog( pluginDir ),
PluginMetadataHelper.getReadme( pluginDir ),
PluginMetadataHelper.getLicense( pluginDir ),
PluginMetadataHelper.getMinServerVersion( pluginDir ),
PluginMetadataHelper.getMaxServerVersion( pluginDir )
);
}
/**
* Constructs a metadata object based on a plugin.
*
* The plugin must be installed in Openfire.
*
* @param plugin The plugin (cannot be null)
* @return Metadata for the plugin (never null).
*/
public static PluginMetadata getInstance( Plugin plugin )
{
return new PluginMetadata(
PluginMetadataHelper.getName( plugin ),
PluginMetadataHelper.getCanonicalName( plugin ),
PluginMetadataHelper.getDescription( plugin ),
PluginMetadataHelper.getVersion( plugin ),
PluginMetadataHelper.getAuthor( plugin ),
PluginMetadataHelper.getIcon( plugin ),
PluginMetadataHelper.getChangelog( plugin ),
PluginMetadataHelper.getReadme( plugin ),
PluginMetadataHelper.getLicense( plugin ),
PluginMetadataHelper.getMinServerVersion( plugin ),
PluginMetadataHelper.getMaxServerVersion( plugin )
);
}
public PluginMetadata( String name, String canonicalName, String description, Version version, String author,
URL icon, URL changelog, URL readme, String license,
Version minServerVersion, Version maxServerVersion )
{
this.name = name;
this.canonicalName = canonicalName;
this.description = description;
this.version = version;
this.author = author;
this.icon = icon;
this.changelog = changelog;
this.readme = readme;
this.license = license;
this.minServerVersion = minServerVersion;
this.maxServerVersion = maxServerVersion;
}
public String getName()
{
return name;
}
public String getCanonicalName()
{
return canonicalName;
}
public String getDescription()
{
return description;
}
public Version getVersion()
{
return version;
}
public String getAuthor()
{
return author;
}
public URL getIcon()
{
return icon;
}
public URL getChangelog()
{
return changelog;
}
public URL getReadme()
{
return readme;
}
public String getLicense()
{
return license;
}
public Version getMinServerVersion()
{
return minServerVersion;
}
public Version getMaxServerVersion()
{
return maxServerVersion;
}
public String getHashCode() {
return String.valueOf( hashCode() );
}
}
......@@ -21,9 +21,12 @@ import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jivesoftware.admin.AdminConsole;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
......@@ -36,6 +39,18 @@ public class PluginMetadataHelper
{
private static final Logger Log = LoggerFactory.getLogger( PluginMetadataHelper.class );
/**
* Returns the name of the directory of the parent for this plugin. The value is retrieved from the plugin.xml file
* of the plugin (which is casted down to lower-case). If the value could not be found, <tt>null</tt> will be returned.
*
* @param plugin The plugin (cannot be null)
* @return the parent plugin's directory name
*/
public static String getParentPlugin( Plugin plugin )
{
return getParentPlugin( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
/**
* Returns the name of the directory of the parent for this plugin. The value is retrieved from the plugin.xml file
* of the plugin (which is casted down to lower-case). If the value could not be found, <tt>null</tt> will be returned.
......@@ -54,7 +69,7 @@ public class PluginMetadataHelper
}
/**
* Returns the canonical name for the plugin, derived from the plugin directory name.
* Returns the canonical name for the plugin, derived from the plugin archive file name.
*
* Note that this value can be different from the 'human readable' plugin name, as returned by {@link #getName(Path)}.
*
......@@ -67,20 +82,32 @@ public class PluginMetadataHelper
*/
public static String getCanonicalName( Plugin plugin )
{
return getCanonicalName( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
return XMPPServer.getInstance().getPluginManager().getCanonicalName( plugin );
}
/**
* Returns the canonical name for the plugin, derived from the plugin directory name.
* Returns the canonical name for the plugin, derived from the plugin directory or archive file name.
*
* The provided path can refer to either the plugin archive file, or the directory in which the archive was
* extracted.
*
* Note that this value can be different from the 'human readable' plugin name, as returned by {@link #getName(Path)}.
*
* @param pluginDir the path of the plugin directory.
* @param pluginPath the path of the plugin directory, or plugin archive file.
* @return the plugin's canonical name.
*/
public static String getCanonicalName( Path pluginDir )
public static String getCanonicalName( Path pluginPath )
{
return pluginDir.getFileName().toString().toLowerCase();
final String pathFileName = pluginPath.getFileName().toString().toLowerCase();
if ( pluginPath.toFile().isDirectory() )
{
return pathFileName;
}
else
{
// Strip file extension
return pathFileName.substring( 0, pathFileName.lastIndexOf( '.' ) );
}
}
/**
......@@ -191,7 +218,7 @@ public class PluginMetadataHelper
* @param plugin The plugin (cannot be null)
* @return the plugin's version.
*/
public static String getVersion( Plugin plugin )
public static Version getVersion( Plugin plugin )
{
return getVersion( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
......@@ -203,9 +230,16 @@ public class PluginMetadataHelper
* @param pluginDir the path of the plugin directory.
* @return the plugin's version.
*/
public static String getVersion( Path pluginDir )
public static Version getVersion( Path pluginDir )
{
return getElementValue( pluginDir, "/plugin/version" );
final String value = getElementValue( pluginDir, "/plugin/version" );
if ( value == null || value.trim().isEmpty() )
{
return null;
}
return new Version( value );
}
/**
......@@ -217,9 +251,9 @@ public class PluginMetadataHelper
* argument.
*
* @param plugin The plugin (cannot be null)
* @return the plugin's minimum server version.
* @return the plugin's minimum server version (possibly null).
*/
public static String getMinServerVersion( Plugin plugin )
public static Version getMinServerVersion( Plugin plugin )
{
return getMinServerVersion( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
......@@ -229,11 +263,52 @@ public class PluginMetadataHelper
* of the plugin. If the value could not be found, <tt>null</tt> will be returned.
*
* @param pluginDir the path of the plugin directory.
* @return the plugin's minimum server version.
* @return the plugin's minimum server version (possibly null).
*/
public static Version getMinServerVersion( Path pluginDir )
{
final String value = getElementValue( pluginDir, "/plugin/minServerVersion" );
if ( value == null || value.trim().isEmpty() )
{
return null;
}
return new Version( value );
}
/**
* Returns the maximum server version this plugin can run within. The value is retrieved from the plugin.xml file
* of the plugin. If the value could not be found, <tt>null</tt> will be returned.
*
* Note that this method will return data only for plugins that have successfully been installed. To obtain data
* from plugin (directories) that have not (yet) been installed, refer to the overloaded method that takes a Path
* argument.
*
* @param plugin The plugin (cannot be null)
* @return the plugin's maximum server version (possibly null).
*/
public static Version getMaxServerVersion( Plugin plugin )
{
return getMaxServerVersion( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
/**
* Returns the maximum server version this plugin can run within. The value is retrieved from the plugin.xml file
* of the plugin. If the value could not be found, <tt>null</tt> will be returned.
*
* @param pluginDir the path of the plugin directory.
* @return the plugin's maximum server version (possibly null).
*/
public static String getMinServerVersion( Path pluginDir )
public static Version getMaxServerVersion( Path pluginDir )
{
return getElementValue( pluginDir, "/plugin/minServerVersion" );
final String value = getElementValue( pluginDir, "/plugin/maxServerVersion" );
if ( value == null || value.trim().isEmpty() )
{
return null;
}
return new Version( value );
}
/**
......@@ -306,7 +381,7 @@ public class PluginMetadataHelper
/**
* Returns the license agreement type that the plugin is governed by. The value is retrieved from the plugin.xml
* file of the plugin. If the value could not be found, {@link License#other} is returned.
* file of the plugin.
*
* Note that this method will return data only for plugins that have successfully been installed. To obtain data
* from plugin (directories) that have not (yet) been installed, refer to the overloaded method that takes a Path
......@@ -315,35 +390,97 @@ public class PluginMetadataHelper
* @param plugin The plugin (cannot be null)
* @return the plugin's license agreement.
*/
public static License getLicense( Plugin plugin )
public static String getLicense( Plugin plugin )
{
return getLicense( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
/**
* Returns the license agreement type that the plugin is governed by. The value is retrieved from the plugin.xml
* file of the plugin. If the value could not be found, {@link License#other} is returned.
* file of the plugin.
*
* @param pluginDir the path of the plugin directory.
* @return the plugin's license agreement.
*/
public static License getLicense( Path pluginDir )
public static String getLicense( Path pluginDir )
{
return getElementValue( pluginDir, "/plugin/licenseType" );
}
public static URL getIcon( Plugin plugin )
{
String licenseString = getElementValue( pluginDir, "/plugin/licenseType" );
if ( licenseString != null )
return getIcon( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
public static URL getIcon( Path pluginDir )
{
Path icon = pluginDir.resolve( "logo_small.png" );
if ( !icon.toFile().exists() )
{
try
{
// Attempt to load the get the license type. We lower-case and trim the license type to give plugin
// author's a break. If the license type is not recognized, we'll log the error and default to "other".
return License.valueOf( licenseString.toLowerCase().trim() );
}
catch ( IllegalArgumentException iae )
{
Log.error( "Unrecognized license type '{}' for plugin '{}'.", licenseString.toLowerCase().trim(), getCanonicalName( pluginDir ), iae );
}
icon = pluginDir.resolve( "logo_small.gif" );
}
if ( !icon.toFile().exists() )
{
return null;
}
try
{
return icon.toUri().toURL();
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to parse URL for icon of plugin '{}'.", getCanonicalName( pluginDir ), e );
return null;
}
}
public static URL getReadme( Plugin plugin )
{
return getReadme( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
public static URL getReadme( Path pluginDir )
{
final Path file = pluginDir.resolve( "readme.html" );
if ( !file.toFile().exists() )
{
return null;
}
try
{
return file.toUri().toURL();
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to parse URL for readme of plugin '{}'.", getCanonicalName( pluginDir ), e );
return null;
}
}
public static URL getChangelog( Plugin plugin )
{
return getChangelog( XMPPServer.getInstance().getPluginManager().getPluginPath( plugin ) );
}
public static URL getChangelog( Path pluginDir )
{
final Path file = pluginDir.resolve( "changelog.html" );
if ( !file.toFile().exists() )
{
return null;
}
try
{
return file.toUri().toURL();
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to parse URL for changelog of plugin '{}'.", getCanonicalName( pluginDir ), e );
return null;
}
return License.other;
}
/**
......
......@@ -151,12 +151,12 @@ public class PluginMonitor
for ( final Path jarFile : ds )
{
final String fileName = jarFile.getFileName().toString();
final String pluginName = fileName.substring( 0, fileName.length() - 4 ).toLowerCase(); // strip extension.
final String canonicalPluginName = fileName.substring( 0, fileName.length() - 4 ).toLowerCase(); // strip extension.
jarSet.add( pluginName );
jarSet.add( canonicalPluginName );
// See if the JAR has already been exploded.
final Path dir = pluginsDirectory.resolve( pluginName );
final Path dir = pluginsDirectory.resolve( canonicalPluginName );
// See if the JAR is newer than the directory. If so, the plugin needs to be unloaded and then reloaded.
if ( Files.exists( dir ) && Files.getLastModifiedTime( jarFile ).toMillis() > Files.getLastModifiedTime( dir ).toMillis() )
......@@ -174,14 +174,14 @@ public class PluginMonitor
else
{
// Not the first time? Properly unload the plugin.
pluginManager.unloadPlugin( pluginName );
pluginManager.unloadPlugin( canonicalPluginName );
}
}
// If the JAR needs to be exploded, do so.
if ( Files.notExists( dir ) )
{
unzipPlugin( pluginName, jarFile, dir );
unzipPlugin( canonicalPluginName, jarFile, dir );
}
}
}
......@@ -270,10 +270,10 @@ public class PluginMonitor
for ( final Path path : hierarchy )
{
// If the plugin hasn't already been started, start it.
final String pluginName = PluginMetadataHelper.getCanonicalName( path );
if ( pluginManager.getPlugin( pluginName ) == null )
final String canonicalName = PluginMetadataHelper.getCanonicalName( path );
if ( pluginManager.getPlugin( canonicalName ) == null )
{
if ( pluginManager.loadPlugin( path ) )
if ( pluginManager.loadPlugin( canonicalName, path ) )
{
loaded++;
}
......@@ -289,7 +289,7 @@ public class PluginMonitor
// of all plugins that attempt to modify the admin panel.
if ( pluginManager.getPlugin( "admin" ) == null )
{
pluginManager.loadPlugin( dirs.getFirst().get( 0 ) );
pluginManager.loadPlugin( "admin", dirs.getFirst().get( 0 ) );
}
// Hierarchies could be processed in parallel. This is likely to be beneficial during the first
......
......@@ -16,191 +16,177 @@
package org.jivesoftware.openfire.update;
import org.dom4j.Element;
import org.jivesoftware.openfire.container.PluginMetadata;
import org.jivesoftware.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Plugin available at igniterealtime.org. The plugin may or may not be locally installed.
*
* @author Gaston Dombiak
*/
public class AvailablePlugin {
public class AvailablePlugin extends PluginMetadata
{
private static final Logger Log = LoggerFactory.getLogger( AvailablePlugin.class );
/**
* Name of the plugin.
*/
private String name;
/**
* Latest version of the plugin that was found.
*/
private String latestVersion;
/**
* URL from where the latest version of the plugin can be downloaded.
*/
private String url;
/**
* Icon's URL of the latest version of the plugin.
*/
private String icon;
/**
* README URL of the latest version of the plugin.
*/
private String readme;
/**
* Changelog URL of the latest version of the plugin.
*/
private String changelog;
/**
* Type of license of the plugin.
*/
private String licenseType;
/**
* Description of the plugin as specified in plugin.xml.
*/
private String description;
/**
* Author of the plugin as specified in plugin.xml.
*/
private String author;
/**
* Minimum server version required by this plugin as specified in plugin.xml.
*/
private String minServerVersion;
private final URL downloadURL;
/**
* Size in bytes of the plugin jar file.
*/
private String fileSize;
public AvailablePlugin(String name, String description, String latestVersion, String author,
String icon, String changelog, String readme, String licenseType,
String minServerVersion, String url, String fileSize) {
this.author = author;
this.icon = icon;
this.changelog = changelog;
this.readme = readme;
this.licenseType = licenseType;
this.description = description;
this.latestVersion = latestVersion;
this.minServerVersion = minServerVersion;
this.name = name;
this.url = url;
this.fileSize = fileSize;
}
private final long fileSize;
/**
* Returns the name of the plugin that is not installed.
*
* @return the name of the plugin that is not installed.
*/
public String getName() {
return name;
}
public static AvailablePlugin getInstance( Element plugin )
{
String pluginName = plugin.attributeValue("name");
Version latestVersion = null;
String latestVersionValue = plugin.attributeValue("latest");
if ( latestVersionValue != null && !latestVersionValue.isEmpty() )
{
latestVersion = new Version( latestVersionValue );
}
/**
* Returns the latest version of the plugin that is not installed.
*
* @return the latest version of the plugin that is not installed.
*/
public String getLatestVersion() {
return latestVersion;
}
URL icon = null;
String iconValue = plugin.attributeValue("icon");
if ( iconValue != null && !iconValue.isEmpty() )
{
try
{
icon = new URL( iconValue );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to create icon URL from value '{}' for plugin {}.", iconValue, pluginName, e );
}
}
/**
* Return the icon's URL of the latest version of the plugin.
*
* @return the icon's URL of the latest version of the plugin.
*/
public String getIcon() {
return icon;
}
URL readme = null;
String readmeValue = plugin.attributeValue("readme");
if ( readmeValue != null && !readmeValue.isEmpty() )
{
try
{
readme = new URL( readmeValue );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to create readme URL from value '{}' for plugin {}.", readmeValue, pluginName, e );
}
}
/**
* Returns the URL to the README file of the latest version of the plugin.
*
* @return the URL to the README file of the latest version of the plugin.
*/
public String getReadme() {
return readme;
}
URL changelog = null;
String changelogValue = plugin.attributeValue("changelog");
if ( changelogValue != null && !changelogValue.isEmpty() )
{
try
{
changelog = new URL( changelogValue );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to create changelog URL from value '{}' for plugin {}.", changelogValue, pluginName, e );
}
}
URL downloadUrl = null;
String downloadUrlValue = plugin.attributeValue("url");
if ( downloadUrlValue != null && !downloadUrlValue.isEmpty() )
{
try
{
downloadUrl = new URL( downloadUrlValue );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to create download URL from value '{}' for plugin {}.", downloadUrlValue, pluginName, e );
}
}
/**
* Returns the URL to the change log of the plugin.
*
* @return the URL to the change log of the plugin.
*/
public String getChangelog() {
return changelog;
}
String license = plugin.attributeValue("licenseType");
String description = plugin.attributeValue("description");
String author = plugin.attributeValue("author");
/**
* Returns the URL from where the plugin.
*
* @return the URL from where the plugin.
*/
public String getURL() {
return url;
}
Version minServerVersion = null;
String minServerVersionValue = plugin.attributeValue("minServerVersion");
if ( minServerVersionValue != null && !minServerVersionValue.isEmpty() )
{
minServerVersion = new Version( minServerVersionValue );
}
/**
* Returns the author of the plugin as specified in plugin.xml.
*
* @return author of the plugin as specified in plugin.xml.
*/
public String getAuthor() {
return author;
}
Version maxServerVersion = null;
String maxServerVersionValue = plugin.attributeValue("maxServerVersion");
if ( maxServerVersionValue != null && !maxServerVersionValue.isEmpty() )
{
maxServerVersion = new Version( maxServerVersionValue );
}
/**
* Returns true if the plugin is commercial.
*
* @return true if the plugin is commercial.
*/
public boolean isCommercial() {
return "commercial".equals(licenseType);
}
long fileSize = -1;
String fileSizeValue = plugin.attributeValue("fileSize");
if ( fileSizeValue != null && !fileSizeValue.isEmpty() )
{
fileSize = Long.parseLong( fileSizeValue );
}
/**
* Returns the type of license the plugin is being released under.
*
* @return the type of license of the plugin.
*/
public String getLicenseType() {
return licenseType;
}
String canonical = downloadUrlValue != null ? downloadUrlValue.substring( downloadUrlValue.lastIndexOf( '/' ) + 1, downloadUrlValue.lastIndexOf( '.' ) ) : null;
return new AvailablePlugin(
pluginName,
canonical,
description,
latestVersion,
author,
icon,
changelog,
readme,
license,
minServerVersion,
maxServerVersion,
downloadUrl,
fileSize
);
/**
* Returns the description of the plugin as specified in plugin.xml.
*
* @return description of the plugin as specified in plugin.xml.
*/
public String getDescription() {
return description;
}
public AvailablePlugin( String name, String canonicalName, String description, Version latestVersion, String author,
URL icon, URL changelog, URL readme, String license,
Version minServerVersion, Version maxServerVersion, URL downloadUrl, long fileSize ) {
super(
name,
canonicalName,
description,
latestVersion,
author,
icon,
changelog,
readme,
license,
minServerVersion,
maxServerVersion
);
this.downloadURL = downloadUrl;
this.fileSize = fileSize;
}
/**
* Returns the minimum server version required by this plugin as specified in plugin.xml.
* URL from where the latest version of the plugin can be downloaded.
*
* @return minimum server version required by this plugin as specified in plugin.xml.
* @return download URL.
*/
public String getMinServerVersion() {
return minServerVersion;
public URL getDownloadURL() {
return downloadURL;
}
/**
* Returns the size in bytes of the plugin jar file.
*
* @return the size in bytes of the plugin jar file.
*/
public long getFileSize() {
if (fileSize == null) {
// Dummy value for old xml files that didn't contain this piece of information
return -1L;
}
return Long.parseLong(fileSize);
}
/**
* Returns the hash code for this object.
* @return the hash code.
*/
public int getHashCode(){
return hashCode();
return fileSize;
}
}
......@@ -25,13 +25,10 @@ import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
......@@ -46,8 +43,7 @@ import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.jivesoftware.openfire.MessageRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.*;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
......@@ -310,40 +306,56 @@ public class UpdateManager extends BasicModule {
*/
public boolean isPluginDownloaded(String url) {
String pluginFilename = url.substring(url.lastIndexOf("/") + 1);
return XMPPServer.getInstance().getPluginManager().isPluginDownloaded(pluginFilename);
return XMPPServer.getInstance().getPluginManager().isInstalled( pluginFilename);
}
/**
* Returns the list of available plugins to install as reported by igniterealtime.org.
* Currently installed plugins will not be included or plugins that require a newer
* server version.
* Returns the list of available plugins, sorted alphabetically, to install as reported by igniterealtime.org.
*
* Currently downloaded plugins will not be included, nor will plugins that require a newer or older server version.
*
* @return the list of available plugins to install as reported by igniterealtime.org.
*/
public List<AvailablePlugin> getNotInstalledPlugins() {
List<AvailablePlugin> plugins = new ArrayList<>(availablePlugins.values());
XMPPServer server = XMPPServer.getInstance();
// Remove installed plugins from the list of available plugins
for (Plugin plugin : server.getPluginManager().getPlugins()) {
String pluginName = server.getPluginManager().getName(plugin);
for (Iterator<AvailablePlugin> it = plugins.iterator(); it.hasNext();) {
AvailablePlugin availablePlugin = it.next();
if (availablePlugin.getName().equals(pluginName)) {
it.remove();
break;
}
public List<AvailablePlugin> getNotInstalledPlugins()
{
final List<AvailablePlugin> result = new ArrayList<>( availablePlugins.values() );
final PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
final Version currentServerVersion = XMPPServer.getInstance().getServerInfo().getVersion();
// Iterate over the plugins, remove those that are of no interest.
final Iterator<AvailablePlugin> iterator = result.iterator();
while ( iterator.hasNext() )
{
final AvailablePlugin availablePlugin = iterator.next();
// Remove plugins that are already downloaded from the list of available plugins.
if ( pluginManager.isInstalled( availablePlugin.getCanonicalName() ) )
{
iterator.remove();
continue;
}
}
// Remove plugins that require a newer server version
Version currentServerVersion = XMPPServer.getInstance().getServerInfo().getVersion();
for (Iterator<AvailablePlugin> it=plugins.iterator(); it.hasNext();) {
AvailablePlugin plugin = it.next();
Version pluginMinServerVersion = new Version(plugin.getMinServerVersion());
if (pluginMinServerVersion.isNewerThan(currentServerVersion)) {
it.remove();
// Remove plugins that require a newer server version.
if ( availablePlugin.getMinServerVersion() != null && availablePlugin.getMinServerVersion().isNewerThan( currentServerVersion ) )
{
iterator.remove();
}
// Remove plugins that require an older server version.
if ( availablePlugin.getMaxServerVersion() != null && currentServerVersion.isNewerThan( availablePlugin.getMaxServerVersion() ) )
{
iterator.remove();
}
}
return plugins;
// Sort alphabetically.
Collections.sort(result, new Comparator<AvailablePlugin>() {
public int compare(AvailablePlugin o1, AvailablePlugin o2) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
});
return result;
}
/**
......@@ -495,12 +507,12 @@ public class UpdateManager extends BasicModule {
* @param currentVersion current version of the plugin that is installed.
* @return the plugin update or null if the plugin is up to date.
*/
public Update getPluginUpdate(String pluginName, String currentVersion) {
public Update getPluginUpdate(String pluginName, Version currentVersion) {
for (Update update : pluginUpdates) {
// Check if this is the requested plugin
if (update.getComponentName().equals(pluginName)) {
// Check if the plugin version is right
if (update.getLatestVersion().compareTo(currentVersion) > 0) {
if (new Version(update.getLatestVersion()).isNewerThan( currentVersion ) ) {
return update;
}
}
......@@ -538,10 +550,27 @@ public class UpdateManager extends BasicModule {
// A new version of openfire was found
Version latestVersion = new Version(openfire.attributeValue("latest"));
if (latestVersion.isNewerThan(XMPPServer.getInstance().getServerInfo().getVersion())) {
String changelog = openfire.attributeValue("changelog");
String url = openfire.attributeValue("url");
URL changelog = null;
try
{
changelog = new URL( openfire.attributeValue("changelog") );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to parse URL from openfire changelog value '{}'.", openfire.attributeValue("changelog"), e );
}
URL url = null;
try
{
url = new URL( openfire.attributeValue("url") );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to parse URL from openfire download url value '{}'.", openfire.attributeValue("url"), e );
}
// Keep information about the available server update
serverUpdate = new Update("Openfire", latestVersion.getVersionString(), changelog, url);
serverUpdate = new Update("Openfire", latestVersion.getVersionString(), changelog.toExternalForm(), url.toExternalForm() );
}
}
// Check if we need to send notifications to admins
......@@ -572,21 +601,9 @@ public class UpdateManager extends BasicModule {
Iterator plugins = xmlResponse.elementIterator("plugin");
while (plugins.hasNext()) {
Element plugin = (Element) plugins.next();
String pluginName = plugin.attributeValue("name");
String latestVersion = plugin.attributeValue("latest");
String icon = plugin.attributeValue("icon");
String readme = plugin.attributeValue("readme");
String changelog = plugin.attributeValue("changelog");
String url = plugin.attributeValue("url");
String licenseType = plugin.attributeValue("licenseType");
String description = plugin.attributeValue("description");
String author = plugin.attributeValue("author");
String minServerVersion = plugin.attributeValue("minServerVersion");
String fileSize = plugin.attributeValue("fileSize");
AvailablePlugin available = new AvailablePlugin(pluginName, description, latestVersion,
author, icon, changelog, readme, licenseType, minServerVersion, url, fileSize);
AvailablePlugin available = AvailablePlugin.getInstance( plugin );
// Add plugin to the list of available plugins at js.org
availablePlugins.put(pluginName, available);
availablePlugins.put(available.getName(), available);
}
// Figure out local plugins that need to be updated
......@@ -621,22 +638,34 @@ public class UpdateManager extends BasicModule {
XMPPServer server = XMPPServer.getInstance();
Version currentServerVersion = XMPPServer.getInstance().getServerInfo().getVersion();
// Compare local plugins versions with latest ones
for (Plugin plugin : server.getPluginManager().getPlugins()) {
String pluginName = server.getPluginManager().getName(plugin);
AvailablePlugin latestPlugin = availablePlugins.get(pluginName);
if (latestPlugin != null) {
Version currentPluginVersion = new Version(server.getPluginManager().getVersion(plugin));
Version latestPluginVersion = new Version(latestPlugin.getLatestVersion());
if (latestPluginVersion.isNewerThan(currentPluginVersion)) {
// Check if the update can run in the current version of the server
Version pluginMinServerVersion = new Version(latestPlugin.getMinServerVersion());
if (!pluginMinServerVersion.isNewerThan(currentServerVersion)) {
Update update = new Update(pluginName, latestPlugin.getLatestVersion(),
latestPlugin.getChangelog(), latestPlugin.getURL());
pluginUpdates.add(update);
}
}
for ( final PluginMetadata plugin : server.getPluginManager().getMetadataExtractedPlugins().values() )
{
final AvailablePlugin latestPlugin = availablePlugins.get( plugin.getName() );
if (latestPlugin == null)
{
continue;
}
final Version latestPluginVersion = latestPlugin.getVersion();
if ( latestPluginVersion.isNewerThan( plugin.getVersion() ) )
{
// Check if the update can run in the current version of the server
final Version pluginMinServerVersion = latestPlugin.getMinServerVersion();
if ( pluginMinServerVersion != null && pluginMinServerVersion.isNewerThan( currentServerVersion ))
{
continue;
}
final Version pluginMaxServerVersion = latestPlugin.getMaxServerVersion();
if ( pluginMaxServerVersion != null && currentServerVersion.isNewerThan( pluginMaxServerVersion ))
{
continue;
}
final Update update = new Update( plugin.getName(), latestPlugin.getVersion().getVersionString(), latestPlugin.getChangelog().toExternalForm(), latestPlugin.getDownloadURL().toExternalForm() );
pluginUpdates.add(update);
}
}
}
......@@ -650,8 +679,8 @@ public class UpdateManager extends BasicModule {
if (serverUpdate != null) {
Element component = xmlResponse.addElement("openfire");
component.addAttribute("latest", serverUpdate.getLatestVersion());
component.addAttribute("changelog", serverUpdate.getChangelog());
component.addAttribute("url", serverUpdate.getURL());
component.addAttribute( "changelog", serverUpdate.getChangelog() );
component.addAttribute( "url", serverUpdate.getURL() );
}
// Write data out to conf/server-update.xml file.
try {
......@@ -688,15 +717,16 @@ public class UpdateManager extends BasicModule {
for (AvailablePlugin plugin : availablePlugins.values()) {
Element component = xml.addElement("plugin");
component.addAttribute("name", plugin.getName());
component.addAttribute("latest", plugin.getLatestVersion());
component.addAttribute("changelog", plugin.getChangelog());
component.addAttribute("url", plugin.getURL());
component.addAttribute("latest", plugin.getVersion() != null ? plugin.getVersion().getVersionString() : null);
component.addAttribute("changelog", plugin.getChangelog() != null ? plugin.getChangelog().toExternalForm() : null );
component.addAttribute("url", plugin.getDownloadURL() != null ? plugin.getDownloadURL().toExternalForm() : null );
component.addAttribute("author", plugin.getAuthor());
component.addAttribute("description", plugin.getDescription());
component.addAttribute("icon", plugin.getIcon());
component.addAttribute("minServerVersion", plugin.getMinServerVersion());
component.addAttribute("readme", plugin.getReadme());
component.addAttribute("licenseType", plugin.getLicenseType());
component.addAttribute("icon", plugin.getIcon() != null ? plugin.getIcon().toExternalForm() : null );
component.addAttribute("minServerVersion", plugin.getMinServerVersion() != null ? plugin.getMinServerVersion().getVersionString() : null);
component.addAttribute("maxServerVersion", plugin.getMaxServerVersion() != null ? plugin.getMaxServerVersion().getVersionString() : null);
component.addAttribute("readme", plugin.getReadme() != null ? plugin.getReadme().toExternalForm() : null );
component.addAttribute( "licenseType", plugin.getLicense() );
component.addAttribute("fileSize", Long.toString(plugin.getFileSize()));
}
// Write data out to conf/available-plugins.xml file.
......@@ -772,12 +802,29 @@ public class UpdateManager extends BasicModule {
Element openfire = xmlResponse.getRootElement().element("openfire");
if (openfire != null) {
Version latestVersion = new Version(openfire.attributeValue("latest"));
String changelog = openfire.attributeValue("changelog");
String url = openfire.attributeValue("url");
URL changelog = null;
try
{
changelog = new URL( openfire.attributeValue("changelog") );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to parse URL from openfire changelog value '{}'.", openfire.attributeValue("changelog"), e );
}
URL url = null;
try
{
url = new URL( openfire.attributeValue("url") );
}
catch ( MalformedURLException e )
{
Log.warn( "Unable to parse URL from openfire download url value '{}'.", openfire.attributeValue("url"), e );
}
// Check if current server version is correct
Version currentServerVersion = XMPPServer.getInstance().getServerInfo().getVersion();
if (latestVersion.isNewerThan(currentServerVersion)) {
serverUpdate = new Update("Openfire", latestVersion.getVersionString(), changelog, url);
serverUpdate = new Update("Openfire", latestVersion.getVersionString(), changelog.toExternalForm(), url.toExternalForm() );
}
}
}
......@@ -807,21 +854,9 @@ public class UpdateManager extends BasicModule {
Iterator it = xmlResponse.getRootElement().elementIterator("plugin");
while (it.hasNext()) {
Element plugin = (Element) it.next();
String pluginName = plugin.attributeValue("name");
String latestVersion = plugin.attributeValue("latest");
String icon = plugin.attributeValue("icon");
String readme = plugin.attributeValue("readme");
String changelog = plugin.attributeValue("changelog");
String url = plugin.attributeValue("url");
String licenseType = plugin.attributeValue("licenseType");
String description = plugin.attributeValue("description");
String author = plugin.attributeValue("author");
String minServerVersion = plugin.attributeValue("minServerVersion");
String fileSize = plugin.attributeValue("fileSize");
AvailablePlugin available = new AvailablePlugin(pluginName, description, latestVersion,
author, icon, changelog, readme, licenseType, minServerVersion, url, fileSize);
final AvailablePlugin instance = AvailablePlugin.getInstance( plugin );
// Add plugin to the list of available plugins at js.org
availablePlugins.put(pluginName, available);
availablePlugins.put(instance.getName(), instance);
}
}
......
......@@ -12,29 +12,26 @@
- limitations under the License.
--%>
<%@ page errorPage="error.jsp" import="org.jivesoftware.util.ByteFormat,
org.jivesoftware.util.Version,
org.jivesoftware.openfire.XMPPServer,
<%@ page errorPage="error.jsp" import="org.jivesoftware.openfire.XMPPServer,
org.jivesoftware.openfire.container.Plugin,
org.jivesoftware.util.StringUtils"
org.jivesoftware.openfire.container.PluginManager,
org.jivesoftware.openfire.container.PluginMetadataHelper,
org.jivesoftware.openfire.update.AvailablePlugin"
%>
<%@ page import="org.jivesoftware.openfire.container.PluginManager" %>
<%@ page import="org.jivesoftware.openfire.update.AvailablePlugin" %>
<%@ page import="org.jivesoftware.openfire.update.UpdateManager" %>
<%@ page import="java.io.File" %>
<%@ page import="java.net.URLEncoder" %>
<%@ page import="org.jivesoftware.util.*" %>
<%@ page import="java.nio.file.Path" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.Comparator" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.util.List" %>
<%@ page import="org.jivesoftware.util.JiveGlobals"%>
<%@ page import="org.jivesoftware.util.StringUtils"%>
<%@ page import="org.jivesoftware.util.ParamUtils"%>
<%@ page import="org.jivesoftware.util.CookieUtils"%>
<%@ page import="java.util.Date"%>
<%@ page import="org.jivesoftware.openfire.container.PluginMetadata" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="admin" prefix="admin" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init(request, response, session, application, out ); %>
......@@ -54,16 +51,13 @@
CookieUtils.setCookie(request, response, "csrf", csrfParam, -1);
pageContext.setAttribute("csrf", csrfParam);
UpdateManager updateManager = XMPPServer.getInstance().getUpdateManager();
List<AvailablePlugin> plugins = updateManager.getNotInstalledPlugins();
final XMPPServer server = XMPPServer.getInstance();
final UpdateManager updateManager = server.getUpdateManager();
String time = JiveGlobals.getProperty("update.lastCheck");
// Sort plugins alphabetically
Collections.sort(plugins, new Comparator<AvailablePlugin>() {
public int compare(AvailablePlugin o1, AvailablePlugin o2) {
return o1.getName().compareTo(o2.getName());
}
});
final String value = JiveGlobals.getProperty( "update.lastCheck" );
pageContext.setAttribute( "lastCheck", value != null ? new Date( Long.parseLong( value ) ) : null );
pageContext.setAttribute( "updateServiceEnabled", updateManager.isServiceEnabled() );
pageContext.setAttribute( "notInstalledPlugins", updateManager.getNotInstalledPlugins() );
if (downloadRequested) {
// Download and install new plugin
......@@ -231,320 +225,134 @@
<body>
<p>
<fmt:message key="plugin.available.info"/>
</p>
<p>
<%if(time == null){ %>
<div style="padding:10px;background:#FFEBB5;border:1px solid #DEB24A;width:75%;">
<fmt:message key="plugin.available.no.list" />&nbsp;<span id="reloaderID"><a href="javascript:updatePluginsList();"><fmt:message key="plugin.available.list" /></a></span>
</div>
<br/>
<div style="width:75%;">
<p>
<fmt:message key="plugin.available.no.list.description" />
</p>
<% if(!updateManager.isServiceEnabled()){ %>
<fmt:message key="plugin.available.auto.update.currently" /> <b><fmt:message key="plugin.available.auto.update.currently.disabled" /></b>. <a href="manage-updates.jsp"><fmt:message key="plugin.available.click.here" /></a> <fmt:message key="plugin.available.change" />
<% } %>
</div>
<% } else {%>
<div id="errorMessage" class="error" style="display:none;">
<fmt:message key="plugin.available.error.downloading" />
</div>
<div class="light-gray-border" style="padding:10px;">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<thead>
<tr style="background:#eee;">
<td class="table-header-left">&nbsp;</td>
<td nowrap colspan="2" class="table-header"><fmt:message key="plugin.available.open_source"/></td>
<td nowrap class="table-header"><fmt:message key="plugin.available.description"/></td>
<td nowrap class="table-header"><fmt:message key="plugin.available.version"/></td>
<td nowrap class="table-header"><fmt:message key="plugin.available.author"/></td>
<td nowrap class="table-header">File Size</td>
<td nowrap class="table-header-right"><fmt:message key="plugin.available.install"/></td>
</tr>
</thead>
<tbody>
<%
// If only the admin plugin is installed, show "none".
if (plugins.isEmpty()) {
%>
<tr>
<td align="center" colspan="8"><fmt:message key="plugin.available.no_plugin"/></td>
</tr>
<%
}
for (AvailablePlugin plugin : plugins) {
String pluginName = plugin.getName();
String pluginDescription = plugin.getDescription();
String pluginAuthor = plugin.getAuthor();
String pluginVersion = plugin.getLatestVersion();
ByteFormat byteFormat = new ByteFormat();
String fileSize = byteFormat.format(plugin.getFileSize());
if (plugin.isCommercial()) {
continue;
}
%>
<tr id="<%= plugin.hashCode()%>">
<td width="1%" class="line-bottom-border">
<% if (plugin.getIcon() != null) { %>
<img src="<%= StringUtils.escapeForXML(plugin.getIcon()) %>" width="16" height="16" alt="Plugin">
<% }
else { %>
<img src="images/plugin-16x16.gif" width="16" height="16" alt="Plugin">
<% } %>
</td>
<td width="20%" nowrap class="line-bottom-border">
<%= (pluginName != null ? StringUtils.escapeHTMLTags(pluginName) : "") %> &nbsp;
</td>
<td nowrap valign="top" class="line-bottom-border">
<% if (plugin.getReadme() != null) { %>
<a href="<%= StringUtils.escapeForXML(plugin.getReadme()) %>"
><img src="images/doc-readme-16x16.gif" width="16" height="16" border="0" alt="README"></a>
<% }
else { %> &nbsp; <% } %>
<% if (plugin.getChangelog() != null) { %>
<a href="<%= StringUtils.escapeForXML(plugin.getChangelog()) %>"
><img src="images/doc-changelog-16x16.gif" width="16" height="16" border="0" alt="changelog"></a>
<% }
else { %> &nbsp; <% } %>
</td>
<td width="60%" class="line-bottom-border">
<%= pluginDescription != null ? StringUtils.escapeHTMLTags(pluginDescription) : "" %>
</td>
<td width="5%" nowrap valign="top" class="line-bottom-border">
<%= pluginVersion != null ? StringUtils.escapeHTMLTags(pluginVersion) : "" %>
</td>
<td width="15%" nowrap valign="top" class="line-bottom-border">
<%= pluginAuthor != null ? StringUtils.escapeHTMLTags(pluginAuthor) : "" %> &nbsp;
</td>
<td width="15%" nowrap valign="top" class="line-bottom-border" align="right">
<%= StringUtils.escapeHTMLTags(fileSize) %>
</td>
<td width="1%" align="center" valign="top" class="line-bottom-border">
<%
String updateURL = plugin.getURL();
if (updateManager.isPluginDownloaded(updateURL)) {
%>
&nbsp;
<% }
else { %>
<%
%>
<a href="javascript:downloadPlugin('<%=StringUtils.escapeForXML(updateURL)%>', '<%= plugin.hashCode()%>')"><span id="<%= plugin.hashCode() %>-image"><img src="images/add-16x16.gif" width="16" height="16" border="0"
alt="<fmt:message key="plugin.available.download" />"></span></a>
<% } %>
</td>
</tr>
<tr id="<%= plugin.hashCode()%>-row" style="display:none;background: #E7FBDE;">
<td width="1%" class="line-bottom-border">
<img src="<%= StringUtils.escapeForXML(plugin.getIcon())%>" width="16" height="16" alt=""/>
</td>
<td colspan="6" nowrap class="line-bottom-border"><%= StringUtils.escapeHTMLTags(plugin.getName())%> <fmt:message key="plugin.available.installation.success" /></td>
<td class="line-bottom-border" align="center">
<img src="images/success-16x16.gif" height="16" width="16" alt=""/>
</td>
</tr>
<%
}
%>
<tr><td><br/></td></tr>
<tr style="background:#eee;">
<td class="table-header-left">&nbsp;</td>
<td nowrap colspan="7" class="row-header"><fmt:message key="plugin.available.commercial_plugins" /></td>
</tr>
<%
for (AvailablePlugin plugin : plugins) {
String pluginName = plugin.getName();
String pluginDescription = plugin.getDescription();
String pluginAuthor = plugin.getAuthor();
String pluginVersion = plugin.getLatestVersion();
ByteFormat byteFormat = new ByteFormat();
String fileSize = byteFormat.format(plugin.getFileSize());
if (!plugin.isCommercial()) {
continue;
}
%>
<tr id="<%= plugin.hashCode()%>">
<td width="1%" class="line-bottom-border">
<% if (plugin.getIcon() != null) { %>
<img src="<%= StringUtils.escapeForXML(plugin.getIcon()) %>" width="16" height="16" alt="Plugin">
<% }
else { %>
<img src="images/plugin-16x16.gif" width="16" height="16" alt="Plugin">
<% } %>
</td>
<td width="20%" nowrap class="line-bottom-border">
<%= (pluginName != null ? StringUtils.escapeHTMLTags(pluginName) : "") %> &nbsp;
</td>
<td nowrap valign="top" class="line-bottom-border">
<% if (plugin.getReadme() != null) { %>
<a href="<%= StringUtils.escapeForXML(plugin.getReadme()) %>"
><img src="images/doc-readme-16x16.gif" width="16" height="16" border="0" alt="README"></a>
<% }
else { %> &nbsp; <% } %>
<% if (plugin.getChangelog() != null) { %>
<a href="<%= StringUtils.escapeForXML(plugin.getChangelog()) %>"
><img src="images/doc-changelog-16x16.gif" width="16" height="16" border="0" alt="changelog"></a>
<% }
else { %> &nbsp; <% } %>
</td>
<td width="60%" class="line-bottom-border">
<%= pluginDescription != null ? StringUtils.escapeHTMLTags(pluginDescription) : "" %>
</td>
<td width="5%" align="center" valign="top" class="line-bottom-border">
<%= pluginVersion != null ? StringUtils.escapeHTMLTags(pluginVersion) : "" %>
</td>
<td width="15%" nowrap valign="top" class="line-bottom-border">
<%= pluginAuthor != null ? StringUtils.escapeHTMLTags(pluginAuthor) : "" %> &nbsp;
</td>
<td width="15%" nowrap valign="top" class="line-bottom-border">
<%= StringUtils.escapeHTMLTags(fileSize) %>
</td>
<td width="1%" align="center" valign="top" class="line-bottom-border">
<%
String updateURL = plugin.getURL();
if (updateManager.isPluginDownloaded(updateURL)) {
%>
&nbsp;
<% }
else { %>
<span id="<%= plugin.hashCode() %>-image"><a href="javascript:downloadPlugin('<%=StringUtils.escapeForXML(updateURL) %>', '<%= plugin.hashCode() %>')"><img src="images/add-16x16.gif" width="16" height="16" border="0"
alt="<fmt:message key="plugin.available.download" />"></a></span>
<% } %>
</td>
</tr>
<tr id="<%= plugin.hashCode()%>-row" style="display:none;background: #E7FBDE;">
<td width="1%" class="line-bottom-border">
<img src="<%= StringUtils.escapeForXML(plugin.getIcon())%>" width="16" height="16" alt=""/>
</td>
<td colspan="6" nowrap class="line-bottom-border"><%= StringUtils.escapeHTMLTags(plugin.getName())%> <fmt:message key="plugin.available.installation.success" /></td>
<td class="line-bottom-border" align="center">
<img src="images/success-16x16.gif" height="16" width="16" alt=""/>
</td>
</tr>
<%
}
%>
<fmt:message key="plugin.available.info"/>
</p>
<c:choose>
<c:when test="${empty lastCheck}">
<div style="padding:10px;background:#FFEBB5;border:1px solid #DEB24A;width:75%;">
<fmt:message key="plugin.available.no.list" />&nbsp;<span id="reloaderID"><a href="javascript:updatePluginsList();"><fmt:message key="plugin.available.list" /></a></span>
</div>
<br/>
<div style="width:75%;">
<p>
<fmt:message key="plugin.available.no.list.description" />
</p>
<c:if test="${not updateServiceEnabled}">
<fmt:message key="plugin.available.auto.update.currently" /> <b><fmt:message key="plugin.available.auto.update.currently.disabled" /></b>. <a href="manage-updates.jsp"><fmt:message key="plugin.available.click.here" /></a> <fmt:message key="plugin.available.change" />
</c:if>
</div>
</c:when>
<c:otherwise>
<div id="errorMessage" class="error" style="display:none;">
<fmt:message key="plugin.available.error.downloading" />
</div>
<div class="light-gray-border" style="padding:10px;">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<thead>
<tr style="background:#eee;">
<td class="table-header-left">&nbsp;</td>
<td nowrap colspan="2" class="table-header"><fmt:message key="plugin.available.open_source"/></td>
<td nowrap class="table-header"><fmt:message key="plugin.available.description"/></td>
<td nowrap class="table-header"><fmt:message key="plugin.available.version"/></td>
<td nowrap class="table-header"><fmt:message key="plugin.available.author"/></td>
<td nowrap class="table-header">File Size</td>
<td nowrap class="table-header-right"><fmt:message key="plugin.available.install"/></td>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${empty notInstalledPlugins}">
<tr>
<td align="center" colspan="8"><fmt:message key="plugin.available.no_plugin"/></td>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${notInstalledPlugins}" var="notInstalledPlugin">
<tr id="${notInstalledPlugin.hashCode}">
<td width="1%" class="line-bottom-border">
<c:choose>
<c:when test="${not empty notInstalledPlugin.icon}">
<img src="${fn:escapeXml(notInstalledPlugin.icon)}" width="16" height="16" alt="Plugin">
</c:when>
<c:otherwise>
<img src="images/plugin-16x16.gif" width="16" height="16" alt="Plugin">
</c:otherwise>
</c:choose>
</td>
<td width="20%" nowrap class="line-bottom-border">
<c:if test="${not empty notInstalledPlugin.name}">
<c:out value="${notInstalledPlugin.name}"/>
</c:if>
</td>
<td nowrap valign="top" class="line-bottom-border">
<c:if test="${not empty notInstalledPlugin.readme}">
<a href="${fn:escapeXml(notInstalledPlugin.readme)}"><img src="images/doc-readme-16x16.gif" width="16" height="16" border="0" alt="README"></a>
</c:if>
<c:if test="${not empty notInstalledPlugin.changelog}">
<a href="${fn:escapeXml(notInstalledPlugin.changelog)}"><img src="images/doc-changelog-16x16.gif" width="16" height="16" border="0" alt="changelog"></a>
</c:if>
</td>
<td width="60%" class="line-bottom-border">
<c:if test="${not empty notInstalledPlugin.description}">
<c:out value="${notInstalledPlugin.description}"/>
</c:if>
</td>
<td width="5%" nowrap valign="top" class="line-bottom-border">
<c:if test="${not empty notInstalledPlugin.version}">
<c:out value="${notInstalledPlugin.version}"/>
</c:if>
</td>
<td width="15%" nowrap valign="top" class="line-bottom-border">
<c:if test="${not empty notInstalledPlugin.author}">
<c:out value="${notInstalledPlugin.author}"/>
</c:if>
</td>
<td width="15%" nowrap valign="top" class="line-bottom-border" align="right">
<c:out value="${admin:byteFormat( notInstalledPlugin.fileSize )}"/>
</td>
<td width="1%" align="center" valign="top" class="line-bottom-border">
<a href="javascript:downloadPlugin('${fn:escapeXml(notInstalledPlugin.downloadURL)}', '${notInstalledPlugin.hashCode}')">
<span id="${notInstalledPlugin.hashCode}-image">
<img src="images/add-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="plugin.available.download" />">
</span>
</a>
</td>
</tr>
<tr id="${notInstalledPlugin.hashCode}-row" style="display:none;background: #E7FBDE;">
<td width="1%" class="line-bottom-border">
<img src="${fn:escapeXml(notInstalledPlugin.icon)}" width="16" height="16" alt=""/>
</td>
<td colspan="6" nowrap class="line-bottom-border">${admin:escapeHTMLTags(notInstalledPlugin.name)} <fmt:message key="plugin.available.installation.success" /></td>
<td class="line-bottom-border" align="center">
<img src="images/success-16x16.gif" height="16" width="16" alt=""/>
</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
</div>
</c:otherwise>
</c:choose>
</tbody>
</table>
</div>
<br/>
<%
final XMPPServer server = XMPPServer.getInstance();
Version serverVersion = server.getServerInfo().getVersion();
List<Plugin> outdatedPlugins = new ArrayList<Plugin>();
for (Plugin plugin : server.getPluginManager().getPlugins()) {
String pluginVersion = server.getPluginManager().getMinServerVersion(plugin);
if (pluginVersion != null) {
Version pluginMinServerVersion = new Version(pluginVersion);
if (pluginMinServerVersion.isNewerThan(serverVersion)) {
outdatedPlugins.add(plugin);
}
}
}
if (outdatedPlugins.size() > 0) {
%>
<div class="light-gray-border" style="padding:10px;">
<p><fmt:message key="plugin.available.outdated" /><a href="http://www.igniterealtime.org/projects/openfire/" target="_blank"><fmt:message key="plugin.available.outdated.update" /></a></p>
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<%
PluginManager pluginManager = server.getPluginManager();
for (Plugin plugin : outdatedPlugins) {
String pluginName = pluginManager.getName(plugin);
String pluginDescription = pluginManager.getDescription(plugin);
String pluginAuthor = pluginManager.getAuthor(plugin);
String pluginVersion = pluginManager.getVersion(plugin);
File pluginDir = pluginManager.getPluginDirectory(plugin);
File icon = new File(pluginDir, "logo_small.png");
boolean readmeExists = new File(pluginDir, "readme.html").exists();
boolean changelogExists = new File(pluginDir, "changelog.html").exists();
if (!icon.exists()) {
icon = new File(pluginDir, "logo_small.gif");
}
%>
<tr>
<td class="line-bottom-border" width="1%">
<% if (icon.exists()) { %>
<img src="/geticon?plugin=<%= URLEncoder.encode(pluginDir.getName(), "utf-8") %>&showIcon=true&decorator=none" width="16" height="16" alt="Plugin">
<% }
else { %>
<img src="images/plugin-16x16.gif" width="16" height="16" alt="Plugin">
<% } %>
</td>
<td class="line-bottom-border" width="1%" nowrap>
<%= pluginName%>
</td>
<td nowrap class="line-bottom-border">
<p><% if (readmeExists) { %>
<a href="plugin-admin.jsp?plugin=<%= URLEncoder.encode(pluginDir.getName(), "utf-8") %>&showReadme=true&decorator=none"
><img src="images/doc-readme-16x16.gif" width="16" height="16" border="0" alt="README"></a>
<% }
else { %> &nbsp; <% } %>
<% if (changelogExists) { %>
<a href="plugin-admin.jsp?plugin=<%= URLEncoder.encode(pluginDir.getName(), "utf-8") %>&showChangelog=true&decorator=none"
><img src="images/doc-changelog-16x16.gif" width="16" height="16" border="0" alt="changelog"></a>
<% }
else { %> &nbsp; <% } %></p>
</td>
<td class="line-bottom-border">
<%= StringUtils.escapeHTMLTags(pluginDescription) %>
</td>
<td class="line-bottom-border">
<%= StringUtils.escapeHTMLTags(pluginVersion) %>
</td>
<td class="line-bottom-border">
<%= StringUtils.escapeHTMLTags(pluginAuthor) %>
</td>
</tr>
<% }%>
</table>
<%} %>
</div>
<br/>
<%
if(time != null){
Date date = new Date(Long.parseLong(time));
time = JiveGlobals.formatDate(date);
}
%>
<p>
<% if(time != null) { %>
<fmt:message key="plugin.available.autoupdate" /> <%= time%>.
<% } %>
<% if(updateManager.isServiceEnabled()){%>
<fmt:message key="plugin.available.autoupdate.on" />
<% } else { %>
<p>
<c:if test="${not empty lastCheck}">
<fmt:message key="plugin.available.autoupdate" /> <c:out value="${admin:formatDateTime(lastCheck)}" />.
</c:if>
<c:choose>
<c:when test="${updateServiceEnabled}">
<fmt:message key="plugin.available.autoupdate.on" />
</c:when>
<c:otherwise>
<fmt:message key="plugin.available.autoupdate.off" />
<% } %>
&nbsp;<span id="reloader2"><a href="javascript:updatePluginsListNow()"><fmt:message key="plugin.available.manual.update" /></a></span>
</p>
<% } %>
</c:otherwise>
</c:choose>
<span id="reloader2"><a href="javascript:updatePluginsListNow()"><fmt:message key="plugin.available.manual.update" /></a></span>
</p>
</body>
</html>
......@@ -14,28 +14,18 @@
- limitations under the License.
--%>
<%@ page import="org.jivesoftware.util.ParamUtils,
org.jivesoftware.util.CookieUtils,
org.jivesoftware.util.StringUtils,
<%@ page import="org.apache.commons.fileupload.FileItem,
org.apache.commons.fileupload.FileItemFactory,
org.apache.commons.fileupload.FileUploadException,
org.apache.commons.fileupload.disk.DiskFileItemFactory,
org.apache.commons.fileupload.servlet.ServletFileUpload,
org.jivesoftware.openfire.XMPPServer,
org.jivesoftware.openfire.container.Plugin,
org.jivesoftware.openfire.container.PluginManager,
org.jivesoftware.openfire.update.Update"
org.jivesoftware.openfire.container.PluginManager"
%>
<%@ page import="org.jivesoftware.openfire.update.UpdateManager" %>
<%@ page import="java.net.URLEncoder" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.Comparator" %>
<%@ page import="org.jivesoftware.util.*" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.List" %>
<%@ page import="java.io.*" %>
<%@ page import="org.jivesoftware.util.JiveGlobals" %>
<%@ page import="org.jivesoftware.util.Log" %>
<%@ page import="org.apache.commons.fileupload.FileItemFactory" %>
<%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>
<%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>
<%@ page import="org.apache.commons.fileupload.FileItem" %>
<%@ page import="org.apache.commons.fileupload.FileUploadException" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
......@@ -48,8 +38,6 @@
<%
String deletePlugin = ParamUtils.getParameter(request, "deleteplugin");
String reloadPlugin = ParamUtils.getParameter(request, "reloadplugin");
boolean showReadme = ParamUtils.getBooleanParameter(request, "showReadme", false);
boolean showChangelog = ParamUtils.getBooleanParameter(request, "showChangelog", false);
boolean downloadRequested = request.getParameter("download") != null;
boolean uploadPlugin = request.getParameter("uploadplugin") != null;
String url = request.getParameter("url");
......@@ -57,10 +45,14 @@
boolean csrf_check = true;
final PluginManager pluginManager = webManager.getXMPPServer().getPluginManager();
final UpdateManager updateManager = XMPPServer.getInstance().getUpdateManager();
List<Plugin> plugins = new ArrayList<Plugin>(pluginManager.getPlugins());
pageContext.setAttribute( "plugins", pluginManager.getMetadataExtractedPlugins() );
pageContext.setAttribute( "pluginManager", pluginManager );
pageContext.setAttribute( "updateManager", updateManager );
pageContext.setAttribute( "uploadEnabled", uploadEnabled );
pageContext.setAttribute( "serverVersion", XMPPServer.getInstance().getServerInfo().getVersion() );
UpdateManager updateManager = XMPPServer.getInstance().getUpdateManager();
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf");
String csrfParam = ParamUtils.getParameter(request, "csrf");
......@@ -71,14 +63,6 @@
CookieUtils.setCookie(request, response, "csrf", csrfParam, -1);
pageContext.setAttribute("csrf", csrfParam);
if (plugins != null) {
Collections.sort(plugins, new Comparator<Plugin>() {
public int compare(Plugin p1, Plugin p2) {
return pluginManager.getName(p1).compareTo(pluginManager.getName(p2));
}
});
}
if (csrf_check && downloadRequested) {
// Download and install new version of plugin
updateManager.downloadPlugin(url);
......@@ -95,15 +79,14 @@
}
if (csrf_check && reloadPlugin != null) {
for (Plugin plugin : plugins) {
File pluginDir = pluginManager.getPluginDirectory(plugin);
if (reloadPlugin.equals(pluginDir.getName())) {
pluginManager.reloadPlugin(reloadPlugin);
// Log the event
webManager.logEvent("reloaded plugin "+reloadPlugin, null);
response.sendRedirect("plugin-admin.jsp?reloadsuccess=true");
return;
}
if ( pluginManager.reloadPlugin(reloadPlugin) ) {
// Log the event
webManager.logEvent("reloaded plugin "+reloadPlugin, null);
response.sendRedirect("plugin-admin.jsp?reloadsuccess=true");
return;
} else {
response.sendRedirect( "plugin-admin.jsp?reloadsuccess=false" );
return;
}
}
......@@ -156,78 +139,7 @@
}
%>
<% if (showReadme) {
String pluginName = ParamUtils.getParameter(request, "plugin");
Plugin plugin = pluginManager.getPlugin(pluginName);
StringBuilder readmeString = new StringBuilder();
if (plugin != null) {
File readme = new File(pluginManager.getPluginDirectory(plugin), "readme.html");
if (readme.exists()) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(readme), "UTF8"));
String line;
while ((line = in.readLine()) != null) {
readmeString.append(line);
readmeString.append(System.getProperty("line.separator"));
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
finally {
if (in != null) {
try {
in.close();
}
catch (Exception e) {
}
}
}
}
}
%>
<%=readmeString%>
<%
return;
}
%>
<% if (showChangelog) {
String pluginName = ParamUtils.getParameter(request, "plugin");
Plugin plugin = pluginManager.getPlugin(pluginName);
StringBuilder changelogString = new StringBuilder();
if (plugin != null) {
File changelog = new File(pluginManager.getPluginDirectory(plugin), "changelog.html");
if (changelog.exists()) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(changelog), "UTF8"));
String line;
while ((line = in.readLine()) != null) {
changelogString.append(line);
changelogString.append(System.getProperty("line.separator"));
}
}
catch (IOException ioe) {
}
finally {
if (in != null) {
try {
in.close();
}
catch (Exception e) {
}
}
}
}
}
%>
<%=changelogString%>
<%
return;
}
%>
<html>
<head>
......@@ -325,83 +237,61 @@
font-size: 8pt;
}
.update {
tr.regular td,
tr.unsupported td,
tr.update td {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 8pt;
background: #E7FBDE;
border-color: #73CB73;
border-style: solid;
border-width: 0 1px 1px 1px;
padding: 5px;
border-style: solid;
border-width: 0;
}
.update-bottom {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 8pt;
font-weight: bold;
background: #E7FBDE;
border-color: #73CB73;
border-style: solid;
border-width: 0 0 1px 0;
padding: 5px;
tr.regular td {
font-size: 9pt;
border-color: #e3e3e3;
}
.update-bottom-left {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
tr.update td {
font-size: 8pt;
font-weight: bold;
background: #E7FBDE;
border-color: #73CB73;
border-style: solid;
border-width: 0 0 1px 1px;
padding: 5px;
}
.update-bottom-right {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
tr.unsupported td {
font-size: 8pt;
font-weight: bold;
background: #E7FBDE;
border-color: #73CB73;
border-style: solid;
border-width: 0 1px 1px 0;
padding: 5px;
background: #FBCBCC;
border-color: #CB2B18;
}
.update-top {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 9pt;
background: #E7FBDE;
border-color: #73CB73;
border-style: solid;
border-width: 1px 0px 0px 0px;
padding: 5px;
tr.singleline > td {
border-top-width: 0;
border-right-width: 0;
border-bottom-width: 1px;
border-left-width: 0;
}
.update-right {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 8pt;
font-weight: bold;
background: #E7FBDE;
border-color: #73CB73;
border-style: solid;
border-width: 1px 1px 0px 0px;
padding: 5px;
tr.upperhalf > td {
border-top-width: 1px;
}
.line-bottom-border {
text-align: left;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 9pt;
border-color: #e3e3e3;
border-style: solid;
border-width: 0px 0px 1px 0px;
padding: 5px;
tr.upperhalf > td:first-child {
border-top-width: 1px;
border-left-width: 1px;
}
tr.upperhalf > td:last-child {
border-top-width: 1px;
border-right-width: 1px;
}
tr.lowerhalf > td {
border-bottom-width: 1px;
}
tr.lowerhalf > td:first-child {
border-bottom-width: 1px;
border-left-width: 1px;
}
tr.lowerhalf > td:last-child {
border-right-width: 1px;
border-bottom-width: 1px;
}
</style>
......@@ -465,7 +355,7 @@
<p>
<div class="light-gray-border" style="padding:10px;">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="update">
<tr style="background:#eee;">
<td nowrap colspan="3" class="table-header-left"><fmt:message key="plugin.admin.name"/></td>
......@@ -478,164 +368,176 @@
<tbody>
<!-- If only the admin plugin is installed, show "none". -->
<c:if test="${plugins.size() eq 1}">
<tr>
<td align="center" colspan="8" style="padding:5px;"><fmt:message key="plugin.admin.no_plugin"/></td>
</tr>
</c:if>
<c:forEach items="${plugins}" var="entry">
<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)}"/>
<c:set var="maxServerVersionFail" value="${not empty plugin.maxServerVersion and serverVersion.isNewerThan(plugin.maxServerVersion)}"/>
<c:set var="unsupported" value="${ minServerVersionFail or maxServerVersionFail }"/>
<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="${not empty update}">
<c:set var="colorClass" value="update"/>
<c:set var="shapeClass" value="upperhalf"/>
</c:when>
<c:otherwise>
<c:set var="colorClass" value="regular"/>
<c:set var="shapeClass" value="singleline"/>
</c:otherwise>
</c:choose>
<tr valign="top" class="${colorClass} ${shapeClass}">
<td width="1%">
<c:choose>
<c:when test="${not empty plugin.icon}">
<img src="geticon?plugin=${admin:urlEncode(plugin.canonicalName)}&showIcon=true&decorator=none" width="16" height="16" alt="Plugin">
</c:when>
<c:otherwise>
<img src="images/plugin-16x16.gif" width="16" height="16" alt="Plugin">
</c:otherwise>
</c:choose>
</td>
<td width="20%" nowrap valign="top">
<c:out value="${plugin.name}"/>
</td>
<td nowrap valign="top">
<c:if test="${not empty plugin.readme}">
<a href="plugin-showfile.jsp?plugin=${fn:escapeXml(plugin.canonicalName)}&showReadme=true&decorator=none"><img src="images/doc-readme-16x16.gif" width="16" height="16" border="0" alt="README"></a>
</c:if>
<c:if test="${not empty plugin.changelog}">
<a href="plugin-showfile.jsp?plugin=${fn:escapeXml(plugin.canonicalName)}&showChangelog=true&decorator=none"><img src="images/doc-changelog-16x16.gif" width="16" height="16" border="0" alt="changelog"></a>
</c:if>
</td>
<td width="60%" valign="top">
<c:if test="${not empty plugin.description}">
<c:out value="${plugin.description}"/>
</c:if>
</td>
<td width="5%" nowrap valign="top">
<c:if test="${not empty plugin.version}">
<c:out value="${plugin.version}"/>
</c:if>
</td>
<td width="15%" nowrap valign="top">
<c:if test="${not empty plugin.author}">
<c:out value="${plugin.author}"/>
</c:if>
</td>
<td width="1%" style="text-align: center" valign="top">
<c:if test="${pluginManager.isLoaded(plugin.canonicalName)}">
<a href="plugin-admin.jsp?csrf=${csrf}&reloadplugin=${admin:urlEncode( plugin.canonicalName )}"
title="<fmt:message key="plugin.admin.click_reload" />"
><img src="images/refresh-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="global.refresh" /> ${plugin.name}"></a>
</c:if>
</td>
<td width="1%" style="text-align: center" valign="top">
<a href="#" onclick="if (confirm('<fmt:message key="plugin.admin.confirm" />')) { location.replace('plugin-admin.jsp?csrf=${csrf}&deleteplugin=${admin:urlEncode( plugin.canonicalName )}'); } "
title="<fmt:message key="global.click_delete" />"
><img src="images/delete-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="global.delete" /> ${plugin.name}"></a>
</td>
</tr>
<c:if test="${unsupported}">
<!-- 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="${maxServerVersionFail}">
<fmt:message key="plugin.admin.failed.maxserverversion">
<fmt:param value="${plugin.maxServerVersion}"/>
</fmt:message>
</c:if>
</span>
</td>
<td style="${overrideStyle}">&nbsp;</td>
</tr>
<%
// If only the admin plugin is installed, show "none".
if (plugins.size() == 1) {
%>
<tr>
<td align="center" colspan="8" style="padding:5px;"><fmt:message key="plugin.admin.no_plugin"/></td>
</tr>
<%
}
int count = 0;
for (Plugin plugin : plugins) {
String dirName = pluginManager.getPluginDirectory(plugin).getName();
// Skip the admin plugin.
if (!"admin".equals(dirName)) {
count++;
String pluginName = pluginManager.getName(plugin);
String pluginDescription = pluginManager.getDescription(plugin);
String pluginAuthor = pluginManager.getAuthor(plugin);
String pluginVersion = pluginManager.getVersion(plugin);
File pluginDir = pluginManager.getPluginDirectory(plugin);
File icon = new File(pluginDir, "logo_small.png");
if (!icon.exists()) {
icon = new File(pluginDir, "logo_small.gif");
}
// Check if there is an update for this plugin
Update update = updateManager.getPluginUpdate(pluginName, pluginVersion);
%>
<tr valign="top">
<td width="1%" class="<%= update != null ? "update-top-left" : "line-bottom-border"%>">
<% if (icon.exists()) { %>
<img src="geticon?plugin=<%= URLEncoder.encode(pluginDir.getName(), "utf-8") %>&showIcon=true&decorator=none" width="16" height="16" alt="Plugin">
<% }
else { %>
<img src="images/plugin-16x16.gif" width="16" height="16" alt="Plugin">
<% } %>
</td>
<td width="20%" nowrap valign="top" class="<%= update != null ? "update-top" : "line-bottom-border"%>">
<%= (pluginName != null ? pluginName : dirName) %> &nbsp;
<%
boolean readmeExists = new File(pluginDir, "readme.html").exists();
boolean changelogExists = new File(pluginDir, "changelog.html").exists();
%>
</td>
<td nowrap valign="top" class="<%= update != null ? "update-top" : "line-bottom-border"%>">
<p><% if (readmeExists) { %>
<a href="plugin-admin.jsp?plugin=<%= URLEncoder.encode(pluginDir.getName(), "utf-8") %>&showReadme=true&decorator=none"
><img src="images/doc-readme-16x16.gif" width="16" height="16" border="0" alt="README"></a>
<% }
else { %> &nbsp; <% } %>
<% if (changelogExists) { %>
<a href="plugin-admin.jsp?plugin=<%= URLEncoder.encode(pluginDir.getName(), "utf-8") %>&showChangelog=true&decorator=none"
><img src="images/doc-changelog-16x16.gif" width="16" height="16" border="0" alt="changelog"></a>
<% }
else { %> &nbsp; <% } %></p>
</td>
<td width="60%" valign="top" class="<%= update != null ? "update-top" : "line-bottom-border"%>">
<%= pluginDescription != null ? pluginDescription : "" %>
</td>
<td width="5%" nowrap valign="top" class="<%= update != null ? "update-top" : "line-bottom-border"%>">
<%= pluginVersion != null ? pluginVersion : "" %>
</td>
<td width="15%" nowrap valign="top" class="<%= update != null ? "update-top" : "line-bottom-border"%>">
<%= pluginAuthor != null ? pluginAuthor : "" %> &nbsp;
</td>
<td width="1%" align="center" valign="top" class="<%= update != null ? "update-top" : "line-bottom-border"%>">
<a href="plugin-admin.jsp?csrf=${csrf}&reloadplugin=<%= dirName %>"
title="<fmt:message key="plugin.admin.click_reload" />"
><img src="images/refresh-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="global.refresh" />"></a>
</td>
<td width="1%" align="center" valign="top" class="<%= update != null ? "update-right" : "line-bottom-border"%>">
<a href="#" onclick="if (confirm('<fmt:message key="plugin.admin.confirm" />')) { location.replace('plugin-admin.jsp?csrf=${csrf}&deleteplugin=<%= dirName %>'); } "
title="<fmt:message key="global.click_delete" />"
><img src="images/delete-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="global.delete" />"></a>
</td>
</tr>
<% if (update != null) { %>
<tr><td></td></tr>
<!-- End of update section -->
</c:if>
<c:if test="${not empty update}">
<!-- Has Updates, show show -->
<tr id="${update.hashCode()}-row" class="${colorClass} lowerhalf">
<td>&nbsp;</td>
<td nowrap>
<span class="small-label">
<fmt:message key="plugin.admin.version.available">
<fmt:param value="${update.latestVersion}" />
</fmt:message>
</span>
</td>
<td nowrap>
<c:if test="${not empty update.changelog}">
<span class="text">(<a href="${update.changelog}"><fmt:message key="plugin.admin.changelog" /></a>)</span>
</c:if>
</td>
<td>
<table>
<tr>
<td><a href="javascript:download('${update.URL}', '${update.hashCode()}')"><img src="images/icon_update-16x16.gif" width="16" height="16" border="0" alt="changelog"></a></td>
<td><a href="javascript:download('${update.URL}', '${update.hashCode()}')"><span class="small-label"><fmt:message key="plugin.admin.update" /></span></a></td>
</tr>
</table>
</td>
<td colspan="3">&nbsp;</td>
<td colspan="3">&nbsp;</td>
</tr>
<!-- Has Updates, show show -->
<%
String updateURL = update.getURL();
if (updateURL.endsWith(".jar") || updateURL.endsWith(".zip") || updateURL.endsWith(".war")) {
// Change it so that the server downloads and installs the new version of the plugin
updateURL = "plugin-admin.jsp?csrf=" + csrfParam + "download=true&url=" + updateURL;
}
%>
<tr id="<%= update.hashCode() %>-row">
<td class="update-bottom-left">&nbsp;</td>
<td class="update-bottom" nowrap>
<span class="small-label">
<fmt:message key="plugin.admin.version.available">
<fmt:param value="<%= update.getLatestVersion()%>" />
</fmt:message>
</span>
</td>
<td nowrap class="update-bottom">
<% if (update.getChangelog() != null) { %>
<span class="text">(<a href="<%= update.getChangelog()%>"><fmt:message key="plugin.admin.changelog" /></a>)</span>
<% }
else { %>
&nbsp;
<% } %>
</td>
<td class="update-bottom">
<table>
<tr>
<td><a href="javascript:download('<%= update.getURL()%>', '<%=update.hashCode()%>')"><img src="images/icon_update-16x16.gif" width="16" height="16" border="0" alt="changelog"></a></td>
<td><a href="javascript:download('<%= update.getURL()%>', '<%=update.hashCode()%>')"><span class="small-label"><fmt:message key="plugin.admin.update" /></span></a></td>
<tr id="${update.hashCode()}-update" style="display:none;" class="${colorClass} lowerhalf">
<td colspan="8" align="center">
<table>
<tr>
<td id="${update.hashCode()}-image"><img src="images/working-16x16.gif" border="0" alt=""/></td>
<td id="${update.hashCode()}-text" class="table-font"><fmt:message key="plugin.admin.updating" /></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td class="update-bottom" colspan="3">&nbsp;</td>
<td class="update-bottom-right" colspan="3">&nbsp;</td>
</tr>
<tr id="<%= update.hashCode()%>-update" style="display:none;">
<td colspan="8" align="center" class="update">
<table>
<tr>
<td id="<%= update.hashCode()%>-image"><img src="images/working-16x16.gif" border="0" alt=""/></td>
<td id="<%= update.hashCode()%>-text" class="table-font"><fmt:message key="plugin.admin.updating" /></td>
</tr>
</table>
</td>
</tr>
<tr><td></td></tr>
<!-- End of update section -->
</c:if>
</c:if>
<% } %>
<tr><td></td></tr>
</c:forEach>
<!-- End of update section -->
<%
}
}
%>
</tbody>
</table>
</div>
<% if (uploadEnabled) { %>
<br /><br />
<div>
<h3><fmt:message key="plugin.admin.upload_plugin" /></h3>
<p><fmt:message key="plugin.admin.upload_plugin.info" /></p>
<form action="plugin-admin.jsp?uploadplugin&amp;csrf=${csrf}" enctype="multipart/form-data" method="post">
<input type="file" name="uploadfile" />
<input type="submit" value="<fmt:message key="plugin.admin.upload_plugin" />" />
</form>
</div>
<% } %>
<c:if test="${uploadEnabled}">
<br /><br />
<div>
<h3><fmt:message key="plugin.admin.upload_plugin" /></h3>
<p><fmt:message key="plugin.admin.upload_plugin.info" /></p>
<form action="plugin-admin.jsp?uploadplugin&amp;csrf=${csrf}" enctype="multipart/form-data" method="post">
<input type="file" name="uploadfile" />
<input type="submit" value="<fmt:message key="plugin.admin.upload_plugin" />" />
</form>
</div>
</c:if>
</body>
</html>
\ No newline at end of file
<%@ page import="org.jivesoftware.util.ParamUtils" %>
<%@ page import="org.jivesoftware.openfire.container.Plugin" %>
<%@ page import="org.jivesoftware.openfire.container.PluginManager" %>
<%@ page import="org.jivesoftware.openfire.container.PluginMetadataHelper" %>
<%@ page import="java.nio.file.Path" %>
<%@ page import="java.io.*" %><%--
- Copyright (C) 2017 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.
- 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.
--%>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<%
webManager.init(request, response, session, application, out );
final boolean showReadme = ParamUtils.getBooleanParameter( request, "showReadme", false);
final boolean showChangelog = ParamUtils.getBooleanParameter(request, "showChangelog", false);
if (showReadme || showChangelog )
{
final PluginManager pluginManager = webManager.getXMPPServer().getPluginManager();
final String pluginName = ParamUtils.getParameter(request, "plugin" );
final Plugin plugin = pluginManager.getPlugin( pluginName );
if ( plugin != null )
{
final Path filePath;
final Path pluginPath = pluginManager.getPluginPath( plugin );
if ( showChangelog )
{
filePath = pluginPath.resolve( "changelog.html" );
}
else
{
filePath = pluginPath.resolve( "readme.html" );
}
if ( filePath.toFile().exists() )
{
try ( final BufferedReader in = new BufferedReader( new InputStreamReader( new FileInputStream( filePath.toFile() ), "UTF8") ) )
{
String line;
while ((line = in.readLine()) != null)
{
response.getWriter().println( line );
}
}
catch ( IOException ioe )
{
ioe.printStackTrace();
}
}
}
}
%>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment