]> source.dussan.org Git - gitblit.git/commitdiff
Improve plugin manager based on upstreamed contributions to pf4j
authorJames Moger <james.moger@gitblit.com>
Thu, 10 Apr 2014 21:33:19 +0000 (17:33 -0400)
committerJames Moger <james.moger@gitblit.com>
Thu, 10 Apr 2014 23:01:30 +0000 (19:01 -0400)
.classpath
build.moxie
gitblit.iml
src/main/java/com/gitblit/manager/GitblitManager.java
src/main/java/com/gitblit/manager/IPluginManager.java
src/main/java/com/gitblit/manager/PluginManager.java
src/main/java/com/gitblit/models/PluginRegistry.java
src/main/java/com/gitblit/transport/ssh/commands/PluginDispatcher.java
src/main/java/com/gitblit/transport/ssh/commands/RootDispatcher.java
src/main/java/com/gitblit/utils/StringUtils.java

index 252a7c96e866346736f1d4f5d71e4d8942c87264..bcf2fbd8919c6eb7e5e17651ee383bbfab03f482 100644 (file)
@@ -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" />
index eb2878a8698e2caa4899c7ba0c06340681fbec9f..bc4d7d5e92251b9b5b615ce372030fb14bebdfff 100644 (file)
@@ -174,7 +174,7 @@ dependencies:
 - 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
index ed067f280f599c5c017d02fba29800f95f56b101..0d5fb690ca9b276b98dc209d0b376ef7d6d27e6a 100644 (file)
       </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>
