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