View Javadoc

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