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.io.InputStream; 025import java.net.MalformedURLException; 026import java.net.URL; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.Dictionary; 031import java.util.Enumeration; 032import java.util.HashMap; 033import java.util.Iterator; 034import java.util.LinkedHashMap; 035import java.util.List; 036import java.util.Map; 037import java.util.concurrent.CopyOnWriteArrayList; 038 039import org.osgi.framework.Bundle; 040import org.osgi.framework.BundleContext; 041import org.osgi.framework.BundleException; 042import org.osgi.framework.Constants; 043import org.osgi.framework.ServiceReference; 044import org.osgi.framework.Version; 045import org.osgi.framework.wiring.BundleCapability; 046import org.osgi.framework.wiring.BundleRevision; 047import org.osgi.framework.wiring.BundleWiring; 048 049/** 050 * Bundle that delegates ClassLoader operations to a collection of {@link Bundle} objects. 051 * 052 * @version $Rev: 1371482 $ $Date: 2012-08-09 22:58:28 +0200 (Thu, 09 Aug 2012) $ 053 */ 054public class DelegatingBundle implements Bundle { 055 056 private static final String PACKAGE_CACHE = DelegatingBundle.class.getName() + ".packageCache"; 057 private static final String RESOURCE_CACHE_SIZE = DelegatingBundle.class.getName() + ".resourceCacheSize"; 058 059 private static final URL NOT_FOUND_RESOURCE; 060 061 static { 062 try { 063 NOT_FOUND_RESOURCE = new URL("file://foo"); 064 } catch (MalformedURLException e) { 065 throw new Error(e); 066 } 067 } 068 069 private CopyOnWriteArrayList<Bundle> bundles; 070 private Bundle bundle; 071 private BundleContext bundleContext; 072 073 private final boolean hasDynamicImports; 074 private final Map<String, URL> resourceCache; 075 private final boolean packageCacheEnabled; 076 private Map<String, Bundle> packageCache; 077 078 public DelegatingBundle(Collection<Bundle> bundles) { 079 if (bundles.isEmpty()) { 080 throw new IllegalArgumentException("At least one bundle is required"); 081 } 082 this.bundles = new CopyOnWriteArrayList<Bundle>(bundles); 083 Iterator<Bundle> iterator = bundles.iterator(); 084 // assume first Bundle is the main bundle 085 this.bundle = iterator.next(); 086 this.bundleContext = new DelegatingBundleContext(this, bundle.getBundleContext()); 087 this.hasDynamicImports = hasDynamicImports(iterator); 088 this.resourceCache = initResourceCache(); 089 this.packageCacheEnabled = initPackageCacheEnabled(); 090 } 091 092 public DelegatingBundle(Bundle bundle) { 093 this(Collections.singletonList(bundle)); 094 } 095 096 private static Map<String, URL> initResourceCache() { 097 String value = System.getProperty(RESOURCE_CACHE_SIZE, "250"); 098 int size = Integer.parseInt(value); 099 if (size > 0) { 100 return Collections.synchronizedMap(new Cache<String, URL>(size)); 101 } else { 102 return null; 103 } 104 } 105 106 private static boolean initPackageCacheEnabled() { 107 String value = System.getProperty(PACKAGE_CACHE, "true"); 108 boolean enabled = Boolean.parseBoolean(value); 109 return enabled; 110 } 111 112 /* 113 * Returns true if a single bundle has Dynamic-ImportPackage: *. False, otherwise. 114 */ 115 private boolean hasDynamicImports(Iterator<Bundle> iterator) { 116 while (iterator.hasNext()) { 117 Bundle delegate = iterator.next(); 118 if (hasWildcardDynamicImport(delegate)) { 119 return true; 120 } 121 } 122 return false; 123 } 124 125 private synchronized Map<String, Bundle> getPackageBundleMap() { 126 if (packageCache == null) { 127 packageCache = buildPackageBundleMap(); 128 } 129 return packageCache; 130 } 131 132 private synchronized void reset() { 133 resourceCache.clear(); 134 packageCache = null; 135 } 136 137 private Map<String, Bundle> buildPackageBundleMap() { 138 Map<String, Bundle> map = new HashMap<String, Bundle>(); 139 Iterator<Bundle> iterator = bundles.iterator(); 140 // skip first bundle 141 iterator.next(); 142 // attempt to load the class from the remaining bundles 143 while (iterator.hasNext()) { 144 Bundle bundle = iterator.next(); 145 BundleWiring wiring = bundle.adapt(BundleWiring.class); 146 if (wiring != null) { 147 List<BundleCapability> capabilities = wiring.getCapabilities(BundleRevision.PACKAGE_NAMESPACE); 148 if (capabilities != null && !capabilities.isEmpty()) { 149 for (BundleCapability capability : capabilities) { 150 Map<String, Object> attributes = capability.getAttributes(); 151 if (attributes != null) { 152 String packageName = String.valueOf(attributes.get(BundleRevision.PACKAGE_NAMESPACE)); 153 if (!map.containsKey(packageName)) { 154 map.put(packageName, bundle); 155 } 156 } 157 } 158 } 159 } 160 } 161 return map; 162 } 163 164 public Bundle getMainBundle() { 165 return bundle; 166 } 167 168 public Class<?> loadClass(String name) throws ClassNotFoundException { 169 try { 170 Class<?> clazz = bundle.loadClass(name); 171 return clazz; 172 } catch (ClassNotFoundException cnfe) { 173 if (name.startsWith("java.")) { 174 throw cnfe; 175 } 176 177 int index = name.lastIndexOf('.'); 178 if (index > 0 && bundles.size() > 1) { 179 String packageName = name.substring(0, index); 180 if (packageCacheEnabled) { 181 return findCachedClass(name, packageName, cnfe); 182 } else { 183 return findClass(name, packageName, cnfe); 184 } 185 } 186 187 throw cnfe; 188 } 189 } 190 191 private Class<?> findCachedClass(String className, String packageName, ClassNotFoundException cnfe) throws ClassNotFoundException { 192 Map<String, Bundle> map = getPackageBundleMap(); 193 Bundle bundle = map.get(packageName); 194 if (bundle == null) { 195 // Work-around for Introspector always looking for classes in sun.beans.infos 196 if (packageName.equals("sun.beans.infos") && className.endsWith("BeanInfo")) { 197 throw cnfe; 198 } 199 return findClass(className, packageName, cnfe); 200 } else { 201 return bundle.loadClass(className); 202 } 203 } 204 205 private Class<?> findClass(String className, String packageName, ClassNotFoundException cnfe) throws ClassNotFoundException { 206 Iterator<Bundle> iterator = bundles.iterator(); 207 // skip first bundle 208 iterator.next(); 209 while (iterator.hasNext()) { 210 Bundle delegate = iterator.next(); 211 if (hasDynamicImports && hasWildcardDynamicImport(delegate)) { 212 // skip any bundles with Dynamic-ImportPackage: * to avoid unnecessary wires 213 continue; 214 } 215 try { 216 return delegate.loadClass(className); 217 } catch (ClassNotFoundException e) { 218 // ignore 219 } 220 } 221 throw cnfe; 222 } 223 224 private static boolean hasWildcardDynamicImport(Bundle bundle) { 225 Dictionary<String, String> headers = bundle.getHeaders(); 226 if (headers != null) { 227 String value = headers.get(Constants.DYNAMICIMPORT_PACKAGE); 228 if (value == null) { 229 return false; 230 } else { 231 return "*".equals(value.trim()); 232 } 233 } else { 234 return false; 235 } 236 } 237 238 public void addBundle(Bundle b) { 239 bundles.add(b); 240 reset(); 241 } 242 243 public void removeBundle(Bundle b) { 244 bundles.remove(b); 245 reset(); 246 } 247 248 public URL getResource(String name) { 249 URL resource = null; 250 if (resourceCache == null) { 251 resource = findResource(name); 252 } else { 253 resource = findCachedResource(name); 254 } 255 return resource; 256 } 257 258 private URL findCachedResource(String name) { 259 URL resource = bundle.getResource(name); 260 if (resource == null) { 261 resource = resourceCache.get(name); 262 if (resource == null) { 263 Iterator<Bundle> iterator = bundles.iterator(); 264 // skip first bundle 265 iterator.next(); 266 // look for resource in the remaining bundles 267 resource = findResource(name, iterator); 268 resourceCache.put(name, (resource == null) ? NOT_FOUND_RESOURCE : resource); 269 } else if (resource == NOT_FOUND_RESOURCE) { 270 resource = null; 271 } 272 } 273 return resource; 274 } 275 276 private URL findResource(String name) { 277 Iterator<Bundle> iterator = bundles.iterator(); 278 return findResource(name, iterator); 279 } 280 281 private URL findResource(String name, Iterator<Bundle> iterator) { 282 URL resource = null; 283 while (iterator.hasNext() && resource == null) { 284 Bundle delegate = iterator.next(); 285 resource = delegate.getResource(name); 286 } 287 return resource; 288 } 289 290 public Enumeration<URL> getResources(String name) throws IOException { 291 ArrayList<URL> allResources = new ArrayList<URL>(); 292 for (Bundle bundle : bundles) { 293 Enumeration<URL> e = bundle.getResources(name); 294 addToList(allResources, e); 295 } 296 return Collections.enumeration(allResources); 297 } 298 299 private static void addToList(List<URL> list, Enumeration<URL> enumeration) { 300 if (enumeration != null) { 301 while (enumeration.hasMoreElements()) { 302 list.add(enumeration.nextElement()); 303 } 304 } 305 } 306 307 public BundleContext getBundleContext() { 308 return bundleContext; 309 } 310 311 public Enumeration findEntries(String arg0, String arg1, boolean arg2) { 312 return bundle.findEntries(arg0, arg1, arg2); 313 } 314 315 public long getBundleId() { 316 return bundle.getBundleId(); 317 } 318 319 public URL getEntry(String arg0) { 320 return bundle.getEntry(arg0); 321 } 322 323 public Enumeration getEntryPaths(String arg0) { 324 return bundle.getEntryPaths(arg0); 325 } 326 327 public Dictionary getHeaders() { 328 return bundle.getHeaders(); 329 } 330 331 public Dictionary getHeaders(String arg0) { 332 return bundle.getHeaders(arg0); 333 } 334 335 public long getLastModified() { 336 return bundle.getLastModified(); 337 } 338 339 public String getLocation() { 340 return bundle.getLocation(); 341 } 342 343 public ServiceReference[] getRegisteredServices() { 344 return bundle.getRegisteredServices(); 345 } 346 347 public ServiceReference[] getServicesInUse() { 348 return bundle.getServicesInUse(); 349 } 350 351 public Map getSignerCertificates(int arg0) { 352 return bundle.getSignerCertificates(arg0); 353 } 354 355 public int getState() { 356 return bundle.getState(); 357 } 358 359 public String getSymbolicName() { 360 return bundle.getSymbolicName(); 361 } 362 363 public Version getVersion() { 364 return bundle.getVersion(); 365 } 366 367 public boolean hasPermission(Object arg0) { 368 return bundle.hasPermission(arg0); 369 } 370 371 public void start() throws BundleException { 372 bundle.start(); 373 } 374 375 public void start(int arg0) throws BundleException { 376 bundle.start(arg0); 377 } 378 379 public void stop() throws BundleException { 380 bundle.stop(); 381 } 382 383 public void stop(int arg0) throws BundleException { 384 bundle.stop(arg0); 385 } 386 387 public void uninstall() throws BundleException { 388 bundle.uninstall(); 389 } 390 391 public void update() throws BundleException { 392 bundle.update(); 393 } 394 395 public void update(InputStream arg0) throws BundleException { 396 bundle.update(arg0); 397 } 398 399 public int compareTo(Bundle other) { 400 return bundle.compareTo(other); 401 } 402 403 public <A> A adapt(Class<A> type) { 404 return bundle.adapt(type); 405 } 406 407 public File getDataFile(String filename) { 408 return bundle.getDataFile(filename); 409 } 410 411 public String toString() { 412 return "[DelegatingBundle: " + bundles + "]"; 413 } 414 415 private static class Cache<K, V> extends LinkedHashMap<K, V> { 416 417 private final int maxSize; 418 419 public Cache(int maxSize) { 420 this(16, maxSize, 0.75f); 421 } 422 423 public Cache(int initialSize, int maxSize, float loadFactor) { 424 super(initialSize, loadFactor, true); 425 this.maxSize = maxSize; 426 } 427 428 @Override 429 protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { 430 if (size() > maxSize) { 431 return true; 432 } else { 433 return false; 434 } 435 } 436 } 437 438}