View Javadoc

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