PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/atlassian-plugins-core/src/main/java/com/atlassian/plugin/repositories/FilePluginInstaller.java

https://bitbucket.org/purewind/atlassian-plugins
Java | 220 lines | 137 code | 26 blank | 57 comment | 12 complexity | 359cd200914cee71175e26de4f5f909a MD5 | raw file
  1. package com.atlassian.plugin.repositories;
  2. import com.atlassian.plugin.PluginArtifact;
  3. import com.atlassian.plugin.RevertablePluginInstaller;
  4. import com.atlassian.util.concurrent.CopyOnWriteMap;
  5. import org.apache.commons.io.FileUtils;
  6. import org.apache.commons.io.IOUtils;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import java.io.File;
  10. import java.io.FileOutputStream;
  11. import java.io.FilenameFilter;
  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.io.OutputStream;
  15. import java.util.Map;
  16. import static com.google.common.base.Preconditions.checkNotNull;
  17. import static com.google.common.base.Preconditions.checkState;
  18. /**
  19. * File-based implementation of a PluginInstaller which writes plugin artifact
  20. * to a specified directory. Handles reverting installs by keeping track of the first installation for a given
  21. * instance, and restores it. Installation of plugin artifacts with different names will overwrite an existing artifact
  22. * of that same name, if it exists, with the only exception being the backup of the first overwritten artifact to
  23. * support reverting.
  24. *
  25. * NOTE: This implementation has a limitation. The issue is that when installing a plugin we are only provided the plugin
  26. * key and do not know the name of the artifact that provided the original plugin. So if someone installs a new version
  27. * of an existing plugin in an artifact that has a different name we have no way of telling what artifact provided
  28. * the original plugin and therefore which artifact to delete. This will result in two of the same plugins, but in
  29. * different artifacts being left in the plugins directory. Hopefully the versions will differ so that the plugins
  30. * framework can decide which plugin to enable.
  31. *
  32. * @see RevertablePluginInstaller
  33. */
  34. public class FilePluginInstaller implements RevertablePluginInstaller {
  35. private static final Logger log = LoggerFactory.getLogger(FilePluginInstaller.class);
  36. public static final String ORIGINAL_PREFIX = ".original-";
  37. private final File directory;
  38. private final Map<String, BackupRepresentation> installedPlugins = CopyOnWriteMap.<String, BackupRepresentation>builder().stableViews().newHashMap();
  39. /**
  40. * @param directory where plugin JARs will be installed.
  41. */
  42. public FilePluginInstaller(File directory) {
  43. this.directory = checkNotNull(directory);
  44. checkState(directory.exists(), "The plugin installation directory must exist, %s", directory.getAbsolutePath());
  45. }
  46. /**
  47. * If there is an existing JAR with the same filename, it is replaced.
  48. *
  49. * @throws RuntimeException if there was an exception reading or writing files.
  50. */
  51. public void installPlugin(String key, PluginArtifact pluginArtifact) {
  52. checkNotNull(key, "The plugin key must be specified");
  53. checkNotNull(pluginArtifact, "The plugin artifact must not be null");
  54. final File newPluginFile = new File(directory, pluginArtifact.getName());
  55. try {
  56. backup(key, newPluginFile);
  57. if (newPluginFile.exists()) {
  58. // would happen if the plugin was installed for a previous instance
  59. newPluginFile.delete();
  60. }
  61. } catch (IOException e) {
  62. log.warn("Unable to backup old file", e);
  63. }
  64. OutputStream os = null;
  65. InputStream in = null;
  66. try {
  67. os = new FileOutputStream(newPluginFile);
  68. in = pluginArtifact.getInputStream();
  69. IOUtils.copy(in, os);
  70. } catch (IOException e) {
  71. throw new RuntimeException("Could not install plugin: " + pluginArtifact, e);
  72. } finally {
  73. IOUtils.closeQuietly(in);
  74. IOUtils.closeQuietly(os);
  75. }
  76. }
  77. /**
  78. * Reverts an installed plugin. Handles plugin file overwrites and different names over time.
  79. *
  80. * @param pluginKey The plugin key to revert
  81. * @since 2.5.0
  82. */
  83. public void revertInstalledPlugin(String pluginKey) {
  84. BackupRepresentation backup = installedPlugins.get(pluginKey);
  85. if (backup != null) {
  86. File currentFile = new File(backup.getBackupFile().getParent(), backup.getCurrentPluginFilename());
  87. if (currentFile.exists()) {
  88. currentFile.delete();
  89. }
  90. // We need to copy the original backed-up file back to the original filename if we overwrote the plugin
  91. // when we first installed it.
  92. if (backup.isUpgrade()) {
  93. try {
  94. FileUtils.moveFile(backup.getBackupFile(), new File(backup.getBackupFile().getParent(), backup.getOriginalPluginArtifactFilename()));
  95. } catch (IOException e) {
  96. log.warn("Unable to restore old plugin for " + pluginKey);
  97. }
  98. }
  99. }
  100. }
  101. /**
  102. * Deletes all backup files in the plugin directory
  103. *
  104. * @since 2.5.0
  105. */
  106. public void clearBackups() {
  107. for (File file : directory.listFiles(new BackupNameFilter())) {
  108. file.delete();
  109. }
  110. installedPlugins.clear();
  111. }
  112. private void backup(String pluginKey, File currentPluginArtifact) throws IOException {
  113. BackupRepresentation orig = null;
  114. // If this is the first time we have seen the pluginkey then we will create a backup representation that may
  115. // refer to the original plugins file, if the artifact is named the same
  116. if (!installedPlugins.containsKey(pluginKey)) {
  117. orig = getBackupRepresentation(pluginKey, currentPluginArtifact);
  118. }
  119. // There is already a backup, we need to delete the intermediate file representation so that we do not
  120. // leave a bunch of files laying around and update the backup with the new current plugin artifact name
  121. else {
  122. final BackupRepresentation oldBackupFile = installedPlugins.get(pluginKey);
  123. // Create a new backup representation that retains the reference to the original backup file but that changes
  124. // the current plugin artifact name to be the new plugin file representation
  125. orig = new BackupRepresentation(oldBackupFile, currentPluginArtifact.getName());
  126. // Delete the previous plugin representation
  127. final File previousPluginFile = new File(oldBackupFile.getBackupFile().getParent(), oldBackupFile.getCurrentPluginFilename());
  128. if (previousPluginFile.exists()) {
  129. previousPluginFile.delete();
  130. }
  131. }
  132. // Lets keep the backup representation for this plugin up-to-date
  133. installedPlugins.put(pluginKey, orig);
  134. }
  135. private BackupRepresentation getBackupRepresentation(final String pluginKey, final File currentPluginArtifact) throws IOException {
  136. // If there is already a file of the same name as our current plugin artifact then we should create a backup copy
  137. // of the original file before we overwrite the old plugin file
  138. if (currentPluginArtifact.exists()) {
  139. File backupFile = new File(currentPluginArtifact.getParent(), ORIGINAL_PREFIX + currentPluginArtifact.getName());
  140. if (backupFile.exists()) {
  141. throw new IOException("Existing backup found for plugin " + pluginKey + ". Cannot install.");
  142. }
  143. FileUtils.copyFile(currentPluginArtifact, backupFile);
  144. return new BackupRepresentation(backupFile, currentPluginArtifact.getName());
  145. }
  146. // Since there was no original file then there is not really anything we need to store as a backup
  147. else {
  148. return new BackupRepresentation(currentPluginArtifact, currentPluginArtifact.getName());
  149. }
  150. }
  151. private static class BackupNameFilter implements FilenameFilter {
  152. public boolean accept(File dir, String name) {
  153. return name.startsWith(ORIGINAL_PREFIX);
  154. }
  155. }
  156. private static class BackupRepresentation {
  157. private final File backupFile;
  158. private final String originalPluginArtifactFilename;
  159. private final String currentPluginFilename;
  160. private final boolean isUpgrade;
  161. /**
  162. * @param backupFile the file reference to the file that we should restore if we need to restore a backup
  163. * @param originalPluginArtifactFilename the name of the original plugin artifact.
  164. */
  165. public BackupRepresentation(File backupFile, String originalPluginArtifactFilename) {
  166. this.backupFile = checkNotNull(backupFile, "backupFile");
  167. this.originalPluginArtifactFilename = checkNotNull(originalPluginArtifactFilename, "originalPluginArtifactFilename");
  168. this.isUpgrade = !backupFile.getName().equals(originalPluginArtifactFilename);
  169. this.currentPluginFilename = originalPluginArtifactFilename;
  170. }
  171. /**
  172. * @param oldBackup defines the backup file, original plugin artifact name and if the backup is an "upgrade", non-null.
  173. * @param currentPluginFilename the name of the current plugin artifact, not null.
  174. */
  175. public BackupRepresentation(BackupRepresentation oldBackup, String currentPluginFilename) {
  176. this.backupFile = checkNotNull(oldBackup, "oldBackup").backupFile;
  177. this.originalPluginArtifactFilename = oldBackup.originalPluginArtifactFilename;
  178. this.isUpgrade = oldBackup.isUpgrade;
  179. this.currentPluginFilename = checkNotNull(currentPluginFilename, "currentPluginFilename");
  180. }
  181. public File getBackupFile() {
  182. return backupFile;
  183. }
  184. public String getOriginalPluginArtifactFilename() {
  185. return originalPluginArtifactFilename;
  186. }
  187. public String getCurrentPluginFilename() {
  188. return currentPluginFilename;
  189. }
  190. public boolean isUpgrade() {
  191. return isUpgrade;
  192. }
  193. }
  194. }