All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.managers.plugin.PluginManager Maven / Gradle / Ivy

There is a newer version: 2.2.1
Show newest version
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.managers.plugin;

import com.alee.global.GlobalConstants;
import com.alee.managers.log.Log;
import com.alee.managers.plugin.data.*;
import com.alee.utils.*;
import com.alee.utils.compare.Filter;
import com.alee.utils.sort.GraphDataProvider;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Base class for any plugin manager you might want to create.
 *
 * @author Mikle Garin
 * @see How to use PluginManager
 * @see com.alee.managers.plugin.Plugin
 */

public abstract class PluginManager
{
    /**
     * Plugins listeners.
     */
    protected List> listeners = new ArrayList> ( 1 );

    /**
     * Plugin checks lock object.
     */
    protected final Object checkLock = new Object ();

    /**
     * Related plugin managers list.
     * These managers are used to check dependencies load state and some other information later on.
     */
    protected final List relatedManagers = new ArrayList ();

    /**
     * Detected plugins list.
     * All plugins with available descriptions will get into this list.
     */
    protected List> detectedPlugins;

    /**
     * Detected plugins cached by plugin file path.
     */
    protected Map> detectedPluginsByPath = new HashMap> ();

    /**
     * Recently detected plugins list.
     * Contains plugins detected while last plugins check.
     */
    protected List> recentlyDetected;

    /**
     * Special filter to filter out unwanted plugins before their initialization.
     * It is up to developer to specify this filter and its conditions.
     */
    protected Filter> pluginFilter = null;

    /**
     * Whether should allow loading multiply plugins with the same ID or not.
     * In case this is set to false only the newest version of the same plugin will be loaded if more than one provided.
     */
    protected boolean allowSimilarPlugins = false;

    /**
     * Whether plugin manager logging is enabled or not.
     */
    protected boolean loggingEnabled = true;

    /**
     * Loaded and running plugins list.
     * This might be less than list of detected plugins in the end due to lots of different reasons.
     * Only those plugins which are actually loaded successfully are getting added here.
     */
    protected List availablePlugins;

    /**
     * Map of plugins cached by their IDs.
     */
    protected Map availablePluginsById = new HashMap ();

    /**
     * Map of plugins cached by their classes.
     */
    protected Map, T> availablePluginsByClass = new HashMap, T> ();

    /**
     * Recently initialized plugins list.
     * Contains plugins initialized while last plugins check.
     */
    protected List recentlyInitialized;

    /**
     * Plugins directory path.
     * It is either absolute path or relative to working directory path.
     */
    protected String pluginsDirectoryPath;

    /**
     * Whether plugins directory subfolders should be checked recursively or not.
     */
    protected boolean checkRecursively;

    /**
     * Plugin directory files filter.
     * By default "*.jar" and "*.plugin" files are accepted.
     */
    protected FileFilter fileFilter;

    /**
     * Whether should create exclusive class loader for each plugin or not.
     * Be aware that you might experience various classpath issues with exclusive class loaders unless you know what you are doing.
     */
    protected boolean createNewClassLoader = false;

    /**
     * Constructs new plugin manager.
     */
    public PluginManager ()
    {
        this ( null );
    }

    /**
     * Constructs new plugin manager.
     *
     * @param pluginsDirectoryPath plugins directory path
     */
    public PluginManager ( final String pluginsDirectoryPath )
    {
        this ( pluginsDirectoryPath, true );
    }

    /**
     * Constructs new plugin manager.
     *
     * @param pluginsDirectoryPath plugins directory path
     * @param checkRecursively     whether plugins directory subfolders should be checked recursively or not
     */
    public PluginManager ( final String pluginsDirectoryPath, final boolean checkRecursively )
    {
        super ();

        // User settings
        this.pluginsDirectoryPath = pluginsDirectoryPath;
        this.checkRecursively = checkRecursively;

        // Default file filter
        this.fileFilter = new FileFilter ()
        {
            @Override
            public boolean accept ( final File file )
            {
                final String name = file.getName ().toLowerCase ( Locale.ROOT );
                return name.endsWith ( ".jar" ) || name.endsWith ( ".plugin" );
            }
        };

        // Runtime variables
        detectedPlugins = new ArrayList> ();
        availablePlugins = new ArrayList ( detectedPlugins.size () );
    }

    /**
     * Returns name of the plugin descriptor file.
     * This file should contain serialized PluginInformation.
     *
     * @return name of the plugin descriptor file
     */
    protected String getPluginDescriptorFile ()
    {
        return "plugin.xml";
    }

    /**
     * Returns name of the plugin logo file.
     * Logo should be placed near the plugin descriptor file.
     *
     * @return name of the plugin logo file
     */
    protected String getPluginLogoFile ()
    {
        return "logo.png";
    }

    /**
     * Returns accepted by this manager plugin type.
     * In case {@code null} is returned this manager accepts any plugin type.
     *
     * @return accepted by this manager plugin type
     */
    protected String getAcceptedPluginType ()
    {
        return null;
    }

