001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.xbean.osgi.bundle.util; 021 022import java.io.File; 023import java.io.IOException; 024import java.net.MalformedURLException; 025import java.net.URL; 026import java.util.Collections; 027import java.util.Dictionary; 028import java.util.Enumeration; 029import java.util.LinkedHashSet; 030import java.util.List; 031 032import org.osgi.framework.Bundle; 033import org.osgi.framework.BundleReference; 034import org.osgi.framework.Constants; 035import org.osgi.framework.ServiceReference; 036import org.osgi.framework.wiring.BundleRevision; 037import org.osgi.framework.wiring.BundleWire; 038import org.osgi.framework.wiring.BundleWiring; 039import org.osgi.service.packageadmin.ExportedPackage; 040import org.osgi.service.packageadmin.PackageAdmin; 041 042/** 043 * @version $Rev: 1347954 $ $Date: 2012-06-08 11:08:40 +0200 (Fri, 08 Jun 2012) $ 044 */ 045public class BundleUtils { 046 047 private static final boolean isOSGi43 = isOSGi43(); 048 049 private static boolean isOSGi43() { 050 try { 051 Class.forName("org.osgi.framework.wiring.BundleWiring"); 052 return true; 053 } catch (Throwable e) { 054 return false; 055 } 056 } 057 058 public final static String REFERENCE_SCHEME = "reference:"; 059 060 public final static String FILE_SCHEMA = "file:"; 061 062 public final static String REFERENCE_FILE_SCHEMA = "reference:file:"; 063 064 /** 065 * Based on the constant field values, if it is bigger than the RESOLVED status value, the bundle has been resolved by the framework 066 * @param bundle 067 * @return true if the bundle is resolved, or false if not. 068 */ 069 public static boolean isResolved(Bundle bundle) { 070 return bundle.getState() >= Bundle.RESOLVED; 071 } 072 073 /** 074 * resolve method will try to load the Object.class, the behavior triggers a resolved request to the OSGI framework. 075 * @param bundle 076 */ 077 public static void resolve(Bundle bundle) { 078 if (isFragment(bundle)) { 079 return; 080 } 081 try { 082 bundle.loadClass(Object.class.getName()); 083 } catch (Exception e) { 084 } 085 } 086 087 /** 088 * If the bundle fulfills the conditions below, it could be started 089 * a. Not in the UNINSTALLED status. 090 * b. Not in the STARTING status. 091 * c. Not a fragment bundle. 092 * @param bundle 093 * @return 094 */ 095 public static boolean canStart(Bundle bundle) { 096 return (bundle.getState() != Bundle.UNINSTALLED) && (bundle.getState() != Bundle.STARTING) && (!isFragment(bundle)); 097 } 098 099 /** 100 * If the bundle fulfills the conditions below, it could be stopped 101 * a. Not in the UNINSTALLED status. 102 * b. Not in the STOPPING status. 103 * c. Not a fragment bundle. 104 * @param bundle 105 * @return 106 */ 107 public static boolean canStop(Bundle bundle) { 108 return (bundle.getState() != Bundle.UNINSTALLED) && (bundle.getState() != Bundle.STOPPING) && (!isFragment(bundle)); 109 } 110 111 /** 112 * If the bundle fulfills the conditions below, it could be un-installed 113 * a. Not in the UNINSTALLED status. 114 * @param bundle 115 * @return 116 */ 117 public static boolean canUninstall(Bundle bundle) { 118 return bundle.getState() != Bundle.UNINSTALLED; 119 } 120 121 public static boolean isFragment(Bundle bundle) { 122 Dictionary headers = bundle.getHeaders(); 123 return (headers != null && headers.get(Constants.FRAGMENT_HOST) != null); 124 } 125 126 /** 127 * Returns bundle (if any) associated with current thread's context classloader. 128 * Invoking this method is equivalent to getBundle(Thread.currentThread().getContextClassLoader(), unwrap) 129 */ 130 public static Bundle getContextBundle(boolean unwrap) { 131 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 132 return classLoader == null ? null : getBundle(classLoader, unwrap); 133 } 134 135 /** 136 * Returns bundle (if any) associated with the classloader. 137 * @param classLoader 138 * @param unwrap if true and if the bundle associated with the context classloader is a 139 * {@link DelegatingBundle}, this function will return the main application bundle 140 * backing the {@link DelegatingBundle}. Otherwise, the bundle associated with 141 * the context classloader is returned as is. See {@link BundleClassLoader#getBundle(boolean)} 142 * for more information. 143 * @return The bundle associated with the classloader. Might be null. 144 */ 145 public static Bundle getBundle(ClassLoader classLoader, boolean unwrap) { 146 if (classLoader instanceof DelegatingBundleReference) { 147 return ((DelegatingBundleReference) classLoader).getBundle(unwrap); 148 } else if (classLoader instanceof BundleReference) { 149 return ((BundleReference) classLoader).getBundle(); 150 } else { 151 return null; 152 } 153 } 154 155 /** 156 * If the given bundle is a {@link DelegatingBundle} this function will return the main 157 * application bundle backing the {@link DelegatingBundle}. Otherwise, the bundle 158 * passed in is returned as is. 159 */ 160 public static Bundle unwrapBundle(Bundle bundle) { 161 if (bundle instanceof DelegatingBundle) { 162 return ((DelegatingBundle) bundle).getMainBundle(); 163 } 164 return bundle; 165 } 166 167 /** 168 * Works like {@link Bundle#getEntryPaths(String)} but also returns paths 169 * in attached fragment bundles. 170 * 171 * @param bundle 172 * @param name 173 * @return 174 */ 175 public static Enumeration<String> getEntryPaths(Bundle bundle, String name) { 176 Enumeration<URL> entries = bundle.findEntries(name, null, false); 177 if (entries == null) { 178 return null; 179 } 180 LinkedHashSet<String> paths = new LinkedHashSet<String>(); 181 while (entries.hasMoreElements()) { 182 URL url = entries.nextElement(); 183 String path = url.getPath(); 184 if (path.startsWith("/")) { 185 path = path.substring(1); 186 } 187 paths.add(path); 188 } 189 return Collections.enumeration(paths); 190 } 191 192 /** 193 * 1, If the bundle was installed with reference directory mode 194 * return the file URL directly. 195 * 2, For traditional package bundle, Works like {@link Bundle#getEntry(String)} 196 * 197 * In addition to the searching abaove, it also checks attached fragment bundles for the given entry. 198 * 199 * @param bundle 200 * @param name 201 * @return 202 * @throws MalformedURLException 203 */ 204 public static URL getEntry(Bundle bundle, String name) throws MalformedURLException { 205 206 if (name.endsWith("/")) { 207 name = name.substring(0, name.length() - 1); 208 } 209 210 File bundleFile = toFile(bundle); 211 if (bundleFile != null && bundleFile.isDirectory()) { 212 File entryFile = new File(bundleFile, name); 213 if (entryFile.exists()) { 214 return entryFile.toURI().toURL(); 215 } 216 } 217 218 if (name.equals("/")) { 219 return bundle.getEntry(name); 220 } 221 222 String path; 223 String pattern; 224 int pos = name.lastIndexOf("/"); 225 if (pos == -1) { 226 path = "/"; 227 pattern = name; 228 } else if (pos == 0) { 229 path = "/"; 230 pattern = name.substring(1); 231 } else { 232 path = name.substring(0, pos); 233 pattern = name.substring(pos + 1); 234 } 235 Enumeration<URL> entries = bundle.findEntries(path, pattern, false); 236 if (entries != null && entries.hasMoreElements()) { 237 return entries.nextElement(); 238 } else { 239 return null; 240 } 241 } 242 243 public static URL getNestedEntry(Bundle bundle, String jarEntryName, String subEntryName) throws MalformedURLException { 244 File bundleFile = toFile(bundle); 245 if (bundleFile != null && bundleFile.isDirectory()) { 246 File entryFile = new File(bundleFile, jarEntryName); 247 if (entryFile.exists()) { 248 if (entryFile.isFile()) { 249 return new URL("jar:" + entryFile.toURI().toURL() + "!/" + subEntryName); 250 } else { 251 return new File(entryFile, subEntryName).toURI().toURL(); 252 } 253 } 254 return null; 255 } 256 return new URL("jar:" + bundle.getEntry(jarEntryName).toString() + "!/" + subEntryName); 257 } 258 259 260 public static File toFile(Bundle bundle) { 261 return toFile(bundle.getLocation()); 262 } 263 264 public static File toFile(URL url) { 265 return toFile(url.toExternalForm()); 266 } 267 268 /** 269 * Translate the reference:file:// style URL to the underlying file instance 270 * @param url 271 * @return 272 */ 273 public static File toFile(String url) { 274 if (url !=null && url.startsWith(REFERENCE_FILE_SCHEMA)) { 275 File file = null; 276 try { 277 file = new File(new URL(url.substring(REFERENCE_SCHEME.length())).toURI()); 278 if (file.exists()) { 279 return file; 280 } 281 } catch (Exception e) { 282 // If url includes special chars: { } [ ] % < > # ^ ? 283 // URISyntaxException or MalformedURLException will be thrown, 284 // so try to use File(String) directly 285 file = new File(url.substring(REFERENCE_FILE_SCHEMA.length())); 286 if (file.exists()) { 287 return file; 288 } 289 } 290 } 291 return null; 292 } 293 294 public static String toReferenceFileLocation(File file) throws IOException { 295 if (!file.exists()) { 296 throw new IOException("file not exist " + file.getAbsolutePath()); 297 } 298 return REFERENCE_SCHEME + file.toURI(); 299 } 300 301 302 public static LinkedHashSet<Bundle> getWiredBundles(Bundle bundle) { 303 if (isOSGi43) { 304 return getWiredBundles43(bundle); 305 } else { 306 return getWiredBundles42(bundle); 307 } 308 } 309 310 private static LinkedHashSet<Bundle> getWiredBundles42(Bundle bundle) { 311 ServiceReference reference = bundle.getBundleContext().getServiceReference(PackageAdmin.class.getName()); 312 PackageAdmin packageAdmin = (PackageAdmin) bundle.getBundleContext().getService(reference); 313 try { 314 return getWiredBundles(packageAdmin, bundle); 315 } finally { 316 bundle.getBundleContext().ungetService(reference); 317 } 318 } 319 320 public static LinkedHashSet<Bundle> getWiredBundles(PackageAdmin packageAdmin, Bundle bundle) { 321 BundleDescription description = new BundleDescription(bundle.getHeaders()); 322 // handle static wire via Import-Package 323 List<BundleDescription.ImportPackage> imports = description.getExternalImports(); 324 LinkedHashSet<Bundle> wiredBundles = new LinkedHashSet<Bundle>(); 325 for (BundleDescription.ImportPackage packageImport : imports) { 326 ExportedPackage[] exports = packageAdmin.getExportedPackages(packageImport.getName()); 327 Bundle wiredBundle = getWiredBundle(bundle, exports); 328 if (wiredBundle != null) { 329 wiredBundles.add(wiredBundle); 330 } 331 } 332 // handle dynamic wire via DynamicImport-Package 333 if (!description.getDynamicImportPackage().isEmpty()) { 334 for (Bundle b : bundle.getBundleContext().getBundles()) { 335 if (!wiredBundles.contains(b)) { 336 ExportedPackage[] exports = packageAdmin.getExportedPackages(b); 337 Bundle wiredBundle = getWiredBundle(bundle, exports); 338 if (wiredBundle != null) { 339 wiredBundles.add(wiredBundle); 340 } 341 } 342 } 343 } 344 return wiredBundles; 345 } 346 347 static Bundle getWiredBundle(Bundle bundle, ExportedPackage[] exports) { 348 if (exports != null) { 349 for (ExportedPackage exportedPackage : exports) { 350 Bundle[] importingBundles = exportedPackage.getImportingBundles(); 351 if (importingBundles != null) { 352 for (Bundle importingBundle : importingBundles) { 353 if (importingBundle == bundle) { 354 return exportedPackage.getExportingBundle(); 355 } 356 } 357 } 358 } 359 } 360 return null; 361 } 362 363 // OSGi 4.3 API 364 365 private static LinkedHashSet<Bundle> getWiredBundles43(Bundle bundle) { 366 LinkedHashSet<Bundle> wiredBundles = new LinkedHashSet<Bundle>(); 367 BundleWiring wiring = bundle.adapt(BundleWiring.class); 368 if (wiring != null) { 369 List<BundleWire> wires; 370 wires = wiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE); 371 for (BundleWire wire : wires) { 372 wiredBundles.add(wire.getProviderWiring().getBundle()); 373 } 374 wires = wiring.getRequiredWires(BundleRevision.BUNDLE_NAMESPACE); 375 for (BundleWire wire : wires) { 376 wiredBundles.add(wire.getProviderWiring().getBundle()); 377 } 378 } 379 return wiredBundles; 380 } 381 382}