1 package com.atlassian.plugin;
2
3 import com.google.common.base.Function;
4 import com.google.common.base.Predicate;
5 import com.google.common.collect.Sets;
6 import org.apache.commons.io.IOUtils;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9
10 import javax.annotation.Nullable;
11 import java.io.BufferedInputStream;
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.FileNotFoundException;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.Set;
18 import java.util.jar.JarEntry;
19 import java.util.jar.JarFile;
20 import java.util.jar.Manifest;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23 import java.util.zip.ZipEntry;
24
25 import static com.google.common.base.Preconditions.checkNotNull;
26 import static com.google.common.collect.Iterators.any;
27 import static com.google.common.collect.Iterators.filter;
28 import static com.google.common.collect.Iterators.forEnumeration;
29 import static com.google.common.collect.Iterators.transform;
30
31
32
33
34
35
36
37 public final class JarPluginArtifact implements PluginArtifact, PluginArtifact.AllowsReference, PluginArtifact.HasExtraModuleDescriptors
38 {
39 private static final Logger log = LoggerFactory.getLogger(JarPluginArtifact.class);
40
41 private final File jarFile;
42
43
44
45
46 final ReferenceMode referenceMode;
47
48
49
50
51
52 public JarPluginArtifact(File jarFile)
53 {
54 this(jarFile, ReferenceMode.FORBID_REFERENCE);
55 }
56
57
58
59
60
61
62 public JarPluginArtifact(File jarFile, ReferenceMode referenceMode)
63 {
64 this.jarFile = checkNotNull(jarFile);
65 this.referenceMode = referenceMode;
66 }
67
68
69 public boolean doesResourceExist(String name)
70 {
71 InputStream in = null;
72 try
73 {
74 in = getResourceAsStream(name);
75 return (in != null);
76 }
77 finally
78 {
79 IOUtils.closeQuietly(in);
80 }
81 }
82
83
84
85
86 public InputStream getResourceAsStream(String fileName) throws PluginParseException
87 {
88 checkNotNull(fileName, "The file name must not be null");
89
90 final JarFile jar = open();
91 final ZipEntry entry = jar.getEntry(fileName);
92 if (entry == null)
93 {
94 closeJarQuietly(jar);
95 return null;
96 }
97
98 try
99 {
100 return new BufferedInputStream(jar.getInputStream(entry))
101 {
102
103
104 public void close() throws IOException
105 {
106 super.close();
107 jar.close();
108 }
109 };
110 }
111 catch (IOException e)
112 {
113 throw new PluginParseException("Cannot retrieve " + fileName + " from plugin JAR [" + jarFile + "]", e);
114 }
115 }
116
117 public String getName()
118 {
119 return jarFile.getName();
120 }
121
122 @Override
123 public String toString()
124 {
125 return getName();
126 }
127
128
129
130
131
132 public InputStream getInputStream()
133 {
134 try
135 {
136 return new BufferedInputStream(new FileInputStream(jarFile));
137 }
138 catch (FileNotFoundException e)
139 {
140 throw new PluginParseException("Could not open JAR file: " + jarFile, e);
141 }
142 }
143
144 public File toFile()
145 {
146 return jarFile;
147 }
148
149 @Override
150 public boolean containsJavaExecutableCode()
151 {
152 final JarFile jar = open();
153 try
154 {
155 final Manifest manifest = getManifest(jar);
156 return hasBundleActivator(manifest)
157 ||
158 hasSpringContext(manifest)
159 ||
160 any(forEnumeration(jar.entries()), new Predicate<JarEntry>()
161 {
162 @Override
163 public boolean apply(JarEntry entry)
164 {
165 return isJavaClass(entry) || isJavaLibrary(entry) || isSpringContext(entry);
166 }
167 });
168 }
169 finally
170 {
171 closeJarQuietly(jar);
172 }
173 }
174
175 @Override
176 public Set<String> extraModuleDescriptorFiles(String rootFolder)
177 {
178 final JarFile jar = open();
179 try
180 {
181 final Matcher m = Pattern.compile(Pattern.quote(rootFolder) + "/[^/.]*\\.(?i)xml$").matcher("");
182 return Sets.newHashSet(transform(filter(forEnumeration(jar.entries()),
183 new Predicate<JarEntry>()
184 {
185 @Override
186 public boolean apply(JarEntry entry)
187 {
188 m.reset(entry.getName());
189 return m.find();
190 }
191 }), new Function<JarEntry, String>()
192 {
193 @Override
194 public String apply(@Nullable JarEntry jarEntry)
195 {
196 return jarEntry.getName();
197 }
198 }));
199 }
200 finally
201 {
202 closeJarQuietly(jar);
203 }
204 }
205
206 @Override
207 public boolean allowsReference()
208 {
209 return referenceMode.allowsReference();
210 }
211
212 private boolean isJavaClass(ZipEntry entry)
213 {
214 return entry.getName().endsWith(".class");
215 }
216
217 private boolean isJavaLibrary(ZipEntry entry)
218 {
219 return entry.getName().endsWith(".jar");
220 }
221
222 private boolean isSpringContext(ZipEntry entry)
223 {
224 final String entryName = entry.getName();
225 return entryName.startsWith("META-INF/spring/") && entryName.endsWith(".xml");
226 }
227
228 private boolean hasSpringContext(Manifest manifest)
229 {
230 return hasManifestEntry(manifest, "Spring-Context");
231 }
232
233 private boolean hasBundleActivator(Manifest manifest)
234 {
235 return hasManifestEntry(manifest, "Bundle-Activator");
236 }
237
238 private boolean hasManifestEntry(Manifest manifest, String manifestEntryName)
239 {
240 return manifest != null
241 && manifest.getMainAttributes() != null
242 && manifest.getMainAttributes().getValue(manifestEntryName) != null;
243 }
244
245 private JarFile open()
246 {
247 try
248 {
249 return new JarFile(jarFile);
250 }
251 catch (IOException e)
252 {
253 throw new PluginParseException("Cannot open JAR file: " + jarFile, e);
254 }
255 }
256
257 private Manifest getManifest(JarFile jar)
258 {
259 try
260 {
261 return jar.getManifest();
262 }
263 catch (IOException e)
264 {
265 throw new PluginParseException("Cannot get manifest for JAR file: " + jarFile, e);
266 }
267 }
268
269 private void closeJarQuietly(JarFile jar)
270 {
271 if (jar != null)
272 {
273 try
274 {
275 jar.close();
276 }
277 catch (IOException ignored)
278 {
279 log.debug("Exception closing jar file " + jarFile + ".", ignored);
280 }
281 }
282 }
283 }