index e3b6cf7d8a866261f3bfd2aea32336c274c41697..191d7cf1c1e429dcbbf7c2727d15b28b8374b85c 100644 (file)
@@ -42,9 +42,8 @@ import org.eclipse.jgit.transport.RefSpec;
 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.ForkModel;
 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,92 +1190,92 @@ public class GitblitManager implements IGitblit {
         */
 
        @Override
-       public <T> List<T> getExtensions(Class<T> clazz) {
-               return pluginManager.getExtensions(clazz);
+       public void startPlugins() {
+               pluginManager.startPlugins();
        }
 
        @Override
-       public PluginWrapper whichPlugin(Class<?> clazz) {
-               return pluginManager.whichPlugin(clazz);
+       public void stopPlugins() {
+               pluginManager.stopPlugins();
        }
 
        @Override
-       public boolean deletePlugin(PluginWrapper wrapper) {
-               return pluginManager.deletePlugin(wrapper);
+       public List<PluginWrapper> getPlugins() {
+               return pluginManager.getPlugins();
        }
 
        @Override
-       public boolean refreshRegistry() {
-               return pluginManager.refreshRegistry();
+       public PluginWrapper getPlugin(String pluginId) {
+               return pluginManager.getPlugin(pluginId);
        }
 
        @Override
-       public boolean installPlugin(String url) {
-               return pluginManager.installPlugin(url);
+       public List<Class<?>> getExtensionClasses(String pluginId) {
+               return pluginManager.getExtensionClasses(pluginId);
        }
 
        @Override
-       public boolean installPlugin(PluginRelease pv) {
-               return pluginManager.installPlugin(pv);
+       public <T> List<T> getExtensions(Class<T> clazz) {
+               return pluginManager.getExtensions(clazz);
        }
 
        @Override
-       public List<PluginRegistration> getRegisteredPlugins() {
-               return pluginManager.getRegisteredPlugins();
+       public PluginWrapper whichPlugin(Class<?> clazz) {
+               return pluginManager.whichPlugin(clazz);
        }
 
        @Override
-       public PluginRegistration lookupPlugin(String idOrName) {
-               return pluginManager.lookupPlugin(idOrName);
+       public PluginState startPlugin(String pluginId) {
+               return pluginManager.startPlugin(pluginId);
        }
 
        @Override
-       public PluginRelease lookupRelease(String idOrName, String version) {
-               return pluginManager.lookupRelease(idOrName, version);
+       public PluginState stopPlugin(String pluginId) {
+               return pluginManager.stopPlugin(pluginId);
        }
 
        @Override
-       public List<PluginWrapper> getPlugins() {
-               return pluginManager.getPlugins();
+       public boolean disablePlugin(String pluginId) {
+               return pluginManager.disablePlugin(pluginId);
        }
 
        @Override
-       public List<PluginWrapper> getResolvedPlugins() {
-               return pluginManager.getResolvedPlugins();
+       public boolean enablePlugin(String pluginId) {
+               return pluginManager.enablePlugin(pluginId);
        }
 
        @Override
-       public List<PluginWrapper> getUnresolvedPlugins() {
-               return pluginManager.getUnresolvedPlugins();
+       public boolean deletePlugin(String pluginId) {
+               return pluginManager.deletePlugin(pluginId);
        }
 
        @Override
-       public List<PluginWrapper> getStartedPlugins() {
-               return pluginManager.getStartedPlugins();
+       public boolean refreshRegistry() {
+               return pluginManager.refreshRegistry();
        }
 
        @Override
-       public void loadPlugins() {
-               pluginManager.loadPlugins();
+       public boolean installPlugin(String url, boolean verifyChecksum) throws IOException {
+               return pluginManager.installPlugin(url, verifyChecksum);
        }
 
        @Override
-       public void startPlugins() {
-               pluginManager.startPlugins();
+       public List<PluginRegistration> getRegisteredPlugins() {
+               return pluginManager.getRegisteredPlugins();
        }
 
        @Override
-       public void stopPlugins() {
-               pluginManager.stopPlugins();
+       public List<PluginRegistration> getRegisteredPlugins(InstallState state) {
+               return pluginManager.getRegisteredPlugins(state);
        }
 
        @Override
-       public PluginClassLoader getPluginClassLoader(String pluginId) {
-               return pluginManager.getPluginClassLoader(pluginId);
+       public PluginRegistration lookupPlugin(String idOrName) {
+               return pluginManager.lookupPlugin(idOrName);
        }
 
        @Override
-       public RuntimeMode getRuntimeMode() {
-               return pluginManager.getRuntimeMode();
+       public PluginRelease lookupRelease(String idOrName, String version) {
+               return pluginManager.lookupRelease(idOrName, version);
        }
 }
index 1f7f85eec209f009ac9a5a14d22fce4af805a1e9..33763aa860f55545c703dcc48ceb6c276eed90f2 100644 (file)
  */
 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,27 +93,41 @@ public interface IPluginManager extends IManager, PluginManager {
     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);
 
     /**
-     * Refresh the plugin registry.
+     * Enable the plugin represented by pluginId.
+     *
+     * @param pluginId
+     * @return true if successful
      */
-    boolean refreshRegistry();
+    boolean enablePlugin(String pluginId);
 
     /**
-     * Install the plugin from the specified url.
+     * Delete the plugin represented by pluginId.
+     *
+     * @param pluginId
+     * @return true if successful
      */
-    boolean installPlugin(String url);
+    boolean deletePlugin(String pluginId);
 
     /**
-     * Install the plugin.
+     * Refresh the plugin registry.
      */
-    boolean installPlugin(PluginRelease pr);
+    boolean refreshRegistry();
+
+    /**
+     * Install the plugin from the specified url.
+     *
+     * @param url
+     * @param verifyChecksum
+     */
+    boolean installPlugin(String url, boolean verifyChecksum) throws IOException;
 
     /**
      * The list of all registered plugins.
@@ -63,6 +136,14 @@ public interface IPluginManager extends IManager, PluginManager {
      */
     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.
      *
index 47154b87d4b821ae1cee5ddd92a684eecb0c40c6..9cefc88d4b625548a42fcd85dabebec167916252 100644 (file)
@@ -18,13 +18,18 @@ package com.gitblit.manager;
 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.Logger;
 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,16 +59,18 @@ import com.google.common.io.InputSupplier;
 
 /**
  * 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;
 
        // timeout defaults of Maven 3.0.4 in seconds
@@ -67,47 +79,168 @@ public class PluginManager extends DefaultPluginManager implements IPluginManage
        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");
+       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 (pluginFolder.exists()) {
-                       FileUtils.delete(pluginFolder);
+               String pluginId = pf4j.loadPlugin(file);
+               if (StringUtils.isEmpty(pluginId)) {
+                       logger.error("Failed to load plugin {}", file);
+                       return false;
                }
-               if (pluginZip.exists()) {
-                       FileUtils.delete(pluginZip);
+
+               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 true;
+               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 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 boolean refreshRegistry() {
+       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);
                }
@@ -124,7 +257,7 @@ public class PluginManager extends DefaultPluginManager implements IPluginManage
                        }
                };
 
-               File [] files = folder.listFiles(jsonFilter);
+               File[] files = folder.listFiles(jsonFilter);
                if (files == null || files.length == 0) {
                        // automatically retrieve the registry if we don't have a local copy
                        refreshRegistry();
@@ -140,6 +273,7 @@ public class PluginManager extends DefaultPluginManager implements IPluginManage
                        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 @@ public class PluginManager extends DefaultPluginManager implements IPluginManage
        }
 
        @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,75 +307,129 @@ public class PluginManager extends DefaultPluginManager implements IPluginManage
        }
 
        @Override
-       public PluginRegistration lookupPlugin(String idOrName) {
-               for (PluginRegistry registry : getRegistries()) {
-                       PluginRegistration reg = registry.lookup(idOrName);
-                       if (reg != null) {
-                               return reg;
+       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 null;
+               return list;
        }
 
        @Override
-       public PluginRelease lookupRelease(String idOrName, String version) {
-               for (PluginRegistry registry : getRegistries()) {
-                       PluginRegistration reg = registry.lookup(idOrName);
-                       if (reg != null) {
-                               PluginRelease pv;
-                               if (StringUtils.isEmpty(version)) {
-                                       pv = reg.getCurrentRelease();
-                               } else {
-                                       pv = reg.getRelease(version);
-                               }
-                               if (pv != null) {
-                                       return pv;
-                               }
+       public synchronized PluginRegistration lookupPlugin(String idOrName) {
+               for (PluginRegistration reg : getRegisteredPlugins()) {
+                       if (reg.id.equalsIgnoreCase(idOrName) || reg.name.equalsIgnoreCase(idOrName)) {
+                               return reg;
                        }
                }
                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);
+       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);
+               }
+               return pv;
        }
 
        /**
-        * 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) {
+               }
+
+               File md5File = null;
+               try {
+                       md5File = downloadFile(url + ".md5");
                } catch (IOException e) {
-                       logger.error("Failed to install plugin from " + url, e);
+
                }
-               return true;
+
+               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");
@@ -257,9 +444,9 @@ public class PluginManager extends DefaultPluginManager implements IPluginManage
                long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
 
                Files.copy(new InputSupplier<InputStream>() {
-                        @Override
+                       @Override
                        public InputStream getInput() throws IOException {
-                                return new BufferedInputStream(conn.getInputStream());
+                               return new BufferedInputStream(conn.getInputStream());
                        }
                }, tmpFile);
 
@@ -270,7 +457,7 @@ public class PluginManager extends DefaultPluginManager implements IPluginManage
                tmpFile.renameTo(destFile);
                destFile.setLastModified(lastModified);
 
-               return true;
+               return destFile;
        }
 
        protected URLConnection getConnection(URL url) throws IOException {
index c81a0f231864b41ee7bd2d503ef6db2cb453c011..b5cf0ee1cc4c242aa8313c12fc76edbacf0c00db 100644 (file)
@@ -19,6 +19,7 @@ import java.io.Serializable;
 import java.util.ArrayList;\r
 import java.util.Date;\r
 import java.util.List;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
 \r
 import org.parboiled.common.StringUtils;\r
 \r
@@ -37,7 +38,13 @@ public class PluginRegistry implements Serializable {
 \r
        public PluginRegistry(String name) {\r
                this.name = name;\r
-               registrations = new ArrayList<PluginRegistration>();\r
+               registrations = new CopyOnWriteArrayList<PluginRegistration>();\r
+       }\r
+\r
+       public void setup() {\r
+               for (PluginRegistration reg : registrations) {\r
+                       reg.registry = name;\r
+               }\r
        }\r
 \r
        public PluginRegistration lookup(String idOrName) {\r
@@ -80,6 +87,8 @@ public class PluginRegistry implements Serializable {
 \r
                public transient String installedRelease;\r
 \r
+               public transient String registry;\r
+\r
                public List<PluginRelease> releases;\r
 \r
                public PluginRegistration(String id) {\r
@@ -90,10 +99,12 @@ public class PluginRegistry implements Serializable {
                public PluginRelease getCurrentRelease() {\r
                        PluginRelease current = null;\r
                        if (!StringUtils.isEmpty(currentRelease)) {\r
+                               // find specified\r
                                current = getRelease(currentRelease);\r
                        }\r
 \r
                        if (current == null) {\r
+                               // find by date\r
                                Date date = new Date(0);\r
                                for (PluginRelease pv : releases) {\r
                                        if (pv.date.after(date)) {\r
@@ -135,9 +146,15 @@ public class PluginRegistry implements Serializable {
                }\r
        }\r
 \r
-       public static class PluginRelease {\r
+       public static class PluginRelease implements Comparable<PluginRelease> {\r
                public String version;\r
                public Date date;\r
+               public String requires;\r
                public String url;\r
+\r
+               @Override\r
+               public int compareTo(PluginRelease o) {\r
+                       return PluginVersion.createVersion(version).compareTo(PluginVersion.createVersion(o.version));\r
+               }\r
        }\r
 }\r
index ba6f30d69e1044993215cdfa6c1399fdf115a9b1..99dd6d13ec08ad2deff67a9a8dc35d5aa1fdedf6 100644 (file)
  */
 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 @@ public class PluginDispatcher extends DispatchCommand {
                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 @@ public class PluginDispatcher extends DispatchCommand {
                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 @@ public class PluginDispatcher extends DispatchCommand {
                                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 @@ public class PluginDispatcher extends DispatchCommand {
                        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 @@ public class PluginDispatcher extends DispatchCommand {
                }
        }
 
+       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()));
                                }
                        }
+               }
+       }
+
+       @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!");
+                       }
+
+                       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 {
 
-               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));
+               @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!");
                        }
-                       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.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));
+                               }
+                               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());
                        }
-                       PluginWrapper pw = plugins.get(index - 1);
-                       PluginDescriptor d = pw.getDescriptor();
 
-                       // 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();
+                       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();
+               }
 
-                       // TODO EXTENSIONS
-                       sb.setLength(0);
-                       List<String> exts = new ArrayList<String>();
+               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
+                       final String fields = buildFieldTable(pw, reg);
+
+                       // 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());
+                                               }
+                                               description.append('\n');
+                                       }
+                                       description.append(ext.getName()).append("\n  â”” ");
+                                       description.append(getExtensionPoint(ext).getName());
+                                       description.append("\n\n");
                                }
-                               extensions = FlipTable.of(headers, data, Borders.COLS);
+                               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 @@ public class PluginDispatcher extends DispatchCommand {
                                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));
                }
-       }
 
