From e5d0bacbf746e09a9194822b231cb27090f58973 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 10 Apr 2014 19:00:52 -0400
Subject: [PATCH] Implement simple JSON-based plugin registry and install command

---
 src/main/java/com/gitblit/manager/PluginManager.java |  250 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 245 insertions(+), 5 deletions(-)

diff --git a/src/main/java/com/gitblit/manager/PluginManager.java b/src/main/java/com/gitblit/manager/PluginManager.java
index e23aaec..7b03f50 100644
--- a/src/main/java/com/gitblit/manager/PluginManager.java
+++ b/src/main/java/com/gitblit/manager/PluginManager.java
@@ -15,30 +15,56 @@
  */
 package com.gitblit.manager;
 
+import java.io.BufferedInputStream;
 import java.io.File;
+import java.io.FileFilter;
+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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import ro.fortsoft.pf4j.DefaultPluginManager;
+import ro.fortsoft.pf4j.PluginVersion;
 import ro.fortsoft.pf4j.PluginWrapper;
 
 import com.gitblit.Keys;
+import com.gitblit.models.PluginRegistry;
+import com.gitblit.models.PluginRegistry.PluginRegistration;
+import com.gitblit.models.PluginRegistry.PluginRelease;
+import com.gitblit.utils.Base64;
 import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.StringUtils;
+import com.google.common.io.Files;
+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.
- * 
+ *
  * @author David Ostrovsky
- * 
+ *
  */
 public class PluginManager extends DefaultPluginManager implements IPluginManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
-	
+
 	private final IRuntimeManager runtimeManager;
+
+	// timeout defaults of Maven 3.0.4 in seconds
+	private int connectTimeout = 20;
+
+	private int readTimeout = 12800;
 
 	public PluginManager(IRuntimeManager runtimeManager) {
 		super(runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"));
@@ -60,13 +86,13 @@
 		stopPlugins();
 		return null;
 	}
-	
+
 	@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);
 		}
@@ -75,4 +101,218 @@
 		}
 		return true;
 	}
+
+	@Override
+	public 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);
+		} catch (Exception e) {
+			logger.error(String.format("Failed to retrieve plugins.json from %s", url), e);
+		}
+		return false;
+	}
+
+	protected List<PluginRegistry> getRegistries() {
+		List<PluginRegistry> list = new ArrayList<PluginRegistry>();
+		File folder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
+		FileFilter jsonFilter = new FileFilter() {
+			@Override
+			public boolean accept(File file) {
+				return !file.isDirectory() && file.getName().toLowerCase().endsWith(".json");
+			}
+		};
+
+		File [] files = folder.listFiles(jsonFilter);
+		if (files == null || files.length == 0) {
+			// automatically retrieve the registry if we don't have a local copy
+			refreshRegistry();
+			files = folder.listFiles(jsonFilter);
+		}
+
+		if (files == null || files.length == 0) {
+			return list;
+		}
+
+		for (File file : files) {
+			PluginRegistry registry = null;
+			try {
+				String json = FileUtils.readContent(file, "\n");
+				registry = JsonUtils.fromJsonString(json, PluginRegistry.class);
+			} catch (Exception e) {
+				logger.error("Failed to deserialize " + file, e);
+			}
+			if (registry != null) {
+				list.add(registry);
+			}
+		}
+		return list;
+	}
+
+	@Override
+	public 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) {
+				reg.installedRelease = null;
+				map.put(reg.id, reg);
+			}
+		}
+		for (PluginWrapper pw : getPlugins()) {
+			String id = pw.getDescriptor().getPluginId();
+			PluginVersion pv = pw.getDescriptor().getVersion();
+			PluginRegistration reg = map.get(id);
+			if (reg != null) {
+				reg.installedRelease = pv.toString();
+			}
+		}
+		return list;
+	}
+
+	@Override
+	public PluginRegistration lookupPlugin(String idOrName) {
+		for (PluginRegistry registry : getRegistries()) {
+			PluginRegistration reg = registry.lookup(idOrName);
+			if (reg != null) {
+				return reg;
+			}
+		}
+		return null;
+	}
+
+	@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;
+				}
+			}
+		}
+		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.
+	 *
+	 * @param url
+	 * @return true if successful
+	 */
+	@Override
+	public boolean installPlugin(String url) {
+		try {
+			if (!download(url)) {
+				return false;
+			}
+			// TODO stop, unload, load
+		} catch (IOException e) {
+			logger.error("Failed to install plugin from " + url, e);
+		}
+		return true;
+	}
+
+	/**
+	 * Download a file to the plugins folder.
+	 *
+	 * @param url
+	 * @return
+	 * @throws IOException
+	 */
+	protected boolean download(String url) throws IOException {
+		File pFolder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
+		File tmpFile = new File(pFolder, StringUtils.getSHA1(url) + ".tmp");
+		if (tmpFile.exists()) {
+			tmpFile.delete();
+		}
+
+		URL u = new URL(url);
+		final URLConnection conn = getConnection(u);
+
+		// try to get the server-specified last-modified date of this artifact
+		long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
+
+		Files.copy(new InputSupplier<InputStream>() {
+			 @Override
+			public InputStream getInput() throws IOException {
+				 return new BufferedInputStream(conn.getInputStream());
+			}
+		}, tmpFile);
+
+		File destFile = new File(pFolder, StringUtils.getLastPathElement(u.getPath()));
+		if (destFile.exists()) {
+			destFile.delete();
+		}
+		tmpFile.renameTo(destFile);
+		destFile.setLastModified(lastModified);
+
+		return true;
+	}
+
+	protected URLConnection getConnection(URL url) throws IOException {
+		java.net.Proxy proxy = getProxy(url);
+		HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
+		if (java.net.Proxy.Type.DIRECT != proxy.type()) {
+			String auth = getProxyAuthorization(url);
+			conn.setRequestProperty("Proxy-Authorization", auth);
+		}
+
+		String username = null;
+		String password = null;
+		if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
+			// set basic authentication header
+			String auth = Base64.encodeBytes((username + ":" + password).getBytes());
+			conn.setRequestProperty("Authorization", "Basic " + auth);
+		}
+
+		// configure timeouts
+		conn.setConnectTimeout(connectTimeout * 1000);
+		conn.setReadTimeout(readTimeout * 1000);
+
+		switch (conn.getResponseCode()) {
+		case HttpURLConnection.HTTP_MOVED_TEMP:
+		case HttpURLConnection.HTTP_MOVED_PERM:
+			// handle redirects by closing this connection and opening a new
+			// one to the new location of the requested resource
+			String newLocation = conn.getHeaderField("Location");
+			if (!StringUtils.isEmpty(newLocation)) {
+				logger.info("following redirect to {0}", newLocation);
+				conn.disconnect();
+				return getConnection(new URL(newLocation));
+			}
+		}
+
+		return conn;
+	}
+
+	protected Proxy getProxy(URL url) {
+		return java.net.Proxy.NO_PROXY;
+	}
+
+	protected String getProxyAuthorization(URL url) {
+		return "";
+	}
 }

--
Gitblit v1.9.1