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 = null;
52  
53          StringBuilder origExports = new StringBuilder();
54          origExports.append("org.osgi.framework; version=1.4.1,");
55          origExports.append("org.osgi.service.packageadmin; version=1.2.0," );
56          origExports.append("org.osgi.service.startlevel; version=1.1.0,");
57          origExports.append("org.osgi.service.url; version=1.0.0,");
58          origExports.append("org.osgi.util; version=1.4.1,");
59          origExports.append("org.osgi.util.tracker; version=1.4.1,");
60          origExports.append("host.service.command; version=1.0.0,");
61  
62          constructJdkExports(origExports, JDK_PACKAGES_PATH);
63          origExports.append(",");
64  
65          if (System.getProperty("java.specification.version").equals("1.6")) {
66              constructJdkExports(origExports, JDK6_PACKAGES_PATH);
67              origExports.append(",");
68          }
69  
70          Collection<ExportPackage> exportList = generateExports(packageScannerConfig);
71          constructAutoExports(origExports, exportList);
72  
73  
74          try
75          {
76              origExports.append(OsgiHeaderUtil.findReferredPackages(regs));
77  
78              Analyzer analyzer = new Analyzer();
79              analyzer.setJar(new Jar("somename.jar"));
80  
81              // we pretend the exports are imports for the sake of the bnd tool, which would otherwise cut out
82              // exports that weren't actually in the jar
83              analyzer.setProperty(Constants.IMPORT_PACKAGE, origExports.toString());
84              Manifest mf = analyzer.calcManifest();
85  
86              exports = mf.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
87          } catch (IOException ex)
88          {
89              log.error("Unable to calculate necessary exports based on host components", ex);
90              exports = origExports.toString();
91          }
92  
93          if (log.isDebugEnabled()) {
94              log.debug("Exports:\n"+exports.replaceAll(",", "\r\n"));
95          }
96          return exports;
97      }
98  
99      void constructAutoExports(StringBuilder sb, Collection<ExportPackage> packageExports) {
100         for (Iterator<ExportPackage> i = packageExports.iterator(); i.hasNext(); ) {
101             ExportPackage pkg = i.next();
102             sb.append(pkg.getPackageName());
103             if (pkg.getVersion() != null) {
104                 try {
105                     Version.parseVersion(pkg.getVersion());
106                     sb.append(";version=").append(pkg.getVersion());
107                 } catch (IllegalArgumentException ex) {
108                     log.info("Unable to parse version: "+pkg.getVersion());
109                 }
110             }
111             sb.append(",");
112         }
113     }
114 
115     Collection<ExportPackage> generateExports(PackageScannerConfiguration packageScannerConfig)
116     {
117         String[] arrType = new String[0];
118         PackageScanner scanner = new PackageScanner()
119            .select(
120                jars(
121                        include(packageScannerConfig.getJarIncludes().toArray(arrType)),
122                        exclude(packageScannerConfig.getJarExcludes().toArray(arrType))),
123                packages(
124                        include(packageScannerConfig.getPackageIncludes().toArray(arrType)),
125                        exclude(packageScannerConfig.getPackageExcludes().toArray(arrType)))
126            )
127            .withMappings(packageScannerConfig.getPackageVersions());
128 
129         if (log.isDebugEnabled())
130         {
131             scanner.enableDebug();
132         }
133 
134         Collection<ExportPackage> exports = scanner.scan();
135 
136         if (!isPackageScanSuccessful(exports) && packageScannerConfig.getServletContext() != null)
137         {
138             log.warn("Unable to find expected packages via classloader scanning.  Trying ServletContext scanning...");
139             ServletContext ctx = packageScannerConfig.getServletContext();
140             try
141             {
142                 exports = scanner.scan(ctx.getResource("/WEB-INF/lib"), ctx.getResource("/WEB-INF/classes"));
143             }
144             catch (MalformedURLException e)
145             {
146                 log.warn(e);
147             }
148         }
149 
150         if (!isPackageScanSuccessful(exports))
151         {
152             throw new IllegalStateException("Unable to find required packages via classloader or servlet context"
153                     + " scanning, most likely due to an application server bug.");
154         }
155         return exports;
156     }
157 
158     /**
159      * Tests to see if a scan of packages to export was successful, using the presence of log4j as the criteria.
160      *
161      * @param exports The exports found so far
162      * @return True if log4j is present, false otherwise
163      */
164     private static boolean isPackageScanSuccessful(Collection<ExportPackage> exports)
165     {
166         boolean log4jFound = false;
167         for (ExportPackage export : exports)
168         {
169             if (export.getPackageName().equals("org.apache.log4j"))
170             {
171                 log4jFound = true;
172                 break;
173             }
174         }
175         return log4jFound;
176     }
177 
178     void constructJdkExports(StringBuilder sb, String packageListPath)
179     {
180         InputStream in = null;
181         try
182         {
183             in = ClassLoaderUtils.getResourceAsStream(packageListPath, ExportsBuilder.class);
184             BufferedReader reader = new BufferedReader(new InputStreamReader(in));
185             String line;
186             while ((line = reader.readLine()) != null)
187             {
188                 line = line.trim();
189                 if (line.length() > 0)
190                 {
191                     if (line.charAt(0) != '#')
192                     {
193                         if (sb.length() > 0)
194                             sb.append(',');
195                         sb.append(line);
196                     }
197                 }
198             }
199         } catch (IOException e)
200         {
201             IOUtils.closeQuietly(in);
202         }
203     }
204 }