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   import com.atlassian.plugin.util.ClassUtils;
7   
8   import java.util.*;
9   import java.util.jar.Manifest;
10  import java.io.IOException;
11  import java.io.InputStream;
12  import java.io.BufferedReader;
13  import java.io.InputStreamReader;
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.Version;
22  import org.osgi.framework.Constants;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.commons.io.IOUtils;
26  import aQute.lib.osgi.Analyzer;
27  import aQute.lib.osgi.Jar;
28  
29  /**
30   * Utilities to help create OSGi headers
31   */
32  public class OsgiHeaderUtil
33  {
34      static final String JDK_PACKAGES_PATH = "jdk-packages.txt";
35      static Log log = LogFactory.getLog(OsgiHeaderUtil.class);
36  
37      /**
38       * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
39       *
40       * @param registrations A list of host component registrations
41       * @return The referred packages in a format compatible with an OSGi header
42       * @throws IOException If there are any problems scanning bytecode
43       */
44      public static String findReferredPackages(List<HostComponentRegistration> registrations) throws IOException
45      {
46          StringBuffer sb = new StringBuffer();
47          Set<String> referredPackages = new HashSet<String>();
48          Set<String> referredClasses = new HashSet<String>();
49          if (registrations == null)
50          {
51              sb.append(",");
52          }
53          else
54          {
55              for (HostComponentRegistration reg : registrations)
56              {
57                  Set<Class> classesToScan = new HashSet<Class>();
58  
59                  // Make sure we scan all extended interfaces as well
60                  for (Class inf : reg.getMainInterfaceClasses())
61                      ClassUtils.findAllTypes(inf, classesToScan);
62  
63                  for (Class inf : classesToScan)
64                  {
65                      String clsName = inf.getName().replace('.','/')+".class";
66                      crawlReferenceTree(clsName, referredClasses, referredPackages, 1);
67                  }
68              }
69              for (String pkg : referredPackages)
70              {
71                  sb.append(pkg).append(",");
72              }
73          }
74          return sb.toString();
75      }
76  
77      /**
78       * This will crawl the class interfaces to the desired level.
79       *
80       * @param className name of the class.
81       * @param scannedClasses set of classes that have been scanned.
82       * @param packageImports set of imports that have been found.
83       * @param level depth of scan (recursion).
84       * @throws IOException error loading a class.
85       */
86      static void crawlReferenceTree(String className, Set<String> scannedClasses, Set<String> packageImports, int level) throws IOException
87      {
88          if (level <= 0)
89          {
90              return;
91          }
92  
93          if (className.startsWith("java/"))
94              return;
95  
96          if (scannedClasses.contains(className))
97              return;
98          else
99              scannedClasses.add(className);
100 
101         if (log.isDebugEnabled())
102             log.debug("Crawling "+className);
103 
104         InputStream in = ClassLoaderUtils.getResourceAsStream(className, OsgiHeaderUtil.class);
105         if (in == null)
106         {
107             log.error("Cannot find interface "+className);
108             return;
109         }
110         Clazz clz = new Clazz(className, in);
111         packageImports.addAll(clz.getReferred().keySet());
112 
113         Set<String> referredClasses = clz.getReferredClasses();
114         for (String ref : referredClasses)
115             crawlReferenceTree(ref, scannedClasses, packageImports, level-1);
116 
117     }
118 
119     /**
120      * Determines framework exports taking into account host components and package scanner configuration.
121      *
122      * @param regs The list of host component registrations
123      * @param packageScannerConfig The configuration for the package scanning
124      * @return A list of exports, in a format compatible with OSGi headers
125      */
126     public static String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig){
127         String exports = null;
128 
129         StringBuilder origExports = new StringBuilder();
130         origExports.append("org.osgi.framework; version=1.3.0,");
131         origExports.append("org.osgi.service.packageadmin; version=1.2.0," );
132         origExports.append("org.osgi.service.startlevel; version=1.0.0,");
133         origExports.append("org.osgi.service.url; version=1.0.0,");
134         origExports.append("org.osgi.util; version=1.3.0,");
135         origExports.append("org.osgi.util.tracker; version=1.3.0,");
136         origExports.append("host.service.command; version=1.0.0,");
137 
138         constructJdkExports(origExports, JDK_PACKAGES_PATH);
139         origExports.append(",");
140 
141         Collection<ExportPackage> exportList = generateExports(packageScannerConfig);
142         constructAutoExports(origExports, exportList);
143 
144 
145         try
146         {
147             origExports.append(findReferredPackages(regs));
148 
149             Analyzer analyzer = new Analyzer();
150             analyzer.setJar(new Jar("somename.jar"));
151             
152             // we pretend the exports are imports for the sake of the bnd tool, which would otherwise cut out
153             // exports that weren't actually in the jar
154             analyzer.setProperty(Constants.IMPORT_PACKAGE, origExports.toString());
155             Manifest mf = analyzer.calcManifest();
156 
157             exports = mf.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
158         } catch (IOException ex)
159         {
160             log.error("Unable to calculate necessary exports based on host components", ex);
161             exports = origExports.toString();
162         }
163 
164         if (log.isDebugEnabled()) {
165             log.debug("Exports:\n"+exports.replaceAll(",", "\r\n"));
166         }
167         return exports;
168     }
169 
170     static void constructAutoExports(StringBuilder sb, Collection<ExportPackage> packageExports) {
171         for (Iterator<ExportPackage> i = packageExports.iterator(); i.hasNext(); ) {
172             ExportPackage pkg = i.next();
173             sb.append(pkg.getPackageName());
174             if (pkg.getVersion() != null) {
175                 try {
176                     Version.parseVersion(pkg.getVersion());
177                     sb.append(";version=").append(pkg.getVersion());
178                 } catch (IllegalArgumentException ex) {
179                     log.info("Unable to parse version: "+pkg.getVersion());
180                 }
181             }
182             sb.append(",");
183         }
184     }
185 
186     static Collection<ExportPackage> generateExports(PackageScannerConfiguration packageScannerConfig)
187     {
188         String[] arrType = new String[0];
189         Collection<ExportPackage> exports = new PackageScanner()
190            .select(
191                jars(
192                        include(packageScannerConfig.getJarIncludes().toArray(arrType)),
193                        exclude(packageScannerConfig.getJarExcludes().toArray(arrType))),
194                packages(
195                        include(packageScannerConfig.getPackageIncludes().toArray(arrType)),
196                        exclude(packageScannerConfig.getPackageExcludes().toArray(arrType)))
197            )
198            .withMappings(packageScannerConfig.getPackageVersions())
199            .scan();
200         return exports;
201     }
202 
203     static void constructJdkExports(StringBuilder sb, String packageListPath)
204     {
205         InputStream in = null;
206         try
207         {
208             in = ClassLoaderUtils.getResourceAsStream(packageListPath, OsgiHeaderUtil.class);
209             BufferedReader reader = new BufferedReader(new InputStreamReader(in));
210             String line;
211             while ((line = reader.readLine()) != null)
212             {
213                 line = line.trim();
214                 if (line.length() > 0)
215                 {
216                     if (line.charAt(0) != '#')
217                     {
218                         if (sb.length() > 0)
219                             sb.append(',');
220                         sb.append(line);
221                     }
222                 }
223             }
224         } catch (IOException e)
225         {
226             IOUtils.closeQuietly(in);
227         }
228     }
229 
230 }