/** * Get the plugin instances that extend a specific class, use to find similar plugins. Note: * beware the classloader fun. * * @param pluginSuperclass The class that your plugin is derived from. * @return The list of plugins implementing the specified class. */ public List<PluginWrapper> getPlugins(Class<? extends Plugin> pluginSuperclass) { List<PluginWrapper> result = new ArrayList<PluginWrapper>(); for (PluginWrapper p : plugins) { if (pluginSuperclass.isInstance(p.getPlugin())) result.add(p); } return Collections.unmodifiableList(result); }
/** Orderly terminates all the plugins. */ public void stop() { for (PluginWrapper p : activePlugins) { p.stop(); p.releaseClassLoader(); } activePlugins.clear(); // Work around a bug in commons-logging. // See http://www.szegedi.org/articles/memleak.html LogFactory.release(uberClassLoader); }
public boolean isActivated() { if (pluginsWithCycle == null) { pluginsWithCycle = new ArrayList<String>(); for (PluginWrapper p : Jenkins.getInstance().getPluginManager().getPlugins()) { if (p.hasCycleDependency()) { pluginsWithCycle.add(p.getShortName()); isActive = true; } } } return isActive; }
/** TODO: revisit where/how to expose this. This is an experiment. */ public void dynamicLoad(File arc) throws IOException, InterruptedException, RestartRequiredException { LOGGER.info("Attempting to dynamic load " + arc); final PluginWrapper p = strategy.createPluginWrapper(arc); String sn = p.getShortName(); if (getPlugin(sn) != null) throw new RestartRequiredException( Messages._PluginManager_PluginIsAlreadyInstalled_RestartRequired(sn)); if (p.supportsDynamicLoad() == YesNoMaybe.NO) throw new RestartRequiredException( Messages._PluginManager_PluginDoesntSupportDynamicLoad_RestartRequired(sn)); // there's no need to do cyclic dependency check, because we are deploying one at a time, // so existing plugins can't be depending on this newly deployed one. plugins.add(p); activePlugins.add(p); try { p.resolvePluginDependencies(); strategy.load(p); Jenkins.getInstance().refreshExtensions(); p.getPlugin().postInitialize(); } catch (Exception e) { failedPlugins.add(new FailedPlugin(sn, e)); activePlugins.remove(p); plugins.remove(p); throw new IOException("Failed to install " + sn + " plugin", e); } // run initializers in the added plugin Reactor r = new Reactor(InitMilestone.ordering()); r.addAll( new InitializerFinder(p.classLoader) { @Override protected boolean filter(Method e) { return e.getDeclaringClass().getClassLoader() != p.classLoader || super.filter(e); } }.discoverTasks(r)); try { new InitReactorRunner().run(r); } catch (ReactorException e) { throw new IOException("Failed to initialize " + sn + " plugin", e); } LOGGER.info("Plugin " + sn + " dynamically installed"); }
/** * Like {@link #doInstallNecessaryPlugins(StaplerRequest)} but only checks if everything is * installed or if some plugins need updates or installation. * * <p>This method runs without side-effect. I'm still requiring the ADMINISTER permission since * XML file can contain various external references and we don't configure parsers properly * against that. * * @since 1.483 */ @RequirePOST public JSONArray doPrevalidateConfig(StaplerRequest req) throws IOException { Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); JSONArray response = new JSONArray(); for (Map.Entry<String, VersionNumber> p : parseRequestedPlugins(req.getInputStream()).entrySet()) { PluginWrapper pw = getPlugin(p.getKey()); JSONObject j = new JSONObject() .accumulate("name", p.getKey()) .accumulate("version", p.getValue().toString()); if (pw == null) { // install new response.add(j.accumulate("mode", "missing")); } else if (pw.isOlderThan(p.getValue())) { // upgrade response.add(j.accumulate("mode", "old")); } // else already good } return response; }
/** * Prepares plugins for some expected XML configuration. If the configuration (typically a job’s * {@code config.xml}) needs some plugins to be installed (or updated), those jobs will be * triggered. Plugins are dynamically loaded whenever possible. Requires {@link * Jenkins#ADMINISTER}. * * @param configXml configuration that might be uploaded * @return an empty list if all is well, else a list of submitted jobs which must be completed * before this configuration can be fully read * @throws IOException if loading or parsing the configuration failed * @see ItemGroupMixIn#createProjectFromXML * @see AbstractItem#updateByXml(javax.xml.transform.Source) * @see XStream2 * @see hudson.model.UpdateSite.Plugin#deploy(boolean) * @see PluginWrapper#supportsDynamicLoad * @see hudson.model.UpdateCenter.DownloadJob.SuccessButRequiresRestart * @since 1.483 */ public List<Future<UpdateCenter.UpdateCenterJob>> prevalidateConfig(InputStream configXml) throws IOException { Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); List<Future<UpdateCenter.UpdateCenterJob>> jobs = new ArrayList<Future<UpdateCenter.UpdateCenterJob>>(); UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); // TODO call uc.updateAllSites() when available? perhaps not, since we should not block on // network here for (Map.Entry<String, VersionNumber> requestedPlugin : parseRequestedPlugins(configXml).entrySet()) { PluginWrapper pw = getPlugin(requestedPlugin.getKey()); if (pw == null) { // install new UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey()); if (toInstall == null) { LOGGER.log(WARNING, "No such plugin {0} to install", requestedPlugin.getKey()); continue; } if (new VersionNumber(toInstall.version).compareTo(requestedPlugin.getValue()) < 0) { LOGGER.log( WARNING, "{0} can only be satisfied in @{1}", new Object[] {requestedPlugin, toInstall.version}); } if (toInstall.isForNewerHudson()) { LOGGER.log( WARNING, "{0}@{1} was built for a newer Jenkins", new Object[] {toInstall.name, toInstall.version}); } jobs.add(toInstall.deploy(true)); } else if (pw.isOlderThan(requestedPlugin.getValue())) { // upgrade UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey()); if (toInstall == null) { LOGGER.log(WARNING, "No such plugin {0} to upgrade", requestedPlugin.getKey()); continue; } if (!pw.isOlderThan(new VersionNumber(toInstall.version))) { LOGGER.log( WARNING, "{0}@{1} is no newer than what we already have", new Object[] {toInstall.name, toInstall.version}); continue; } if (new VersionNumber(toInstall.version).compareTo(requestedPlugin.getValue()) < 0) { LOGGER.log( WARNING, "{0} can only be satisfied in @{1}", new Object[] {requestedPlugin, toInstall.version}); } if (toInstall.isForNewerHudson()) { LOGGER.log( WARNING, "{0}@{1} was built for a newer Jenkins", new Object[] {toInstall.name, toInstall.version}); } if (!toInstall.isCompatibleWithInstalledVersion()) { LOGGER.log( WARNING, "{0}@{1} is incompatible with the installed @{2}", new Object[] {toInstall.name, toInstall.version, pw.getVersion()}); } jobs.add( toInstall.deploy( true)); // dynamicLoad=true => sure to throw RestartRequiredException, but at least // message is nicer } // else already good } return jobs; }
/** * Get the plugin instance that implements a specific class, use to find your plugin singleton. * Note: beware the classloader fun. * * @param pluginClazz The class that your plugin implements. * @return The plugin singleton or <code>null</code> if for some reason the plugin is not loaded. */ public PluginWrapper getPlugin(Class<? extends Plugin> pluginClazz) { for (PluginWrapper p : plugins) { if (pluginClazz.isInstance(p.getPlugin())) return p; } return null; }
/** * Get the plugin instance with the given short name. * * @param shortName the short name of the plugin * @return The plugin singleton or <code>null</code> if a plugin with the given short name does * not exist. */ public PluginWrapper getPlugin(String shortName) { for (PluginWrapper p : plugins) { if (p.getShortName().equals(shortName)) return p; } return null; }