    /**
     * Adds plugin manager into related managers list.
     * These managers are used to check dependencies load state and some other information later on.
     *
     * @param manager plugin manager to add into related managers list
     */
    public void addRelatedManager ( final PluginManager manager )
    {
        if ( !relatedManagers.contains ( manager ) )
        {
            relatedManagers.add ( manager );
        }
        else
        {
            Log.get ().warn ( ReflectUtils.getClassName ( manager.getClass () ) + " is already added into related managers list" );
        }
    }

    /**
     * Removes plugin manager from related managers list.
     * These managers are used to check dependencies load state and some other information later on.
     *
     * @param manager plugin manager to add into related managers list
     */
    public void removeRelatedManager ( final PluginManager manager )
    {
        relatedManagers.remove ( manager );
    }

    /**
     * Registers programmatically loaded plugin within this PluginManager.
     * This call will add the specified plugin into available plugins list.
     * It will also create a custom DetectedPlugin data based on provided information.
     *
     * @param plugin plugin to register
     */
    public void registerPlugin ( final T plugin )
    {
        registerPlugin ( plugin, plugin.getPluginInformation (), GraphicsEnvironment.isHeadless () ? null : plugin.getPluginLogo () );
    }

    /**
     * Registers programmatically loaded plugin within this PluginManager.
     * This call will add the specified plugin into available plugins list.
     * It will also create a custom DetectedPlugin data based on provided information.
     *
     * @param plugin      plugin to register
     * @param information about this plugin
     * @param logo        plugin logo
     */
    public void registerPlugin ( final T plugin, final PluginInformation information, final ImageIcon logo )
    {
        final String prefix = "[" + information + "] ";
        Log.info ( this, prefix + "Initializing pre-loaded plugin..." );

        // Creating base detected plugin information
        final DetectedPlugin detectedPlugin = new DetectedPlugin ( null, null, information, logo );
        detectedPlugin.setStatus ( PluginStatus.loaded );
        detectedPlugin.setPlugin ( plugin );
        plugin.setPluginManager ( PluginManager.this );
        plugin.setDetectedPlugin ( detectedPlugin );

        // Saving plugin
        detectedPlugins.add ( detectedPlugin );
        availablePlugins.add ( plugin );
        availablePluginsById.put ( plugin.getId (), plugin );
        availablePluginsByClass.put ( plugin.getClass (), plugin );

        Log.info ( this, prefix + "Pre-loaded plugin initialized" );

        // Informing
        firePluginsInitialized ( Arrays.asList ( plugin ) );
    }

    /**
     * Downloads plugin from the specified URL and tries to load it.
     * In case the file is not a plugin it will simply be ignored.
     * Plugins added this way will also be filtered and checked for other means.
     *
     * @param pluginFileURL plugin file URL
     */
    public void scanPlugin ( final URL pluginFileURL )
    {
        try
        {
            final String url = pluginFileURL.toURI ().toASCIIString ();
            final File tmpFile = File.createTempFile ( "plugin", ".jar" );
            final File downloadedPlugin = FileUtils.downloadFile ( url, tmpFile );
            scanPlugin ( downloadedPlugin );
        }
        catch ( final URISyntaxException e )
        {
            Log.error ( this, "Unable to parse plugin URL", e );
        }
        catch ( final IOException e )
        {
            Log.error ( this, "Unable to create local file to download plugin", e );
        }
    }

    /**
     * Tries to load plugin from the specified file.
     * In case the file is not a plugin it will simply be ignored.
     * Plugins added this way will also be filtered and checked for other means.
     *
     * @param pluginFile plugin file path
     */
    public void scanPlugin ( final String pluginFile )
    {
        scanPlugin ( new File ( pluginFile ) );
    }

    /**
     * Tries to load plugin from the specified file.
     * In case the file is not a plugin it will simply be ignored.
     * Plugins added this way will also be filtered and checked for other means.
     *
     * @param pluginFile plugin file
     */
    public void scanPlugin ( final File pluginFile )
    {
        synchronized ( checkLock )
        {
            Log.info ( this, "Scanning plugin file: " + FileUtils.canonicalPath ( pluginFile ) );

            // Resetting recently detected plugins list
            recentlyDetected = new ArrayList> ();

            // Collecting plugins information
            if ( collectPluginInformation ( pluginFile ) )
            {
                // Initializing detected plugins
                initializeDetectedPlugins ();
            }
        }
    }

    /**
     * Performs plugins search within the specified plugins directory.
     * This call might be performed as many times as you like.
     * It will simply ignore plugins detected before and will process newly found plugins appropriately.
     */
    public void scanPluginsDirectory ()
    {
        scanPluginsDirectory ( pluginsDirectoryPath, checkRecursively );
    }

