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.osgi.factory.OsgiPlugin;
6   import com.atlassian.plugin.util.ClassLoaderUtils;
7   import com.atlassian.plugin.util.ClassUtils;
8   
9   import java.util.*;
10  import java.util.jar.Manifest;
11  import java.io.*;
12  import java.net.MalformedURLException;
13  
14  import org.twdata.pkgscanner.ExportPackage;
15  import org.twdata.pkgscanner.PackageScanner;
16  import static org.twdata.pkgscanner.PackageScanner.jars;
17  import static org.twdata.pkgscanner.PackageScanner.include;
18  import static org.twdata.pkgscanner.PackageScanner.exclude;
19  import static org.twdata.pkgscanner.PackageScanner.packages;
20  import org.osgi.framework.Version;
21  import org.osgi.framework.Constants;
22  import org.osgi.framework.Bundle;
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  import aQute.lib.header.OSGiHeader;
29  
30  import javax.servlet.ServletContext;
31  
32  /**
33   * Utilities to help create OSGi headers
34   */
35  public class OsgiHeaderUtil
36  {
37      static final String JDK_PACKAGES_PATH = "jdk-packages.txt";
38      static final String JDK6_PACKAGES_PATH = "jdk6-packages.txt";
39      static Log log = LogFactory.getLog(OsgiHeaderUtil.class);
40  
41      /**
42       * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
43       *
44       * @param registrations A list of host component registrations
45       * @return The referred packages in a format compatible with an OSGi header
46       * @throws IOException If there are any problems scanning bytecode
47       * @since 2.4.0
48       */
49      public static String findReferredPackages(List<HostComponentRegistration> registrations) throws IOException
50      {
51          return findReferredPackages(registrations, Collections.<String, String>emptyMap());
52      }
53  
54      /**
55       * Finds all referred packages for host component registrations by scanning their declared interfaces' bytecode.
56       *
57       * @param registrations A list of host component registrations
58       * @return The referred packages in a format compatible with an OSGi header
59       * @throws IOException If there are any problems scanning bytecode
60       */
61      public static String findReferredPackages(List<HostComponentRegistration> registrations, Map<String, String> packageVersions) throws IOException
62      {
63          StringBuffer sb = new StringBuffer();
64          Set<String> referredPackages = new HashSet<String>();
65          Set<String> referredClasses = new HashSet<String>();
66          if (registrations == null)
67          {
68              sb.append(",");
69          }
70          else
71          {
72              for (HostComponentRegistration reg : registrations)
73              {
74                  Set<Class> classesToScan = new HashSet<Class>();
75  
76                  // Make sure we scan all extended interfaces as well
77                  for (Class inf : reg.getMainInterfaceClasses())
78                      ClassUtils.findAllTypes(inf, classesToScan);
79  
80                  for (Class inf : classesToScan)
81                  {
82                      String clsName = inf.getName().replace('.','/')+".class";
83                      crawlReferenceTree(clsName, referredClasses, referredPackages, 1);
84                  }
85              }
86              for (String pkg : referredPackages)
87              {
88                  String version = packageVersions.get(pkg);
89                  sb.append(pkg);
90                  if (version != null) {
91                      try {
92                          Version.parseVersion(version);
93                          sb.append(";version=").append(version);
94                      } catch (IllegalArgumentException ex) {
95                          log.info("Unable to parse version: "+version);
96                      }
97                  }
98                  sb.append(",");
99              }
100         }
101         return sb.toString();
102     }
103 
104     /**
105      * This will crawl the class interfaces to the desired level.
106      *
107      * @param className name of the class.
108      * @param scannedClasses set of classes that have been scanned.
109      * @param packageImports set of imports that have been found.
110      * @param level depth of scan (recursion).
111      * @throws IOException error loading a class.
112      */
113     static void crawlReferenceTree(String className, Set<String> scannedClasses, Set<String> packageImports, int level) throws IOException
114     {
115         if (level <= 0)
116         {
117             return;
118         }
119 
120         if (className.startsWith("java/"))
121             return;
122 
123         if (scannedClasses.contains(className))
124             return;
125         else
126             scannedClasses.add(className);
127 
128         if (log.isDebugEnabled())
129             log.debug("Crawling "+className);
130 
131         InputStream in = null;
132         try
133         {
134             in = ClassLoaderUtils.getResourceAsStream(className, OsgiHeaderUtil.class);
135 
136             if (in == null)
137             {
138                 log.error("Cannot find interface "+className);
139                 return;
140             }
141             Clazz clz = new Clazz(className, in);
142             packageImports.addAll(clz.getReferred().keySet());
143 
144             Set<String> referredClasses = clz.getReferredClasses();
145             for (String ref : referredClasses)
146                 crawlReferenceTree(ref, scannedClasses, packageImports, level-1);
147         }
148         finally
149         {
150             IOUtils.closeQuietly(in);
151         }
152 
153     }
154 
155     /**
156      * Parses an OSGi header line into a map structure
157      *
158      * @param header The header line
159      * @return A map with the key the entry value and the value a map of attributes
160      * @since 2.2.0
161      */
162     public static Map<String,Map<String,String>> parseHeader(String header)
163     {
164         return OSGiHeader.parseHeader(header);
165     }
166 
167     /**
168      * Builds the header string from a map
169      * @param key The header value
170      * @param attrs The map of attributes
171      * @return A string, suitable for inclusion into an OSGI header string
172      * @since 2.2.0
173      */
174     public static String buildHeader(String key, Map<String,String> attrs)
175     {
176         StringBuilder fullPkg = new StringBuilder(key);
177         if (attrs != null && !attrs.isEmpty())
178         {
179             for (Map.Entry<String,String> entry : attrs.entrySet())
180             {
181                 fullPkg.append(";");
182                 fullPkg.append(entry.getKey());
183                 fullPkg.append("=\"");
184                 fullPkg.append(entry.getValue());
185                 fullPkg.append("\"");
186             }
187         }
188         return fullPkg.toString();
189     }
190 
191     /**
192      * Gets the plugin key from the bundle
193      *
194      * WARNING: shamelessly copied at {@link com.atlassian.plugin.osgi.bridge.PluginBundleUtils}, which can't use
195      * this class due to creating a cyclic build dependency.  Ensure these two implementations are in sync.
196      *
197      * @param bundle The plugin bundle
198      * @return The plugin key, cannot be null
199      * @since 2.2.0
200      */
201     public static String getPluginKey(Bundle bundle)
202     {
203         return getPluginKey(
204                 bundle.getSymbolicName(),
205                 bundle.getHeaders().get(OsgiPlugin.ATLASSIAN_PLUGIN_KEY),
206                 bundle.getHeaders().get(Constants.BUNDLE_VERSION)
207         );
208     }
209 
210     /**
211      * Gets the plugin key from the jar manifest
212      *
213      * @param mf The plugin jar manifest
214      * @return The plugin key, cannot be null
215      * @since 2.2.0
216      */
217     public static String getPluginKey(Manifest mf)
218     {
219         return getPluginKey(
220                 mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME),
221                 mf.getMainAttributes().getValue(OsgiPlugin.ATLASSIAN_PLUGIN_KEY),
222                 mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION)
223         );
224     }
225 
226     private static String getPluginKey(Object bundleName, Object atlKey, Object version)
227     {
228         Object key = atlKey;
229         if (key == null)
230         {
231             key = bundleName + "-" + version;
232         }
233         return key.toString();
234 
235     }
236 }