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

com.abiquo.commons.plugin.AbstractPluginManager Maven / Gradle / Ivy

/**
 * The Abiquo Platform
 * Cloud management application for hybrid clouds
 * Copyright (C) 2008 - Abiquo Holdings S.L.
 *
 * This application is free software; you can redistribute it and/or
 * modify it under the terms of the GNU LESSER GENERAL PUBLIC
 * LICENSE as published by the Free Software Foundation under
 * version 3 of the License
 *
 * This software 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
 * LESSER GENERAL PUBLIC LICENSE v.3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
package com.abiquo.commons.plugin;

import static com.google.common.base.Predicates.in;
import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Collections.unmodifiableMap;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.abiquo.commons.model.enumerator.ConstraintKey;
import com.abiquo.commons.plugin.annotation.PluginMetadata;
import com.abiquo.commons.plugin.exception.ComputeException;
import com.abiquo.commons.plugin.exception.HypervisorPluginError;
import com.abiquo.commons.plugin.exception.HypervisorPluginException;
import com.abiquo.commons.plugin.internal.ContextPropertiesInvocationHandler;
import com.abiquo.commons.plugin.internal.PluginInvocationHandler;
import com.abiquo.commons.plugin.internal.TryInvocationHandler;
import com.abiquo.commons.plugin.predicate.MethodNameEquivalence;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.reflect.Reflection;

/**
 * Hold {@link AbsPluginInterface} instances loaded using a {@link ServiceLoader}
 */
public class AbstractPluginManager
{
    private final static Logger LOG = LoggerFactory.getLogger(AbstractPluginManager.class);

    private final static TypeResolver typeResolver = new TypeResolver();

    private final PluginAccessControl plugins;

    private final Class absPluginInterfaceClass;

    private final Class absIsPluginSupportedClass;

    private final Class absContextPropertiesClass;

    private Class< ? > genericPluginClass;

    protected class LoadedAbsPlugin
    {
        private final Class connectionClass;

        private final AbsPluginInterface proxy;

        private final PluginMetadata metadata;

        private final Map constraints;

        private final AbsIsPluginSupported triable;

        private final AbsContextProperties contextProperties;

        private final Map>> operations;

        LoadedAbsPlugin(final String type, final Class connectionClass,
            final AbsPluginInterface computableObject)
        {
            this.connectionClass = connectionClass;
            this.proxy =
                Reflection.newProxy(absPluginInterfaceClass,
                    new PluginInvocationHandler(computableObject));
            this.metadata = computableObject.getClass().getAnnotation(PluginMetadata.class);

            this.triable =
                Reflection.newProxy(absIsPluginSupportedClass,
                    new TryInvocationHandler(computableObject, type, genericPluginClass));
            this.contextProperties =
                Reflection.newProxy(absContextPropertiesClass,
                    new ContextPropertiesInvocationHandler(computableObject));

            this.constraints = unmodifiableMap(loadConstraints(computableObject));
            this.operations = getOperations(computableObject, type);
        }
    }