    /**
     * Performs plugins search within the specified plugins directory.
     * This call might be performed as many times as you like.
     * It will simply ignore plugins detected before and will process newly found plugins appropriately.
     *
     * @param checkRecursively whether plugins directory subfolders should be checked recursively or not
     */
    public void scanPluginsDirectory ( final boolean checkRecursively )
    {
        scanPluginsDirectory ( pluginsDirectoryPath, checkRecursively );
    }

    /**
     * Performs plugins search within the specified plugins directory.
     * This call might be performed as many times as you like.
     * It will simply ignore plugins detected before and will process newly found plugins appropriately.
     *
     * @param pluginsDirectoryPath plugins directory path
     */
    public void scanPluginsDirectory ( final String pluginsDirectoryPath )
    {
        scanPluginsDirectory ( pluginsDirectoryPath, checkRecursively );
    }

    /**
     * Performs plugins search within the specified plugins directory.
     * This call might be performed as many times as you like.
     * It will simply ignore plugins detected before and will process newly found plugins appropriately.
     *
     * @param pluginsDirectoryPath plugins directory path
     * @param checkRecursively     whether plugins directory subfolders should be checked recursively or not
     */
    public void scanPluginsDirectory ( final String pluginsDirectoryPath, final boolean checkRecursively )
    {
        synchronized ( checkLock )
        {
            // Ignore check if check path is not specified
            if ( pluginsDirectoryPath == null )
            {
                return;
            }

            // Informing about plugins check start
            firePluginsCheckStarted ( pluginsDirectoryPath, checkRecursively );

            // Resetting recently detected plugins list
            recentlyDetected = new ArrayList> ();

            // Collecting plugins information
            if ( collectPluginsInformation ( pluginsDirectoryPath, checkRecursively ) )
            {
                // Initializing detected plugins
                initializeDetectedPlugins ();
            }

            // Informing about plugins check end
            firePluginsCheckEnded ( pluginsDirectoryPath, checkRecursively );
        }
    }

    /**
     * Collects information about available plugins.
     *
     * @return true if operation succeeded, false otherwise
     */
    protected boolean collectPluginsInformation ( final String pluginsDirectoryPath, final boolean checkRecursively )
    {
        if ( pluginsDirectoryPath != null )
        {
            collectPluginsInformationImpl ( new File ( pluginsDirectoryPath ), checkRecursively );
            sortRecentlyDetectedPluginsByDependencies ();
            return true;
        }
        else
        {
            Log.warn ( this, "Plugins directory is not yet specified" );
            return false;
        }
    }

    /**
     * Collects information about available plugins.
     *
     * @param dir plugins directory
     */
    protected void collectPluginsInformationImpl ( final File dir, final boolean checkRecursively )
    {
        Log.info ( this, "Scanning plugins directory" + ( checkRecursively ? " recursively" : "" ) + ": " + pluginsDirectoryPath );

        // Checking all files
        final File[] files = dir.listFiles ( getFileFilter () );
        if ( files != null )
        {
            for ( final File file : files )
            {
                collectPluginInformation ( file );
            }
        }

        // Checking sub-directories recursively
        if ( checkRecursively )
        {
            final File[] subfolders = dir.listFiles ( GlobalConstants.DIRECTORIES_FILTER );
            if ( subfolders != null )
            {
                for ( final File subfolder : subfolders )
                {
                    collectPluginsInformationImpl ( subfolder, checkRecursively );
                }
            }
        }
    }

