View Javadoc

1   package com.atlassian.plugin.osgi.container.felix;
2   
3   import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
4   import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
5   import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
6   import com.atlassian.plugin.util.PluginFrameworkUtils;
7   import com.google.common.base.Predicate;
8   import com.google.common.collect.Sets;
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  import org.twdata.pkgscanner.DefaultOsgiVersionConverter;
12  import org.twdata.pkgscanner.ExportPackage;
13  import org.twdata.pkgscanner.PackageScanner;
14  
15  import static com.atlassian.plugin.osgi.container.felix.ExportBuilderUtils.parseExportFile;
16  import static com.atlassian.plugin.osgi.container.felix.ExportBuilderUtils.copyUnlessExist;
17  import static org.twdata.pkgscanner.PackageScanner.exclude;
18  import static org.twdata.pkgscanner.PackageScanner.include;
19  import static org.twdata.pkgscanner.PackageScanner.jars;
20  import static org.twdata.pkgscanner.PackageScanner.packages;
21  
22  import javax.servlet.ServletContext;
23  import java.io.File;
24  import java.io.IOException;
25  import java.net.MalformedURLException;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
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      static final String JDK_6 = "1.6";
37      static final String JDK_7 = "1.7";
38  
39      static final String OSGI_PACKAGES_PATH = "osgi-packages.txt";
40      static final String JDK_PACKAGES_PATH = "jdk-packages.txt";
41  
42      private static Logger log = LoggerFactory.getLogger(ExportsBuilder.class);
43      private static String exportStringCache;
44  
45      private static final Predicate<String> UNDER_PLUGIN_FRAMEWORK = new Predicate<String>()
46      {
47          public boolean apply(String pkg)
48          {
49              return pkg.startsWith("com.atlassian.plugin.");
50          }
51      };
52  
53      /**
54       * Gets the framework exports taking into account host components and package scanner configuration.
55       * <p>
56       * Often, this information will not change without a system restart, so we determine this once and then cache the value.
57       * The cache is only useful if the plugin system is thrown away and re-initialised. This is done thousands of times
58       * during JIRA functional testing, and the cache was added to speed this up.
59       *
60       * If needed, call {@link #clearExportCache()} to clear the cache.
61       *
62       * @param regs The list of host component registrations
63       * @param packageScannerConfig The configuration for the package scanning
64       * @return A list of exports, in a format compatible with OSGi headers
65       */
66      public String getExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig)
67      {
68          if (exportStringCache == null)
69          {
70              exportStringCache = determineExports(regs, packageScannerConfig);
71          }
72          return exportStringCache;
73      }
74  
75      /**
76       * Clears the export string cache. This results in {@link #getExports(java.util.List, com.atlassian.plugin.osgi.container.PackageScannerConfiguration)}
77       * having to recalculate the export string next time which can significantly slow down the start up time of plugin framework.
78       * @since 2.9.0
79       */
80      public void clearExportCache()
81      {
82          exportStringCache = null;
83      }
84  
85      /**
86       * Determines framework exports taking into account host components and package scanner configuration.
87       *
88       * @param regs The list of host component registrations
89       * @param packageScannerConfig The configuration for the package scanning
90       * @param cacheDir No longer used. (method deprecated).
91       * @return A list of exports, in a format compatible with OSGi headers
92       * @deprecated Please use {@link #getExports}. Deprecated since 2.3.6
93       */
94      @SuppressWarnings ({ "UnusedDeclaration" })
95      public String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig, File cacheDir)
96      {
97          return determineExports(regs, packageScannerConfig);
98      }
99  
100     /**
101      * Determines framework exports taking into account host components and package scanner configuration.
102      *
103      * @param regs The list of host component registrations
104      * @param packageScannerConfig The configuration for the package scanning
105      * @return A list of exports, in a format compatible with OSGi headers
106      */
107     String determineExports(List<HostComponentRegistration> regs, PackageScannerConfiguration packageScannerConfig)
108     {
109         Map<String, String> exportPackages = new HashMap<String, String>();
110 
111         // The first part is osgi related packages.
112         copyUnlessExist(exportPackages, parseExportFile(OSGI_PACKAGES_PATH));
113 
114         // The second part is JDK packages.
115         copyUnlessExist(exportPackages, parseExportFile(JDK_PACKAGES_PATH));
116 
117         // Third part by scanning packages available via classloader. The versions are determined by jar names.
118         Collection<ExportPackage> scannedPackages = generateExports(packageScannerConfig);
119         copyUnlessExist(exportPackages, ExportBuilderUtils.toMap(scannedPackages));
120 
121         // Fourth part by scanning host components since all the classes referred to by them must be available to consumers.
122         try
123         {
124             Map<String,String> referredPackages = OsgiHeaderUtil.findReferredPackageVersions(regs, packageScannerConfig.getPackageVersions());
125             copyUnlessExist(exportPackages, referredPackages);
126         }
127         catch (IOException ex)
128         {
129             log.error("Unable to calculate necessary exports based on host components", ex);
130         }
131 
132         // All the packages under plugin framework namespace must be exported as the plugin framework's version.
133         enforceFrameworkVersion(exportPackages);
134 
135         // Generate the actual export string in OSGi spec.
136         final String exports = OsgiHeaderUtil.generatePackageVersionString(exportPackages);
137 
138         if (log.isDebugEnabled())
139         {
140             log.debug("Exports:\n"+exports.replaceAll(",", "\r\n"));
141         }
142 
143         return exports;
144     }
145 
146     private void enforceFrameworkVersion(Map<String, String> exportPackages)
147     {
148         final String frameworkVersion = PluginFrameworkUtils.getPluginFrameworkVersion();
149 
150         // convert the version to OSGi format.
151         DefaultOsgiVersionConverter converter = new DefaultOsgiVersionConverter();
152         final String frameworkVersionOsgi = converter.getVersion(frameworkVersion);
153 
154         for(String pkg: Sets.filter(exportPackages.keySet(), UNDER_PLUGIN_FRAMEWORK))
155         {
156             exportPackages.put(pkg, frameworkVersionOsgi);
157         }
158     }
159 
160     Collection<ExportPackage> generateExports(PackageScannerConfiguration packageScannerConfig)
161     {
162         String[] arrType = new String[0];
163 
164         Map<String,String> pkgVersions = new HashMap<String,String>(packageScannerConfig.getPackageVersions());
165         if (packageScannerConfig.getServletContext() != null)
166         {
167             String ver = packageScannerConfig.getServletContext().getMajorVersion() + "." + packageScannerConfig.getServletContext().getMinorVersion();
168             pkgVersions.put("javax.servlet*", ver);
169         }
170 
171         PackageScanner scanner = new PackageScanner()
172            .select(
173                jars(
174                        include(packageScannerConfig.getJarIncludes().toArray(arrType)),
175                        exclude(packageScannerConfig.getJarExcludes().toArray(arrType))),
176                packages(
177                        include(packageScannerConfig.getPackageIncludes().toArray(arrType)),
178                        exclude(packageScannerConfig.getPackageExcludes().toArray(arrType)))
179            )
180            .withMappings(pkgVersions);
181 
182         if (log.isDebugEnabled())
183         {
184             scanner.enableDebug();
185         }
186 
187         Collection<ExportPackage> exports = scanner.scan();
188         log.info("Package scan completed. Found " + exports.size() + " packages to export.");
189 
190         if (!isPackageScanSuccessful(exports) && packageScannerConfig.getServletContext() != null)
191         {
192             log.warn("Unable to find expected packages via classloader scanning.  Trying ServletContext scanning...");
193             ServletContext ctx = packageScannerConfig.getServletContext();
194             try
195             {
196                 exports = scanner.scan(ctx.getResource("/WEB-INF/lib"), ctx.getResource("/WEB-INF/classes"));
197             }
198             catch (MalformedURLException e)
199             {
200                 log.warn("Unable to scan webapp for packages", e);
201             }
202         }
203 
204         if (!isPackageScanSuccessful(exports))
205         {
206             throw new IllegalStateException("Unable to find required packages via classloader or servlet context"
207                     + " scanning, most likely due to an application server bug.");
208         }
209         return exports;
210     }
211 
212     /**
213      * Tests to see if a scan of packages to export was successful, using the presence of slf4j as the criteria.
214      *
215      * @param exports The exports found so far
216      * @return True if slf4j is present, false otherwise
217      */
218     private static boolean isPackageScanSuccessful(Collection<ExportPackage> exports)
219     {
220         boolean slf4jFound = false;
221         for (ExportPackage export : exports)
222         {
223             if (export.getPackageName().equals("org.slf4j"))
224             {
225                 slf4jFound = true;
226                 break;
227             }
228         }
229         return slf4jFound;
230     }
231 }