    /**
     * Stores the plugins and the white list.
     * 

* The plugin white list is mutable and controlled by the Abiquo licenses. Any access to get a * plugin instance must be "approved" by the whitelist; otherwise the plugin is not returned. */ private class PluginAccessControl { private final Map instances; private final Set whiteList; public PluginAccessControl(final Map instances) { this.instances = Collections.unmodifiableMap(instances); this.whiteList = Collections.synchronizedSet(new HashSet<>(instances.keySet())); } public Set loadedTypes() { synchronized (whiteList) { return Sets.intersection(whiteList, instances.keySet()); } } public boolean has(final String type) { return whiteList.contains(type) && instances.containsKey(type); } public Optional tryGet(final String type) { return has(type) ? Optional.of(instances.get(type)) : Optional . absent(); } public void setEnabledPlugins(final Collection types) { synchronized (whiteList) { whiteList.clear(); whiteList.addAll(types); } } public void enableAllPlugins() { setEnabledPlugins(instances.keySet()); } } protected AbstractPluginManager(final Class< ? > genericPluginClass, final Set otherPluginTypes, final Class absPluginInterfaceClass, final Class absIsPluginSupportedClass, final Class absContextPropertiesClass) { this.genericPluginClass = genericPluginClass; this.absPluginInterfaceClass = absPluginInterfaceClass; this.absIsPluginSupportedClass = absIsPluginSupportedClass; this.absContextPropertiesClass = absContextPropertiesClass; Map loadedPlugins = Maps.newHashMap(); for (AbsPluginInterface pluggable : ServiceLoader.load(absPluginInterfaceClass)) { Optional> plugin = loadPlugin(pluggable); if (plugin.isPresent()) { add(loadedPlugins, plugin, otherPluginTypes); } } this.plugins = new PluginAccessControl(loadedPlugins); } public String getDeviceInterface() { return this.absPluginInterfaceClass.getSimpleName(); } private void add(final Map plugins, final Optional> plugin, final Set otherPluginTypes) { for (Entry e : plugin.get().entrySet()) { checkDuplicatedPlugins(plugins, e.getKey(), otherPluginTypes); plugins.put(e.getKey(), e.getValue()); } } /** Only one instance per computable plugin. */ private void checkDuplicatedPlugins(final Map plugins, final String type, final Set otherPluginTypes) { if (plugins.containsKey(type)) { String message = String.format("A plugin for %s has been already loaded", type); LOG.error(message); throw new IllegalStateException(message); } else if (otherPluginTypes.contains(type)) { String message = String.format( "A plugin implementing another interface for %s has been already loaded", type); LOG.error(message); throw new IllegalStateException(message); } } /** * Returns the loaded plugins names. * * @return the loaded plugins names. */ public Set getSupportedTypes() { return plugins.loadedTypes(); } /** * Modifies the whitelisted plugins. *

* This method should be ONLY invoked with the plugins provided by the Abiquo licenses. * * @param types The new plugin whitelist. */ public void setWhiteList(final Collection types) { plugins.setEnabledPlugins(types); } /** * Resets the whitelist to enable all plugins. *

* This method should be ONLY invoked with the plugins provided by the Abiquo licenses. */ public void resetWhiteList() { plugins.enableAllPlugins(); } public AbsPluginInterface get(final String type) { Optional plugin = plugins.tryGet(type); if (!plugin.isPresent()) { LOG.error("Trying to get the plugin instance for the non supported type {}", type); throw new IllegalStateException("Plugin " + type + " is not loaded!"); } return plugin.get().proxy; } public Optional tryFind(final String type) { Optional plugin = plugins.tryGet(type); if (!plugin.isPresent()) { return Optional.absent(); } return Optional.of(plugin.get().proxy); } public AbsIsPluginSupported isSupported(final String type) { Optional plugin = plugins.tryGet(type); if (!plugin.isPresent()) { LOG.error("Trying to check supported for the non supported type {}", type); // Always unsupported return Reflection.newProxy(absIsPluginSupportedClass, new TryInvocationHandler(null, type, genericPluginClass)); } return plugin.get().triable; } public AbsContextProperties contextProperties(final String type) { Optional plugin = plugins.tryGet(type); if (!plugin.isPresent()) { LOG.error("Trying to get context properties for the non supported type {}", type); throw new IllegalStateException("Plugin " + type + " is not loaded!"); } return plugin.get().contextProperties; } /** * Returns the metadata of a plugin based on the given computable type * * @param type the computable type * @return the metadata of a plugin based on the given computable type * @throws HypervisorPluginException if there is not a loaded plugin for the given computable * type */ public Optional getPluginMetadata(final String type) { Optional plugin = plugins.tryGet(type); if (!plugin.isPresent()) { LOG.warn("Trying to get the plugin metadata for the non supported hypervisor type {}", type); return Optional.absent(); } return Optional.fromNullable(plugin.get().metadata); } /** * Returns the friendly name for plugin or absent if not loaded. * * @param type plugin * @return friendly name or absent. */ public Optional getFriendlyName(final String type) { Optional metadata = getPluginMetadata(type); if (!metadata.isPresent()) { LOG.warn("getFriendlyName: Trying to aquire name for an unloaded plugin"); return Optional.absent(); } int friendlyName = Arrays.asList(metadata.get().type()).indexOf(type); return Optional.of(metadata.get().friendlyName()[friendlyName]); } public String getConstraintValue(final String hypervisorType, final ConstraintKey constraintKey) { Optional plugin = plugins.tryGet(hypervisorType); if (plugin.isPresent()) { String value = plugin.get().constraints.get(constraintKey); return value == null ? constraintKey.getDefaultValue() : value; } else { return constraintKey.getDefaultValue(); } } public Map getConstraints(final String hypervisorType) { Optional plugin = plugins.tryGet(hypervisorType); return plugin.isPresent() ? plugin.get().constraints : ImmutableMap . of(); } /** * Check if there is a plugin loaded for the given hypervisor type. * * @param hypervisorType the hypervisor type to check * @return True if there is a plugin loaded for the given hypervisor type and false otherwise */ public boolean isLoaded(final String hypervisorType) { return plugins.has(hypervisorType); } /** * Creates a new instance of {@link IConnection} based on the given computable type * * @param type the computable type * @return The {@link IConnection} instance * @throws HypervisorPluginException if there is not a loaded plugin for the given computable * type */ public IConnection newConnectionData(final String type) throws ComputeException { Optional plugin = plugins.tryGet(type); if (!plugin.isPresent()) { LOG.error("Trying to get the connection instance for the non supported plugin type {}", type); throw new HypervisorPluginException(HypervisorPluginError.THURMAN0, "Plugin type " + type); } try { return plugin.get().connectionClass.newInstance(); } catch (Exception e) { LOG.error("Unable to create new plugin instance plugin type {}", type); throw new HypervisorPluginException(HypervisorPluginError.PLUGIN_NEW_INSTANCE_ERROR, "Plugin type " + type); } } protected Optional> loadPlugin(final AbsPluginInterface plugin) throws IllegalStateException { Optional optionalMetadata = extractPluginMetadata(plugin); if (!optionalMetadata.isPresent()) { LOG.error( "The plugin {} has invalid format. {} annotation is missing. Ignoring this plugin", new Object[] {plugin.getClass().getName(), PluginMetadata.class.getName()}); return Optional.absent(); } PluginMetadata metadata = optionalMetadata.get(); // Computable type must be informed if (metadata.type().length == 0) { LOG.error("The type of the plugin {} is null or empty. Ignoring this plugin", new Object[] {plugin.getClass().getName()}); return Optional.absent(); } // Computable friendly name must be informed if (metadata.friendlyName().length == 0) { LOG.error("The friendly name of the plugin {} is null or empty. Ignoring this plugin", new Object[] {metadata.type()}); return Optional.absent(); } try { // Validate specific plugin configuration validatePluginConfiguration(plugin); } catch (IllegalStateException e) { LOG.error("The plugin {} has invalid configuration: {}", new Object[] {String.join(",", metadata.type()), e.getMessage()}); return Optional.absent(); } try { Class connectionClass = resolveIConnectionData(plugin.getClass()); Builder builder = ImmutableMap. builder(); for (String pluginType : metadata.type()) { LoadedAbsPlugin loadedPlugin = new LoadedAbsPlugin(pluginType, connectionClass, plugin); builder.put(pluginType, loadedPlugin); LOG.debug("Loaded plugin for type {}", pluginType); } return Optional.of(builder.build()); } catch (Exception e) { LOG.error("Unable to load plugin for type {}", String.join(",", metadata.type()), e); } return Optional.absent(); } protected void validatePluginConfiguration(final AbsPluginInterface plugin) { plugin.validateConfiguration(); } private Optional extractPluginMetadata(final Pluggable plugin) { return Optional.fromNullable(plugin.getClass().getAnnotation(PluginMetadata.class)); } private Map loadConstraints(final Pluggable plugin) { Map constraints = Maps.newHashMap(); for (ConstraintKey key : ConstraintKey.values()) { String value = plugin.getConstraint(key.name()); if (value != null) { constraints.put(key, value); } } return constraints; } /** * Introspect an {@link ICompute} object to know the class of its {@link IConnection} * * @param hclass the class of an {@link ICompute} implementation * @return the class of the associated generic type for {@link IConnection} */ @SuppressWarnings("unchecked") // verified by ''typeParametersFor(ICompute.class)'' private Class resolveIConnectionData(final Type hclass) throws HypervisorPluginException { List rt = typeResolver.resolve(hclass).typeParametersFor(absPluginInterfaceClass); if (rt == null || rt.size() != 1) { throw new HypervisorPluginException(HypervisorPluginError.THURMAN0, hclass.toString()); } return (Class) rt.get(0).getErasedType(); } /** * Immutable map of key values. Where key is implemented interface and values are methods * annotated unsupported for this type. Empty if plugin not loaded. *

* A map containing all implemented interfaces (simple name) and a list with their methods * annotated with unsupported. *

* Ouptut for a plugin that only implements ICompute and IHypervisor and the powerOff method not * supported in all regions and exist unsupported in region location: *

* {IHypervisor: [], ICompute: {powerOff : [], exist: [location]} */ public Map>> operations(final String type) { Optional plugin = plugins.tryGet(type); if (!plugin.isPresent()) { return ImmutableMap.>> of(); } return plugin.get().operations; } /** * A map containing all implemented interfaces (simple name) and a list with their methods * annotated with unsupported. *

* Ouptut for a plugin that only implements ICompute and IHypervisor and the powerOff method not * supported in all regions and exist unsupported in region location: *

* {IHypervisor: [], ICompute: {powerOff : [], exist: [location]} */ private Map>> getOperations(final Pluggable plugin, final String type) { Builder>> operations = ImmutableMap.>> builder(); List> methods = FluentIterable.from(newArrayList(plugin.getClass().getMethods())) .transform(MethodNameEquivalence.WRAP).toList(); for (Class< ? > clazz : hyerarchyInterfaces(plugin.getClass())) { Set> interfaceMethods = FluentIterable.from(newHashSet(clazz.getMethods())) .transform(MethodNameEquivalence.WRAP).toSet(); Iterable> elegibleMethods = filter(methods, in(interfaceMethods)); Builder> unsupporteds = ImmutableMap.> builder(); for (Wrapper wrapper : elegibleMethods) { Method m = wrapper.get(); if (!m.isSynthetic() && !m.isBridge() && m.isAnnotationPresent(UnsupportedOperation.class)) { UnsupportedOperation unsupported = m.getAnnotation(UnsupportedOperation.class); if (unsupported.types().length == 0 || Arrays.asList(unsupported.types()).contains(type)) { unsupporteds.put(m.getName(), copyOf(unsupported.regions())); } } } operations.put(clazz.getSimpleName(), unsupporteds.build()); } return operations.build(); } private List> hyerarchyInterfaces(final Class< ? > plugin) { List> interfaces = new ArrayList<>(); if (plugin.getSuperclass() != null) { interfaces.addAll(hyerarchyInterfaces(plugin.getSuperclass())); } interfaces.addAll(Arrays.asList(plugin.getInterfaces())); return interfaces; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy