You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PluginManager.java 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /*
  2. * Copyright 2014 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.manager;
  17. import java.io.BufferedInputStream;
  18. import java.io.File;
  19. import java.io.FileFilter;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.net.HttpURLConnection;
  23. import java.net.Proxy;
  24. import java.net.URL;
  25. import java.net.URLConnection;
  26. import java.util.ArrayList;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.TreeMap;
  30. import org.slf4j.Logger;
  31. import org.slf4j.LoggerFactory;
  32. import ro.fortsoft.pf4j.DefaultPluginManager;
  33. import ro.fortsoft.pf4j.PluginVersion;
  34. import ro.fortsoft.pf4j.PluginWrapper;
  35. import com.gitblit.Keys;
  36. import com.gitblit.models.PluginRegistry;
  37. import com.gitblit.models.PluginRegistry.PluginRegistration;
  38. import com.gitblit.models.PluginRegistry.PluginRelease;
  39. import com.gitblit.utils.Base64;
  40. import com.gitblit.utils.FileUtils;
  41. import com.gitblit.utils.JsonUtils;
  42. import com.gitblit.utils.StringUtils;
  43. import com.google.common.io.Files;
  44. import com.google.common.io.InputSupplier;
  45. /**
  46. * The plugin manager maintains the lifecycle of plugins. It is exposed as
  47. * Dagger bean. The extension consumers supposed to retrieve plugin manager
  48. * from the Dagger DI and retrieve extensions provided by active plugins.
  49. *
  50. * @author David Ostrovsky
  51. *
  52. */
  53. public class PluginManager extends DefaultPluginManager implements IPluginManager {
  54. private final Logger logger = LoggerFactory.getLogger(getClass());
  55. private final IRuntimeManager runtimeManager;
  56. // timeout defaults of Maven 3.0.4 in seconds
  57. private int connectTimeout = 20;
  58. private int readTimeout = 12800;
  59. public PluginManager(IRuntimeManager runtimeManager) {
  60. super(runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"));
  61. this.runtimeManager = runtimeManager;
  62. }
  63. @Override
  64. public PluginManager start() {
  65. logger.info("Loading plugins...");
  66. loadPlugins();
  67. logger.info("Starting loaded plugins...");
  68. startPlugins();
  69. return this;
  70. }
  71. @Override
  72. public PluginManager stop() {
  73. logger.info("Stopping loaded plugins...");
  74. stopPlugins();
  75. return null;
  76. }
  77. @Override
  78. public boolean deletePlugin(PluginWrapper pw) {
  79. File folder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
  80. File pluginFolder = new File(folder, pw.getPluginPath());
  81. File pluginZip = new File(folder, pw.getPluginPath() + ".zip");
  82. if (pluginFolder.exists()) {
  83. FileUtils.delete(pluginFolder);
  84. }
  85. if (pluginZip.exists()) {
  86. FileUtils.delete(pluginZip);
  87. }
  88. return true;
  89. }
  90. @Override
  91. public boolean refreshRegistry() {
  92. String dr = "http://gitblit.github.io/gitblit-registry/plugins.json";
  93. String url = runtimeManager.getSettings().getString(Keys.plugins.registry, dr);
  94. try {
  95. return download(url);
  96. } catch (Exception e) {
  97. logger.error(String.format("Failed to retrieve plugins.json from %s", url), e);
  98. }
  99. return false;
  100. }
  101. protected List<PluginRegistry> getRegistries() {
  102. List<PluginRegistry> list = new ArrayList<PluginRegistry>();
  103. File folder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
  104. FileFilter jsonFilter = new FileFilter() {
  105. @Override
  106. public boolean accept(File file) {
  107. return !file.isDirectory() && file.getName().toLowerCase().endsWith(".json");
  108. }
  109. };
  110. File [] files = folder.listFiles(jsonFilter);
  111. if (files == null || files.length == 0) {
  112. // automatically retrieve the registry if we don't have a local copy
  113. refreshRegistry();
  114. files = folder.listFiles(jsonFilter);
  115. }
  116. if (files == null || files.length == 0) {
  117. return list;
  118. }
  119. for (File file : files) {
  120. PluginRegistry registry = null;
  121. try {
  122. String json = FileUtils.readContent(file, "\n");
  123. registry = JsonUtils.fromJsonString(json, PluginRegistry.class);
  124. } catch (Exception e) {
  125. logger.error("Failed to deserialize " + file, e);
  126. }
  127. if (registry != null) {
  128. list.add(registry);
  129. }
  130. }
  131. return list;
  132. }
  133. @Override
  134. public List<PluginRegistration> getRegisteredPlugins() {
  135. List<PluginRegistration> list = new ArrayList<PluginRegistration>();
  136. Map<String, PluginRegistration> map = new TreeMap<String, PluginRegistration>();
  137. for (PluginRegistry registry : getRegistries()) {
  138. List<PluginRegistration> registrations = registry.registrations;
  139. list.addAll(registrations);
  140. for (PluginRegistration reg : registrations) {
  141. reg.installedRelease = null;
  142. map.put(reg.id, reg);
  143. }
  144. }
  145. for (PluginWrapper pw : getPlugins()) {
  146. String id = pw.getDescriptor().getPluginId();
  147. PluginVersion pv = pw.getDescriptor().getVersion();
  148. PluginRegistration reg = map.get(id);
  149. if (reg != null) {
  150. reg.installedRelease = pv.toString();
  151. }
  152. }
  153. return list;
  154. }
  155. @Override
  156. public PluginRegistration lookupPlugin(String idOrName) {
  157. for (PluginRegistry registry : getRegistries()) {
  158. PluginRegistration reg = registry.lookup(idOrName);
  159. if (reg != null) {
  160. return reg;
  161. }
  162. }
  163. return null;
  164. }
  165. @Override
  166. public PluginRelease lookupRelease(String idOrName, String version) {
  167. for (PluginRegistry registry : getRegistries()) {
  168. PluginRegistration reg = registry.lookup(idOrName);
  169. if (reg != null) {
  170. PluginRelease pv;
  171. if (StringUtils.isEmpty(version)) {
  172. pv = reg.getCurrentRelease();
  173. } else {
  174. pv = reg.getRelease(version);
  175. }
  176. if (pv != null) {
  177. return pv;
  178. }
  179. }
  180. }
  181. return null;
  182. }
  183. /**
  184. * Installs the plugin from the plugin version.
  185. *
  186. * @param pv
  187. * @throws IOException
  188. * @return true if successful
  189. */
  190. @Override
  191. public boolean installPlugin(PluginRelease pv) {
  192. return installPlugin(pv.url);
  193. }
  194. /**
  195. * Installs the plugin from the url.
  196. *
  197. * @param url
  198. * @return true if successful
  199. */
  200. @Override
  201. public boolean installPlugin(String url) {
  202. try {
  203. if (!download(url)) {
  204. return false;
  205. }
  206. // TODO stop, unload, load
  207. } catch (IOException e) {
  208. logger.error("Failed to install plugin from " + url, e);
  209. }
  210. return true;
  211. }
  212. /**
  213. * Download a file to the plugins folder.
  214. *
  215. * @param url
  216. * @return
  217. * @throws IOException
  218. */
  219. protected boolean download(String url) throws IOException {
  220. File pFolder = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
  221. pFolder.mkdirs();
  222. File tmpFile = new File(pFolder, StringUtils.getSHA1(url) + ".tmp");
  223. if (tmpFile.exists()) {
  224. tmpFile.delete();
  225. }
  226. URL u = new URL(url);
  227. final URLConnection conn = getConnection(u);
  228. // try to get the server-specified last-modified date of this artifact
  229. long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
  230. Files.copy(new InputSupplier<InputStream>() {
  231. @Override
  232. public InputStream getInput() throws IOException {
  233. return new BufferedInputStream(conn.getInputStream());
  234. }
  235. }, tmpFile);
  236. File destFile = new File(pFolder, StringUtils.getLastPathElement(u.getPath()));
  237. if (destFile.exists()) {
  238. destFile.delete();
  239. }
  240. tmpFile.renameTo(destFile);
  241. destFile.setLastModified(lastModified);
  242. return true;
  243. }
  244. protected URLConnection getConnection(URL url) throws IOException {
  245. java.net.Proxy proxy = getProxy(url);
  246. HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
  247. if (java.net.Proxy.Type.DIRECT != proxy.type()) {
  248. String auth = getProxyAuthorization(url);
  249. conn.setRequestProperty("Proxy-Authorization", auth);
  250. }
  251. String username = null;
  252. String password = null;
  253. if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
  254. // set basic authentication header
  255. String auth = Base64.encodeBytes((username + ":" + password).getBytes());
  256. conn.setRequestProperty("Authorization", "Basic " + auth);
  257. }
  258. // configure timeouts
  259. conn.setConnectTimeout(connectTimeout * 1000);
  260. conn.setReadTimeout(readTimeout * 1000);
  261. switch (conn.getResponseCode()) {
  262. case HttpURLConnection.HTTP_MOVED_TEMP:
  263. case HttpURLConnection.HTTP_MOVED_PERM:
  264. // handle redirects by closing this connection and opening a new
  265. // one to the new location of the requested resource
  266. String newLocation = conn.getHeaderField("Location");
  267. if (!StringUtils.isEmpty(newLocation)) {
  268. logger.info("following redirect to {0}", newLocation);
  269. conn.disconnect();
  270. return getConnection(new URL(newLocation));
  271. }
  272. }
  273. return conn;
  274. }
  275. protected Proxy getProxy(URL url) {
  276. return java.net.Proxy.NO_PROXY;
  277. }
  278. protected String getProxyAuthorization(URL url) {
  279. return "";
  280. }
  281. }