1 package com.atlassian.plugin.osgi.container.felix;
2
3 import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
4 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
5 import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
6 import com.atlassian.plugin.util.PluginFrameworkUtils;
7 import com.google.common.annotations.VisibleForTesting;
8 import com.google.common.base.Predicate;
9 import com.google.common.collect.Sets;
10 import org.dom4j.Document;
11 import org.dom4j.DocumentException;
12 import org.dom4j.Element;
13 import org.dom4j.io.SAXReader;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16 import org.twdata.pkgscanner.DefaultOsgiVersionConverter;
17 import org.twdata.pkgscanner.ExportPackage;
18 import org.twdata.pkgscanner.PackageScanner;
19
20 import static com.atlassian.plugin.osgi.container.felix.ExportBuilderUtils.parseExportFile;
21 import static com.atlassian.plugin.osgi.container.felix.ExportBuilderUtils.copyUnlessExist;
22 import static com.google.common.collect.Iterables.any;
23 import static com.google.common.collect.Lists.newArrayList;
24 import static org.twdata.pkgscanner.PackageScanner.exclude;
25 import static org.twdata.pkgscanner.PackageScanner.include;
26 import static org.twdata.pkgscanner.PackageScanner.jars;
27 import static org.twdata.pkgscanner.PackageScanner.packages;
28
29 import javax.annotation.Nullable;
30 import javax.servlet.ServletContext;
31 import java.io.File;
32 import java.io.IOException;
33 import java.net.MalformedURLException;
34 import java.net.URL;
35 import java.util.*;
36
37
38
39
40 class ExportsBuilder
41 {
42 static final String JDK_6 = "1.6";
43 static final String JDK_7 = "1.7";
44
45 static final String OSGI_PACKAGES_PATH = "osgi-packages.txt";
46 static final String JDK_PACKAGES_PATH = "jdk-packages.txt";
47
48 private static Logger log = LoggerFactory.getLogger(ExportsBuilder.class);
49 private static String exportStringCache;
50
51 @VisibleForTesting
52 static final Predicate<String> UNDER_PLUGIN_FRAMEWORK = new Predicate<String>()
53 {
54 private Iterable<String> packagesNotInPlugins = newArrayList(
55 "com.atlassian.plugin.remotable",
56
57 "com.atlassian.plugin.cache.filecache",
58 "com.atlassian.plugin.webresource",
59 "com.atlassian.plugin.web"
60 );
61
62 public boolean apply(final String pkg)
63 {
64 Predicate<String> underPackage = new Predicate<String>() {
65 @Override
66 public boolean apply(@Nullable String input) {
67 return pkg.equals(input) || pkg.startsWith(input + ".");
68 }
69 };
70 return pkg.startsWith("com.atlassian.plugin.") && !any(packagesNotInPlugins, underPackage);
71 }
72 };
73
74 public static interface CachedExportPackageLoader
75 {
76 Collection<ExportPackage> load();
77 }
78
79 private final CachedExportPackageLoader cachedExportPackageLoader;
80
81 public ExportsBuilder()
82 {
83 this(new PackageScannerExportsFileLoader("package-scanner-exports.xml"));
84 }
85
86 public ExportsBuilder(CachedExportPackageLoader loader)
87 {
88 this.cachedExportPackageLoader = loader;
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103 public String getExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig)
104 {
105 if (exportStringCache == null)
106 {
107 exportStringCache = determineExports(regs, packageScannerConfig);
108 }
109 return exportStringCache;
110 }
111
112
113
114
115
116
117 public void clearExportCache()
118 {
119 exportStringCache = null;
120 }
121
122
123
124
125
126
127
128
129
130
131 @SuppressWarnings ({ "UnusedDeclaration" })
132 public String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig, File cacheDir)
133 {
134 return determineExports(regs, packageScannerConfig);
135 }
136
137
138
139
140
141
142
143
144 String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig)
145 {
146 Map<String, String> exportPackages = new HashMap<String, String>();
147
148
149 copyUnlessExist(exportPackages, parseExportFile(OSGI_PACKAGES_PATH));
150
151
152 copyUnlessExist(exportPackages, parseExportFile(JDK_PACKAGES_PATH));
153
154
155 Collection<ExportPackage> scannedPackages = generateExports(packageScannerConfig);
156 copyUnlessExist(exportPackages, ExportBuilderUtils.toMap(scannedPackages));
157
158
159 try
160 {
161 Map<String,String> referredPackages = OsgiHeaderUtil.findReferredPackageVersions(regs, packageScannerConfig.getPackageVersions());
162 copyUnlessExist(exportPackages, referredPackages);
163 }
164 catch (IOException ex)
165 {
166 log.error("Unable to calculate necessary exports based on host components", ex);
167 }
168
169
170 enforceFrameworkVersion(exportPackages);
171
172
173 final String exports = OsgiHeaderUtil.generatePackageVersionString(exportPackages);
174
175 if (log.isDebugEnabled())
176 {
177 log.debug("Exports:\n"+exports.replaceAll(",", "\r\n"));
178 }
179
180 return exports;
181 }
182
183 private void enforceFrameworkVersion(Map<String, String> exportPackages)
184 {
185 final String frameworkVersion = PluginFrameworkUtils.getPluginFrameworkVersion();
186
187
188 DefaultOsgiVersionConverter converter = new DefaultOsgiVersionConverter();
189 final String frameworkVersionOsgi = converter.getVersion(frameworkVersion);
190
191 for(String pkg: Sets.filter(exportPackages.keySet(), UNDER_PLUGIN_FRAMEWORK))
192 {
193 exportPackages.put(pkg, frameworkVersionOsgi);
194 }
195 }
196
197 Collection<ExportPackage> generateExports(PackageScannerConfiguration packageScannerConfig)
198 {
199 String[] arrType = new String[0];
200
201 Map<String,String> pkgVersions = new HashMap<String,String>(packageScannerConfig.getPackageVersions());
202 if (packageScannerConfig.getServletContext() != null)
203 {
204 String ver = packageScannerConfig.getServletContext().getMajorVersion() + "." + packageScannerConfig.getServletContext().getMinorVersion();
205 pkgVersions.put("javax.servlet*", ver);
206 }
207
208 PackageScanner scanner = new PackageScanner()
209 .select(
210 jars(
211 include(packageScannerConfig.getJarIncludes().toArray(arrType)),
212 exclude(packageScannerConfig.getJarExcludes().toArray(arrType))),
213 packages(
214 include(packageScannerConfig.getPackageIncludes().toArray(arrType)),
215 exclude(packageScannerConfig.getPackageExcludes().toArray(arrType)))
216 )
217 .withMappings(pkgVersions);
218
219 if (log.isDebugEnabled())
220 {
221 scanner.enableDebug();
222 }
223
224 Collection<ExportPackage> exports = cachedExportPackageLoader.load();
225 if (exports == null)
226 {
227 exports = scanner.scan();
228 }
229 log.info("Package scan completed. Found " + exports.size() + " packages to export.");
230
231 if (!isPackageScanSuccessful(exports) && packageScannerConfig.getServletContext() != null)
232 {
233 log.warn("Unable to find expected packages via classloader scanning. Trying ServletContext scanning...");
234 ServletContext ctx = packageScannerConfig.getServletContext();
235 try
236 {
237 exports = scanner.scan(ctx.getResource("/WEB-INF/lib"), ctx.getResource("/WEB-INF/classes"));
238 }
239 catch (MalformedURLException e)
240 {
241 log.warn("Unable to scan webapp for packages", e);
242 }
243 }
244
245 if (!isPackageScanSuccessful(exports))
246 {
247 throw new IllegalStateException("Unable to find required packages via classloader or servlet context"
248 + " scanning, most likely due to an application server bug.");
249 }
250 return exports;
251 }
252
253
254
255
256
257
258
259 private static boolean isPackageScanSuccessful(Collection<ExportPackage> exports)
260 {
261 boolean slf4jFound = false;
262 for (ExportPackage export : exports)
263 {
264 if (export.getPackageName().equals("org.slf4j"))
265 {
266 slf4jFound = true;
267 break;
268 }
269 }
270 return slf4jFound;
271 }
272
273 static class PackageScannerExportsFileLoader implements CachedExportPackageLoader
274 {
275 private final String path;
276
277 public PackageScannerExportsFileLoader(String path)
278 {
279 this.path = path;
280 }
281
282 @Override
283 public Collection<ExportPackage> load()
284 {
285 URL exportsUrl = getClass().getClassLoader().getResource(path);
286 if (exportsUrl != null)
287 {
288 log.debug("Precalculated exports found, loading...");
289 List<ExportPackage> result = newArrayList();
290 try
291 {
292 Document doc = new SAXReader().read(exportsUrl);
293 for (Element export : ((List<Element>)doc.getRootElement().elements()))
294 {
295 String packageName = export.attributeValue("package");
296 String version = export.attributeValue("version");
297 String location = export.attributeValue("location");
298
299 if (packageName == null || location == null)
300 {
301 log.warn("Invalid configuration: package({}) and location({}) are required, " +
302 "aborting precalculated exports and reverting to normal scanning",
303 packageName, location);
304 return Collections.emptyList();
305 }
306 result.add(new ExportPackage(packageName, version, new File(location)));
307 }
308 log.debug("Loaded {} precalculated exports", result.size());
309
310 return result;
311 }
312 catch (DocumentException e)
313 {
314 log.warn("Unable to load exports from " + path + " due to malformed XML", e);
315 }
316 }
317 log.debug("No precalculated exports found");
318 return null;
319 }
320 }
321 }