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
30  {
31      private final Logger logger = LoggerFactory.getLogger(this.getClass());
32  
33      private final File jarFile;
34  
35      public JarPluginArtifact(File jarFile)
36      {
37          this.jarFile = checkNotNull(jarFile);
38      }
39  
40      public boolean doesResourceExist(String name)
41      {
42          InputStream in = null;
43          try
44          {
45              in = getResourceAsStream(name);
46              return (in != null);
47          }
48          finally
49          {
50              IOUtils.closeQuietly(in);
51          }
52      }
53  
54      /**
55       * @return an input stream for the this file in the jar. Closing this stream also closes the jar file this stream comes from.
56       */
57      public InputStream getResourceAsStream(String fileName) throws PluginParseException
58      {
59          checkNotNull(fileName, "The file name must not be null");
60  
61          final JarFile jar = open();
62          final ZipEntry entry = jar.getEntry(fileName);
63          if (entry == null)
64          {
65              closeJarQuietly(jar);
66              return null;
67          }
68  
69          try
70          {
71              return new BufferedInputStream(jar.getInputStream(entry))
72              {
73                  // because we do not expose a handle to the jar file this stream is associated with, we need to make sure
74                  // we explicitly close the jar file when we're done with the stream (else we'll have a file handle leak)
75                  public void close() throws IOException
76                  {
77                      super.close();
78                      jar.close();
79                  }
80              };
81          }
82          catch (IOException e)
83          {
84              throw new PluginParseException("Cannot retrieve " + fileName + " from plugin JAR [" + jarFile + "]", e);
85          }
86      }
87  
88      public String getName()
89      {
90          return jarFile.getName();
91      }
92  
93      @Override
94      public String toString()
95      {
96          return getName();
97      }
98  
99      /**
100      * @return a buffered file input stream of the file on disk. This input stream
101      *         is not resettable.
102      */
103     public InputStream getInputStream()
104     {
105         try
106         {
107             return new BufferedInputStream(new FileInputStream(jarFile));
108         }
109         catch (FileNotFoundException e)
110         {
111             throw new PluginParseException("Could not open JAR file: " + jarFile, e);
112         }
113     }
114 
115     public File toFile()
116     {
117         return jarFile;
118     }
119 
120     @Override
121     public boolean containsJavaExecutableCode()
122     {
123         final JarFile jar = open();
124         try
125         {
126             final Manifest manifest = getManifest(jar);
127             return hasBundleActivator(manifest)
128                     ||
129                     hasSpringContext(manifest)
130                     ||
131                     any(forEnumeration(jar.entries()), new Predicate<JarEntry>()
132                     {
133                         @Override
134                         public boolean apply(JarEntry entry)
135                         {
136                             return isJavaClass(entry) || isJavaLibrary(entry) || isSpringContext(entry);
137                         }
138                     });
139         }
140         finally
141         {
142             closeJarQuietly(jar);
143         }
144     }
145 
146     private boolean isJavaClass(ZipEntry entry)
147     {
148         return entry.getName().endsWith(".class");
149     }
150 
151     private boolean isJavaLibrary(ZipEntry entry)
152     {
153         return entry.getName().endsWith(".jar");
154     }
155 
156     private boolean isSpringContext(ZipEntry entry)
157     {
158         final String entryName = entry.getName();
159         return entryName.startsWith("META-INF/spring/") && entryName.endsWith(".xml");
160     }
161 
162     private boolean hasSpringContext(Manifest manifest)
163     {
164         return hasManifestEntry(manifest, "Spring-Context");
165     }
166 
167     private boolean hasBundleActivator(Manifest manifest)
168     {
169         return hasManifestEntry(manifest, "Bundle-Activator");
170     }
171 
172     private boolean hasManifestEntry(Manifest manifest, String manifestEntryName)
173     {
174         return manifest != null
175                 && manifest.getMainAttributes() != null
176                 && manifest.getMainAttributes().getValue(manifestEntryName) != null;
177     }
178 
179     private JarFile open()
180     {
181         try
182         {
183             return new JarFile(jarFile);
184         }
185         catch (IOException e)
186         {
187             throw new PluginParseException("Cannot open JAR file: " + jarFile, e);
188         }
189     }
190 
191     private Manifest getManifest(JarFile jar)
192     {
193         try
194         {
195             return jar.getManifest();
196         }
197         catch (IOException e)
198         {
199             throw new PluginParseException("Cannot get manifest for JAR file: " + jarFile, e);
200         }
201     }
202 
203     private void closeJarQuietly(JarFile jar)
204     {
205         if (jar != null)
206         {
207             try
208             {
209                 jar.close();
210             }
211             catch (IOException ignored)
212             {
213                 logger.debug("Exception closing jar file " + jarFile + ".", ignored);
214             }
215         }
216     }
217 }