com.eviware.soapui.plugins.PluginLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.eviware.soapui.plugins.PluginLoader.java

Source

/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.plugins;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.model.iface.SoapUIListener;
import com.eviware.soapui.support.action.SoapUIAction;
import com.eviware.soapui.support.action.SoapUIActionRegistry;
import com.eviware.soapui.support.factory.SoapUIFactoryRegistry;
import com.eviware.soapui.support.listener.ListenerRegistry;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Responsible for loading plugins into SoapUI.
 */
public class PluginLoader extends LoaderBase {

    public static Logger log = Logger.getLogger(PluginLoader.class);

    public PluginLoader(SoapUIFactoryRegistry factoryRegistry, SoapUIActionRegistry actionRegistry,
            ListenerRegistry listenerRegistry) {
        super(listenerRegistry, actionRegistry, factoryRegistry);
    }

    InstalledPluginRecord loadPlugin(File pluginFile, Collection<JarClassLoader> dependencyClassLoaders)
            throws IOException {
        ReflectionsAndClassLoader tuple = makeJarFileScanner(pluginFile, dependencyClassLoaders);
        Class<?> pluginClass = readPluginConfigurationClasses(pluginFile, tuple.reflections);
        Plugin plugin = loadPlugin(pluginClass, tuple.reflections);
        return new InstalledPluginRecord(plugin, tuple.jarClassLoader);
    }

    private ReflectionsAndClassLoader makeJarFileScanner(File pluginFile,
            Collection<JarClassLoader> dependencyClassLoaders) throws IOException {
        File tempFile = File.createTempFile("soapuios", ".jar");
        tempFile.deleteOnExit();
        FileUtils.copyFile(pluginFile, tempFile);
        JarClassLoader jarClassLoader = new JarClassLoader(tempFile, PluginLoader.class.getClassLoader(),
                dependencyClassLoaders);
        ConfigurationBuilder configurationBuilder = new ConfigurationBuilder().setUrls(jarClassLoader.getURLs())
                .addClassLoader(jarClassLoader);

        if (jarClassLoader.hasScripts()) {
            configurationBuilder.addClassLoader(jarClassLoader.getScriptClassLoader());
            configurationBuilder.addScanners(new TypeAnnotationsScanner());
            configurationBuilder.setMetadataAdapter(new GroovyAndJavaReflectionAdapter(jarClassLoader));
        }

        return new ReflectionsAndClassLoader(new Reflections(configurationBuilder), jarClassLoader);
    }

    private Class<?> readPluginConfigurationClasses(File pluginFile, Reflections jarFileScanner) {
        Set<Class<?>> pluginClasses = jarFileScanner.getTypesAnnotatedWith(PluginConfiguration.class);
        if (pluginClasses.isEmpty()) {
            log.warn("No plugin classes found in JAR file " + pluginFile);
            throw new MissingPluginClassException("No plugin class found in " + pluginFile);
        } else if (pluginClasses.size() > 1) {
            throw new InvalidPluginException(
                    "Multiple plugin classes found in " + pluginFile + ": " + pluginClasses);
        }
        return pluginClasses.iterator().next();
    }

    Plugin loadPlugin(Class<?> pluginClass, Reflections jarFileScanner) {
        try {
            PluginConfiguration configurationAnnotation = pluginClass.getAnnotation(PluginConfiguration.class);
            Version minimumReadyApiVersion = Version.fromString(configurationAnnotation.minimumReadyApiVersion());
            Version installedReadyApiVersion = Version.fromString(SoapUI.SOAPUI_VERSION);
            if (minimumReadyApiVersion.compareTo(installedReadyApiVersion) > 0) {
                throw new InvalidPluginException(
                        "Plugin " + configurationAnnotation.name() + " requires version " + minimumReadyApiVersion
                                + " of ReadyAPI. Current application version: " + installedReadyApiVersion);
            }
            Plugin plugin;
            if (Plugin.class.isAssignableFrom(pluginClass)) {
                plugin = (Plugin) pluginClass.newInstance();
            } else {
                plugin = new EmptyPlugin(configurationAnnotation);
            }

            boolean autoDetect = configurationAnnotation.autoDetect();
            if (plugin.isActive()) {
                plugin.initialize();
                Collection<SoapUIFactory> factories = loadPluginFactories(plugin, autoDetect, jarFileScanner);
                List<SoapUIAction> actions = loadPluginActions(plugin, autoDetect, jarFileScanner);
                List<Class<? extends SoapUIListener>> listeners = loadPluginListeners(plugin, autoDetect,
                        jarFileScanner);
                return createLoadedPluginInstance(plugin, factories, actions, listeners);
            }

            return plugin;
        } catch (InvalidPluginException e) {
            throw e;
        } catch (Throwable e) {
            throw new InvalidPluginException("Error loading plugin " + pluginClass, e);
        }
    }

