View Javadoc

1   package com.atlassian.plugin.osgi.util;
2   
3   import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
4   import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
5   import com.atlassian.plugin.util.ClassLoaderUtils;
6   
7   import java.util.*;
8   import java.util.jar.Manifest;
9   import java.io.IOException;
10  import java.io.InputStream;
11  
12  import org.twdata.pkgscanner.ExportPackage;
13  import org.twdata.pkgscanner.PackageScanner;
14  import static org.twdata.pkgscanner.PackageScanner.jars;
15  import static org.twdata.pkgscanner.PackageScanner.include;
16  import static org.twdata.pkgscanner.PackageScanner.exclude;
17  import static org.twdata.pkgscanner.PackageScanner.packages;
18  import org.osgi.framework.Version;
19  import org.osgi.framework.Constants;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import aQute.lib.osgi.Clazz;
23  import aQute.lib.osgi.Analyzer;
24  import aQute.lib.osgi.Jar;
25  
26  /**
27   * Utilities to help create OSGi headers
28   */
29  public class OsgiHeaderUtil
30  {
31      static Log log = LogFactory.getLog(OsgiHeaderUtil.class);
32  
33      /**
34       * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
35       *
36       * @param registrations A list of host component registrations
37       * @return The referred packages in a format compatible with an OSGi header
38       * @throws IOException If there are any problems scanning bytecode
39       */
40      public static String findReferredPackages(List<HostComponentRegistration> registrations) throws IOException
41      {
42          StringBuffer sb = new StringBuffer();
43          Set<String> referredPackages = new HashSet<String>();
44          Set<String> referredClasses = new HashSet<String>();
45          if (registrations == null)
46          {
47              sb.append(",");
48          }
49          else
50          {
51              for (HostComponentRegistration reg : registrations)
52              {
53                  for (String inf : reg.getMainInterfaces())
54                  {
55                      String clsName = inf.replace('.','/')+".class";
56                      crawlReferenceTree(clsName, referredClasses, referredPackages);
57                  }
58              }
59              for (String pkg : referredPackages)
60              {
61                  sb.append(pkg).append(",");
62              }
63          }
64          return sb.toString();
65      }
66  
67      static void crawlReferenceTree(String className, Set<String> scannedClasses, Set<String> packageImports) throws IOException
68      {
69          if (className.startsWith("java/"))
70              return;
71  
72          if (scannedClasses.contains(className))
73              return;
74          else
75              scannedClasses.add(className);
76  
77          if (log.isDebugEnabled())
78              log.debug("Crawling "+className);
79  
80          InputStream in = ClassLoaderUtils.getResourceAsStream(className, OsgiHeaderUtil.class);
81          if (in == null)
82          {
83              log.error("Cannot find interface "+className);
84              return;
85          }
86          Clazz clz = new Clazz(className, in);
87          packageImports.addAll(clz.getReferred().keySet());
88  
89          Set<String> referredClasses = clz.getReferredClasses();
90          for (String ref : referredClasses)
91              crawlReferenceTree(ref, scannedClasses, packageImports);
92  
93      }
94  
95      /**
96       * Determines framework exports taking into account host components and package scanner configuration.
97       *
98       * @param regs The list of host component registrations
99       * @param packageScannerConfig The configuration for the package scanning
100      * @return A list of exports, in a format compatible with OSGi headers
101      */
102     public static String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig){
103         String exports = null;
104 
105         StringBuilder origExports = new StringBuilder();
106         origExports.append("org.osgi.framework; version=1.3.0,");
107         origExports.append("org.osgi.service.packageadmin; version=1.2.0," );
108         origExports.append("org.osgi.service.startlevel; version=1.0.0,");
109         origExports.append("org.osgi.service.url; version=1.0.0,");
110         origExports.append("org.osgi.util; version=1.3.0,");
111         origExports.append("org.osgi.util.tracker; version=1.3.0,");
112         origExports.append("host.service.command; version=1.0.0,");
113         origExports.append("javax.swing.tree,javax.swing,org.xml.sax,org.xml.sax.helpers,");
114         origExports.append("javax.xml,javax.xml.parsers,javax.xml.transform,javax.xml.transform.sax,");
115         origExports.append("javax.xml.transform.stream,javax.xml.transform.dom,org.w3c.dom,javax.naming,javax.naming.spi,");
116         origExports.append("javax.swing.border,javax.swing.event,javax.swing.text,");
117 
118         Collection<ExportPackage> exportList = generateExports(packageScannerConfig);
119         constructAutoExports(origExports, exportList);
120 
121 
122         try
123         {
124             origExports.append(findReferredPackages(regs));
125 
126             Analyzer analyzer = new Analyzer();
127             analyzer.setJar(new Jar("somename.jar"));
128             
129             // we pretend the exports are imports for the sake of the bnd tool, which would otherwise cut out
130             // exports that weren't actually in the jar
131             analyzer.setProperty(Constants.IMPORT_PACKAGE, origExports.toString());
132             Manifest mf = analyzer.calcManifest();
133 
134             exports = mf.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
135         } catch (IOException ex)
136         {
137             log.error("Unable to calculate necessary exports based on host components", ex);
138             exports = origExports.toString();
139         }
140 
141         if (log.isDebugEnabled()) {
142             log.debug("Exports:\n"+exports.replaceAll(",", "\r\n"));
143         }
144         return exports;
145     }
146 
147     static void constructAutoExports(StringBuilder sb, Collection<ExportPackage> packageExports) {
148 
149         for (Iterator<ExportPackage> i = packageExports.iterator(); i.hasNext(); ) {
150             ExportPackage pkg = i.next();
151             sb.append(pkg.getPackageName());
152             if (pkg.getVersion() != null) {
153                 try {
154                     Version.parseVersion(pkg.getVersion());
155                     sb.append(";version=").append(pkg.getVersion());
156                 } catch (IllegalArgumentException ex) {
157                     log.info("Unable to parse version: "+pkg.getVersion());
158                 }
159             }
160             sb.append(",");
161         }
162     }
163 
164     static Collection<ExportPackage> generateExports(PackageScannerConfiguration packageScannerConfig)
165     {
166         String[] arrType = new String[0];
167         Collection<ExportPackage> exports = new PackageScanner()
168            .select(
169                jars(
170                        include(packageScannerConfig.getJarIncludes().toArray(arrType)),
171                        exclude(packageScannerConfig.getJarExcludes().toArray(arrType))),
172                packages(
173                        include(packageScannerConfig.getPackageIncludes().toArray(arrType)),
174                        exclude(packageScannerConfig.getPackageExcludes().toArray(arrType)))
175            )
176            .withMappings(packageScannerConfig.getPackageVersions())
177            .scan();
178         return exports;
179     }
180 }