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