/** * The MIT License * * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.hudsonci.inject.internal.plugin; import org.hudsonci.inject.SmoothieContainer; import org.hudsonci.inject.internal.extension.ExtensionLocator; import hudson.ExtensionComponent; import hudson.ExtensionFinder; import hudson.Plugin; import hudson.PluginStrategy; import hudson.PluginWrapper; import hudson.PluginWrapper.Dependency; import hudson.model.Hudson; import hudson.util.IOException2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; /** * Smoothie {@link PluginStrategy}. * * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> * @since 1.397 */ @Named("default") @Singleton public class SmoothiePluginStrategy implements PluginStrategy { private static final Logger log = LoggerFactory.getLogger(SmoothiePluginStrategy.class); private final SmoothieContainer container; private final PluginWrapperFactory pluginFactory; private final ExtensionLocator extensionLocator; private final List<ExtensionFinder> extensionFinders; @Inject public SmoothiePluginStrategy(final SmoothieContainer container, final PluginWrapperFactory pluginFactory, final @Named("default") ExtensionLocator extensionLocator, final List<ExtensionFinder> extensionFinders) { this.container = checkNotNull(container); this.pluginFactory = checkNotNull(pluginFactory); this.extensionLocator = checkNotNull(extensionLocator); this.extensionFinders = checkNotNull(extensionFinders); } private String basename(String name) { assert name != null; if (name.endsWith("/")) { name = name.substring(0, name.length() - 1); } int i = name.lastIndexOf("/"); if (i != -1) { return name.substring(i + 1, name.length()); } return name; } /** * Load the plugins wrapper and inject it with the {@link SmoothieContainer}. */ public PluginWrapper createPluginWrapper(final File archive) throws IOException { checkNotNull(archive); PluginWrapper plugin; try { plugin = pluginFactory.create(archive); } catch (Exception e) { throw new IOException2(e); } if (log.isDebugEnabled()) { logPluginDetails(plugin); } return plugin; } private void logPluginDetails(final PluginWrapper plugin) { assert plugin != null; log.debug("Loaded plugin: {} ({})", plugin.getShortName(), plugin.getVersion()); // Some details are not valid until the createPluginWrapper() has returned... like bundled status log.debug(" State: active={}, enabled={}, pinned={}, downgradable={}", new Object[] { plugin.isActive(), plugin.isEnabled(), plugin.isPinned(), plugin.isDowngradable() }); // Spit out some debug/trace details about the classpath PluginClassLoader cl = (PluginClassLoader)plugin.classLoader; URL[] classpath = cl.getURLs(); if (classpath.length > 1) { log.debug(" Classpath:"); int i=0; boolean trace = log.isTraceEnabled(); for (URL url : classpath) { // skip the classes/ dir its always there if (i++ == 0) { continue; } // for trace still log as debug, but flip on the full URL log.debug(" {}", trace ? url.toString() : basename(url.getFile())); } } // Spit out some debug information about the plugin dependencies List<Dependency> dependencies = plugin.getDependencies(); if (dependencies != null && !dependencies.isEmpty()) { log.debug(" Dependencies:"); for (Dependency dependency : dependencies) { log.debug(" {}", dependency); } } dependencies = plugin.getOptionalDependencies(); if (dependencies != null && !dependencies.isEmpty()) { log.debug(" Optional dependencies:"); for (Dependency dependency : plugin.getOptionalDependencies()) { log.debug(" {}", dependency); } } } /** * Loads the optional {@link hudson.Plugin} instance, configures and starts it. */ public void load(final PluginWrapper plugin) throws IOException { checkNotNull(plugin); if (log.isDebugEnabled()) { log.debug("Configuring plugin: {}", plugin.getShortName()); } container.register(plugin); ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(plugin.classLoader); try { Plugin instance; // Load the plugin instance, if one has been configured. if (plugin.getPluginClass() == null) { instance = new Plugin.DummyImpl(); } else { try { // Ask the container to construct the instance Class<? extends Plugin> type = loadPluginClass(plugin); instance = container.injector(plugin).getInstance(type); log.trace("Plugin instance: {}", instance); } catch (Throwable e) { throw new IOException2("Failed to load plugin instance for: " + plugin.getShortName(), e); } } plugin.setPlugin(instance); try { start(plugin); } catch (Exception e) { throw new IOException2("Failed to start plugin: " + plugin.getShortName(), e); } } finally { Thread.currentThread().setContextClassLoader(old); } } /** * Isolates cast compiler warning. */ @SuppressWarnings({"unchecked"}) private Class<? extends Plugin> loadPluginClass(final PluginWrapper plugin) throws ClassNotFoundException { assert plugin != null; return (Class<? extends Plugin>) plugin.classLoader.loadClass(plugin.getPluginClass()); } /** * Configures and starts the {@link hudson.Plugin} instance. */ private void start(final PluginWrapper plugin) throws Exception { assert plugin != null; if (log.isDebugEnabled()) { log.debug("Starting plugin: {}", plugin.getShortName()); } Plugin instance = plugin.getPlugin(); instance.setServletContext(Hudson.getInstance().servletContext); instance.start(); } /** * This method of the PluginStrategy interface is completely unused. */ public void initializeComponents(final PluginWrapper plugin) { throw new Error("Unused operation"); } public <T> List<ExtensionComponent<T>> findComponents(final Class<T> type, final Hudson hudson) { List<ExtensionComponent<T>> components = extensionLocator.locate(type); for (ExtensionFinder finder : extensionFinders) { try { try { components.addAll(finder._find(type, hudson)); } catch (AbstractMethodError e) { // support legacy finders that only have the old method for (T instance : finder.findExtensions(type, hudson)) { components.add(new ExtensionComponent<T>(instance)); } } } catch (Throwable e) { log.warn("Failed to query ExtensionFinder: "+finder, e); } } return components; } }