    private LoadedPlugin createLoadedPluginInstance(Plugin plugin, Collection<SoapUIFactory> factories,
            List<SoapUIAction> actions, List<Class<? extends SoapUIListener>> listeners) {
        LoadedPlugin loadedPlugin = new LoadedPlugin(plugin, factories, actions, listeners);
        for (SoapUIFactory factory : factories) {
            if (factory instanceof PluginAware) {
                ((PluginAware) factory).setPlugin(loadedPlugin);
            }
        }
        for (SoapUIAction action : actions) {
            if (action instanceof PluginAware) {
                ((PluginAware) action).setPlugin(loadedPlugin);
            }
        }
        return loadedPlugin;
    }

    private Collection<SoapUIFactory> loadPluginFactories(Plugin plugin, boolean autoDetect,
            Reflections jarFileScanner) throws IllegalAccessException, InstantiationException {
        Collection<SoapUIFactory> factories = new HashSet<SoapUIFactory>(plugin.getFactories());
        if (!factories.isEmpty())
            registerFactories(factories);

        if (autoDetect) {
            factories.addAll(loadFactories(jarFileScanner));
        }

        return factories;
    }

    private List<Class<? extends SoapUIListener>> loadPluginListeners(Plugin plugin, boolean autoDetect,
            Reflections jarFileScanner) throws IllegalAccessException, InstantiationException {
        List<Class<? extends SoapUIListener>> listeners = new ArrayList<Class<? extends SoapUIListener>>(
                plugin.getListeners());
        if (!listeners.isEmpty())
            registerListeners(listeners);

        if (autoDetect) {
            listeners.addAll(loadListeners(jarFileScanner));
        }

        return listeners;
    }

    private List<SoapUIAction> loadPluginActions(Plugin plugin, boolean autoDetect, Reflections jarFileScanner)
            throws InstantiationException, IllegalAccessException {
        List<SoapUIAction> actions = new ArrayList<SoapUIAction>(plugin.getActions());
        if (!actions.isEmpty())
            registerActions(actions);

        if (autoDetect) {
            actions.addAll(loadActions(jarFileScanner));
        }
        return actions;
    }

    public void unloadPlugin(Plugin plugin) {
        unregisterActions(plugin.getActions());
        unregisterListeners(plugin.getListeners());
        unregisterFactories(plugin.getFactories());
    }

    public PluginInfo loadPluginInfoFrom(File pluginFile, Collection<JarClassLoader> dependencyClassLoaders)
            throws IOException {
        ReflectionsAndClassLoader tuple = makeJarFileScanner(pluginFile, dependencyClassLoaders);
        Class<?> pluginClass = readPluginConfigurationClasses(pluginFile, tuple.reflections);
        return readPluginInfoFrom(pluginClass);
    }

    static PluginInfo readPluginInfoFrom(Class<?> pluginClass) {
        PluginInfo pluginInfo = readPluginInfoFromAnnotation(pluginClass.getAnnotation(PluginConfiguration.class));
        addDependency(pluginInfo, pluginClass.getAnnotation(PluginDependency.class));
        PluginDependencies pluginDependenciesAnnotation = pluginClass.getAnnotation(PluginDependencies.class);
        if (pluginDependenciesAnnotation != null) {
            for (PluginDependency pluginDependency : pluginDependenciesAnnotation.value()) {
                addDependency(pluginInfo, pluginDependency);
            }
        }
        return pluginInfo;
    }

    private static void addDependency(PluginInfo pluginInfo, PluginDependency dependencyAnnotation) {
        if (dependencyAnnotation != null) {
            PluginId id = new PluginId(dependencyAnnotation.groupId(), dependencyAnnotation.name());
            pluginInfo.addDependency(
                    new PluginInfo(id, Version.fromString(dependencyAnnotation.minimumVersion()), "", ""));
        }
    }

    static PluginInfo readPluginInfoFromAnnotation(PluginConfiguration annotation) {
        PluginId id = new PluginId(annotation.groupId(), annotation.name());
        Version version = Version.fromString(annotation.version());
        String infoUrl = annotation.infoUrl();
        return new PluginInfo(id, version, annotation.description(), infoUrl);
    }

    // due to Reflections internals (or my misunderstanding of them) this class has to be
    // named as its superclass
    private static class TypeAnnotationsScanner extends org.reflections.scanners.TypeAnnotationsScanner {
        @Override
        public boolean acceptsInput(String file) {
            if (file.endsWith(".groovy")) {
                return true;
            } else {
                return super.acceptsInput(file);
            }
        }
    }

    private class LoadedPlugin implements Plugin {
        private final Plugin plugin;
        private final Collection<SoapUIFactory> factories;
        private final List<SoapUIAction> actions;
        private final List<Class<? extends SoapUIListener>> listeners;

        public LoadedPlugin(Plugin plugin, Collection<SoapUIFactory> factories, List<SoapUIAction> actions,
                List<Class<? extends SoapUIListener>> listeners) {
            this.plugin = plugin;
            this.factories = factories;
            this.actions = actions;
            this.listeners = listeners;
        }

        @Override
        public PluginInfo getInfo() {
            return plugin.getInfo();
        }

        @Override
        public boolean isActive() {
            return plugin.isActive();
        }

        @Override
        public void initialize() {
            throw new IllegalStateException("Plugin has already been initialized");
        }

        @Override
        public List<Class<? extends SoapUIListener>> getListeners() {
            return listeners;
        }

        @Override
        public List<? extends SoapUIAction> getActions() {
            return actions;
        }

        @Override
        public Collection<? extends ApiImporter> getApiImporters() {
            return Collections.emptySet();
        }

        @Override
        public Collection<? extends SoapUIFactory> getFactories() {
            return factories;
        }

        @Override
        public boolean hasSameIdAs(Plugin otherPlugin) {
            return plugin.hasSameIdAs(otherPlugin);
        }

        @Override
        public String toString() {
            return plugin.toString();
        }
    }

    private class EmptyPlugin implements Plugin {

        private PluginInfo pluginInfo;

        private EmptyPlugin(PluginConfiguration annotation) {
            pluginInfo = readPluginInfoFromAnnotation(annotation);
        }

        @Override
        public PluginInfo getInfo() {
            return pluginInfo;
        }

        @Override
        public boolean isActive() {
            return true;
        }

        @Override
        public void initialize() {

        }

        @Override
        public List<Class<? extends SoapUIListener>> getListeners() {
            return Collections.emptyList();
        }

        @Override
        public List<? extends SoapUIAction> getActions() {
            return Collections.emptyList();
        }

        @Override
        public Collection<? extends ApiImporter> getApiImporters() {
            return Collections.emptySet();
        }

        @Override
        public Collection<? extends SoapUIFactory> getFactories() {
            return Collections.emptySet();
        }

        @Override
        public boolean hasSameIdAs(Plugin otherPlugin) {
            return pluginInfo.getId().equals(otherPlugin.getInfo().getId());
        }
    }

    private class ReflectionsAndClassLoader {
        Reflections reflections;
        JarClassLoader jarClassLoader;

        private ReflectionsAndClassLoader(Reflections reflections, JarClassLoader jarClassLoader) {
            this.reflections = reflections;
            this.jarClassLoader = jarClassLoader;
        }
    }
}