diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt index dd0c44c70f..5af9d1c40e 100644 --- a/Documentation/dev-plugins.txt +++ b/Documentation/dev-plugins.txt @@ -176,6 +176,69 @@ reload:: To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload] command can be used. +[[init_step]] +Init step +~~~~~~~~~ + +Plugins can contribute their own "init step" during the Gerrit init +wizard. This is useful for guiding the Gerrit administrator through +the settings needed by the plugin to work propertly. + +For instance plugins to integrate Jira issues to Gerrit changes may +contribute their own "init step" to allow configuring the Jira URL, +credentials and possibly verify connectivity to validate them. + +==== + Gerrit-InitStep: tld.example.project.MyInitStep +==== + +MyInitStep needs to follow the standard Gerrit InitStep syntax +and behaviour: writing to the console using the injected ConsoleUI +and accessing / changing configuration settings using Section.Factory. + +In addition to the standard Gerrit init injections, plugins receive +the @PluginName String injection containing their own plugin name. + +Bear in mind that the Plugin's InitStep class will be loaded but +the standard Gerrit runtime environment is not available and the plugin's +own Guice modules were not initialized. +This means the InitStep for a plugin is not executed in the same way that +the plugin executes within the server, and may mean a plugin author cannot +trivially reuse runtime code during init. + +For instance a plugin that wants to verify connectivity may need to statically +call the constructor of their connection class, passing in values obtained +from the Section.Factory rather than from an injected Config object. + +Plugins InitStep are executing during the "Gerrit Plugin init" phase, after +the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins +and before the DB Schema initialization or upgrade. +Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime +objects injected at startup. + +==== +public class MyInitStep implements InitStep { + private final ConsoleUI ui; + private final Section.Factory sections; + private final String pluginName; + + @Inject + public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) { + this.ui = ui; + this.sections = sections; + this.pluginName = pluginName; + } + + @Override + public void run() throws Exception { + ui.header("\nMy plugin"); + + Section mySection = getSection("myplugin", null); + mySection.string("Link name", "linkname", "MyLink"); + } +} +==== + [[classpath]] Classpath --------- diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java index fa4dc142b3..ea12008d43 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java @@ -32,8 +32,8 @@ class InitAuth implements InitStep { @Inject InitAuth(final ConsoleUI ui, final Section.Factory sections) { this.ui = ui; - this.auth = sections.get("auth"); - this.ldap = sections.get("ldap"); + this.auth = sections.get("auth", null); + this.ldap = sections.get("ldap", null); } public void run() { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java index fb1a924c8f..2b5729f7bd 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java @@ -31,7 +31,7 @@ class InitCache implements InitStep { @Inject InitCache(final SitePaths site, final Section.Factory sections) { this.site = site; - this.cache = sections.get("cache"); + this.cache = sections.get("cache", null); } public void run() { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java index 7063f5440f..f34e22d0be 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java @@ -44,7 +44,7 @@ class InitContainer implements InitStep { final Section.Factory sections) { this.ui = ui; this.site = site; - this.container = sections.get("container"); + this.container = sections.get("container", null); } public void run() throws FileNotFoundException, IOException { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java index 479b9e6eda..4ce963a3ca 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java @@ -47,7 +47,7 @@ class InitDatabase implements InitStep { this.ui = ui; this.site = site; this.libraries = libraries; - this.database = sections.get("database"); + this.database = sections.get("database", null); } public void run() { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java index f0cd31fcd9..b6f8519d6c 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java @@ -31,7 +31,7 @@ class InitGitManager implements InitStep { @Inject InitGitManager(final ConsoleUI ui, final Section.Factory sections) { this.ui = ui; - this.gerrit = sections.get("gerrit"); + this.gerrit = sections.get("gerrit", null); } public void run() { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java index 98d2e472d6..feb00e1d63 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java @@ -48,8 +48,8 @@ class InitHttpd implements InitStep { this.ui = ui; this.site = site; this.flags = flags; - this.httpd = sections.get("httpd"); - this.gerrit = sections.get("gerrit"); + this.httpd = sections.get("httpd", null); + this.gerrit = sections.get("gerrit", null); } public void run() throws IOException, InterruptedException { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java new file mode 100644 index 0000000000..08422fa284 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java @@ -0,0 +1,132 @@ +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.pgm.init; + +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileFilter; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarFile; + +@Singleton +public class InitPluginStepsLoader { + private final File pluginsDir; + private final Injector initInjector; + final ConsoleUI ui; + + @Inject + public InitPluginStepsLoader(final ConsoleUI ui, final SitePaths sitePaths, + final Injector initInjector) { + this.pluginsDir = sitePaths.plugins_dir; + this.initInjector = initInjector; + this.ui = ui; + } + + public Collection getInitSteps() { + List jars = scanJarsInPluginsDirectory(); + ArrayList pluginsInitSteps = new ArrayList(); + + for (File jar : jars) { + InitStep init = loadInitStep(jar); + if (init != null) { + pluginsInitSteps.add(init); + } + } + return pluginsInitSteps; + } + + private InitStep loadInitStep(File jar) { + try { + ClassLoader pluginLoader = + new URLClassLoader(new URL[] {jar.toURI().toURL()}, + InitPluginStepsLoader.class.getClassLoader()); + JarFile jarFile = new JarFile(jar); + Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes(); + String initClassName = jarFileAttributes.getValue("Gerrit-InitStep"); + if (initClassName == null) { + return null; + } + @SuppressWarnings("unchecked") + Class initStepClass = + (Class) pluginLoader.loadClass(initClassName); + return getPluginInjector(jar).getInstance(initStepClass); + } catch (ClassCastException e) { + ui.message( + "WARN: InitStep from plugin %s does not implement %s (Exception: %s)", + jar.getName(), InitStep.class.getName(), e.getMessage()); + return null; + } catch (Exception e) { + ui.message( + "WARN: Cannot load and get plugin init step for %s (Exception: %s)", + jar, e.getMessage()); + return null; + } + } + + private Injector getPluginInjector(File jarFile) { + String jarFileName = jarFile.getName(); + final String pluginName = + jarFileName.substring(0, jarFileName.lastIndexOf('.')); + return initInjector.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(String.class).annotatedWith(PluginName.class).toInstance( + pluginName); + } + }); + } + + private List scanJarsInPluginsDirectory() { + if (pluginsDir == null || !pluginsDir.exists()) { + return Collections.emptyList(); + } + File[] matches = pluginsDir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + String n = pathname.getName(); + return (n.endsWith(".jar") && pathname.isFile()); + } + }); + if (matches == null) { + ui.message("WARN: Cannot list %s", pluginsDir.getAbsolutePath()); + return Collections.emptyList(); + } + Arrays.sort(matches, new Comparator() { + @Override + public int compare(File o1, File o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + return Arrays.asList(matches); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java index 155fe4c859..aecb808130 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.Enumeration; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -39,17 +40,24 @@ public class InitPlugins implements InitStep { private final ConsoleUI ui; private final SitePaths site; + private InitPluginStepsLoader pluginLoader; @Inject - InitPlugins(final ConsoleUI ui, final SitePaths site) { + InitPlugins(final ConsoleUI ui, final SitePaths site, InitPluginStepsLoader pluginLoader) { this.ui = ui; this.site = site; + this.pluginLoader = pluginLoader; } @Override public void run() throws Exception { ui.header("Plugins"); + installPlugins(); + initPlugins(); + } + + private void installPlugins() throws IOException { final File myWar; try { myWar = GerritLauncher.getDistributionArchive(); @@ -127,6 +135,12 @@ public class InitPlugins implements InitStep { } } + private void initPlugins() throws Exception { + for (InitStep initStep : pluginLoader.getInitSteps()) { + initStep.run(); + } + } + private static String getVersion(final File plugin) throws IOException { final JarFile jarFile = new JarFile(plugin); try { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java index c5732e9a3e..e4b827d1c0 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java @@ -34,7 +34,7 @@ class InitSendEmail implements InitStep { InitSendEmail(final ConsoleUI ui, final SitePaths site, final Section.Factory sections) { this.ui = ui; - this.sendemail = sections.get("sendemail"); + this.sendemail = sections.get("sendemail", null); this.site = site; } diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java index bfc0eaf2ce..0c2a3c65bb 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java @@ -45,7 +45,7 @@ class InitSshd implements InitStep { this.ui = ui; this.site = site; this.libraries = libraries; - this.sshd = sections.get("sshd"); + this.sshd = sections.get("sshd", null); } public void run() throws Exception { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java index c5f719f174..387b93a324 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java @@ -25,52 +25,58 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Set; +import javax.annotation.Nullable; + /** Helper to edit a section of the configuration files. */ -class Section { - interface Factory { - Section get(String name); +public class Section { + public interface Factory { + Section get(@Assisted("section") String section, + @Assisted("subsection") String subsection); } private final InitFlags flags; private final SitePaths site; private final ConsoleUI ui; private final String section; + private final String subsection; @Inject - Section(final InitFlags flags, final SitePaths site, final ConsoleUI ui, - @Assisted final String section) { + public Section(final InitFlags flags, final SitePaths site, + final ConsoleUI ui, @Assisted("section") final String section, + @Assisted("subsection") @Nullable final String subsection) { this.flags = flags; this.site = site; this.ui = ui; this.section = section; + this.subsection = subsection; } String get(String name) { return flags.cfg.getString(section, null, name); } - void set(final String name, final String value) { + public void set(final String name, final String value) { final ArrayList all = new ArrayList(); - all.addAll(Arrays.asList(flags.cfg.getStringList(section, null, name))); + all.addAll(Arrays.asList(flags.cfg.getStringList(section, subsection, name))); if (value != null) { if (all.size() == 0 || all.size() == 1) { - flags.cfg.setString(section, null, name, value); + flags.cfg.setString(section, subsection, name, value); } else { all.set(0, value); - flags.cfg.setStringList(section, null, name, all); + flags.cfg.setStringList(section, subsection, name, all); } } else if (all.size() == 0) { } else if (all.size() == 1) { - flags.cfg.unset(section, null, name); + flags.cfg.unset(section, subsection, name); } else { all.remove(0); - flags.cfg.setStringList(section, null, name, all); + flags.cfg.setStringList(section, subsection, name, all); } } - > void set(final String name, final T value) { + public > void set(final String name, final T value) { if (value != null) { set(name, value.name()); } else { @@ -78,15 +84,15 @@ class Section { } } - void unset(String name) { + public void unset(String name) { set(name, (String) null); } - String string(final String title, final String name, final String dv) { + public String string(final String title, final String name, final String dv) { return string(title, name, dv, false); } - String string(final String title, final String name, final String dv, + public String string(final String title, final String name, final String dv, final boolean nullIfDefault) { final String ov = get(name); String nv = ui.readString(ov != null ? ov : dv, "%s", title); @@ -99,19 +105,19 @@ class Section { return nv; } - File path(final String title, final String name, final String defValue) { + public File path(final String title, final String name, final String defValue) { return site.resolve(string(title, name, defValue)); } - > T select(final String title, final String name, + public > T select(final String title, final String name, final T defValue) { return select(title, name, defValue, false); } - > T select(final String title, final String name, + public > T select(final String title, final String name, final T defValue, final boolean nullIfDefault) { final boolean set = get(name) != null; - T oldValue = ConfigUtil.getEnum(flags.cfg, section, null, name, defValue); + T oldValue = ConfigUtil.getEnum(flags.cfg, section, subsection, name, defValue); T newValue = ui.readEnum(oldValue, "%s", title); if (nullIfDefault && newValue == defValue) { newValue = null; @@ -126,7 +132,7 @@ class Section { return newValue; } - String select(final String title, final String name, final String dv, + public String select(final String title, final String name, final String dv, Set allowedValues) { final String ov = get(name); String nv = ui.readString(ov != null ? ov : dv, allowedValues, "%s", title); @@ -136,16 +142,16 @@ class Section { return nv; } - String password(final String username, final String password) { + public String password(final String username, final String password) { final String ov = getSecure(password); - String user = flags.sec.getString(section, null, username); + String user = flags.sec.getString(section, subsection, username); if (user == null) { user = get(username); } if (user == null) { - flags.sec.unset(section, null, password); + flags.sec.unset(section, subsection, password); return null; } @@ -165,15 +171,15 @@ class Section { return nv; } - String getSecure(String name) { - return flags.sec.getString(section, null, name); + public String getSecure(String name) { + return flags.sec.getString(section, subsection, name); } - void setSecure(String name, String value) { + public void setSecure(String name, String value) { if (value != null) { - flags.sec.setString(section, null, name, value); + flags.sec.setString(section, subsection, name, value); } else { - flags.sec.unset(section, null, name); + flags.sec.unset(section, subsection, name); } } diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java index 4a8357b512..b982ae1caf 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java @@ -117,7 +117,7 @@ class UpgradeFrom2_0_x implements InitStep { final Properties oldprop = readGerritServerProperties(); if (oldprop != null) { - final Section database = sections.get("database"); + final Section database = sections.get("database", null); String url = oldprop.getProperty("url"); if (url != null && !convertUrl(database, url)) { diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java index 493a440092..86f0260ddc 100644 --- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java +++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java @@ -72,8 +72,8 @@ public class UpgradeFrom2_0_xTest extends InitTestCase { final ConsoleUI ui = createStrictMock(ConsoleUI.class); Section.Factory sections = new Section.Factory() { @Override - public Section get(String name) { - return new Section(flags, site, ui, name); + public Section get(String name, String subsection) { + return new Section(flags, site, ui, name, subsection); } }; diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml index 0f9569364f..81a5098467 100644 --- a/gerrit-plugin-api/pom.xml +++ b/gerrit-plugin-api/pom.xml @@ -45,6 +45,18 @@ limitations under the License. ${project.version} + + com.google.gerrit + gerrit-pgm + ${project.version} + + + org.eclipse.jetty + jetty-servlet + + + + org.apache.tomcat servlet-api