.classpath | ●●●●● patch | view | raw | blame | history | |
build.moxie | ●●●●● patch | view | raw | blame | history | |
gitblit.iml | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/manager/GitblitManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/manager/IPluginManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/manager/PluginManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/models/PluginRegistry.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/utils/StringUtils.java | ●●●●● patch | view | raw | blame | history |
.classpath
@@ -76,7 +76,7 @@ <classpathentry kind="lib" path="ext/args4j-2.0.26.jar" sourcepath="ext/src/args4j-2.0.26.jar" /> <classpathentry kind="lib" path="ext/jedis-2.3.1.jar" sourcepath="ext/src/jedis-2.3.1.jar" /> <classpathentry kind="lib" path="ext/commons-pool2-2.0.jar" sourcepath="ext/src/commons-pool2-2.0.jar" /> <classpathentry kind="lib" path="ext/pf4j-0.6.jar" sourcepath="ext/src/pf4j-0.6.jar" /> <classpathentry kind="lib" path="ext/pf4j-0.7.0.jar" sourcepath="ext/src/pf4j-0.7.0.jar" /> <classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" /> <classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" /> <classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" /> build.moxie
@@ -174,7 +174,7 @@ - compile 'args4j:args4j:2.0.26' :war :fedclient :authority - compile 'commons-codec:commons-codec:1.7' :war - compile 'redis.clients:jedis:2.3.1' :war - compile 'ro.fortsoft.pf4j:pf4j:0.6' :war - compile 'ro.fortsoft.pf4j:pf4j:0.7.0' :war - test 'junit' # Dependencies for Selenium web page testing - test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar gitblit.iml
@@ -791,13 +791,13 @@ </library> </orderEntry> <orderEntry type="module-library"> <library name="pf4j-0.6.jar"> <library name="pf4j-0.7.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/pf4j-0.6.jar!/" /> <root url="jar://$MODULE_DIR$/ext/pf4j-0.7.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.6.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.7.0.jar!/" /> </SOURCES> </library> </orderEntry> src/main/java/com/gitblit/manager/GitblitManager.java
@@ -42,9 +42,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.PluginClassLoader; import ro.fortsoft.pf4j.PluginState; import ro.fortsoft.pf4j.PluginWrapper; import ro.fortsoft.pf4j.RuntimeMode; import com.gitblit.Constants; import com.gitblit.Constants.AccessPermission; @@ -61,6 +60,7 @@ import com.gitblit.models.GitClientApplication; import com.gitblit.models.Mailing; import com.gitblit.models.Metric; import com.gitblit.models.PluginRegistry.InstallState; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; import com.gitblit.models.ProjectModel; @@ -1190,76 +1190,6 @@ */ @Override public <T> List<T> getExtensions(Class<T> clazz) { return pluginManager.getExtensions(clazz); } @Override public PluginWrapper whichPlugin(Class<?> clazz) { return pluginManager.whichPlugin(clazz); } @Override public boolean deletePlugin(PluginWrapper wrapper) { return pluginManager.deletePlugin(wrapper); } @Override public boolean refreshRegistry() { return pluginManager.refreshRegistry(); } @Override public boolean installPlugin(String url) { return pluginManager.installPlugin(url); } @Override public boolean installPlugin(PluginRelease pv) { return pluginManager.installPlugin(pv); } @Override public List<PluginRegistration> getRegisteredPlugins() { return pluginManager.getRegisteredPlugins(); } @Override public PluginRegistration lookupPlugin(String idOrName) { return pluginManager.lookupPlugin(idOrName); } @Override public PluginRelease lookupRelease(String idOrName, String version) { return pluginManager.lookupRelease(idOrName, version); } @Override public List<PluginWrapper> getPlugins() { return pluginManager.getPlugins(); } @Override public List<PluginWrapper> getResolvedPlugins() { return pluginManager.getResolvedPlugins(); } @Override public List<PluginWrapper> getUnresolvedPlugins() { return pluginManager.getUnresolvedPlugins(); } @Override public List<PluginWrapper> getStartedPlugins() { return pluginManager.getStartedPlugins(); } @Override public void loadPlugins() { pluginManager.loadPlugins(); } @Override public void startPlugins() { pluginManager.startPlugins(); } @@ -1270,12 +1200,82 @@ } @Override public PluginClassLoader getPluginClassLoader(String pluginId) { return pluginManager.getPluginClassLoader(pluginId); public List<PluginWrapper> getPlugins() { return pluginManager.getPlugins(); } @Override public RuntimeMode getRuntimeMode() { return pluginManager.getRuntimeMode(); public PluginWrapper getPlugin(String pluginId) { return pluginManager.getPlugin(pluginId); } @Override public List<Class<?>> getExtensionClasses(String pluginId) { return pluginManager.getExtensionClasses(pluginId); } @Override public <T> List<T> getExtensions(Class<T> clazz) { return pluginManager.getExtensions(clazz); } @Override public PluginWrapper whichPlugin(Class<?> clazz) { return pluginManager.whichPlugin(clazz); } @Override public PluginState startPlugin(String pluginId) { return pluginManager.startPlugin(pluginId); } @Override public PluginState stopPlugin(String pluginId) { return pluginManager.stopPlugin(pluginId); } @Override public boolean disablePlugin(String pluginId) { return pluginManager.disablePlugin(pluginId); } @Override public boolean enablePlugin(String pluginId) { return pluginManager.enablePlugin(pluginId); } @Override public boolean deletePlugin(String pluginId) { return pluginManager.deletePlugin(pluginId); } @Override public boolean refreshRegistry() { return pluginManager.refreshRegistry(); } @Override public boolean installPlugin(String url, boolean verifyChecksum) throws IOException { return pluginManager.installPlugin(url, verifyChecksum); } @Override public List<PluginRegistration> getRegisteredPlugins() { return pluginManager.getRegisteredPlugins(); } @Override public List<PluginRegistration> getRegisteredPlugins(InstallState state) { return pluginManager.getRegisteredPlugins(state); } @Override public PluginRegistration lookupPlugin(String idOrName) { return pluginManager.lookupPlugin(idOrName); } @Override public PluginRelease lookupRelease(String idOrName, String version) { return pluginManager.lookupRelease(idOrName, version); } } src/main/java/com/gitblit/manager/IPluginManager.java
@@ -15,15 +15,74 @@ */ package com.gitblit.manager; import java.io.IOException; import java.util.List; import ro.fortsoft.pf4j.PluginManager; import ro.fortsoft.pf4j.PluginState; import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.models.PluginRegistry.InstallState; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; public interface IPluginManager extends IManager, PluginManager { public interface IPluginManager extends IManager { /** * Starts all plugins. */ void startPlugins(); /** * Stops all plugins. */ void stopPlugins(); /** * Starts the specified plugin. * * @param pluginId * @return the state of the plugin */ PluginState startPlugin(String pluginId); /** * Stops the specified plugin. * * @param pluginId * @return the state of the plugin */ PluginState stopPlugin(String pluginId); /** * Returns the list of extensions the plugin provides. * * @param type * @return a list of extensions the plugin provides */ List<Class<?>> getExtensionClasses(String pluginId); /** * Returns the list of extension instances for a given extension point. * * @param type * @return a list of extension instances */ <T> List<T> getExtensions(Class<T> type); /** * Returns the list of all resolved plugins. * * @return a list of resolved plugins */ List<PluginWrapper> getPlugins(); /** * Retrieves the {@link PluginWrapper} for the specified plugin id. * * @param pluginId * @return the plugin wrapper */ PluginWrapper getPlugin(String pluginId); /** * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'. @@ -34,12 +93,28 @@ PluginWrapper whichPlugin(Class<?> clazz); /** * Delete the plugin represented by {@link PluginWrapper}. * Disable the plugin represented by pluginId. * * @param wrapper * @param pluginId * @return true if successful */ boolean deletePlugin(PluginWrapper wrapper); boolean disablePlugin(String pluginId); /** * Enable the plugin represented by pluginId. * * @param pluginId * @return true if successful */ boolean enablePlugin(String pluginId); /** * Delete the plugin represented by pluginId. * * @param pluginId * @return true if successful */ boolean deletePlugin(String pluginId); /** * Refresh the plugin registry. @@ -48,13 +123,11 @@ /** * Install the plugin from the specified url. * * @param url * @param verifyChecksum */ boolean installPlugin(String url); /** * Install the plugin. */ boolean installPlugin(PluginRelease pr); boolean installPlugin(String url, boolean verifyChecksum) throws IOException; /** * The list of all registered plugins. @@ -64,6 +137,14 @@ List<PluginRegistration> getRegisteredPlugins(); /** * Return a list of registered plugins that match the install state. * * @param state * @return the list of plugins that match the install state */ List<PluginRegistration> getRegisteredPlugins(InstallState state); /** * Lookup a plugin registration from the plugin registries. * * @param idOrName src/main/java/com/gitblit/manager/PluginManager.java
@@ -18,13 +18,18 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; import java.net.URLConnection; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -33,11 +38,16 @@ import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.DefaultPluginManager; import ro.fortsoft.pf4j.PluginClassLoader; import ro.fortsoft.pf4j.PluginState; import ro.fortsoft.pf4j.PluginStateEvent; import ro.fortsoft.pf4j.PluginStateListener; import ro.fortsoft.pf4j.PluginVersion; import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.Keys; import com.gitblit.models.PluginRegistry; import com.gitblit.models.PluginRegistry.InstallState; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; import com.gitblit.utils.Base64; @@ -49,15 +59,17 @@ /** * The plugin manager maintains the lifecycle of plugins. It is exposed as * Dagger bean. The extension consumers supposed to retrieve plugin manager * from the Dagger DI and retrieve extensions provided by active plugins. * Dagger bean. The extension consumers supposed to retrieve plugin manager from * the Dagger DI and retrieve extensions provided by active plugins. * * @author David Ostrovsky * */ public class PluginManager extends DefaultPluginManager implements IPluginManager { public class PluginManager implements IPluginManager, PluginStateListener { private final Logger logger = LoggerFactory.getLogger(getClass()); private final DefaultPluginManager pf4j; private final IRuntimeManager runtimeManager; @@ -67,47 +79,168 @@ private int readTimeout = 12800; public PluginManager(IRuntimeManager runtimeManager) { super(runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins")); File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); this.runtimeManager = runtimeManager; this.pf4j = new DefaultPluginManager(dir); } @Override public void pluginStateChanged(PluginStateEvent event) { logger.debug(event.toString()); } @Override public PluginManager start() { logger.info("Loading plugins..."); loadPlugins(); logger.info("Starting loaded plugins..."); startPlugins(); pf4j.loadPlugins(); logger.debug("Starting plugins"); pf4j.startPlugins(); return this; } @Override public PluginManager stop() { logger.info("Stopping loaded plugins..."); stopPlugins(); logger.debug("Stopping plugins"); pf4j.stopPlugins(); return null; } /** * Installs the plugin from the url. * * @param url * @param verifyChecksum * @return true if successful */ @Override public boolean deletePlugin(PluginWrapper pw) { File folder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); File pluginFolder = new File(folder, pw.getPluginPath()); File pluginZip = new File(folder, pw.getPluginPath() + ".zip"); if (pluginFolder.exists()) { FileUtils.delete(pluginFolder); public synchronized boolean installPlugin(String url, boolean verifyChecksum) throws IOException { File file = download(url, verifyChecksum); if (file == null || !file.exists()) { logger.error("Failed to download plugin {}", url); return false; } if (pluginZip.exists()) { FileUtils.delete(pluginZip); String pluginId = pf4j.loadPlugin(file); if (StringUtils.isEmpty(pluginId)) { logger.error("Failed to load plugin {}", file); return false; } PluginState state = pf4j.startPlugin(pluginId); return PluginState.STARTED.equals(state); } @Override public synchronized boolean disablePlugin(String pluginId) { return pf4j.disablePlugin(pluginId); } @Override public synchronized boolean enablePlugin(String pluginId) { if (pf4j.enablePlugin(pluginId)) { return PluginState.STARTED == pf4j.startPlugin(pluginId); } return false; } @Override public synchronized boolean deletePlugin(String pluginId) { PluginWrapper pluginWrapper = getPlugin(pluginId); final String name = pluginWrapper.getPluginPath().substring(1); if (pf4j.deletePlugin(pluginId)) { // delete the checksums File pFolder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); File [] checksums = pFolder.listFiles(new FileFilter() { @Override public boolean accept(File file) { if (!file.isFile()) { return false; } return file.getName().startsWith(name) && (file.getName().toLowerCase().endsWith(".sha1") || file.getName().toLowerCase().endsWith(".md5")); } }); if (checksums != null) { for (File checksum : checksums) { checksum.delete(); } } return true; } return false; } @Override public boolean refreshRegistry() { public synchronized PluginState startPlugin(String pluginId) { return pf4j.startPlugin(pluginId); } @Override public synchronized PluginState stopPlugin(String pluginId) { return pf4j.stopPlugin(pluginId); } @Override public synchronized void startPlugins() { pf4j.startPlugins(); } @Override public synchronized void stopPlugins() { pf4j.stopPlugins(); } @Override public synchronized List<PluginWrapper> getPlugins() { return pf4j.getPlugins(); } @Override public synchronized PluginWrapper getPlugin(String pluginId) { return pf4j.getPlugin(pluginId); } @Override public synchronized List<Class<?>> getExtensionClasses(String pluginId) { List<Class<?>> list = new ArrayList<Class<?>>(); PluginClassLoader loader = pf4j.getPluginClassLoader(pluginId); for (String className : pf4j.getExtensionClassNames(pluginId)) { try { list.add(loader.loadClass(className)); } catch (ClassNotFoundException e) { logger.error(String.format("Failed to find %s in %s", className, pluginId), e); } } return list; } @Override public synchronized <T> List<T> getExtensions(Class<T> type) { return pf4j.getExtensions(type); } @Override public synchronized PluginWrapper whichPlugin(Class<?> clazz) { return pf4j.whichPlugin(clazz); } @Override public synchronized boolean refreshRegistry() { String dr = "http://gitblit.github.io/gitblit-registry/plugins.json"; String url = runtimeManager.getSettings().getString(Keys.plugins.registry, dr); try { return download(url); File file = download(url, true); if (file != null && file.exists()) { URL selfUrl = new URL(url.substring(0, url.lastIndexOf('/'))); // replace ${self} with the registry url String content = FileUtils.readContent(file, "\n"); content = content.replace("${self}", selfUrl.toString()); FileUtils.writeContent(file, content); } } catch (Exception e) { logger.error(String.format("Failed to retrieve plugins.json from %s", url), e); } @@ -140,6 +273,7 @@ try { String json = FileUtils.readContent(file, "\n"); registry = JsonUtils.fromJsonString(json, PluginRegistry.class); registry.setup(); } catch (Exception e) { logger.error("Failed to deserialize " + file, e); } @@ -151,18 +285,17 @@ } @Override public List<PluginRegistration> getRegisteredPlugins() { public synchronized List<PluginRegistration> getRegisteredPlugins() { List<PluginRegistration> list = new ArrayList<PluginRegistration>(); Map<String, PluginRegistration> map = new TreeMap<String, PluginRegistration>(); for (PluginRegistry registry : getRegistries()) { List<PluginRegistration> registrations = registry.registrations; list.addAll(registrations); for (PluginRegistration reg : registrations) { list.addAll(registry.registrations); for (PluginRegistration reg : list) { reg.installedRelease = null; map.put(reg.id, reg); } } for (PluginWrapper pw : getPlugins()) { for (PluginWrapper pw : pf4j.getPlugins()) { String id = pw.getDescriptor().getPluginId(); PluginVersion pv = pw.getDescriptor().getVersion(); PluginRegistration reg = map.get(id); @@ -174,10 +307,21 @@ } @Override public PluginRegistration lookupPlugin(String idOrName) { for (PluginRegistry registry : getRegistries()) { PluginRegistration reg = registry.lookup(idOrName); if (reg != null) { public synchronized List<PluginRegistration> getRegisteredPlugins(InstallState state) { List<PluginRegistration> list = getRegisteredPlugins(); Iterator<PluginRegistration> itr = list.iterator(); while (itr.hasNext()) { if (state != itr.next().getInstallState()) { itr.remove(); } } return list; } @Override public synchronized PluginRegistration lookupPlugin(String idOrName) { for (PluginRegistration reg : getRegisteredPlugins()) { if (reg.id.equalsIgnoreCase(idOrName) || reg.name.equalsIgnoreCase(idOrName)) { return reg; } } @@ -185,64 +329,107 @@ } @Override public PluginRelease lookupRelease(String idOrName, String version) { for (PluginRegistry registry : getRegistries()) { PluginRegistration reg = registry.lookup(idOrName); if (reg != null) { public synchronized PluginRelease lookupRelease(String idOrName, String version) { PluginRegistration reg = lookupPlugin(idOrName); if (reg == null) { return null; } PluginRelease pv; if (StringUtils.isEmpty(version)) { pv = reg.getCurrentRelease(); } else { pv = reg.getRelease(version); } if (pv != null) { return pv; } } } return null; } /** * Installs the plugin from the plugin version. * * @param pv * @throws IOException * @return true if successful */ @Override public boolean installPlugin(PluginRelease pv) { return installPlugin(pv.url); } /** * Installs the plugin from the url. * Downloads a file with optional checksum verification. * * @param url * @return true if successful * @param verifyChecksum * @return * @throws IOException */ @Override public boolean installPlugin(String url) { protected File download(String url, boolean verifyChecksum) throws IOException { File file = downloadFile(url); File sha1File = null; try { if (!download(url)) { return false; } // TODO stop, unload, load sha1File = downloadFile(url + ".sha1"); } catch (IOException e) { logger.error("Failed to install plugin from " + url, e); } return true; File md5File = null; try { md5File = downloadFile(url + ".md5"); } catch (IOException e) { } if (sha1File == null && md5File == null && verifyChecksum) { throw new IOException("Missing SHA1 and MD5 checksums for " + url); } String expected; MessageDigest md = null; if (sha1File != null && sha1File.exists()) { // prefer SHA1 to MD5 expected = FileUtils.readContent(sha1File, "\n").split(" ")[0].trim(); try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { logger.error(null, e); } } else { expected = FileUtils.readContent(md5File, "\n").split(" ")[0].trim(); try { md = MessageDigest.getInstance("MD5"); } catch (Exception e) { logger.error(null, e); } } // calculate the checksum FileInputStream is = null; try { is = new FileInputStream(file); DigestInputStream dis = new DigestInputStream(is, md); byte [] buffer = new byte[1024]; while ((dis.read(buffer)) > -1) { // read } dis.close(); byte [] digest = md.digest(); String calculated = StringUtils.toHex(digest).trim(); if (!expected.equals(calculated)) { String msg = String.format("Invalid checksum for %s\nAlgorithm: %s\nExpected: %s\nCalculated: %s", file.getAbsolutePath(), md.getAlgorithm(), expected, calculated); file.delete(); throw new IOException(msg); } } finally { if (is != null) { is.close(); } } return file; } /** * Download a file to the plugins folder. * * @param url * @return * @return the downloaded file * @throws IOException */ protected boolean download(String url) throws IOException { protected File downloadFile(String url) throws IOException { File pFolder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); pFolder.mkdirs(); File tmpFile = new File(pFolder, StringUtils.getSHA1(url) + ".tmp"); @@ -270,7 +457,7 @@ tmpFile.renameTo(destFile); destFile.setLastModified(lastModified); return true; return destFile; } protected URLConnection getConnection(URL url) throws IOException { src/main/java/com/gitblit/models/PluginRegistry.java
@@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.parboiled.common.StringUtils; @@ -37,7 +38,13 @@ public PluginRegistry(String name) { this.name = name; registrations = new ArrayList<PluginRegistration>(); registrations = new CopyOnWriteArrayList<PluginRegistration>(); } public void setup() { for (PluginRegistration reg : registrations) { reg.registry = name; } } public PluginRegistration lookup(String idOrName) { @@ -80,6 +87,8 @@ public transient String installedRelease; public transient String registry; public List<PluginRelease> releases; public PluginRegistration(String id) { @@ -90,10 +99,12 @@ public PluginRelease getCurrentRelease() { PluginRelease current = null; if (!StringUtils.isEmpty(currentRelease)) { // find specified current = getRelease(currentRelease); } if (current == null) { // find by date Date date = new Date(0); for (PluginRelease pv : releases) { if (pv.date.after(date)) { @@ -135,9 +146,15 @@ } } public static class PluginRelease { public static class PluginRelease implements Comparable<PluginRelease> { public String version; public Date date; public String requires; public String url; @Override public int compareTo(PluginRelease o) { return PluginVersion.createVersion(version).compareTo(PluginVersion.createVersion(o.version)); } } } src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java
@@ -15,23 +15,26 @@ */ package com.gitblit.transport.ssh.commands; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; import ro.fortsoft.pf4j.ExtensionPoint; import ro.fortsoft.pf4j.PluginDependency; import ro.fortsoft.pf4j.PluginDescriptor; import ro.fortsoft.pf4j.PluginState; import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.manager.IGitblit; import com.gitblit.models.PluginRegistry.InstallState; import com.gitblit.models.PluginRegistry.PluginRegistration; import com.gitblit.models.PluginRegistry.PluginRelease; import com.gitblit.models.UserModel; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; import com.google.common.base.Joiner; /** * The plugin dispatcher and commands for runtime plugin management. @@ -47,13 +50,16 @@ register(user, ListPlugins.class); register(user, StartPlugin.class); register(user, StopPlugin.class); register(user, EnablePlugin.class); register(user, DisablePlugin.class); register(user, ShowPlugin.class); register(user, RemovePlugin.class); register(user, InstallPlugin.class); register(user, RefreshPlugins.class); register(user, AvailablePlugins.class); register(user, InstallPlugin.class); register(user, UninstallPlugin.class); } @CommandMetaData(name = "list", aliases = { "ls" }, description = "List the loaded plugins") @CommandMetaData(name = "list", aliases = { "ls" }, description = "List plugins") public static class ListPlugins extends ListCommand<PluginWrapper> { @Override @@ -67,7 +73,7 @@ protected void asTable(List<PluginWrapper> list) { String[] headers; if (verbose) { String [] h = { "#", "Id", "Version", "State", "Mode", "Path", "Provider"}; String [] h = { "#", "Id", "Version", "State", "Path", "Provider"}; headers = h; } else { String [] h = { "#", "Id", "Version", "State", "Path"}; @@ -78,7 +84,7 @@ PluginWrapper p = list.get(i); PluginDescriptor d = p.getDescriptor(); if (verbose) { data[i] = new Object[] { "" + (i + 1), d.getPluginId(), d.getVersion(), p.getPluginState(), p.getRuntimeMode(), p.getPluginPath(), d.getProvider() }; data[i] = new Object[] { "" + (i + 1), d.getPluginId(), d.getVersion(), p.getPluginState(), p.getPluginPath(), d.getProvider() }; } else { data[i] = new Object[] { "" + (i + 1), d.getPluginId(), d.getVersion(), p.getPluginState(), p.getPluginPath() }; } @@ -92,7 +98,7 @@ for (PluginWrapper pw : list) { PluginDescriptor d = pw.getDescriptor(); if (verbose) { outTabbed(d.getPluginId(), d.getVersion(), pw.getPluginState(), pw.getRuntimeMode(), pw.getPluginPath(), d.getProvider()); outTabbed(d.getPluginId(), d.getVersion(), pw.getPluginState(), pw.getPluginPath(), d.getProvider()); } else { outTabbed(d.getPluginId(), d.getVersion(), pw.getPluginState(), pw.getPluginPath()); } @@ -100,146 +106,265 @@ } } static abstract class PluginCommand extends SshCommand { protected PluginWrapper getPlugin(String id) throws Failure { IGitblit gitblit = getContext().getGitblit(); PluginWrapper pluginWrapper = null; try { int index = Integer.parseInt(id); List<PluginWrapper> plugins = gitblit.getPlugins(); if (index > plugins.size()) { throw new UnloggedFailure(1, "Invalid plugin index specified!"); } pluginWrapper = plugins.get(index - 1); } catch (NumberFormatException e) { pluginWrapper = gitblit.getPlugin(id); if (pluginWrapper == null) { PluginRegistration reg = gitblit.lookupPlugin(id); if (reg == null) { throw new UnloggedFailure("Invalid plugin specified!"); } pluginWrapper = gitblit.getPlugin(reg.id); } } return pluginWrapper; } } @CommandMetaData(name = "start", description = "Start a plugin") public static class StartPlugin extends SshCommand { public static class StartPlugin extends PluginCommand { @Argument(index = 0, required = true, metaVar = "ALL|<id>", usage = "the plugin to start") protected String plugin; protected String id; @Override public void run() throws UnloggedFailure { public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); if (plugin.equalsIgnoreCase("ALL")) { if (id.equalsIgnoreCase("ALL")) { gitblit.startPlugins(); stdout.println("All plugins started"); } else { try { int index = Integer.parseInt(plugin); List<PluginWrapper> plugins = gitblit.getPlugins(); if (index > plugins.size()) { throw new UnloggedFailure(1, "Invalid plugin index specified!"); } PluginWrapper pw = plugins.get(index - 1); start(pw); } catch (NumberFormatException n) { for (PluginWrapper pw : gitblit.getPlugins()) { PluginDescriptor pd = pw.getDescriptor(); if (pd.getPluginId().equalsIgnoreCase(plugin)) { start(pw); break; } } } } PluginWrapper pluginWrapper = getPlugin(id); if (pluginWrapper == null) { throw new UnloggedFailure(String.format("Plugin %s is not installed!", id)); } protected void start(PluginWrapper pw) throws UnloggedFailure { String id = pw.getDescriptor().getPluginId(); if (pw.getPluginState() == PluginState.STARTED) { throw new UnloggedFailure(1, String.format("%s is already started.", id)); } try { pw.getPlugin().start(); // pw.setPluginState(PluginState.STARTED); stdout.println(String.format("%s started", id)); } catch (Exception pe) { throw new UnloggedFailure(1, String.format("Failed to start %s", id), pe); PluginState state = gitblit.startPlugin(pluginWrapper.getPluginId()); if (PluginState.STARTED.equals(state)) { stdout.println(String.format("Started %s", pluginWrapper.getPluginId())); } else { throw new Failure(1, String.format("Failed to start %s", pluginWrapper.getPluginId())); } } } } @CommandMetaData(name = "stop", description = "Stop a plugin") public static class StopPlugin extends SshCommand { public static class StopPlugin extends PluginCommand { @Argument(index = 0, required = true, metaVar = "ALL|<id>", usage = "the plugin to stop") protected String plugin; protected String id; @Override public void run() throws UnloggedFailure { public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); if (plugin.equalsIgnoreCase("ALL")) { if (id.equalsIgnoreCase("ALL")) { gitblit.stopPlugins(); stdout.println("All plugins stopped"); } else { try { int index = Integer.parseInt(plugin); List<PluginWrapper> plugins = gitblit.getPlugins(); if (index > plugins.size()) { throw new UnloggedFailure(1, "Invalid plugin index specified!"); PluginWrapper pluginWrapper = getPlugin(id); if (pluginWrapper == null) { throw new UnloggedFailure(String.format("Plugin %s is not installed!", id)); } PluginWrapper pw = plugins.get(index - 1); stop(pw); } catch (NumberFormatException n) { for (PluginWrapper pw : gitblit.getPlugins()) { PluginDescriptor pd = pw.getDescriptor(); if (pd.getPluginId().equalsIgnoreCase(plugin)) { stop(pw); break; } PluginState state = gitblit.stopPlugin(pluginWrapper.getPluginId()); if (PluginState.STOPPED.equals(state)) { stdout.println(String.format("Stopped %s", pluginWrapper.getPluginId())); } else { throw new Failure(1, String.format("Failed to stop %s", pluginWrapper.getPluginId())); } } } } protected void stop(PluginWrapper pw) throws UnloggedFailure { String id = pw.getDescriptor().getPluginId(); if (pw.getPluginState() == PluginState.STOPPED) { throw new UnloggedFailure(1, String.format("%s is already stopped.", id)); @CommandMetaData(name = "enable", description = "Enable a plugin") public static class EnablePlugin extends PluginCommand { @Argument(index = 0, required = true, metaVar = "<id>", usage = "the plugin id to enable") protected String id; @Override public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); PluginWrapper pluginWrapper = getPlugin(id); if (pluginWrapper == null) { throw new UnloggedFailure("Invalid plugin specified!"); } try { pw.getPlugin().stop(); // pw.setPluginState(PluginState.STOPPED); stdout.println(String.format("%s stopped", id)); } catch (Exception pe) { throw new UnloggedFailure(1, String.format("Failed to stop %s", id), pe); if (gitblit.enablePlugin(pluginWrapper.getPluginId())) { stdout.println(String.format("Enabled %s", pluginWrapper.getPluginId())); } else { throw new Failure(1, String.format("Failed to enable %s", pluginWrapper.getPluginId())); } } } @CommandMetaData(name = "disable", description = "Disable a plugin") public static class DisablePlugin extends PluginCommand { @Argument(index = 0, required = true, metaVar = "<id>", usage = "the plugin to disable") protected String id; @Override public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); PluginWrapper pluginWrapper = getPlugin(id); if (pluginWrapper == null) { throw new UnloggedFailure("Invalid plugin specified!"); } if (gitblit.disablePlugin(pluginWrapper.getPluginId())) { stdout.println(String.format("Disabled %s", pluginWrapper.getPluginId())); } else { throw new Failure(1, String.format("Failed to disable %s", pluginWrapper.getPluginId())); } } } @CommandMetaData(name = "show", description = "Show the details of a plugin") public static class ShowPlugin extends SshCommand { public static class ShowPlugin extends PluginCommand { @Argument(index = 0, required = true, metaVar = "<id>", usage = "the plugin to stop") protected int index; @Argument(index = 0, required = true, metaVar = "<id>", usage = "the plugin to show") protected String id; @Override public void run() throws UnloggedFailure { public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); List<PluginWrapper> plugins = gitblit.getPlugins(); if (index > plugins.size()) { throw new UnloggedFailure(1, "Invalid plugin index specified!"); PluginWrapper pw = getPlugin(id); if (pw == null) { PluginRegistration registration = gitblit.lookupPlugin(id); if (registration == null) { throw new Failure(1, String.format("Unknown plugin %s", id)); } PluginWrapper pw = plugins.get(index - 1); PluginDescriptor d = pw.getDescriptor(); show(registration); } else { show(pw); } } protected String buildFieldTable(PluginWrapper pw, PluginRegistration reg) { final String id = pw == null ? reg.id : pw.getPluginId(); final String name = reg == null ? "" : reg.name; final String version = pw == null ? "" : pw.getDescriptor().getVersion().toString(); final String provider = pw == null ? reg.provider : pw.getDescriptor().getProvider(); final String registry = reg == null ? "" : reg.registry; final String path = pw == null ? "" : pw.getPluginPath(); final String projectUrl = reg == null ? "" : reg.projectUrl; final String state; if (pw == null) { // plugin could be installed state = InstallState.NOT_INSTALLED.toString(); } else if (reg == null) { // unregistered, installed plugin state = Joiner.on(", ").join(InstallState.INSTALLED, pw.getPluginState()); } else { // registered, installed plugin state = Joiner.on(", ").join(reg.getInstallState(), pw.getPluginState()); } StringBuilder sb = new StringBuilder(); sb.append("ID : ").append(id).append('\n'); sb.append("Version : ").append(version).append('\n'); sb.append("State : ").append(state).append('\n'); sb.append("Path : ").append(path).append('\n'); sb.append('\n'); sb.append("Name : ").append(name).append('\n'); sb.append("Provider : ").append(provider).append('\n'); sb.append("Project URL : ").append(projectUrl).append('\n'); sb.append("Registry : ").append(registry).append('\n'); return sb.toString(); } protected String buildReleaseTable(PluginRegistration reg) { List<PluginRelease> releases = reg.releases; Collections.sort(releases); String releaseTable; if (releases.isEmpty()) { releaseTable = FlipTable.EMPTY; } else { String[] headers = { "Version", "Date", "Requires" }; Object[][] data = new Object[releases.size()][]; for (int i = 0; i < releases.size(); i++) { PluginRelease release = releases.get(i); data[i] = new Object[] { (release.version.equals(reg.installedRelease) ? ">" : " ") + release.version, release.date, release.requires }; } releaseTable = FlipTable.of(headers, data, Borders.COLS); } return releaseTable; } /** * Show an uninstalled plugin. * * @param reg */ protected void show(PluginRegistration reg) { // REGISTRATION final String fields = buildFieldTable(null, reg); final String releases = buildReleaseTable(reg); String[] headers = { reg.id }; Object[][] data = new Object[3][]; data[0] = new Object[] { fields }; data[1] = new Object[] { "RELEASES" }; data[2] = new Object[] { releases }; stdout.println(FlipTable.of(headers, data)); } /** * Show an installed plugin. * * @param pw */ protected void show(PluginWrapper pw) { IGitblit gitblit = getContext().getGitblit(); PluginRegistration reg = gitblit.lookupPlugin(pw.getPluginId()); // FIELDS StringBuilder sb = new StringBuilder(); sb.append("Version : ").append(d.getVersion()).append('\n'); sb.append("Provider : ").append(d.getProvider()).append('\n'); sb.append("Path : ").append(pw.getPluginPath()).append('\n'); sb.append("State : ").append(pw.getPluginState()).append('\n'); final String fields = sb.toString(); final String fields = buildFieldTable(pw, reg); // TODO EXTENSIONS sb.setLength(0); List<String> exts = new ArrayList<String>(); // EXTENSIONS StringBuilder sb = new StringBuilder(); List<Class<?>> exts = gitblit.getExtensionClasses(pw.getPluginId()); String extensions; if (exts.isEmpty()) { extensions = FlipTable.EMPTY; } else { String[] headers = { "Id", "Version" }; Object[][] data = new Object[exts.size()][]; StringBuilder description = new StringBuilder(); for (int i = 0; i < exts.size(); i++) { String ext = exts.get(i); data[0] = new Object[] { ext.toString(), ext.toString() }; Class<?> ext = exts.get(i); if (ext.isAnnotationPresent(CommandMetaData.class)) { CommandMetaData meta = ext.getAnnotation(CommandMetaData.class); description.append(meta.name()); if (meta.description().length() > 0) { description.append(": ").append(meta.description()); } extensions = FlipTable.of(headers, data, Borders.COLS); description.append('\n'); } description.append(ext.getName()).append("\n â”” "); description.append(getExtensionPoint(ext).getName()); description.append("\n\n"); } extensions = description.toString(); } // DEPENDENCIES sb.setLength(0); List<PluginDependency> deps = d.getDependencies(); List<PluginDependency> deps = pw.getDescriptor().getDependencies(); String dependencies; if (deps.isEmpty()) { dependencies = FlipTable.EMPTY; @@ -248,80 +373,47 @@ Object[][] data = new Object[deps.size()][]; for (int i = 0; i < deps.size(); i++) { PluginDependency dep = deps.get(i); data[0] = new Object[] { dep.getPluginId(), dep.getPluginVersion() }; data[i] = new Object[] { dep.getPluginId(), dep.getPluginVersion() }; } dependencies = FlipTable.of(headers, data, Borders.COLS); } String[] headers = { d.getPluginId() }; Object[][] data = new Object[5][]; // RELEASES String releases; if (reg == null) { releases = FlipTable.EMPTY; } else { releases = buildReleaseTable(reg); } String[] headers = { pw.getPluginId() }; Object[][] data = new Object[7][]; data[0] = new Object[] { fields }; data[1] = new Object[] { "EXTENSIONS" }; data[2] = new Object[] { extensions }; data[3] = new Object[] { "DEPENDENCIES" }; data[4] = new Object[] { dependencies }; data[5] = new Object[] { "RELEASES" }; data[6] = new Object[] { releases }; stdout.println(FlipTable.of(headers, data)); } /* Find the ExtensionPoint */ protected Class<?> getExtensionPoint(Class<?> clazz) { Class<?> superClass = clazz.getSuperclass(); if (ExtensionPoint.class.isAssignableFrom(superClass)) { return superClass; } return getExtensionPoint(superClass); } } @CommandMetaData(name = "remove", aliases= { "rm", "del" }, description = "Remove a plugin", hidden = true) public static class RemovePlugin extends SshCommand { @Argument(index = 0, required = true, metaVar = "<id>", usage = "the plugin to stop") protected int index; @CommandMetaData(name = "refresh", description = "Refresh the plugin registry data") public static class RefreshPlugins extends SshCommand { @Override public void run() throws UnloggedFailure { public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); List<PluginWrapper> plugins = gitblit.getPlugins(); if (index > plugins.size()) { throw new UnloggedFailure(1, "Invalid plugin index specified!"); } PluginWrapper pw = plugins.get(index - 1); PluginDescriptor d = pw.getDescriptor(); if (gitblit.deletePlugin(pw)) { stdout.println(String.format("Deleted %s %s", d.getPluginId(), d.getVersion())); } else { throw new UnloggedFailure(1, String.format("Failed to delete %s %s", d.getPluginId(), d.getVersion())); } } } @CommandMetaData(name = "install", description = "Download and installs a plugin", hidden = true) public static class InstallPlugin extends SshCommand { @Argument(index = 0, required = true, metaVar = "<URL>|<ID>|<NAME>", usage = "the id, name, or the url of the plugin to download and install") protected String urlOrIdOrName; @Option(name = "--version", usage = "The specific version to install") private String version; @Override public void run() throws UnloggedFailure { IGitblit gitblit = getContext().getGitblit(); try { String ulc = urlOrIdOrName.toLowerCase(); if (ulc.startsWith("http://") || ulc.startsWith("https://")) { if (gitblit.installPlugin(urlOrIdOrName)) { stdout.println(String.format("Installed %s", urlOrIdOrName)); } else { new UnloggedFailure(1, String.format("Failed to install %s", urlOrIdOrName)); } } else { PluginRelease pv = gitblit.lookupRelease(urlOrIdOrName, version); if (pv == null) { throw new UnloggedFailure(1, String.format("Plugin \"%s\" is not in the registry!", urlOrIdOrName)); } if (gitblit.installPlugin(pv)) { stdout.println(String.format("Installed %s", urlOrIdOrName)); } else { throw new UnloggedFailure(1, String.format("Failed to install %s", urlOrIdOrName)); } } } catch (Exception e) { log.error("Failed to install " + urlOrIdOrName, e); throw new UnloggedFailure(1, String.format("Failed to install %s", urlOrIdOrName), e); } gitblit.refreshRegistry(); } } @@ -331,13 +423,22 @@ @Option(name = "--refresh", aliases = { "-r" }, usage = "refresh the plugin registry") protected boolean refresh; @Option(name = "--updates", aliases = { "-u" }, usage = "show available updates") protected boolean updates; @Override protected List<PluginRegistration> getItems() throws UnloggedFailure { IGitblit gitblit = getContext().getGitblit(); if (refresh) { gitblit.refreshRegistry(); } List<PluginRegistration> list = gitblit.getRegisteredPlugins(); List<PluginRegistration> list; if (updates) { list = gitblit.getRegisteredPlugins(InstallState.CAN_UPDATE); } else { list = gitblit.getRegisteredPlugins(); } return list; } @@ -350,19 +451,20 @@ protected void asTable(List<PluginRegistration> list) { String[] headers; if (verbose) { String [] h = { "Name", "Description", "Installed", "Release", "State", "Id", "Provider" }; String [] h = { "Id", "Name", "Description", "Installed", "Current", "Requires", "State", "Registry" }; headers = h; } else { String [] h = { "Name", "Description", "Installed", "Release", "State" }; String [] h = { "Id", "Name", "Installed", "Current", "Requires", "State" }; headers = h; } Object[][] data = new Object[list.size()][]; for (int i = 0; i < list.size(); i++) { PluginRegistration p = list.get(i); PluginRelease curr = p.getCurrentRelease(); if (verbose) { data[i] = new Object[] {p.name, p.description, p.installedRelease, p.currentRelease, p.getInstallState(), p.id, p.provider}; data[i] = new Object[] {p.id, p.name, p.description, p.installedRelease, curr.version, curr.requires, p.getInstallState(), p.registry}; } else { data[i] = new Object[] {p.name, p.description, p.installedRelease, p.currentRelease, p.getInstallState()}; data[i] = new Object[] {p.id, p.name, p.installedRelease, curr.version, curr.requires, p.getInstallState()}; } } @@ -372,12 +474,76 @@ @Override protected void asTabbed(List<PluginRegistration> list) { for (PluginRegistration p : list) { PluginRelease curr = p.getCurrentRelease(); if (verbose) { outTabbed(p.name, p.description, p.currentRelease, p.getInstallState(), p.id, p.provider); outTabbed(p.id, p.name, p.description, p.installedRelease, curr.version, curr.requires, p.getInstallState(), p.provider, p.registry); } else { outTabbed(p.name, p.description, p.currentRelease, p.getInstallState()); outTabbed(p.id, p.name, p.installedRelease, curr.version, curr.requires, p.getInstallState()); } } } } @CommandMetaData(name = "install", description = "Download and installs a plugin") public static class InstallPlugin extends SshCommand { @Argument(index = 0, required = true, metaVar = "<URL>|<ID>|<NAME>", usage = "the id, name, or the url of the plugin to download and install") protected String urlOrIdOrName; @Option(name = "--version", usage = "The specific version to install") private String version; @Option(name = "--noverify", usage = "Disable checksum verification") private boolean disableChecksum; @Override public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); try { String ulc = urlOrIdOrName.toLowerCase(); if (ulc.startsWith("http://") || ulc.startsWith("https://")) { if (gitblit.installPlugin(urlOrIdOrName, !disableChecksum)) { stdout.println(String.format("Installed %s", urlOrIdOrName)); } else { new Failure(1, String.format("Failed to install %s", urlOrIdOrName)); } } else { PluginRelease pv = gitblit.lookupRelease(urlOrIdOrName, version); if (pv == null) { throw new Failure(1, String.format("Plugin \"%s\" is not in the registry!", urlOrIdOrName)); } if (gitblit.installPlugin(pv.url, !disableChecksum)) { stdout.println(String.format("Installed %s", urlOrIdOrName)); } else { throw new Failure(1, String.format("Failed to install %s", urlOrIdOrName)); } } } catch (Exception e) { log.error("Failed to install " + urlOrIdOrName, e); throw new Failure(1, String.format("Failed to install %s", urlOrIdOrName), e); } } } @CommandMetaData(name = "uninstall", aliases = { "rm", "del" }, description = "Uninstall a plugin") public static class UninstallPlugin extends PluginCommand { @Argument(index = 0, required = true, metaVar = "<id>", usage = "the plugin to uninstall") protected String id; @Override public void run() throws Failure { IGitblit gitblit = getContext().getGitblit(); PluginWrapper pluginWrapper = getPlugin(id); if (pluginWrapper == null) { throw new UnloggedFailure(String.format("Plugin %s is not installed!", id)); } if (gitblit.deletePlugin(pluginWrapper.getPluginId())) { stdout.println(String.format("Uninstalled %s", pluginWrapper.getPluginId())); } else { throw new Failure(1, String.format("Failed to uninstall %s", pluginWrapper.getPluginId())); } } } } src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java
@@ -20,6 +20,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.fortsoft.pf4j.PluginWrapper; import com.gitblit.manager.IGitblit; import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.SshDaemonClient; @@ -49,9 +51,10 @@ List<DispatchCommand> exts = gitblit.getExtensions(DispatchCommand.class); for (DispatchCommand ext : exts) { Class<? extends DispatchCommand> extClass = ext.getClass(); String plugin = gitblit.whichPlugin(extClass).getDescriptor().getPluginId(); PluginWrapper wrapper = gitblit.whichPlugin(extClass); String plugin = wrapper.getDescriptor().getPluginId(); CommandMetaData meta = extClass.getAnnotation(CommandMetaData.class); log.info("Dispatcher {} is loaded from plugin {}", meta.name(), plugin); log.debug("Dispatcher {} is loaded from plugin {}", meta.name(), plugin); register(user, ext); } } src/main/java/com/gitblit/utils/StringUtils.java
@@ -307,7 +307,7 @@ * @param bytes * @return byte array as hex string */ private static String toHex(byte[] bytes) { public static String toHex(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (int i = 0; i < bytes.length; i++) { if ((bytes[i] & 0xff) < 0x10) {