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, 1);
57                  }
58              }
59              for (String pkg : referredPackages)
60              {
61                  sb.append(pkg).append(",");
62              }
63          }
64          return sb.toString();
65      }
66  
67      /**
68       * This will crawl the class interfaces to the desired level.
69       *
70       * @param className name of the class.
71       * @param scannedClasses set of classes that have been scanned.
72       * @param packageImports set of imports that have been found.
73       * @param level depth of scan (recursion).
74       * @throws IOException error loading a class.
75       */
76      static void crawlReferenceTree(String className, Set<String> scannedClasses, Set<String> packageImports, int level) throws IOException
77      {
78          if (level <= 0)
79          {
80              return;
81          }
82  
83          if (className.startsWith("java/"))
84              return;
85  
86          if (scannedClasses.contains(className))
87              return;
88          else
89              scannedClasses.add(className);
90  
91          if (log.isDebugEnabled())
92              log.debug("Crawling "+className);
93  
94          InputStream in = ClassLoaderUtils.getResourceAsStream(className, OsgiHeaderUtil.class);
95          if (in == null)
96          {
97              log.error("Cannot find interface "+className);
98              return;
99          }
100         Clazz clz = new Clazz(className, in);
101         packageImports.addAll(clz.getReferred().keySet());
102 
103         Set<String> referredClasses = clz.getReferredClasses();
104         for (String ref : referredClasses)
105             crawlReferenceTree(ref, scannedClasses, packageImports, level-1);
106 
107     }
108 
109     /**
110      * Determines framework exports taking into account host components and package scanner configuration.
111      *
112      * @param regs The list of host component registrations
113      * @param packageScannerConfig The configuration for the package scanning
114      * @return A list of exports, in a format compatible with OSGi headers
115      */
116     public static String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig){
117         String exports = null;
118 
119         StringBuilder origExports = new StringBuilder();
120         origExports.append("org.osgi.framework; version=1.3.0,");
121         origExports.append("org.osgi.service.packageadmin; version=1.2.0," );
122         origExports.append("org.osgi.service.startlevel; version=1.0.0,");
123         origExports.append("org.osgi.service.url; version=1.0.0,");
124         origExports.append("org.osgi.util; version=1.3.0,");
125         origExports.append("org.osgi.util.tracker; version=1.3.0,");
126         origExports.append("host.service.command; version=1.0.0,");
127         origExports.append("javax.swing.tree,javax.swing,org.xml.sax,org.xml.sax.helpers,");
128         origExports.append("javax.xml,javax.xml.parsers,javax.xml.transform,javax.xml.transform.sax,");
129         origExports.append("javax.xml.transform.stream,javax.xml.transform.dom,org.w3c.dom,javax.naming,javax.naming.spi,");
130         origExports.append("javax.swing.border,javax.swing.event,javax.swing.text,");
131 
132         Collection<ExportPackage> exportList = generateExports(packageScannerConfig);
133         constructAutoExports(origExports, exportList);
134 
135 
136         try
137         {
138             origExports.append(findReferredPackages(regs));
139 
140             Analyzer analyzer = new Analyzer();
141             analyzer.setJar(new Jar("somename.jar"));
142             
143             // we pretend the exports are imports for the sake of the bnd tool, which would otherwise cut out
144             // exports that weren't actually in the jar
145             analyzer.setProperty(Constants.IMPORT_PACKAGE, origExports.toString());
146             Manifest mf = analyzer.calcManifest();
147 
148             exports = mf.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
149         } catch (IOException ex)
150         {
151             log.error("Unable to calculate necessary exports based on host components", ex);
152             exports = origExports.toString();
153         }
154 
155         if (log.isDebugEnabled()) {
156             log.debug("Exports:\n"+exports.replaceAll(",", "\r\n"));
157         }
158         return exports;
159     }
160 
161     static void constructAutoExports(StringBuilder sb, Collection<ExportPackage> packageExports) {
162 
163         for (Iterator<ExportPackage> i = packageExports.iterator(); i.hasNext(); ) {
164             ExportPackage pkg = i.next();
165             sb.append(pkg.getPackageName());
166             if (pkg.getVersion() != null) {
167                 try {
168                     Version.parseVersion(pkg.getVersion());
169                     sb.append(";version=").append(pkg.getVersion());
170                 } catch (IllegalArgumentException ex) {
171                     log.info("Unable to parse version: "+pkg.getVersion());
172                 }
173             }
174             sb.append(",");
175         }
176     }
177 
178     static Collection<ExportPackage> generateExports(PackageScannerConfiguration packageScannerConfig)
179     {
180         String[] arrType = new String[0];
181         Collection<ExportPackage> exports = new PackageScanner()
182            .select(
183                jars(
184                        include(packageScannerConfig.getJarIncludes().toArray(arrType)),
185                        exclude(packageScannerConfig.getJarExcludes().toArray(arrType))),
186                packages(
187                        include(packageScannerConfig.getPackageIncludes().toArray(arrType)),
188                        exclude(packageScannerConfig.getPackageExcludes().toArray(arrType)))
189            )
190            .withMappings(packageScannerConfig.getPackageVersions())
191            .scan();
192         return exports;
193     }
194 }