View Javadoc

1   package com.atlassian.plugin;
2   
3   import com.google.common.base.Predicate;
4   import org.apache.commons.io.IOUtils;
5   import org.slf4j.Logger;
6   import org.slf4j.LoggerFactory;
7   
8   import java.io.BufferedInputStream;
9   import java.io.File;
10  import java.io.FileInputStream;
11  import java.io.FileNotFoundException;
12  import java.io.IOException;
13  import java.io.InputStream;
14  import java.util.jar.JarEntry;
15  import java.util.jar.JarFile;
16  import java.util.jar.Manifest;
17  import java.util.zip.ZipEntry;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  import static com.google.common.collect.Iterators.any;
21  import static com.google.common.collect.Iterators.forEnumeration;
22  
23  /**
24   * The implementation of PluginArtifact that is backed by a jar file.
25   *
26   * @see PluginArtifact
27   * @since 2.0.0
28   */
29  public final class JarPluginArtifact implements PluginArtifact, PluginArtifact.AllowsReference
30  {
31      private final Logger logger = LoggerFactory.getLogger(this.getClass());
32  
33      private final File jarFile;
34      private final boolean allowsReference;
35  
36      /**
37       * Construct a PluginArtifact for a jar file which does not allow reference installation.
38       * @param jarFile the jar file comprising the artifact.
39       */
40      public JarPluginArtifact(File jarFile)
41      {
42          this(jarFile, false);
43      }
44  
45      /**
46       * Construct a PluginArtifact for a jar file and specify whether reference installation is supported.
47       * @param jarFile the jar file comprising the artifact.
48       * @param allowsReference true iff reference installation is permitted.
49       */
50      public JarPluginArtifact(File jarFile, boolean allowsReference)
51      {
52          this.jarFile = checkNotNull(jarFile);
53          this.allowsReference = allowsReference;
54      }
55  
56  
57      public boolean doesResourceExist(String name)
58      {
59          InputStream in = null;
60          try
61          {
62              in = getResourceAsStream(name);
63              return (in != null);
64          }
65          finally
66          {
67              IOUtils.closeQuietly(in);
68          }
69      }
70  
71      /**
72       * @return an input stream for the this file in the jar. Closing this stream also closes the jar file this stream comes from.
73       */
74      public InputStream getResourceAsStream(String fileName) throws PluginParseException
75      {
76          checkNotNull(fileName, "The file name must not be null");
77  
78          final JarFile jar = open();
79          final ZipEntry entry = jar.getEntry(fileName);
80          if (entry == null)
81          {
82              closeJarQuietly(jar);
83              return null;
84          }
85  
86          try
87          {
88              return new BufferedInputStream(jar.getInputStream(entry))
89              {
90                  // because we do not expose a handle to the jar file this stream is associated with, we need to make sure
91                  // we explicitly close the jar file when we're done with the stream (else we'll have a file handle leak)
92                  public void close() throws IOException
93                  {
94                      super.close();
95                      jar.close();
96                  }
97              };
98          }
99          catch (IOException e)
100         {
101             throw new PluginParseException("Cannot retrieve " + fileName + " from plugin JAR [" + jarFile + "]", e);
102         }
103     }
104 
105     public String getName()
106     {
107         return jarFile.getName();
108     }
109 
110     @Override
111     public String toString()
112     {
113         return getName();
114     }
115 
116     /**
117      * @return a buffered file input stream of the file on disk. This input stream
118      *         is not resettable.
119      */
120     public InputStream getInputStream()
121     {
122         try
123         {
124             return new BufferedInputStream(new FileInputStream(jarFile));
125         }
126         catch (FileNotFoundException e)
127         {
128             throw new PluginParseException("Could not open JAR file: " + jarFile, e);
129         }
130     }
131 
132     public File toFile()
133     {
134         return jarFile;
135     }
136 
137     @Override
138     public boolean containsJavaExecutableCode()
139     {
140         final JarFile jar = open();
141         try
142         {
143             final Manifest manifest = getManifest(jar);
144             return hasBundleActivator(manifest)
145                     ||
146                     hasSpringContext(manifest)
147                     ||
148                     any(forEnumeration(jar.entries()), new Predicate<JarEntry>()
149                     {
150                         @Override
151                         public boolean apply(JarEntry entry)
152                         {
153                             return isJavaClass(entry) || isJavaLibrary(entry) || isSpringContext(entry);
154                         }
155                     });
156         }
157         finally
158         {
159             closeJarQuietly(jar);
160         }
161     }
162 
163     @Override
164     public boolean allowsReference()
165     {
166         return allowsReference;
167     }
168 
169     private boolean isJavaClass(ZipEntry entry)
170     {
171         return entry.getName().endsWith(".class");
172     }
173 
174     private boolean isJavaLibrary(ZipEntry entry)
175     {
176         return entry.getName().endsWith(".jar");
177     }
178 
179     private boolean isSpringContext(ZipEntry entry)
180     {
181         final String entryName = entry.getName();
182         return entryName.startsWith("META-INF/spring/") && entryName.endsWith(".xml");
183     }
184 
185     private boolean hasSpringContext(Manifest manifest)
186     {
187         return hasManifestEntry(manifest, "Spring-Context");
188     }
189 
190     private boolean hasBundleActivator(Manifest manifest)
191     {
192         return hasManifestEntry(manifest, "Bundle-Activator");
193     }
194 
195     private boolean hasManifestEntry(Manifest manifest, String manifestEntryName)
196     {
197         return manifest != null
198                 && manifest.getMainAttributes() != null
199                 && manifest.getMainAttributes().getValue(manifestEntryName) != null;
200     }
201 
202     private JarFile open()
203     {
204         try
205         {
206             return new JarFile(jarFile);
207         }
208         catch (IOException e)
209         {
210             throw new PluginParseException("Cannot open JAR file: " + jarFile, e);
211         }
212     }
213 
214     private Manifest getManifest(JarFile jar)
215     {
216         try
217         {
218             return jar.getManifest();
219         }
220         catch (IOException e)
221         {
222             throw new PluginParseException("Cannot get manifest for JAR file: " + jarFile, e);
223         }
224     }
225 
226     private void closeJarQuietly(JarFile jar)
227     {
228         if (jar != null)
229         {
230             try
231             {
232                 jar.close();
233             }
234             catch (IOException ignored)
235             {
236                 logger.debug("Exception closing jar file " + jarFile + ".", ignored);
237             }
238         }
239     }
240 }