    /**
     * Tries to collect plugin information from the specified file.
     * This call will simply be ignored if this is not a plugin file or if something goes wrong.
     *
     * @param file plugin file to process
     * @return true if operation succeeded, false otherwise
     */
    protected boolean collectPluginInformation ( final File file )
    {
        final DetectedPlugin plugin = getPluginInformation ( file );
        if ( plugin != null )
        {
            recentlyDetected.add ( plugin );
            Log.info ( this, "Plugin detected: " + plugin );
            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * Tries to sort recently detected plugins list by known plugin dependencies.
     * This sorting will have effect only if dependencies are pointing at plugins of the same type.
     * 

* In case you setup dependencies on other type of plugin you will have to manually check whether those are loaded or not. * That can be done by setting plugin filter into this manager and checking dependencies there. */ protected void sortRecentlyDetectedPluginsByDependencies () { if ( recentlyDetected.size () > 1 ) { try { Log.info ( this, "Sorting detected plugins according to known dependencies" ); // Collecting plugins that doesn't have any dependencies or their dependencies are loaded // Also mapping dependencies for quick access later final int s = recentlyDetected.size (); final List> root = new ArrayList> ( s ); final Map>> references = new HashMap>> (); for ( final DetectedPlugin plugin : recentlyDetected ) { final List dependencies = plugin.getInformation ().getDependencies (); if ( dependencies != null ) { // Iterating through detected plugin dependencies boolean dependenciesMet = true; for ( final PluginDependency dependency : dependencies ) { // Checking whether or not each dependency is met boolean met = false; for ( final T availablePlugin : availablePlugins ) { // todo This is a bad optional workaround, it should actually take part in sorting if ( dependency.isOptional () || dependency.accept ( availablePlugin ) ) { met = true; break; } } if ( !met ) { dependenciesMet = false; } // Mapping dependency references final String id = dependency.getPluginId (); List> ref = references.get ( id ); if ( ref == null ) { ref = new ArrayList> ( 1 ); references.put ( id, ref ); } ref.add ( plugin ); } // Adding plugin into root plugins list if its dependencies are met if ( dependenciesMet ) { root.add ( plugin ); } } else { // Adding plugin into root plugins list as it doesn't have any dependencies root.add ( plugin ); } } // Creating graph provider for further topological sorting final GraphDataProvider> graphDataProvider = new GraphDataProvider> () { @Override public List> getRoots () { return root; } @Override public List> getChildren ( final DetectedPlugin data ) { final List> children = references.get ( data.getInformation ().getId () ); return children != null ? children : Collections.EMPTY_LIST; } }; // Performing topological sorting // Saving result as new recently detected plugins list final List> sorted = SortUtils.doTopologicalSort ( graphDataProvider ); // Adding plugins which didn't get into graph into the end // There might be such plugin for example in case it has some side dependencies // It might still be properly initialized, we just don't know anything about its dependencies // Such plugins should be initialized after anything else to increase their chances for ( final DetectedPlugin plugin : recentlyDetected ) { if ( !sorted.contains ( plugin ) ) { sorted.add ( plugin ); } } recentlyDetected = sorted; } catch ( final Throwable e ) { Log.warn ( this, "Unable to perform proper dependencies sorting", e ); } } } /** * Returns plugin information from the specified plugin file. * Returns null in case plugin file cannot be read or if it is incorrect. * * @param file plugin file to process * @return plugin information from the specified plugin file or null */ protected DetectedPlugin getPluginInformation ( final File file ) { try { final String pluginDescriptor = getPluginDescriptorFile (); final String pluginLogo = getPluginLogoFile (); final ZipFile zipFile = new ZipFile ( file ); final Enumeration entries = zipFile.entries (); while ( entries.hasMoreElements () ) { final ZipEntry entry = ( ZipEntry ) entries.nextElement (); if ( entry.getName ().endsWith ( pluginDescriptor ) ) { // Reading plugin information final InputStream inputStream = zipFile.getInputStream ( entry ); final PluginInformation info = XmlUtils.fromXML ( inputStream ); inputStream.close (); // Reading plugin icon final ZipEntry logoEntry = new ZipEntry ( ZipUtils.getZipEntryFileLocation ( entry ) + pluginLogo ); final InputStream logoInputStream = zipFile.getInputStream ( logoEntry ); final ImageIcon logo; if ( logoInputStream != null ) { logo = new ImageIcon ( ImageIO.read ( logoInputStream ) ); logoInputStream.close (); } else { logo = null; } // Checking whether we have already detected this plugin or not if ( !wasDetected ( file.getParent (), file.getName () ) ) { // Cache and return new plugin information // This cache map is filled here since it has different usage cases final DetectedPlugin plugin = new DetectedPlugin ( file.getParent (), file.getName (), info, logo ); detectedPluginsByPath.put ( FileUtils.canonicalPath ( file ), plugin ); return plugin; } break; } } zipFile.close (); } catch ( final IOException e ) { Log.error ( this, e ); } return null; } /** * Returns plugin information from the specified plugin file. * * @param file plugin file to process * @return plugin information from the specified plugin file */ public DetectedPlugin getDetectedPlugin ( final File file ) { if ( file == null ) { return null; } final String path = FileUtils.canonicalPath ( file ); if ( detectedPluginsByPath.containsKey ( path ) ) { // Cached plugin information return detectedPluginsByPath.get ( path ); } else { // Loading plugin information return getPluginInformation ( file ); } } /** * Returns whether this plugin file was already detected before or not. * * @param pluginFolder plugin directory * @param pluginFile plugin file * @return true if this plugin file was already detected before, false otherwise */ protected boolean wasDetected ( final String pluginFolder, final String pluginFile ) { for ( final DetectedPlugin plugin : detectedPlugins ) { if ( plugin.getPluginFileName () != null && plugin.getPluginFolder ().equals ( pluginFolder ) && plugin.getPluginFileName ().equals ( pluginFile ) ) { return true; } } return false; } /** * Initializes earlier detected plugins. * Also informs listeners about appropriate events. */ protected void initializeDetectedPlugins () { if ( !recentlyDetected.isEmpty () ) { // Informing about newly detected plugins firePluginsDetected ( recentlyDetected ); Log.info ( this, "Initializing plugins..." ); // Initializing plugins initializeDetectedPluginsImpl (); // Sorting plugins according to their initialization strategies applyInitializationStrategy (); // Properly sorting recently initialized plugins Collections.sort ( recentlyInitialized, new Comparator () { @Override public int compare ( final T o1, final T o2 ) { final Integer i1 = availablePlugins.indexOf ( o1 ); final Integer i2 = availablePlugins.indexOf ( o2 ); return i1.compareTo ( i2 ); } } ); // Informing about new plugins initialization firePluginsInitialized ( recentlyInitialized ); Log.info ( this, "Plugins initialization finished" ); } else { Log.info ( this, "No new plugins found" ); } } /** * Initializes earlier detected plugins. */ protected void initializeDetectedPluginsImpl () { // Map to store plugin libraries final Map> pluginLibraries = new HashMap> (); // List to collect newly initialized plugins // This is required to properly inform about newly loaded plugins later recentlyInitialized = new ArrayList (); // Adding recently detected into the end of the detected plugins list detectedPlugins.addAll ( recentlyDetected ); // Initializing detected plugins final String acceptedPluginType = getAcceptedPluginType (); for ( final DetectedPlugin dp : detectedPlugins ) { // Skip plugins we have already tried to initialize if ( dp.getStatus () != PluginStatus.detected ) { continue; } final File pluginFile = dp.getFile (); final PluginInformation info = dp.getInformation (); final String prefix = "[" + FileUtils.getRelativePath ( pluginFile, new File ( pluginsDirectoryPath ) ) + "] [" + info + "] "; try { // Starting to load plugin now Log.info ( this, prefix + "Initializing plugin..." ); dp.setStatus ( PluginStatus.loading ); // Checking plugin type as we don't want (for example) to load server plugins on client side if ( acceptedPluginType != null && ( info.getType () == null || !info.getType ().equals ( acceptedPluginType ) ) ) { Log.error ( this, prefix + "Plugin of type \"" + info.getType () + "\" cannot be loaded, " + "required plugin type is \"" + acceptedPluginType + "\"" ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Wrong type" ); dp.setExceptionMessage ( "Detected plugin type: " + info.getType () + "\", " + "required plugin type: \"" + acceptedPluginType + "\"" ); continue; } // Checking that this is latest plugin version of all available // Usually there shouldn't be different versions of the same plugin but everyone make mistakes if ( isDeprecatedVersion ( dp ) ) { Log.warn ( this, prefix + "This plugin is deprecated, newer version loaded instead" ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Deprecated" ); dp.setExceptionMessage ( "This plugin is deprecated, newer version loaded instead" ); continue; } // Checking that this plugin version is not yet loaded // This might occur in case the same plugin appears more than once in different files if ( isSameVersionAlreadyLoaded ( dp, detectedPlugins ) ) { Log.warn ( this, prefix + "Plugin is duplicate, it will be loaded from another file" ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Duplicate" ); dp.setExceptionMessage ( "This plugin is duplicate, it will be loaded from another file" ); continue; } // Checking that plugin filter accepts this plugin if ( getPluginFilter () != null && !getPluginFilter ().accept ( dp ) ) { Log.info ( this, prefix + "Plugin was not accepted by plugin filter" ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Filtered" ); dp.setExceptionMessage ( "Plugin was not accepted by plugin filter" ); continue; } // Checking plugin dependencies final List dependencies = dp.getInformation ().getDependencies (); if ( dependencies != null ) { for ( final PluginDependency dependency : dependencies ) { // Checking whether or not dependency is mandatory and whether or not it is available final String did = dependency.getPluginId (); if ( !dependency.isOptional () && !isPluginAvailable ( did ) ) { // If it is mandatory and not available - check related managers for that dependency boolean available = false; for ( final PluginManager relatedManager : relatedManagers ) { if ( relatedManager.isPluginAvailable ( did ) ) { available = true; break; } } if ( !available ) { Log.error ( this, prefix + "Mandatory plugin dependency was not found: " + did ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Incomplete" ); dp.setExceptionMessage ( "Mandatory plugin dependency was not found: " + did ); break; } } } if ( dp.getStatus () == PluginStatus.failed ) { continue; } } // Collecting plugin and its libraries JAR paths final List jarPaths = new ArrayList ( 1 + info.getLibrariesCount () ); jarPaths.add ( pluginFile.toURI ().toURL () ); if ( info.getLibraries () != null ) { for ( final PluginLibrary library : info.getLibraries () ) { final File file = new File ( dp.getPluginFolder (), library.getFile () ); if ( file.exists () ) { // Adding library URI to path jarPaths.add ( file.toURI ().toURL () ); // Saving library information for further checks Map libraries = pluginLibraries.get ( library.getId () ); if ( libraries == null ) { libraries = new HashMap ( 1 ); pluginLibraries.put ( library.getId (), libraries ); } libraries.put ( library, info ); } else { Log.error ( this, prefix + "Plugin library was not found: " + file.getAbsolutePath () ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Incomplete" ); dp.setExceptionMessage ( "Plugin library was not found: " + file.getAbsolutePath () ); break; } } if ( dp.getStatus () == PluginStatus.failed ) { continue; } } try { // Choosing class loader final ClassLoader cl = getClass ().getClassLoader (); final ClassLoader classLoader; if ( createNewClassLoader || !( cl instanceof URLClassLoader ) ) { // todo Use single class loader for all plugins within this manager (or all managers?) // Create new class loader classLoader = URLClassLoader.newInstance ( jarPaths.toArray ( new URL[ jarPaths.size () ] ), cl ); } else { // Use current class loader classLoader = cl; for ( final URL url : jarPaths ) { ReflectUtils.callMethodSafely ( classLoader, "addURL", url ); } } // Loading plugin final Class pluginClass = classLoader.loadClass ( info.getMainClass () ); final T plugin = ReflectUtils.createInstance ( pluginClass ); plugin.setPluginManager ( PluginManager.this ); plugin.setDetectedPlugin ( dp ); // Saving initialized plugin availablePlugins.add ( plugin ); availablePluginsById.put ( plugin.getId (), plugin ); availablePluginsByClass.put ( plugin.getClass (), plugin ); recentlyInitialized.add ( plugin ); // Updating detected plugin status Log.info ( this, prefix + "Plugin initialized" ); dp.setStatus ( PluginStatus.loaded ); dp.setPlugin ( plugin ); } catch ( final Throwable e ) { // Something happened while performing plugin class load Log.error ( this, prefix + "Unable to initialize plugin", e ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Internal exception" ); dp.setException ( e ); } } catch ( final Throwable e ) { // Something happened while checking plugin information Log.error ( this, prefix + "Unable to initialize plugin data", e ); dp.setStatus ( PluginStatus.failed ); dp.setFailureCause ( "Data exception" ); dp.setException ( e ); } } // Checking for same/similar libraries used within plugins boolean sameLibrariesInPlugins = false; for ( final Map.Entry> libraries : pluginLibraries.entrySet () ) { final Map sameLibraries = libraries.getValue (); if ( sameLibraries.size () > 1 ) { final String title = sameLibraries.keySet ().iterator ().next ().getTitle (); final StringBuilder sb = new StringBuilder ( "Library [ " ).append ( title ).append ( " ] was found in plugins: " ); for ( final Map.Entry library : sameLibraries.entrySet () ) { final PluginInformation plugin = library.getValue (); final String libraryVersion = library.getKey ().getVersion (); sb.append ( "[ " ).append ( plugin.toString () ).append ( ", version " ).append ( libraryVersion ).append ( " ] " ); } Log.warn ( this, sb.toString () ); sameLibrariesInPlugins = true; break; } } if ( sameLibrariesInPlugins ) { Log.warn ( this, "Make sure that the same library usage within different plugins was actually your intent" ); } } /** * Returns whether the list of detected plugins contain a newer version of the specified plugin or not. * * @param plugin plugin to compare with other detected plugins * @return true if the list of detected plugins contain a newer version of the specified plugin, false otherwise */ public boolean isDeprecatedVersion ( final DetectedPlugin plugin ) { return isDeprecatedVersion ( plugin, detectedPlugins ); } /** * Returns whether the list of detected plugins contain a newer version of the specified plugin or not. * * @param plugin plugin to compare with other detected plugins * @param detectedPlugins list of detected plugins * @return true if the list of detected plugins contain a newer version of the specified plugin, false otherwise */ public boolean isDeprecatedVersion ( final DetectedPlugin plugin, final List> detectedPlugins ) { final PluginInformation pluginInfo = plugin.getInformation (); for ( final DetectedPlugin detectedPlugin : detectedPlugins ) { if ( detectedPlugin != plugin ) { final PluginInformation detectedPluginInfo = detectedPlugin.getInformation (); if ( detectedPluginInfo.getId ().equals ( pluginInfo.getId () ) && detectedPluginInfo.getVersion () != null && pluginInfo.getVersion () != null && detectedPluginInfo.getVersion ().isNewerThan ( pluginInfo.getVersion () ) ) { return true; } } } return false; } /** * Returns whether the list of detected plugins contain the same version of the specified plugin or not. * * @param plugin plugin to compare with other detected plugins * @param detectedPlugins list of detected plugins * @return true if the list of detected plugins contain the same version of the specified plugin, false otherwise */ protected boolean isSameVersionAlreadyLoaded ( final DetectedPlugin plugin, final List> detectedPlugins ) { final PluginInformation pluginInfo = plugin.getInformation (); for ( final DetectedPlugin detectedPlugin : detectedPlugins ) { if ( detectedPlugin != plugin ) { final PluginInformation detectedPluginInfo = detectedPlugin.getInformation (); if ( detectedPluginInfo.getId ().equals ( pluginInfo.getId () ) && ( detectedPluginInfo.getVersion () == null && pluginInfo.getVersion () == null || detectedPluginInfo.getVersion ().isSame ( pluginInfo.getVersion () ) ) && detectedPlugin.getStatus () == PluginStatus.loaded ) { return true; } } } return false; } /** * Sorting plugins according to their initialization strategies. */ protected void applyInitializationStrategy () { // Skip if no available plugins if ( availablePlugins.size () == 0 ) { return; } // todo Take plugin dependencies into account with top priority here // Splitting plugins by initial groups final List beforeAll = new ArrayList ( availablePlugins.size () ); final List middle = new ArrayList ( availablePlugins.size () ); final List afterAll = new ArrayList ( availablePlugins.size () ); for ( final T plugin : availablePlugins ) { final InitializationStrategy strategy = plugin.getInitializationStrategy (); if ( strategy.getId ().equals ( InitializationStrategy.ALL_ID ) ) { switch ( strategy.getType () ) { case before: { beforeAll.add ( plugin ); break; } case any: { middle.add ( plugin ); break; } case after: { afterAll.add ( plugin ); break; } } } else { middle.add ( plugin ); } } // Sorting plugins in appropriate order // This order is not used by PluginManager itself due to possible unstructured plugin loading if ( middle.size () == 0 ) { // Combining all plugins into single list availablePlugins.clear (); availablePlugins.addAll ( beforeAll ); availablePlugins.addAll ( afterAll ); } else { // Sorting middle plugins properly final List sortedMiddle = new ArrayList ( middle ); for ( final T plugin : middle ) { final InitializationStrategy strategy = plugin.getInitializationStrategy (); final String id = strategy.getId (); if ( !plugin.getId ().equals ( id ) ) { final int oldIndex = sortedMiddle.indexOf ( plugin ); for ( int index = 0; index < sortedMiddle.size (); index++ ) { if ( sortedMiddle.get ( index ).getId ().equals ( id ) ) { switch ( strategy.getType () ) { case before: { sortedMiddle.remove ( oldIndex ); if ( oldIndex < index ) { sortedMiddle.add ( index - 1, plugin ); } else { sortedMiddle.add ( index, plugin ); } break; } case after: { sortedMiddle.remove ( oldIndex ); if ( oldIndex < index ) { sortedMiddle.add ( index, plugin ); } else { sortedMiddle.add ( index + 1, plugin ); } break; } } break; } } } } // Combining all plugins into single list availablePlugins.clear (); availablePlugins.addAll ( beforeAll ); availablePlugins.addAll ( sortedMiddle ); availablePlugins.addAll ( afterAll ); } } /** * Returns list of detected plugins. * * @return list of detected plugins */ public List> getDetectedPlugins () { return CollectionUtils.copy ( detectedPlugins ); } /** * Returns list of available loaded plugins. * * @return list of available loaded plugins */ public List getAvailablePlugins () { return CollectionUtils.copy ( availablePlugins ); } /** * Returns available plugin instance by its ID. * * @param pluginId plugin ID * @return available plugin instance by its ID */ public

P getPlugin ( final String pluginId ) { return ( P ) availablePluginsById.get ( pluginId ); } /** * Returns whether plugin is available or not. * * @param pluginId plugin ID * @return true if plugin is available, false otherwise */ public boolean isPluginAvailable ( final String pluginId ) { return getPlugin ( pluginId ) != null; } /** * Returns available plugin instance by its class. * * @param pluginClass plugin class * @return available plugin instance by its class */ public

P getPlugin ( final Class

pluginClass ) { return ( P ) availablePluginsByClass.get ( pluginClass ); } /** * Returns amount of detected plugins. * * @return amount of detected loaded plugins */ public int getDetectedPluginsAmount () { return getDetectedPlugins ().size (); } /** * Returns amount of successfully loaded plugins. * * @return amount of successfully loaded plugins */ public int getLoadedPluginsAmount () { return getAvailablePlugins ().size (); } /** * Returns amount of plugins which have failed to load. * There might be a lot of reasons why they failed to load - exception, broken JAR, missing libraries etc. * Simply check the log or retrieve failure cause from DetectedPlugin to understand what happened. * * @return amount of plugins which have failed to load */ public int getFailedPluginsAmount () { return getDetectedPlugins ().size () - getAvailablePlugins ().size (); } /** * Returns plugins directory path. * * @return plugins directory path */ public String getPluginsDirectoryPath () { return pluginsDirectoryPath; } /** * Sets plugins directory path. * * @param path new plugins directory path */ public void setPluginsDirectoryPath ( final String path ) { this.pluginsDirectoryPath = path; } /** * Returns whether plugins directory subfolders should be checked recursively or not. * * @return true if plugins directory subfolders should be checked recursively, false otherwise */ public boolean isCheckRecursively () { return checkRecursively; } /** * Sets whether plugins directory subfolders should be checked recursively or not. * * @param checkRecursively whether plugins directory subfolders should be checked recursively or not */ public void setCheckRecursively ( final boolean checkRecursively ) { this.checkRecursively = checkRecursively; } /** * Returns plugins directory file filter. * * @return plugins directory file filter */ public FileFilter getFileFilter () { return fileFilter; } /** * Sets plugins directory file filter. * Note that setting this filter will not have any effect on plugins which are already initialized. * * @param filter plugins directory file filter */ public void setFileFilter ( final FileFilter filter ) { this.fileFilter = filter; } /** * Returns whether should create new class loader for each loaded plugin or not. * * @return true if should create new class loader for each loaded plugin, false otherwise */ public boolean isCreateNewClassLoader () { return createNewClassLoader; } /** * Sets whether should create new class loader for each loaded plugin or not. * * @param createNewClassLoader whether should create new class loader for each loaded plugin or not */ public void setCreateNewClassLoader ( final boolean createNewClassLoader ) { this.createNewClassLoader = createNewClassLoader; } /** * Returns special filter that filters out unwanted plugins before their initialization. * * @return special filter that filters out unwanted plugins before their initialization */ public Filter> getPluginFilter () { return pluginFilter; } /** * Sets special filter that filters out unwanted plugins before their initialization. * * @param pluginFilter special filter that filters out unwanted plugins before their initialization */ public void setPluginFilter ( final Filter> pluginFilter ) { this.pluginFilter = pluginFilter; } /** * Returns whether should allow loading multiply plugins with the same ID or not. * * @return true if should allow loading multiply plugins with the same ID, false otherwise */ public boolean isAllowSimilarPlugins () { return allowSimilarPlugins; } /** * Sets whether should allow loading multiply plugins with the same ID or not. * * @param allow whether should allow loading multiply plugins with the same ID or not */ public void setAllowSimilarPlugins ( final boolean allow ) { this.allowSimilarPlugins = allow; } /** * Returns whether plugin manager logging is enabled or not. * * @return true if plugin manager logging is enabled, false otherwise */ public boolean isLoggingEnabled () { return loggingEnabled; } /** * Sets whether plugin manager logging is enabled or not. * * @param loggingEnabled whether plugin manager logging is enabled or not */ public void setLoggingEnabled ( final boolean loggingEnabled ) { this.loggingEnabled = loggingEnabled; Log.setLoggingEnabled ( this, loggingEnabled ); } /** * Adds plugins listener. * * @param listener new plugins listener */ public void addPluginsListener ( final PluginsListener listener ) { listeners.add ( listener ); } /** * Adds plugins scan start event action. * * @param runnable action to perform * @return added plugins listener */ public PluginsAdapter onPluginsScanStart ( final DirectoryRunnable runnable ) { final PluginsAdapter listener = new PluginsAdapter () { @Override public void pluginsCheckStarted ( final String directory, final boolean recursive ) { runnable.run ( directory, recursive ); } }; addPluginsListener ( listener ); return listener; } /** * Adds plugins scan end event action. * * @param runnable action to perform * @return added plugins listener */ public PluginsAdapter onPluginsScanEnd ( final DirectoryRunnable runnable ) { final PluginsAdapter listener = new PluginsAdapter () { @Override public void pluginsCheckEnded ( final String directory, final boolean recursive ) { runnable.run ( directory, recursive ); } }; addPluginsListener ( listener ); return listener; } /** * Adds plugins detection event action. * * @param runnable action to perform * @return added plugins listener */ public PluginsAdapter onPluginsDetection ( final DetectedPluginsRunnable runnable ) { final PluginsAdapter listener = new PluginsAdapter () { @Override public void pluginsDetected ( final List> detectedPlugins ) { runnable.run ( detectedPlugins ); } }; addPluginsListener ( listener ); return listener; } /** * Adds plugins initialization end event action. * * @param runnable action to perform * @return added plugins listener */ public PluginsAdapter onPluginsInitialization ( final PluginsRunnable runnable ) { final PluginsAdapter listener = new PluginsAdapter () { @Override public void pluginsInitialized ( final List plugins ) { runnable.run ( plugins ); } }; addPluginsListener ( listener ); return listener; } /** * Removes plugins listener. * * @param listener plugins listener to remove */ public void removePluginsListener ( final PluginsListener listener ) { listeners.remove ( listener ); } /** * Informs about plugins check operation start. * * @param directory checked plugins directory path * @param recursive whether plugins directory subfolders are checked recursively or not */ public void firePluginsCheckStarted ( final String directory, final boolean recursive ) { for ( final PluginsListener listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsCheckStarted ( directory, recursive ); } } /** * Informs about plugins check operation end. * * @param directory checked plugins directory path * @param recursive whether plugins directory subfolders are checked recursively or not */ public void firePluginsCheckEnded ( final String directory, final boolean recursive ) { for ( final PluginsListener listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsCheckEnded ( directory, recursive ); } } /** * Informs about newly detected plugins. * * @param plugins newly detected plugins list */ public void firePluginsDetected ( final List> plugins ) { for ( final PluginsListener listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsDetected ( CollectionUtils.copy ( plugins ) ); } } /** * Informs about newly initialized plugins. * * @param plugins newly initialized plugins list */ public void firePluginsInitialized ( final List plugins ) { for ( final PluginsListener listener : CollectionUtils.copy ( listeners ) ) { listener.pluginsInitialized ( CollectionUtils.copy ( plugins ) ); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy