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.blueprint; 018 019import java.io.IOException; 020import java.util.Map; 021import java.util.Properties; 022import java.util.concurrent.atomic.AtomicBoolean; 023 024import org.apache.camel.TypeConverter; 025import org.apache.camel.blueprint.handler.CamelNamespaceHandler; 026import org.apache.camel.core.osgi.OsgiCamelContextHelper; 027import org.apache.camel.core.osgi.OsgiCamelContextPublisher; 028import org.apache.camel.core.osgi.OsgiFactoryFinderResolver; 029import org.apache.camel.core.osgi.OsgiTypeConverter; 030import org.apache.camel.core.osgi.utils.BundleContextUtils; 031import org.apache.camel.core.osgi.utils.BundleDelegatingClassLoader; 032import org.apache.camel.impl.DefaultCamelContext; 033import org.apache.camel.spi.EventNotifier; 034import org.apache.camel.spi.FactoryFinder; 035import org.apache.camel.spi.ModelJAXBContextFactory; 036import org.apache.camel.spi.Registry; 037import org.apache.camel.util.LoadPropertiesException; 038import org.osgi.framework.BundleContext; 039import org.osgi.framework.ServiceEvent; 040import org.osgi.framework.ServiceListener; 041import org.osgi.framework.ServiceRegistration; 042import org.osgi.service.blueprint.container.BlueprintContainer; 043import org.osgi.service.blueprint.container.BlueprintEvent; 044import org.osgi.service.blueprint.container.BlueprintListener; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048/** 049 * OSGi Blueprint based {@link org.apache.camel.CamelContext}. 050 */ 051public class BlueprintCamelContext extends DefaultCamelContext implements ServiceListener, BlueprintListener { 052 053 private static final Logger LOG = LoggerFactory.getLogger(BlueprintCamelContext.class); 054 055 protected final AtomicBoolean routeDefinitionValid = new AtomicBoolean(true); 056 057 private BundleContext bundleContext; 058 private BlueprintContainer blueprintContainer; 059 private ServiceRegistration<?> registration; 060 061 public BlueprintCamelContext() { 062 } 063 064 public BlueprintCamelContext(BundleContext bundleContext, BlueprintContainer blueprintContainer) { 065 this.bundleContext = bundleContext; 066 this.blueprintContainer = blueprintContainer; 067 068 // inject common osgi 069 OsgiCamelContextHelper.osgiUpdate(this, bundleContext); 070 071 // and these are blueprint specific 072 setComponentResolver(new BlueprintComponentResolver(bundleContext)); 073 setLanguageResolver(new BlueprintLanguageResolver(bundleContext)); 074 setDataFormatResolver(new BlueprintDataFormatResolver(bundleContext)); 075 setApplicationContextClassLoader(new BundleDelegatingClassLoader(bundleContext.getBundle())); 076 } 077 078 @Override 079 protected ModelJAXBContextFactory createModelJAXBContextFactory() { 080 // must use classloader of the namespace handler 081 return new BlueprintModelJAXBContextFactory(CamelNamespaceHandler.class.getClassLoader()); 082 } 083 084 public BundleContext getBundleContext() { 085 return bundleContext; 086 } 087 088 public void setBundleContext(BundleContext bundleContext) { 089 this.bundleContext = bundleContext; 090 } 091 092 public BlueprintContainer getBlueprintContainer() { 093 return blueprintContainer; 094 } 095 096 public void setBlueprintContainer(BlueprintContainer blueprintContainer) { 097 this.blueprintContainer = blueprintContainer; 098 } 099 100 public void init() throws Exception { 101 LOG.trace("init {}", this); 102 103 // add service listener so we can be notified when blueprint container is done 104 // and we would be ready to start CamelContext 105 bundleContext.addServiceListener(this); 106 // add blueprint listener as service, as we need this for the blueprint container 107 // to support change events when it changes states 108 registration = bundleContext.registerService(BlueprintListener.class, this, null); 109 } 110 111 public void destroy() throws Exception { 112 LOG.trace("destroy {}", this); 113 114 // remove listener and stop this CamelContext 115 try { 116 bundleContext.removeServiceListener(this); 117 } catch (Exception e) { 118 LOG.warn("Error removing ServiceListener: " + this + ". This exception is ignored.", e); 119 } 120 if (registration != null) { 121 try { 122 registration.unregister(); 123 } catch (Exception e) { 124 LOG.warn("Error unregistering service registration: " + registration + ". This exception is ignored.", e); 125 } 126 registration = null; 127 } 128 129 // must stop Camel 130 stop(); 131 } 132 133 @Override 134 public Map<String, Properties> findComponents() throws LoadPropertiesException, IOException { 135 return BundleContextUtils.findComponents(bundleContext, this); 136 } 137 138 @Override 139 public void blueprintEvent(BlueprintEvent event) { 140 if (LOG.isDebugEnabled()) { 141 String eventTypeString; 142 143 switch (event.getType()) { 144 case BlueprintEvent.CREATING: 145 eventTypeString = "CREATING"; 146 break; 147 case BlueprintEvent.CREATED: 148 eventTypeString = "CREATED"; 149 break; 150 case BlueprintEvent.DESTROYING: 151 eventTypeString = "DESTROYING"; 152 break; 153 case BlueprintEvent.DESTROYED: 154 eventTypeString = "DESTROYED"; 155 break; 156 case BlueprintEvent.GRACE_PERIOD: 157 eventTypeString = "GRACE_PERIOD"; 158 break; 159 case BlueprintEvent.WAITING: 160 eventTypeString = "WAITING"; 161 break; 162 case BlueprintEvent.FAILURE: 163 eventTypeString = "FAILURE"; 164 break; 165 default: 166 eventTypeString = "UNKNOWN"; 167 break; 168 } 169 170 LOG.debug("Received BlueprintEvent[replay={} type={} bundle={}] %s", event.isReplay(), eventTypeString, event.getBundle().getSymbolicName(), event.toString()); 171 } 172 173 if (!event.isReplay() && this.getBundleContext().getBundle().getBundleId() == event.getBundle().getBundleId()) { 174 if (event.getType() == BlueprintEvent.CREATED) { 175 try { 176 LOG.info("Attempting to start CamelContext: {}", this.getName()); 177 this.maybeStart(); 178 } catch (Exception startEx) { 179 LOG.error("Error occurred during starting CamelContext: " + this.getName(), startEx); 180 } 181 } else if (event.getType() == BlueprintEvent.DESTROYING) { 182 try { 183 LOG.info("Stopping CamelContext: {}", this.getName()); 184 this.stop(); 185 } catch (Exception stopEx) { 186 LOG.error("Error occurred during stopping CamelContext: " + this.getName(), stopEx); 187 } 188 } 189 } 190 } 191 192 @Override 193 public void serviceChanged(ServiceEvent event) { 194 if (LOG.isTraceEnabled()) { 195 String eventTypeString; 196 197 switch (event.getType()) { 198 case ServiceEvent.REGISTERED: 199 eventTypeString = "REGISTERED"; 200 break; 201 case ServiceEvent.MODIFIED: 202 eventTypeString = "MODIFIED"; 203 break; 204 case ServiceEvent.UNREGISTERING: 205 eventTypeString = "UNREGISTERING"; 206 break; 207 case ServiceEvent.MODIFIED_ENDMATCH: 208 eventTypeString = "MODIFIED_ENDMATCH"; 209 break; 210 default: 211 eventTypeString = "UNKNOWN"; 212 break; 213 } 214 215 // use trace logging as this is very noisy 216 LOG.trace("Service: {} changed to: {}", event.toString(), eventTypeString); 217 } 218 } 219 220 @Override 221 protected TypeConverter createTypeConverter() { 222 // CAMEL-3614: make sure we use a bundle context which imports org.apache.camel.impl.converter package 223 BundleContext ctx = BundleContextUtils.getBundleContext(getClass()); 224 if (ctx == null) { 225 ctx = bundleContext; 226 } 227 FactoryFinder finder = new OsgiFactoryFinderResolver(bundleContext).resolveDefaultFactoryFinder(getClassResolver()); 228 return new OsgiTypeConverter(ctx, this, getInjector(), finder); 229 } 230 231 @Override 232 protected Registry createRegistry() { 233 Registry reg = new BlueprintContainerRegistry(getBlueprintContainer()); 234 return OsgiCamelContextHelper.wrapRegistry(this, reg, bundleContext); 235 } 236 237 @Override 238 public void start() throws Exception { 239 final ClassLoader original = Thread.currentThread().getContextClassLoader(); 240 try { 241 // let's set a more suitable TCCL while starting the context 242 Thread.currentThread().setContextClassLoader(getApplicationContextClassLoader()); 243 super.start(); 244 } catch (Exception e) { 245 routeDefinitionValid.set(false); 246 throw e; 247 } finally { 248 Thread.currentThread().setContextClassLoader(original); 249 } 250 } 251 252 private void maybeStart() throws Exception { 253 LOG.trace("maybeStart: {}", this); 254 255 if (!routeDefinitionValid.get()) { 256 LOG.trace("maybeStart: {} is skipping since CamelRoute definition is not correct.", this); 257 return; 258 } 259 260 // allow to register the BluerintCamelContext eager in the OSGi Service Registry, which ex is needed 261 // for unit testing with camel-test-blueprint 262 boolean eager = "true".equalsIgnoreCase(System.getProperty("registerBlueprintCamelContextEager")); 263 if (eager) { 264 for (EventNotifier notifier : getManagementStrategy().getEventNotifiers()) { 265 if (notifier instanceof OsgiCamelContextPublisher) { 266 OsgiCamelContextPublisher publisher = (OsgiCamelContextPublisher) notifier; 267 publisher.registerCamelContext(this); 268 break; 269 } 270 } 271 } 272 273 // for example from unit testing we want to start Camel later and not 274 // when blueprint loading the bundle 275 boolean skip = "true".equalsIgnoreCase(System.getProperty("skipStartingCamelContext")); 276 if (skip) { 277 LOG.trace("maybeStart: {} is skipping as System property skipStartingCamelContext is set", this); 278 return; 279 } 280 281 if (!isStarted() && !isStarting()) { 282 LOG.debug("Starting {}", this); 283 start(); 284 } else { 285 // ignore as Camel is already started 286 LOG.trace("Ignoring maybeStart() as {} is already started", this); 287 } 288 } 289 290}