PageRenderTime 28ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/plugin/src/main/java/com/atlassian/labs/speakeasy/manager/AbstractOsgiPluginTypeHandler.java

https://github.com/mrdon/speakeasy-plugin
Java | 371 lines | 329 code | 37 blank | 5 comment | 17 complexity | c5722194373275bf08674973f3956421 MD5 | raw file
  1. package com.atlassian.labs.speakeasy.manager;
  2. import com.atlassian.labs.speakeasy.git.GitRepositoryManager;
  3. import com.atlassian.labs.speakeasy.util.RepositoryDirectoryUtil;
  4. import com.atlassian.labs.speakeasy.util.exec.ReadOnlyOperation;
  5. import com.atlassian.plugin.JarPluginArtifact;
  6. import com.atlassian.plugin.PluginArtifact;
  7. import com.atlassian.templaterenderer.TemplateRenderer;
  8. import org.apache.commons.io.FileUtils;
  9. import org.apache.commons.io.IOUtils;
  10. import org.eclipse.jgit.lib.Repository;
  11. import org.osgi.framework.Bundle;
  12. import java.io.*;
  13. import java.net.URL;
  14. import java.util.Enumeration;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.regex.Pattern;
  18. import java.util.zip.ZipEntry;
  19. import java.util.zip.ZipFile;
  20. import java.util.zip.ZipOutputStream;
  21. import static com.atlassian.labs.speakeasy.util.ExtensionValidate.isValidExtensionKey;
  22. import static com.atlassian.labs.speakeasy.util.KeyExtractor.createExtractableTempFile;
  23. import static java.util.Arrays.asList;
  24. /**
  25. *
  26. */
  27. public abstract class AbstractOsgiPluginTypeHandler implements PluginTypeHandler
  28. {
  29. protected static final Iterable<Pattern> CORE_WHITELIST = asList(
  30. Pattern.compile(".*[._]js", Pattern.CASE_INSENSITIVE),
  31. Pattern.compile(".*[._]eot", Pattern.CASE_INSENSITIVE),
  32. Pattern.compile(".*[._]ttf", Pattern.CASE_INSENSITIVE),
  33. Pattern.compile(".*[._]woff", Pattern.CASE_INSENSITIVE),
  34. Pattern.compile(".*[._]svg", Pattern.CASE_INSENSITIVE),
  35. Pattern.compile(".*[._]svgz", Pattern.CASE_INSENSITIVE),
  36. Pattern.compile(".*[._]mu", Pattern.CASE_INSENSITIVE),
  37. Pattern.compile(".*[._]json", Pattern.CASE_INSENSITIVE),
  38. Pattern.compile(".*[._]gif", Pattern.CASE_INSENSITIVE),
  39. Pattern.compile(".*[._]png", Pattern.CASE_INSENSITIVE),
  40. Pattern.compile(".*[._]jpg", Pattern.CASE_INSENSITIVE),
  41. Pattern.compile(".*[._]jpeg", Pattern.CASE_INSENSITIVE),
  42. // Pattern.compile(".*\\.xml", Pattern.CASE_INSENSITIVE), // excluded for now as you could add a spring XML file and load other classes
  43. Pattern.compile(".*[._]css", Pattern.CASE_INSENSITIVE),
  44. Pattern.compile(".*/\\._[^.]+", Pattern.CASE_INSENSITIVE),
  45. Pattern.compile(".*/$"),
  46. Pattern.compile("META-INF/MANIFEST.MF"),
  47. Pattern.compile(".*/pom.xml"),
  48. Pattern.compile(".*/pom.properties"),
  49. Pattern.compile("README.txt"),
  50. Pattern.compile("LICENSE.txt"),
  51. Pattern.compile("README.md"),
  52. Pattern.compile("\\.gitignore"),
  53. Pattern.compile(".*\\.DS_Store"));
  54. private final TemplateRenderer templateRenderer;
  55. private final GitRepositoryManager gitRepositoryManager;
  56. public AbstractOsgiPluginTypeHandler(TemplateRenderer templateRenderer, GitRepositoryManager repositoryManager)
  57. {
  58. this.templateRenderer = templateRenderer;
  59. this.gitRepositoryManager = repositoryManager;
  60. }
  61. public final boolean allowEntryPath(String path)
  62. {
  63. Iterable<Pattern> whitelistPatterns = getWhitelistPatterns();
  64. for (Pattern whitelist : whitelistPatterns)
  65. {
  66. if (whitelist.matcher(path).matches())
  67. {
  68. return true;
  69. }
  70. }
  71. return false;
  72. }
  73. public final File createTempFile(String pluginKey) throws IOException
  74. {
  75. return createExtractableTempFile(pluginKey, "." + getExtension());
  76. }
  77. public final String canInstall(File file)
  78. {
  79. PluginArtifact artifact = new JarPluginArtifact(file);
  80. if (artifact.doesResourceExist(getDescriptorPath()))
  81. {
  82. String key = extractPluginKey(artifact);
  83. if (isValidExtensionKey(key))
  84. {
  85. return key;
  86. }
  87. }
  88. return null;
  89. }
  90. public final PluginArtifact createArtifact(File uploadedFile)
  91. {
  92. PluginArtifact pluginArtifact = new JarPluginArtifact(uploadedFile);
  93. verifyContents(pluginArtifact);
  94. pluginArtifact = validatePluginArtifact(pluginArtifact);
  95. return pluginArtifact;
  96. }
  97. public String getPluginFile(final String pluginKey, final String fileName) throws IOException
  98. {
  99. return gitRepositoryManager.operateOnRepository(pluginKey, new ReadOnlyOperation<Repository, String>()
  100. {
  101. public String operateOn(Repository repo) throws Exception
  102. {
  103. File dir = repo.getWorkTree();
  104. File file = new File(dir, fileName);
  105. if (file.exists())
  106. {
  107. return FileUtils.readFileToString(file);
  108. }
  109. throw new FileNotFoundException(fileName);
  110. }
  111. });
  112. }
  113. public File getPluginArtifact(String pluginKey) throws IOException
  114. {
  115. return gitRepositoryManager.buildJarFromRepository(pluginKey);
  116. }
  117. public List<String> getPluginFileNames(String pluginKey)
  118. {
  119. return gitRepositoryManager.operateOnRepository(pluginKey, new ReadOnlyOperation<Repository, List<String>>()
  120. {
  121. public List<String> operateOn(Repository repo) throws Exception
  122. {
  123. final File dir = repo.getWorkTree();
  124. return RepositoryDirectoryUtil.getEntries(dir);
  125. }
  126. });
  127. }
  128. public File getPluginAsProject(String pluginKey, final Map<String, Object> context)
  129. {
  130. return gitRepositoryManager.operateOnRepository(pluginKey, new ReadOnlyOperation<Repository, File>()
  131. {
  132. public File operateOn(Repository repo) throws Exception
  133. {
  134. FileOutputStream fout = null;
  135. ZipOutputStream zout = null;
  136. File file = null;
  137. try
  138. {
  139. file = File.createTempFile("speakeasy-plugin-project", ".zip");
  140. fout = new FileOutputStream(file);
  141. zout = new ZipOutputStream(fout);
  142. zout.putNextEntry(new ZipEntry("src/"));
  143. zout.putNextEntry(new ZipEntry("src/main/"));
  144. zout.putNextEntry(new ZipEntry("src/main/resources/"));
  145. List<String> paths = RepositoryDirectoryUtil.getEntries(repo.getWorkTree());
  146. for (String path : paths)
  147. {
  148. String actualPath = "src/main/resources/" + path;
  149. ZipEntry entry = new ZipEntry(actualPath);
  150. zout.putNextEntry(entry);
  151. if (!path.endsWith("/"))
  152. {
  153. byte[] data = FileUtils.readFileToByteArray(new File(repo.getWorkTree(), path));
  154. zout.write(data, 0, data.length);
  155. }
  156. }
  157. zout.putNextEntry(new ZipEntry("pom.xml"));
  158. String pomContents = renderPom(context);
  159. IOUtils.copy(new StringReader(pomContents), zout);
  160. }
  161. catch (IOException e)
  162. {
  163. throw new RuntimeException("Unable to create plugin project", e);
  164. }
  165. finally
  166. {
  167. IOUtils.closeQuietly(zout);
  168. IOUtils.closeQuietly(fout);
  169. }
  170. return file;
  171. }
  172. });
  173. }
  174. public File createExample(String pluginKey, String name, String description) throws IOException
  175. {
  176. ZipOutputStream zout = null;
  177. File tmpFile = null;
  178. try
  179. {
  180. tmpFile = createTempFile(pluginKey);
  181. zout = new ZipOutputStream(new FileOutputStream(tmpFile));
  182. createExampleContents(zout, pluginKey, name, description);
  183. zout.close();
  184. }
  185. finally
  186. {
  187. IOUtils.closeQuietly(zout);
  188. }
  189. return tmpFile;
  190. }
  191. public File createFork(String pluginKey, final String forkPluginKey, String user, final String description) throws IOException
  192. {
  193. return gitRepositoryManager.operateOnRepository(pluginKey, new ReadOnlyOperation<Repository, File>()
  194. {
  195. public File operateOn(Repository repo) throws Exception
  196. {
  197. ZipOutputStream zout = null;
  198. File tmpFile = null;
  199. try
  200. {
  201. tmpFile = createTempFile(forkPluginKey);
  202. zout = new ZipOutputStream(new FileOutputStream(tmpFile));
  203. final File repoDir = repo.getWorkTree();
  204. List<String> bundlePaths = RepositoryDirectoryUtil.getEntries(repoDir);
  205. bundlePaths.remove(getDescriptorPath());
  206. for (String path : bundlePaths)
  207. {
  208. ZipEntry entry = new ZipEntry(path);
  209. zout.putNextEntry(entry);
  210. if (!path.endsWith("/"))
  211. {
  212. byte[] data = FileUtils.readFileToByteArray(new File(repoDir, path));
  213. zout.write(data, 0, data.length);
  214. }
  215. }
  216. zout.putNextEntry(new ZipEntry(getDescriptorPath()));
  217. forkDescriptor(new ByteArrayInputStream(FileUtils.readFileToByteArray(new File(repoDir, getDescriptorPath()))),
  218. zout, forkPluginKey, description);
  219. zout.close();
  220. }
  221. finally
  222. {
  223. IOUtils.closeQuietly(zout);
  224. }
  225. return tmpFile;
  226. }
  227. });
  228. }
  229. public File rebuildPlugin(final String pluginKey, final String fileName, final String contents) throws IOException
  230. {
  231. return gitRepositoryManager.operateOnRepository(pluginKey, new ReadOnlyOperation<Repository, File>()
  232. {
  233. public File operateOn(Repository repo) throws Exception
  234. {
  235. ZipOutputStream zout = null;
  236. File tmpFile = null;
  237. try
  238. {
  239. tmpFile = createTempFile(pluginKey);
  240. zout = new ZipOutputStream(new FileOutputStream(tmpFile));
  241. final File repoDir = repo.getWorkTree();
  242. for (String path : RepositoryDirectoryUtil.getEntries(repoDir))
  243. {
  244. if (!path.equals(fileName) && !path.contains("-min."))
  245. {
  246. ZipEntry entry = new ZipEntry(path);
  247. zout.putNextEntry(entry);
  248. if (!path.endsWith("/"))
  249. {
  250. byte[] data = FileUtils.readFileToByteArray(new File(repoDir, path));
  251. zout.write(data, 0, data.length);
  252. }
  253. }
  254. }
  255. ZipEntry entry = new ZipEntry(fileName);
  256. byte[] data = contents.getBytes();
  257. entry.setSize(data.length);
  258. zout.putNextEntry(entry);
  259. zout.write(data);
  260. zout.close();
  261. }
  262. finally
  263. {
  264. IOUtils.closeQuietly(zout);
  265. }
  266. return tmpFile;
  267. }
  268. });
  269. }
  270. private String renderPom(Map<String,Object> context) throws IOException
  271. {
  272. StringWriter writer = new StringWriter();
  273. templateRenderer.render("templates/pom.vm", context, writer);
  274. return writer.toString();
  275. }
  276. protected abstract Iterable<Pattern> getWhitelistPatterns();
  277. protected abstract void createExampleContents(ZipOutputStream zout, String pluginKey, String name, String description) throws IOException;
  278. protected abstract String extractPluginKey(PluginArtifact artifact);
  279. protected abstract String getExtension();
  280. protected abstract PluginArtifact validatePluginArtifact(PluginArtifact pluginArtifact);
  281. protected abstract String getDescriptorPath();
  282. protected abstract void forkDescriptor(InputStream byteArrayInputStream, OutputStream zout, String key, String description) throws IOException;
  283. private void verifyContents(PluginArtifact plugin) throws PluginOperationFailedException
  284. {
  285. ZipFile zip = null;
  286. try
  287. {
  288. zip = new ZipFile(plugin.toFile());
  289. for (Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements();)
  290. {
  291. ZipEntry entry = e.nextElement();
  292. if (!allowEntryPath(entry.getName()))
  293. {
  294. throw new PluginOperationFailedException("Invalid plugin entry: " + entry.getName(), null);
  295. }
  296. }
  297. }
  298. catch (IOException e)
  299. {
  300. throw new PluginOperationFailedException("Unable to open plugin zip", e, null);
  301. }
  302. finally
  303. {
  304. if (zip != null)
  305. {
  306. try
  307. {
  308. zip.close();
  309. }
  310. catch (IOException e)
  311. {
  312. // ignore
  313. }
  314. }
  315. }
  316. }
  317. private byte[] readEntry(Bundle bundle, String path)
  318. throws IOException
  319. {
  320. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  321. URL url = bundle.getEntry(path);
  322. InputStream urlIn = null;
  323. try
  324. {
  325. urlIn = url.openStream();
  326. IOUtils.copy(urlIn, bout);
  327. }
  328. finally
  329. {
  330. IOUtils.closeQuietly(urlIn);
  331. }
  332. return bout.toByteArray();
  333. }
  334. }