-       @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;
-
-               @Override
-               public void run() throws UnloggedFailure {
-                       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()));
+               /* Find the ExtensionPoint */
+               protected Class<?> getExtensionPoint(Class<?> clazz) {
+                       Class<?> superClass = clazz.getSuperclass();
+                       if (ExtensionPoint.class.isAssignableFrom(superClass)) {
+                               return superClass;
                        }
+                       return getExtensionPoint(superClass);
                }
        }
 
-       @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;
-
+       @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();
-                       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 @@ public class PluginDispatcher extends DispatchCommand {
                @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 @@ public class PluginDispatcher extends DispatchCommand {
                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 @@ public class PluginDispatcher extends DispatchCommand {
                @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()));
+                       }
+               }
+       }
 }
index 3c378669ae027bee9e1c67c3aa0b915a8af8bad1..bebb4ac92e0493420ee0ef736492b3d344311f3b 100644 (file)
@@ -20,6 +20,8 @@ import java.util.List;
 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 @@ class RootDispatcher extends DispatchCommand {
                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);
                }
        }
index 5813c3aeb21ef7a9571f6f320193d3d33e38eb43..7605fe01b21e41a5647685843484a12e31ac98fc 100644 (file)
@@ -307,7 +307,7 @@ public class StringUtils {
         * @param bytes\r
         * @return byte array as hex string\r
         */\r
-       private static String toHex(byte[] bytes) {\r
+       public static String toHex(byte[] bytes) {\r
                StringBuilder sb = new StringBuilder(bytes.length * 2);\r
                for (int i = 0; i < bytes.length; i++) {\r
                        if ((bytes[i] & 0xff) < 0x10) {\r