PageRenderTime 43ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/atlassian-plugins-osgi/src/main/java/com/atlassian/plugin/osgi/factory/transform/DefaultPluginTransformer.java

https://bitbucket.org/purewind/atlassian-plugins
Java | 261 lines | 163 code | 28 blank | 70 comment | 14 complexity | 83d3435024b2b93d1d9105a8e1bd75d9 MD5 | raw file
  1. package com.atlassian.plugin.osgi.factory.transform;
  2. import com.atlassian.plugin.Application;
  3. import com.atlassian.plugin.JarPluginArtifact;
  4. import com.atlassian.plugin.PluginArtifact;
  5. import com.atlassian.plugin.osgi.container.OsgiContainerManager;
  6. import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
  7. import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
  8. import com.atlassian.plugin.osgi.factory.transform.stage.AddBundleOverridesStage;
  9. import com.atlassian.plugin.osgi.factory.transform.stage.ComponentImportSpringStage;
  10. import com.atlassian.plugin.osgi.factory.transform.stage.ComponentSpringStage;
  11. import com.atlassian.plugin.osgi.factory.transform.stage.GenerateManifestStage;
  12. import com.atlassian.plugin.osgi.factory.transform.stage.HostComponentSpringStage;
  13. import com.atlassian.plugin.osgi.factory.transform.stage.ModuleTypeSpringStage;
  14. import com.atlassian.plugin.osgi.factory.transform.stage.ScanDescriptorForHostClassesStage;
  15. import com.atlassian.plugin.osgi.factory.transform.stage.ScanInnerJarsStage;
  16. import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
  17. import com.google.common.collect.ImmutableList;
  18. import org.apache.commons.io.IOUtils;
  19. import org.slf4j.Logger;
  20. import org.slf4j.LoggerFactory;
  21. import java.io.BufferedOutputStream;
  22. import java.io.ByteArrayInputStream;
  23. import java.io.File;
  24. import java.io.FileInputStream;
  25. import java.io.FileOutputStream;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.util.ArrayList;
  29. import java.util.Arrays;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.Set;
  33. import java.util.SortedMap;
  34. import java.util.TreeMap;
  35. import java.util.zip.Deflater;
  36. import java.util.zip.ZipEntry;
  37. import java.util.zip.ZipInputStream;
  38. import java.util.zip.ZipOutputStream;
  39. import static com.google.common.base.Preconditions.checkNotNull;
  40. /**
  41. * Default implementation of plugin transformation that uses stages to convert a plain JAR into an OSGi bundle.
  42. */
  43. public class DefaultPluginTransformer implements PluginTransformer {
  44. private static final Logger log = LoggerFactory.getLogger(DefaultPluginTransformer.class);
  45. public static final String TRANSFORM_COMPRESSION_LEVEL = "atlassian.plugins.plugin.transformer.compression";
  46. private final String pluginDescriptorPath;
  47. private final List<TransformStage> stages;
  48. private final File bundleCacheDir;
  49. private final SystemExports systemExports;
  50. private final Set<Application> applications;
  51. private final OsgiContainerManager osgiContainerManager;
  52. /**
  53. * Gets the default list of transform stages performed by the transformer. Clients wishing to add stages to the
  54. * transformation process should use this list as a template rather than creating their own from scratch.
  55. */
  56. public static ArrayList<TransformStage> getDefaultTransformStages() {
  57. return new ArrayList<TransformStage>(Arrays.asList(
  58. new AddBundleOverridesStage(),
  59. new ScanInnerJarsStage(),
  60. new ComponentImportSpringStage(),
  61. new ComponentSpringStage(),
  62. new ScanDescriptorForHostClassesStage(),
  63. new ModuleTypeSpringStage(),
  64. new HostComponentSpringStage(),
  65. new GenerateManifestStage()
  66. ));
  67. }
  68. /**
  69. * Constructs a transformer with the default stages
  70. *
  71. * @param cache The OSGi cache configuration for transformed plugins
  72. * @param systemExports The packages the system bundle exports
  73. * @param pluginDescriptorPath The path to the plugin descriptor
  74. * @since 2.2.0
  75. */
  76. public DefaultPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applications, String pluginDescriptorPath, OsgiContainerManager osgiContainerManager) {
  77. this(cache, systemExports, applications, pluginDescriptorPath, osgiContainerManager, getDefaultTransformStages());
  78. }
  79. /**
  80. * Constructs a transformer and its stages
  81. *
  82. * @param cache The OSGi cache configuration for transformed plugins
  83. * @param systemExports The packages the system bundle exports
  84. * @param pluginDescriptorPath The descriptor path
  85. * @param stages A set of stages
  86. * @since 2.2.0
  87. */
  88. public DefaultPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applications, String pluginDescriptorPath, OsgiContainerManager osgiContainerManager, List<TransformStage> stages) {
  89. this.pluginDescriptorPath = checkNotNull(pluginDescriptorPath, "The plugin descriptor path is required");
  90. this.osgiContainerManager = checkNotNull(osgiContainerManager);
  91. this.stages = ImmutableList.copyOf(checkNotNull(stages, "A list of stages is required"));
  92. this.bundleCacheDir = checkNotNull(cache).getTransformedPluginCache();
  93. this.systemExports = systemExports;
  94. this.applications = applications;
  95. }
  96. /**
  97. * Transforms the file into an OSGi bundle
  98. *
  99. * @param pluginJar The plugin jar
  100. * @param regs The list of registered host components
  101. * @return The new OSGi-enabled plugin jar
  102. * @throws PluginTransformationException If anything goes wrong
  103. */
  104. public File transform(File pluginJar, List<HostComponentRegistration> regs) throws PluginTransformationException {
  105. return transform(new JarPluginArtifact(pluginJar), regs);
  106. }
  107. /**
  108. * Transforms the file into an OSGi bundle
  109. *
  110. * @param pluginArtifact The plugin artifact, usually a jar
  111. * @param regs The list of registered host components
  112. * @return The new OSGi-enabled plugin jar
  113. * @throws PluginTransformationException If anything goes wrong
  114. */
  115. public File transform(PluginArtifact pluginArtifact, List<HostComponentRegistration> regs) throws PluginTransformationException {
  116. checkNotNull(pluginArtifact, "The plugin artifact is required");
  117. checkNotNull(regs, "The host component registrations are required");
  118. File artifactFile = pluginArtifact.toFile();
  119. // Look in cache first
  120. File cachedPlugin = getFromCache(artifactFile);
  121. if (cachedPlugin != null) {
  122. return cachedPlugin;
  123. }
  124. final TransformContext context = new TransformContext(regs, systemExports, pluginArtifact, applications, pluginDescriptorPath, osgiContainerManager);
  125. for (TransformStage stage : stages) {
  126. stage.execute(context);
  127. }
  128. // Create a new jar by overriding the specified files
  129. try {
  130. if (log.isDebugEnabled()) {
  131. StringBuilder sb = new StringBuilder();
  132. sb.append("Overriding files in ").append(pluginArtifact.toString()).append(":\n");
  133. for (Map.Entry<String, byte[]> entry : context.getFileOverrides().entrySet()) {
  134. sb.append("==").append(entry.getKey()).append("==\n");
  135. // Yes, this doesn't take into account encoding, but since only text files are overridden, that
  136. // should be fine
  137. sb.append(new String(entry.getValue()));
  138. }
  139. log.debug(sb.toString());
  140. }
  141. return addFilesToExistingZip(artifactFile, context.getFileOverrides());
  142. } catch (IOException e) {
  143. throw new PluginTransformationException("Unable to add files to plugin jar", e);
  144. }
  145. }
  146. private File getFromCache(File artifact) {
  147. String name = generateCacheName(artifact);
  148. for (File child : bundleCacheDir.listFiles()) {
  149. if (child.getName().equals(name))
  150. return child;
  151. }
  152. return null;
  153. }
  154. /**
  155. * Generate a cache name that incorporates the timestap and preserves the extension
  156. *
  157. * @param file The original file to cache
  158. * @return The new file name
  159. */
  160. static String generateCacheName(File file) {
  161. int dotPos = file.getName().lastIndexOf('.');
  162. if (dotPos > 0 && file.getName().length() - 1 > dotPos) {
  163. return file.getName().substring(0, dotPos) + "_" + file.lastModified() + file.getName().substring(dotPos);
  164. } else {
  165. return file.getName() + "_" + file.lastModified();
  166. }
  167. }
  168. /**
  169. * Creates a new jar by overriding the specified files in the existing one
  170. *
  171. * @param zipFile The existing zip file
  172. * @param files The files to override
  173. * @return The new zip
  174. * @throws IOException If there are any problems processing the streams
  175. */
  176. File addFilesToExistingZip(File zipFile,
  177. Map<String, byte[]> files) throws IOException {
  178. // get a temp file
  179. File tempFile = new File(bundleCacheDir, generateCacheName(zipFile));
  180. ZipInputStream zin = null;
  181. ZipOutputStream out = null;
  182. try {
  183. zin = new ZipInputStream(new FileInputStream(zipFile));
  184. out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempFile)));
  185. final int requestedCompressionLevel = Integer.getInteger(TRANSFORM_COMPRESSION_LEVEL, Deflater.NO_COMPRESSION);
  186. final int clampedCompressionLevel =
  187. Math.max(Deflater.NO_COMPRESSION, Math.min(requestedCompressionLevel, Deflater.BEST_COMPRESSION));
  188. out.setLevel(clampedCompressionLevel);
  189. // Copy any entries that are unmodified from the existing JAR to the new JAR
  190. ZipEntry entry = zin.getNextEntry();
  191. while (entry != null) {
  192. String name = entry.getName();
  193. if (!files.containsKey(name)) {
  194. // Add ZIP entry to output stream, preserve existing modification time
  195. final ZipEntry newEntry = new ZipEntry(name);
  196. newEntry.setTime(entry.getTime());
  197. out.putNextEntry(newEntry);
  198. // Transfer bytes from the ZIP file to the output file
  199. IOUtils.copyLarge(zin, out);
  200. }
  201. entry = zin.getNextEntry();
  202. }
  203. // Close the streams
  204. zin.close();
  205. // Override/append any entries present in files
  206. final SortedMap<String, byte[]> sortedFiles = new TreeMap(files);
  207. for (Map.Entry<String, byte[]> fentry : sortedFiles.entrySet()) {
  208. InputStream in = null;
  209. try {
  210. in = new ByteArrayInputStream(fentry.getValue());
  211. // Add ZIP entry to output stream, use the last modified time of the original jar
  212. // for any new files
  213. final ZipEntry newEntry = new ZipEntry(fentry.getKey());
  214. newEntry.setTime(zipFile.lastModified());
  215. out.putNextEntry(newEntry);
  216. // Transfer bytes from the file to the ZIP file
  217. IOUtils.copyLarge(in, out);
  218. // Complete the entry
  219. out.closeEntry();
  220. } finally {
  221. IOUtils.closeQuietly(in);
  222. }
  223. }
  224. // Complete the ZIP file
  225. out.close();
  226. } finally {
  227. // Close just in case
  228. IOUtils.closeQuietly(zin);
  229. IOUtils.closeQuietly(out);
  230. }
  231. return tempFile;
  232. }
  233. }