@Override
    public Future<IAsyncResult<Plugin>> loadPlugin(final PluginCoordinates coordinates, final IAsyncResultHandler<Plugin> userHandler) {
        final PluginFuture future = new PluginFuture();
        final boolean isSnapshot = PluginUtils.isSnapshot(coordinates);

        // Wrap the user provided handler so we can hook into the response.  We want to cache
        // the result (regardless of whether it's a success or failure)
        final IAsyncResultHandler<Plugin> handler = (IAsyncResult<Plugin> result) -> {
            synchronized (pluginCache) {
                if (result.isError()) {
                    errorCache.put(coordinates, result.getError());
                } else {
                    // Make sure we *always* use whatever is in the cache.  This resolves a
                    // race condition where multiple threads could ask for the plugin at the
                    // same time, resulting in two or more threads downloading the plugin.
                    // This is OK as long as we make sure we only ever use one.
                    if (pluginCache.containsKey(coordinates)) {
                        try {
                            result.getResult().getLoader().close();
                        } catch (IOException e) {
                            LOGGER.error(e);
                        }
                        result = AsyncResultImpl.create(pluginCache.get(coordinates));
                    } else {
                        pluginCache.put(coordinates, result.getResult());
                    }
                }
            }
            if (userHandler != null) {
                userHandler.handle(result);
            }
            future.setResult(result);
        };

        boolean handled = false;
        synchronized (pluginCache) {

            // First check the cache.
            if (pluginCache.containsKey(coordinates)) {
                // Invoke the user handler directly - we know we don't need to re-cache it.
                AsyncResultImpl<Plugin> result = AsyncResultImpl.create(pluginCache.get(coordinates));
                if (userHandler != null) {
                    userHandler.handle(result);
                }
                future.setResult(result);
                handled = true;
            }

            // Check the error cache - don't keep trying again and again for a failure.
            if (!handled && errorCache.containsKey(coordinates)) {
                // Invoke the user handle directly - we know we don't need to re-cache it.
                AsyncResultImpl<Plugin> result = AsyncResultImpl.create(errorCache.get(coordinates), Plugin.class);
                if (userHandler != null) {
                    userHandler.handle(result);
                }
                future.setResult(result);
                handled = true;
            }
        }

        String pluginRelativePath = PluginUtils.getPluginRelativePath(coordinates);
        File pluginDir = new File(pluginsDir, pluginRelativePath);
        File pluginFile = new File(pluginDir, "plugin." + coordinates.getType()); //$NON-NLS-1$

        // Next try to load it from the plugin file registry
        if (!handled && pluginFile.isFile()) {
            // If it's a snapshot, delete it here. (first time loading this plugin from the plugin file registry).  This
            // means that snapshot plugins will be redownloaded each time the server is restarted.
            if (isSnapshot) {
                try {
                    FileUtils.deleteDirectory(pluginDir);
                } catch (IOException | IllegalArgumentException e) {
                }
            } else {
                handled = true;
                try {
                    handler.handle(AsyncResultImpl.create(readPluginFile(coordinates, pluginFile)));
                } catch (Exception error) {
                    handler.handle(AsyncResultImpl.<Plugin>create(error));
                }
            }
        }

        if (!pluginDir.exists()) {
            pluginDir.mkdirs();
        }

        // Next try to load it from the user's .m2 directory (copy it into the plugin file
        // registry first though)
        if (!handled) {
            File m2Dir = PluginUtils.getUserM2Repository();
            if (m2Dir != null) {
                File artifactFile = PluginUtils.getM2Path(m2Dir, coordinates);
                if (artifactFile.isFile()) {
                    handled = true;
                    try {
                        File tmpFile = File.createTempFile("plugin", ".tmp", pluginDir);
                        tmpFile.deleteOnExit();
                        FileUtils.copyFile(artifactFile, tmpFile);
                        tmpFile.renameTo(pluginFile);
                        handler.handle(AsyncResultImpl.create(readPluginFile(coordinates, pluginFile)));
                    } catch (Exception error) {
                        handler.handle(AsyncResultImpl.<Plugin>create(error));
                    }
                }
            }
        }

        // Last effort - try to download it from a remote maven repository.  If this fails, then
        // we have to simply report "plugin not found".
        if (!handled) {
            downloadPlugin(coordinates, (IAsyncResult<File> result) -> {
                if (result.isSuccess()) {
                    File downloadedArtifactFile = result.getResult();
                    if (downloadedArtifactFile == null || !downloadedArtifactFile.isFile()) {
                        handler.handle(AsyncResultImpl.<Plugin>create(new Exception(Messages.i18n.format("DefaultPluginRegistry.PluginNotFound")))); //$NON-NLS-1$
                    } else {
                        try {
                            String pluginRelativePath1 = PluginUtils.getPluginRelativePath(coordinates);
                            File pluginDir1 = new File(pluginsDir, pluginRelativePath1);
                            if (!pluginDir1.exists()) {
                                pluginDir1.mkdirs();
                            }
                            File pluginFile1 = new File(pluginDir1, "plugin." + coordinates.getType()); //$NON-NLS-1$
                            if (!pluginFile1.exists()) {
                                FileUtils.copyFile(downloadedArtifactFile, pluginFile1);
                                FileUtils.deleteQuietly(downloadedArtifactFile);
                            } else {
                                FileUtils.deleteQuietly(downloadedArtifactFile);
                            }
                            handler.handle(AsyncResultImpl.create(readPluginFile(coordinates, pluginFile1)));
                        } catch (Exception error) {
                            handler.handle(AsyncResultImpl.<Plugin>create(error));
                        }
                    }
                } else {
                    handler.handle(AsyncResultImpl.<Plugin>create(result.getError()));
                }
            });
        }

        return future;
    }
@see io.apiman.gateway.engine.IPluginRegistry#loadPlugin(io.apiman.common.plugin.PluginCoordinates, io.apiman.gateway.engine.async.IAsyncResultHandler)
gateway/engine/core/src/main/java/io/apiman/gateway/engine/impl/DefaultPluginRegistry.java