1 package com.atlassian.plugin.osgi.factory.transform;
2
3 import com.atlassian.plugin.Application;
4 import com.atlassian.plugin.JarPluginArtifact;
5 import com.atlassian.plugin.PluginArtifact;
6 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
7 import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
8 import com.atlassian.plugin.osgi.factory.transform.model.SystemExports;
9 import com.atlassian.plugin.osgi.factory.transform.stage.AddBundleOverridesStage;
10 import com.atlassian.plugin.osgi.factory.transform.stage.ComponentImportSpringStage;
11 import com.atlassian.plugin.osgi.factory.transform.stage.ComponentSpringStage;
12 import com.atlassian.plugin.osgi.factory.transform.stage.GenerateManifestStage;
13 import com.atlassian.plugin.osgi.factory.transform.stage.HostComponentSpringStage;
14 import com.atlassian.plugin.osgi.factory.transform.stage.ModuleTypeSpringStage;
15 import com.atlassian.plugin.osgi.factory.transform.stage.ScanDescriptorForHostClassesStage;
16 import com.atlassian.plugin.osgi.factory.transform.stage.ScanInnerJarsStage;
17 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
18 import com.google.common.collect.ImmutableList;
19 import org.apache.commons.io.IOUtils;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 import java.io.BufferedOutputStream;
24 import java.io.ByteArrayInputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.SortedMap;
36 import java.util.TreeMap;
37 import java.util.zip.Deflater;
38 import java.util.zip.ZipEntry;
39 import java.util.zip.ZipInputStream;
40 import java.util.zip.ZipOutputStream;
41
42 import static com.google.common.base.Preconditions.checkNotNull;
43
44
45
46
47 public class DefaultPluginTransformer implements PluginTransformer {
48 private static final Logger log = LoggerFactory.getLogger(DefaultPluginTransformer.class);
49
50 public static final String TRANSFORM_COMPRESSION_LEVEL = "atlassian.plugins.plugin.transformer.compression";
51
52 private final String pluginDescriptorPath;
53 private final List<TransformStage> stages;
54 private final File bundleCacheDir;
55 private final SystemExports systemExports;
56 private final Set<Application> applications;
57 private final OsgiContainerManager osgiContainerManager;
58
59
60
61
62
63 public static ArrayList<TransformStage> getDefaultTransformStages() {
64 return new ArrayList<>(Arrays.asList(
65 new AddBundleOverridesStage(),
66 new ScanInnerJarsStage(),
67 new ComponentImportSpringStage(),
68 new ComponentSpringStage(),
69 new ScanDescriptorForHostClassesStage(),
70 new ModuleTypeSpringStage(),
71 new HostComponentSpringStage(),
72 new GenerateManifestStage()
73 ));
74 }
75
76
77
78
79
80
81
82
83
84 public DefaultPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applications, String pluginDescriptorPath, OsgiContainerManager osgiContainerManager) {
85 this(cache, systemExports, applications, pluginDescriptorPath, osgiContainerManager, getDefaultTransformStages());
86 }
87
88
89
90
91
92
93
94
95
96
97 public DefaultPluginTransformer(OsgiPersistentCache cache, SystemExports systemExports, Set<Application> applications, String pluginDescriptorPath, OsgiContainerManager osgiContainerManager, List<TransformStage> stages) {
98 this.pluginDescriptorPath = checkNotNull(pluginDescriptorPath, "The plugin descriptor path is required");
99 this.osgiContainerManager = checkNotNull(osgiContainerManager);
100 this.stages = ImmutableList.copyOf(checkNotNull(stages, "A list of stages is required"));
101 this.bundleCacheDir = checkNotNull(cache).getTransformedPluginCache();
102 this.systemExports = systemExports;
103 this.applications = applications;
104 }
105
106
107
108
109
110
111
112
113
114 public File transform(File pluginJar, List<HostComponentRegistration> regs) throws PluginTransformationException {
115 return transform(new JarPluginArtifact(pluginJar), regs);
116 }
117
118
119
120
121
122
123
124
125
126 public File transform(PluginArtifact pluginArtifact, List<HostComponentRegistration> regs) throws PluginTransformationException {
127 checkNotNull(pluginArtifact, "The plugin artifact is required");
128 checkNotNull(regs, "The host component registrations are required");
129
130 File artifactFile = pluginArtifact.toFile();
131
132
133 File cachedPlugin = getFromCache(artifactFile);
134 if (cachedPlugin != null) {
135 return cachedPlugin;
136 }
137
138 final TransformContext context = new TransformContext(regs, systemExports, pluginArtifact, applications, pluginDescriptorPath, osgiContainerManager);
139 for (TransformStage stage : stages) {
140 stage.execute(context);
141 }
142
143
144 try {
145 if (log.isDebugEnabled()) {
146 StringBuilder sb = new StringBuilder();
147 sb.append("Overriding files in ").append(pluginArtifact.toString()).append(":\n");
148 for (Map.Entry<String, byte[]> entry : context.getFileOverrides().entrySet()) {
149 sb.append("==").append(entry.getKey()).append("==\n");
150
151
152
153 sb.append(new String(entry.getValue()));
154 }
155 log.debug(sb.toString());
156 }
157 return addFilesToExistingZip(artifactFile, context.getFileOverrides());
158 } catch (IOException e) {
159 throw new PluginTransformationException("Unable to add files to plugin jar", e);
160 }
161 }
162
163 private File getFromCache(File artifact) {
164 String name = generateCacheName(artifact);
165 File[] files = bundleCacheDir.listFiles();
166 if (files == null) {
167 return null;
168 }
169 for (File child : files) {
170 if (child.getName().equals(name))
171 return child;
172 }
173 return null;
174 }
175
176
177
178
179
180
181
182 static String generateCacheName(File file) {
183 int dotPos = file.getName().lastIndexOf('.');
184 if (dotPos > 0 && file.getName().length() - 1 > dotPos) {
185 return file.getName().substring(0, dotPos) + "_" + file.lastModified() + file.getName().substring(dotPos);
186 } else {
187 return file.getName() + "_" + file.lastModified();
188 }
189 }
190
191
192
193
194
195
196
197
198
199
200 File addFilesToExistingZip(File zipFile,
201 Map<String, byte[]> files) throws IOException {
202
203 File tempFile = new File(bundleCacheDir, generateCacheName(zipFile));
204
205 ZipInputStream zin = null;
206 ZipOutputStream out = null;
207 try {
208 zin = new ZipInputStream(new FileInputStream(zipFile));
209 out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempFile)));
210 final int requestedCompressionLevel = Integer.getInteger(TRANSFORM_COMPRESSION_LEVEL, Deflater.NO_COMPRESSION);
211 final int clampedCompressionLevel =
212 Math.max(Deflater.NO_COMPRESSION, Math.min(requestedCompressionLevel, Deflater.BEST_COMPRESSION));
213 out.setLevel(clampedCompressionLevel);
214
215
216 ZipEntry entry = zin.getNextEntry();
217 while (entry != null) {
218 String name = entry.getName();
219 if (!files.containsKey(name)) {
220
221 final ZipEntry newEntry = new ZipEntry(name);
222 newEntry.setTime(entry.getTime());
223 out.putNextEntry(newEntry);
224
225
226 IOUtils.copyLarge(zin, out);
227 }
228 entry = zin.getNextEntry();
229 }
230
231 zin.close();
232
233
234 final SortedMap<String, byte[]> sortedFiles = new TreeMap<>(files);
235 for (Map.Entry<String, byte[]> fentry : sortedFiles.entrySet()) {
236 InputStream in = null;
237 try {
238 in = new ByteArrayInputStream(fentry.getValue());
239
240
241
242 final ZipEntry newEntry = new ZipEntry(fentry.getKey());
243 newEntry.setTime(zipFile.lastModified());
244 out.putNextEntry(newEntry);
245
246
247 IOUtils.copyLarge(in, out);
248
249
250 out.closeEntry();
251 } finally {
252 IOUtils.closeQuietly(in);
253 }
254 }
255
256 out.close();
257 } finally {
258
259 IOUtils.closeQuietly(zin);
260 IOUtils.closeQuietly(out);
261 }
262 return tempFile;
263 }
264
265 }