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