View Javadoc

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   * Builds the OSGi package exports string.  Uses a file to cache the scanned results, keyed by the application version.
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       * Determines framework exports taking into account host components and package scanner configuration.
44       *
45       * @param regs The list of host component registrations
46       * @param packageScannerConfig The configuration for the package scanning
47       * @return A list of exports, in a format compatible with OSGi headers
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                  // we pretend the exports are imports for the sake of the bnd tool, which would otherwise cut out
85                  // exports that weren't actually in the jar
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         // Don't bother saving exports if a version isn't supplied
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         // Don't bother loading exports if a version isn't supplied
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                 // Should never happen
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      * Tests to see if a scan of packages to export was successful, using the presence of log4j as the criteria.
232      *
233      * @param exports The exports found so far
234      * @return True if log4j is present, false otherwise
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 }