001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.impl.osgi; 018 019import java.io.BufferedInputStream; 020import java.io.BufferedReader; 021import java.io.IOException; 022import java.io.InputStreamReader; 023import java.lang.reflect.Method; 024import java.net.URL; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Dictionary; 028import java.util.Enumeration; 029import java.util.HashMap; 030import java.util.Hashtable; 031import java.util.LinkedHashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Properties; 035import java.util.Set; 036import java.util.StringTokenizer; 037import java.util.concurrent.ConcurrentHashMap; 038 039import org.apache.camel.CamelContext; 040import org.apache.camel.Component; 041import org.apache.camel.Converter; 042import org.apache.camel.TypeConverter; 043import org.apache.camel.TypeConverterLoaderException; 044import org.apache.camel.impl.converter.AnnotationTypeConverterLoader; 045import org.apache.camel.impl.osgi.tracker.BundleTracker; 046import org.apache.camel.impl.osgi.tracker.BundleTrackerCustomizer; 047import org.apache.camel.impl.scan.AnnotatedWithPackageScanFilter; 048import org.apache.camel.model.DataFormatDefinition; 049import org.apache.camel.spi.ComponentResolver; 050import org.apache.camel.spi.DataFormat; 051import org.apache.camel.spi.DataFormatResolver; 052import org.apache.camel.spi.Injector; 053import org.apache.camel.spi.Language; 054import org.apache.camel.spi.LanguageResolver; 055import org.apache.camel.spi.PackageScanFilter; 056import org.apache.camel.spi.TypeConverterLoader; 057import org.apache.camel.spi.TypeConverterRegistry; 058import org.apache.camel.util.IOHelper; 059import org.apache.camel.util.ObjectHelper; 060import org.apache.camel.util.StringHelper; 061import org.osgi.framework.Bundle; 062import org.osgi.framework.BundleActivator; 063import org.osgi.framework.BundleContext; 064import org.osgi.framework.BundleEvent; 065import org.osgi.framework.Constants; 066import org.osgi.framework.ServiceRegistration; 067import org.slf4j.Logger; 068import org.slf4j.LoggerFactory; 069 070public class Activator implements BundleActivator, BundleTrackerCustomizer { 071 072 public static final String META_INF_COMPONENT = "META-INF/services/org/apache/camel/component/"; 073 public static final String META_INF_LANGUAGE = "META-INF/services/org/apache/camel/language/"; 074 public static final String META_INF_LANGUAGE_RESOLVER = "META-INF/services/org/apache/camel/language/resolver/"; 075 public static final String META_INF_DATAFORMAT = "META-INF/services/org/apache/camel/dataformat/"; 076 public static final String META_INF_TYPE_CONVERTER = "META-INF/services/org/apache/camel/TypeConverter"; 077 public static final String META_INF_FALLBACK_TYPE_CONVERTER = "META-INF/services/org/apache/camel/FallbackTypeConverter"; 078 079 private static final Logger LOG = LoggerFactory.getLogger(Activator.class); 080 081 private BundleTracker tracker; 082 private Map<Long, List<BaseService>> resolvers = new ConcurrentHashMap<Long, List<BaseService>>(); 083 084 public void start(BundleContext context) throws Exception { 085 LOG.info("Camel activator starting"); 086 tracker = new BundleTracker(context, Bundle.ACTIVE, this); 087 tracker.open(); 088 LOG.info("Camel activator started"); 089 } 090 091 public void stop(BundleContext context) throws Exception { 092 LOG.info("Camel activator stopping"); 093 tracker.close(); 094 LOG.info("Camel activator stopped"); 095 } 096 097 public Object addingBundle(Bundle bundle, BundleEvent event) { 098 LOG.debug("Bundle started: {}", bundle.getSymbolicName()); 099 List<BaseService> r = new ArrayList<BaseService>(); 100 registerComponents(bundle, r); 101 registerLanguages(bundle, r); 102 registerDataFormats(bundle, r); 103 registerTypeConverterLoader(bundle, r); 104 for (BaseService service : r) { 105 service.register(); 106 } 107 resolvers.put(bundle.getBundleId(), r); 108 return bundle; 109 } 110 111 public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) { 112 } 113 114 public void removedBundle(Bundle bundle, BundleEvent event, Object object) { 115 LOG.debug("Bundle stopped: {}", bundle.getSymbolicName()); 116 List<BaseService> r = resolvers.remove(bundle.getBundleId()); 117 if (r != null) { 118 for (BaseService service : r) { 119 service.unregister(); 120 } 121 } 122 } 123 124 protected void registerComponents(Bundle bundle, List<BaseService> resolvers) { 125 if (checkCompat(bundle, Component.class)) { 126 Map<String, String> components = new HashMap<String, String>(); 127 for (Enumeration<?> e = bundle.getEntryPaths(META_INF_COMPONENT); e != null && e.hasMoreElements();) { 128 String path = (String) e.nextElement(); 129 LOG.debug("Found entry: {} in bundle {}", path, bundle.getSymbolicName()); 130 String name = path.substring(path.lastIndexOf("/") + 1); 131 components.put(name, path); 132 } 133 if (!components.isEmpty()) { 134 resolvers.add(new BundleComponentResolver(bundle, components)); 135 } 136 } 137 } 138 139 protected void registerLanguages(Bundle bundle, List<BaseService> resolvers) { 140 if (checkCompat(bundle, Language.class)) { 141 Map<String, String> languages = new HashMap<String, String>(); 142 for (Enumeration<?> e = bundle.getEntryPaths(META_INF_LANGUAGE); e != null && e.hasMoreElements();) { 143 String path = (String) e.nextElement(); 144 LOG.debug("Found entry: {} in bundle {}", path, bundle.getSymbolicName()); 145 String name = path.substring(path.lastIndexOf("/") + 1); 146 languages.put(name, path); 147 } 148 if (!languages.isEmpty()) { 149 resolvers.add(new BundleLanguageResolver(bundle, languages)); 150 } 151 for (Enumeration<?> e = bundle.getEntryPaths(META_INF_LANGUAGE_RESOLVER); e != null && e.hasMoreElements();) { 152 String path = (String) e.nextElement(); 153 LOG.debug("Found entry: {} in bundle {}", path, bundle.getSymbolicName()); 154 String name = path.substring(path.lastIndexOf("/") + 1); 155 resolvers.add(new BundleMetaLanguageResolver(bundle, name, path)); 156 } 157 } 158 } 159 160 protected void registerDataFormats(Bundle bundle, List<BaseService> resolvers) { 161 if (checkCompat(bundle, DataFormat.class)) { 162 Map<String, String> dataformats = new HashMap<String, String>(); 163 for (Enumeration<?> e = bundle.getEntryPaths(META_INF_DATAFORMAT); e != null && e.hasMoreElements();) { 164 String path = (String) e.nextElement(); 165 LOG.debug("Found entry: {} in bundle {}", path, bundle.getSymbolicName()); 166 String name = path.substring(path.lastIndexOf("/") + 1); 167 dataformats.put(name, path); 168 } 169 if (!dataformats.isEmpty()) { 170 resolvers.add(new BundleDataFormatResolver(bundle, dataformats)); 171 } 172 } 173 } 174 175 protected void registerTypeConverterLoader(Bundle bundle, List<BaseService> resolvers) { 176 if (checkCompat(bundle, TypeConverter.class)) { 177 URL url1 = bundle.getEntry(META_INF_TYPE_CONVERTER); 178 URL url2 = bundle.getEntry(META_INF_FALLBACK_TYPE_CONVERTER); 179 if (url1 != null || url2 != null) { 180 LOG.debug("Found TypeConverter in bundle {}", bundle.getSymbolicName()); 181 resolvers.add(new BundleTypeConverterLoader(bundle, url2 != null)); 182 } 183 } 184 } 185 186 protected static class BundleComponentResolver extends BaseResolver<Component> implements ComponentResolver { 187 188 private final Map<String, String> components; 189 190 public BundleComponentResolver(Bundle bundle, Map<String, String> components) { 191 super(bundle, Component.class); 192 this.components = components; 193 } 194 195 public Component resolveComponent(String name, CamelContext context) throws Exception { 196 return createInstance(name, components.get(name), context); 197 } 198 199 public void register() { 200 doRegister(ComponentResolver.class, "component", components.keySet()); 201 } 202 } 203 204 protected static class BundleLanguageResolver extends BaseResolver<Language> implements LanguageResolver { 205 206 private final Map<String, String> languages; 207 208 public BundleLanguageResolver(Bundle bundle, Map<String, String> languages) { 209 super(bundle, Language.class); 210 this.languages = languages; 211 } 212 213 public Language resolveLanguage(String name, CamelContext context) { 214 return createInstance(name, languages.get(name), context); 215 } 216 217 public void register() { 218 doRegister(LanguageResolver.class, "language", languages.keySet()); 219 } 220 } 221 222 protected static class BundleMetaLanguageResolver extends BaseResolver<LanguageResolver> implements LanguageResolver { 223 224 private final String name; 225 private final String path; 226 227 public BundleMetaLanguageResolver(Bundle bundle, String name, String path) { 228 super(bundle, LanguageResolver.class); 229 this.name = name; 230 this.path = path; 231 } 232 233 public Language resolveLanguage(String name, CamelContext context) { 234 LanguageResolver resolver = createInstance(this.name, path, context); 235 return resolver.resolveLanguage(name, context); 236 } 237 238 public void register() { 239 doRegister(LanguageResolver.class, "resolver", name); 240 } 241 } 242 243 protected static class BundleDataFormatResolver extends BaseResolver<DataFormat> implements DataFormatResolver { 244 245 private final Map<String, String> dataformats; 246 247 public BundleDataFormatResolver(Bundle bundle, Map<String, String> dataformats) { 248 super(bundle, DataFormat.class); 249 this.dataformats = dataformats; 250 } 251 252 public DataFormat resolveDataFormat(String name, CamelContext context) { 253 return createInstance(name, dataformats.get(name), context); 254 } 255 256 public DataFormatDefinition resolveDataFormatDefinition(String name, CamelContext context) { 257 return null; 258 } 259 260 public void register() { 261 doRegister(DataFormatResolver.class, "dataformat", dataformats.keySet()); 262 } 263 } 264 265 protected static class BundleTypeConverterLoader extends BaseResolver<TypeConverter> implements TypeConverterLoader { 266 267 private final AnnotationTypeConverterLoader loader = new Loader(); 268 private final Bundle bundle; 269 private final boolean hasFallbackTypeConverter; 270 271 public BundleTypeConverterLoader(Bundle bundle, boolean hasFallbackTypeConverter) { 272 super(bundle, TypeConverter.class); 273 ObjectHelper.notNull(bundle, "bundle"); 274 this.bundle = bundle; 275 this.hasFallbackTypeConverter = hasFallbackTypeConverter; 276 } 277 278 public synchronized void load(TypeConverterRegistry registry) throws TypeConverterLoaderException { 279 // must be synchronized to ensure we don't load type converters concurrently 280 // which cause Camel apps to fails in OSGi thereafter 281 try { 282 loader.load(registry); 283 } catch (Exception e) { 284 throw new TypeConverterLoaderException("Cannot load type converters using OSGi bundle: " + bundle.getBundleId(), e); 285 } 286 } 287 288 public void register() { 289 if (hasFallbackTypeConverter) { 290 // The FallbackTypeConverter should have a higher ranking 291 doRegister(TypeConverterLoader.class, Constants.SERVICE_RANKING, new Integer(100)); 292 } else { 293 // The default service ranking is Integer(0); 294 doRegister(TypeConverterLoader.class); 295 } 296 } 297 298 class Loader extends AnnotationTypeConverterLoader { 299 300 Loader() { 301 super(null); 302 } 303 304 public void load(TypeConverterRegistry registry) throws TypeConverterLoaderException { 305 PackageScanFilter test = new AnnotatedWithPackageScanFilter(Converter.class, true); 306 Set<Class<?>> classes = new LinkedHashSet<Class<?>>(); 307 Set<String> packages = getConverterPackages(bundle.getEntry(META_INF_TYPE_CONVERTER)); 308 309 if (LOG.isTraceEnabled()) { 310 LOG.trace("Found {} {} packages: {}", new Object[]{packages.size(), META_INF_TYPE_CONVERTER, packages}); 311 } 312 // if we only have camel-core on the classpath then we have already pre-loaded all its type converters 313 // but we exposed the "org.apache.camel.core" package in camel-core. This ensures there is at least one 314 // packageName to scan, which triggers the scanning process. That allows us to ensure that we look for 315 // META-INF/services in all the JARs. 316 if (packages.size() == 1 && "org.apache.camel.core".equals(packages.iterator().next())) { 317 LOG.debug("No additional package names found in classpath for annotated type converters."); 318 // no additional package names found to load type converters so break out 319 return; 320 } 321 322 // now filter out org.apache.camel.core as its not needed anymore (it was just a dummy) 323 packages.remove("org.apache.camel.core"); 324 325 for (String pkg : packages) { 326 327 if (StringHelper.isClassName(pkg)) { 328 // its a FQN class name so load it directly 329 LOG.trace("Loading {} class", pkg); 330 try { 331 Class<?> clazz = bundle.loadClass(pkg); 332 if (test.matches(clazz)) { 333 classes.add(clazz); 334 } 335 // the class could be found and loaded so continue to next 336 continue; 337 } catch (Throwable t) { 338 // Ignore 339 LOG.trace("Failed to load " + pkg + " class due " + t.getMessage() + ". This exception will be ignored.", t); 340 } 341 } 342 343 // its not a FQN but a package name so scan for classes in the bundle 344 Enumeration<URL> e = bundle.findEntries("/" + pkg.replace('.', '/'), "*.class", true); 345 while (e != null && e.hasMoreElements()) { 346 String path = e.nextElement().getPath(); 347 String externalName = path.substring(path.charAt(0) == '/' ? 1 : 0, path.indexOf('.')).replace('/', '.'); 348 LOG.trace("Loading {} class", externalName); 349 try { 350 Class<?> clazz = bundle.loadClass(externalName); 351 if (test.matches(clazz)) { 352 classes.add(clazz); 353 } 354 } catch (Throwable t) { 355 // Ignore 356 LOG.trace("Failed to load " + externalName + " class due " + t.getMessage() + ". This exception will be ignored.", t); 357 } 358 } 359 } 360 361 // load the classes into type converter registry 362 LOG.debug("Found {} @Converter classes to load", classes.size()); 363 for (Class<?> type : classes) { 364 if (LOG.isTraceEnabled()) { 365 LOG.trace("Loading converter class: {}", ObjectHelper.name(type)); 366 } 367 loadConverterMethods(registry, type); 368 } 369 370 // register fallback converters 371 URL fallbackUrl = bundle.getEntry(META_INF_FALLBACK_TYPE_CONVERTER); 372 if (fallbackUrl != null) { 373 LOG.debug("Found {} to load the FallbackTypeConverter", META_INF_FALLBACK_TYPE_CONVERTER); 374 TypeConverter tc = createInstance("FallbackTypeConverter", fallbackUrl, registry.getInjector()); 375 registry.addFallbackTypeConverter(tc, false); 376 } 377 378 // now clear the maps so we do not hold references 379 visitedClasses.clear(); 380 visitedURIs.clear(); 381 } 382 } 383 384 } 385 386 protected abstract static class BaseResolver<T> extends BaseService { 387 388 private final Class<T> type; 389 390 public BaseResolver(Bundle bundle, Class<T> type) { 391 super(bundle); 392 this.type = type; 393 } 394 395 protected T createInstance(String name, String path, CamelContext context) { 396 if (path == null) { 397 return null; 398 } 399 URL url = bundle.getEntry(path); 400 LOG.trace("The entry {}'s url is {}", name, url); 401 //Setup the TCCL with Camel context application class loader 402 ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); 403 try { 404 ClassLoader newClassLoader = context.getApplicationContextClassLoader(); 405 if (newClassLoader != null) { 406 Thread.currentThread().setContextClassLoader(newClassLoader); 407 } 408 T answer = createInstance(name, url, context.getInjector()); 409 if (answer != null) { 410 initBundleContext(answer); 411 } 412 return answer; 413 } finally { 414 Thread.currentThread().setContextClassLoader(oldClassLoader); 415 } 416 } 417 418 private void initBundleContext(T answer) { 419 try { 420 Method method = answer.getClass().getMethod("setBundleContext", BundleContext.class); 421 if (method != null) { 422 method.invoke(answer, bundle.getBundleContext()); 423 } 424 } catch (Exception e) { 425 // ignore 426 } 427 } 428 429 @SuppressWarnings("unchecked") 430 protected T createInstance(String name, URL url, Injector injector) { 431 try { 432 Properties properties = loadProperties(url); 433 String classname = (String) properties.get("class"); 434 Class<?> type = bundle.loadClass(classname); 435 if (!this.type.isAssignableFrom(type)) { 436 throw new IllegalArgumentException("Type is not a " + this.type.getName() + " implementation. Found: " + type.getName()); 437 } 438 return injector.newInstance((Class<T>) type); 439 } catch (ClassNotFoundException e) { 440 throw new IllegalArgumentException("Invalid URI, no " + this.type.getName() + " registered for scheme : " + name, e); 441 } 442 } 443 444 } 445 446 protected abstract static class BaseService { 447 448 protected final Bundle bundle; 449 private ServiceRegistration<?> reg; 450 451 protected BaseService(Bundle bundle) { 452 this.bundle = bundle; 453 } 454 455 public abstract void register(); 456 457 protected void doRegister(Class<?> type, String key, Collection<String> value) { 458 doRegister(type, key, value.toArray(new String[value.size()])); 459 } 460 461 protected void doRegister(Class<?> type, String key, Object value) { 462 Dictionary<String, Object> props = new Hashtable<String, Object>(); 463 props.put(key, value); 464 doRegister(type, props); 465 } 466 467 protected void doRegister(Class<?> type) { 468 doRegister(type, null); 469 } 470 471 protected void doRegister(Class<?> type, Dictionary<String, ?> props) { 472 reg = bundle.getBundleContext().registerService(type.getName(), this, props); 473 } 474 475 public void unregister() { 476 reg.unregister(); 477 } 478 } 479 480 protected static Properties loadProperties(URL url) { 481 Properties properties = new Properties(); 482 BufferedInputStream reader = null; 483 try { 484 reader = IOHelper.buffered(url.openStream()); 485 properties.load(reader); 486 } catch (IOException e) { 487 throw new RuntimeException(e); 488 } finally { 489 IOHelper.close(reader, "properties", LOG); 490 } 491 return properties; 492 } 493 494 protected static boolean checkCompat(Bundle bundle, Class<?> clazz) { 495 // Check bundle compatibility 496 try { 497 if (bundle.loadClass(clazz.getName()) != clazz) { 498 return false; 499 } 500 } catch (Throwable t) { 501 return false; 502 } 503 return true; 504 } 505 506 protected static Set<String> getConverterPackages(URL resource) { 507 Set<String> packages = new LinkedHashSet<String>(); 508 if (resource != null) { 509 BufferedReader reader = null; 510 try { 511 reader = IOHelper.buffered(new InputStreamReader(resource.openStream())); 512 while (true) { 513 String line = reader.readLine(); 514 if (line == null) { 515 break; 516 } 517 line = line.trim(); 518 if (line.startsWith("#") || line.length() == 0) { 519 continue; 520 } 521 StringTokenizer iter = new StringTokenizer(line, ","); 522 while (iter.hasMoreTokens()) { 523 String name = iter.nextToken().trim(); 524 if (name.length() > 0) { 525 packages.add(name); 526 } 527 } 528 } 529 } catch (Exception ignore) { 530 // Do nothing here 531 } finally { 532 IOHelper.close(reader, null, LOG); 533 } 534 } 535 return packages; 536 } 537 538} 539