1 package com.atlassian.plugin.osgi.container.felix;
2
3 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
4 import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
5 import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
6 import com.atlassian.plugin.util.ClassLoaderUtils;
7
8 import java.util.List;
9 import java.util.Collection;
10 import java.util.Iterator;
11 import java.util.jar.Manifest;
12 import java.io.*;
13 import java.net.MalformedURLException;
14
15 import org.twdata.pkgscanner.ExportPackage;
16 import org.twdata.pkgscanner.PackageScanner;
17 import static org.twdata.pkgscanner.PackageScanner.jars;
18 import static org.twdata.pkgscanner.PackageScanner.include;
19 import static org.twdata.pkgscanner.PackageScanner.exclude;
20 import static org.twdata.pkgscanner.PackageScanner.packages;
21 import org.osgi.framework.Constants;
22 import org.osgi.framework.Version;
23 import org.apache.commons.io.IOUtils;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import aQute.lib.osgi.Analyzer;
27 import aQute.lib.osgi.Jar;
28
29 import javax.servlet.ServletContext;
30
31
32
33
34 class ExportsBuilder
35 {
36
37 static final String JDK_PACKAGES_PATH = "jdk-packages.txt";
38 static final String JDK6_PACKAGES_PATH = "jdk6-packages.txt";
39 private static Log log = LogFactory.getLog(ExportsBuilder.class);
40 private static final String EXPORTS_TXT = "exports.txt";
41
42
43
44
45
46
47
48
49 public String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig, File cacheDir){
50
51 String exports = loadExportsFromCache(cacheDir, packageScannerConfig.getCurrentHostVersion());
52
53 if (exports == null)
54 {
55
56 StringBuilder origExports = new StringBuilder();
57 origExports.append("org.osgi.framework; version=1.4.1,");
58 origExports.append("org.osgi.service.packageadmin; version=1.2.0," );
59 origExports.append("org.osgi.service.startlevel; version=1.1.0,");
60 origExports.append("org.osgi.service.url; version=1.0.0,");
61 origExports.append("org.osgi.util; version=1.4.1,");
62 origExports.append("org.osgi.util.tracker; version=1.4.1,");
63 origExports.append("host.service.command; version=1.0.0,");
64
65 constructJdkExports(origExports, JDK_PACKAGES_PATH);
66 origExports.append(",");
67
68 if (System.getProperty("java.specification.version").equals("1.6")) {
69 constructJdkExports(origExports, JDK6_PACKAGES_PATH);
70 origExports.append(",");
71 }
72
73 Collection<ExportPackage> exportList = generateExports(packageScannerConfig);
74 constructAutoExports(origExports, exportList);
75
76
77 try
78 {
79 origExports.append(OsgiHeaderUtil.findReferredPackages(regs));
80
81 Analyzer analyzer = new Analyzer();
82 analyzer.setJar(new Jar("somename.jar"));
83
84
85
86 analyzer.setProperty(Constants.IMPORT_PACKAGE, origExports.toString());
87 Manifest mf = analyzer.calcManifest();
88
89 exports = mf.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
90 saveExportsToCache(cacheDir, packageScannerConfig.getCurrentHostVersion(), exports);
91 } catch (IOException ex)
92 {
93 log.error("Unable to calculate necessary exports based on host components", ex);
94 exports = origExports.toString();
95 }
96 }
97
98 if (log.isDebugEnabled()) {
99 log.debug("Exports:\n"+exports.replaceAll(",", "\r\n"));
100 }
101 return exports;
102 }
103
104 private void saveExportsToCache(File cacheDir, String currentHostVersion, String exports)
105 {
106
107 if (currentHostVersion == null)
108 return;
109
110 File cache = new File(cacheDir, EXPORTS_TXT);
111 FileWriter fout = null;
112 try
113 {
114 fout = new FileWriter(cache);
115 fout.write(currentHostVersion);
116 fout.write("\n");
117 fout.write(exports);
118 }
119 catch (FileNotFoundException e)
120 {
121 log.warn("Cache directory not available: "+cacheDir.getAbsolutePath(), e);
122 }
123 catch (IOException e)
124 {
125 log.warn("Unable to save exports cache", e);
126 }
127 finally
128 {
129 IOUtils.closeQuietly(fout);
130 }
131
132 }
133
134 private String loadExportsFromCache(File cacheDir, String currentHostVersion)
135 {
136
137 if (currentHostVersion == null)
138 return null;
139
140 File cache = new File(cacheDir, EXPORTS_TXT);
141 if (cache.exists())
142 {
143 FileReader reader = null;
144 try
145 {
146 reader = new FileReader(cache);
147 String contents = IOUtils.toString(reader);
148 int pos = contents.indexOf('\n');
149 if (pos == -1)
150 {
151 throw new IOException("Invalid cache file format");
152 }
153 String cacheVersion = contents.substring(0, pos);
154 if (currentHostVersion.equals(cacheVersion))
155 {
156 return contents.substring(pos + 1);
157 }
158 }
159 catch (FileNotFoundException e)
160 {
161
162 throw new RuntimeException(e);
163 }
164 catch (IOException e)
165 {
166 log.warn("Unable to write exports cache", e);
167 }
168 finally
169 {
170 IOUtils.closeQuietly(reader);
171 }
172 }
173 return null;
174 }
175
176 void constructAutoExports(StringBuilder sb, Collection<ExportPackage> packageExports) {
177 for (Iterator<ExportPackage> i = packageExports.iterator(); i.hasNext(); ) {
178 ExportPackage pkg = i.next();
179 sb.append(pkg.getPackageName());
180 if (pkg.getVersion() != null) {
181 try {
182 Version.parseVersion(pkg.getVersion());
183 sb.append(";version=").append(pkg.getVersion());
184 } catch (IllegalArgumentException ex) {
185 log.info("Unable to parse version: "+pkg.getVersion());
186 }
187 }
188 sb.append(",");
189 }
190 }
191
192 Collection<ExportPackage> generateExports(PackageScannerConfiguration packageScannerConfig)
193 {
194 String[] arrType = new String[0];
195 PackageScanner scanner = new PackageScanner()
196 .select(
197 jars(
198 include(packageScannerConfig.getJarIncludes().toArray(arrType)),
199 exclude(packageScannerConfig.getJarExcludes().toArray(arrType))),
200 packages(
201 include(packageScannerConfig.getPackageIncludes().toArray(arrType)),
202 exclude(packageScannerConfig.getPackageExcludes().toArray(arrType)))
203 )
204 .withMappings(packageScannerConfig.getPackageVersions());
205
206 Collection<ExportPackage> exports = scanner.scan();
207
208 if (!isPackageScanSuccessful(exports) && packageScannerConfig.getServletContext() != null)
209 {
210 log.warn("Unable to find expected packages via classloader scanning. Trying ServletContext scanning...");
211 ServletContext ctx = packageScannerConfig.getServletContext();
212 try
213 {
214 exports = scanner.scan(ctx.getResource("/WEB-INF/lib"), ctx.getResource("/WEB-INF/classes"));
215 }
216 catch (MalformedURLException e)
217 {
218 log.warn(e);
219 }
220 }
221
222 if (!isPackageScanSuccessful(exports))
223 {
224 throw new IllegalStateException("Unable to find required packages via classloader or servlet context"
225 + " scanning, most likely due to an application server bug.");
226 }
227 return exports;
228 }
229
230
231
232
233
234
235
236 private static boolean isPackageScanSuccessful(Collection<ExportPackage> exports)
237 {
238 boolean log4jFound = false;
239 for (ExportPackage export : exports)
240 {
241 if (export.getPackageName().equals("org.apache.log4j"))
242 {
243 log4jFound = true;
244 break;
245 }
246 }
247 return log4jFound;
248 }
249
250 void constructJdkExports(StringBuilder sb, String packageListPath)
251 {
252 InputStream in = null;
253 try
254 {
255 in = ClassLoaderUtils.getResourceAsStream(packageListPath, ExportsBuilder.class);
256 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
257 String line;
258 while ((line = reader.readLine()) != null)
259 {
260 line = line.trim();
261 if (line.length() > 0)
262 {
263 if (line.charAt(0) != '#')
264 {
265 if (sb.length() > 0)
266 sb.append(',');
267 sb.append(line);
268 }
269 }
270 }
271 } catch (IOException e)
272 {
273 IOUtils.closeQuietly(in);
274 }
275 }
276 }