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.util.concurrent.atomic.AtomicBoolean; 020 021import org.apache.camel.TypeConverter; 022import org.apache.camel.blueprint.handler.CamelNamespaceHandler; 023import org.apache.camel.core.osgi.OsgiBeanRepository; 024import org.apache.camel.core.osgi.OsgiCamelContextHelper; 025import org.apache.camel.core.osgi.OsgiCamelContextPublisher; 026import org.apache.camel.core.osgi.OsgiTypeConverter; 027import org.apache.camel.core.osgi.utils.BundleContextUtils; 028import org.apache.camel.core.osgi.utils.BundleDelegatingClassLoader; 029import org.apache.camel.impl.DefaultCamelContext; 030import org.apache.camel.spi.BeanRepository; 031import org.apache.camel.spi.EventNotifier; 032import org.apache.camel.spi.ModelJAXBContextFactory; 033import org.apache.camel.support.DefaultRegistry; 034import org.osgi.framework.BundleContext; 035import org.osgi.framework.ServiceEvent; 036import org.osgi.framework.ServiceListener; 037import org.osgi.framework.ServiceRegistration; 038import org.osgi.service.blueprint.container.BlueprintContainer; 039import org.osgi.service.blueprint.container.BlueprintEvent; 040import org.osgi.service.blueprint.container.BlueprintListener; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044/** 045 * OSGi Blueprint based {@link org.apache.camel.CamelContext}. 046 */ 047public class BlueprintCamelContext extends DefaultCamelContext implements ServiceListener, BlueprintListener { 048 049 private static final Logger LOG = LoggerFactory.getLogger(BlueprintCamelContext.class); 050 051 protected final AtomicBoolean routeDefinitionValid = new AtomicBoolean(true); 052 053 private BundleContext bundleContext; 054 private BlueprintContainer blueprintContainer; 055 private ServiceRegistration<?> registration; 056 private BlueprintCamelStateService bundleStateService; 057 058 public BlueprintCamelContext(BundleContext bundleContext, BlueprintContainer blueprintContainer) { 059 super(false); 060 this.bundleContext = bundleContext; 061 this.blueprintContainer = blueprintContainer; 062 063 // remove the OnCamelContextLifecycleStrategy that camel-core adds by default which does not work well for OSGi 064 getLifecycleStrategies().removeIf(l -> l.getClass().getSimpleName().contains("OnCamelContextLifecycleStrategy")); 065 066 // inject common osgi 067 OsgiCamelContextHelper.osgiUpdate(this, bundleContext); 068 069 // and these are blueprint specific 070 BeanRepository repo1 = new BlueprintContainerBeanRepository(getBlueprintContainer()); 071 OsgiBeanRepository repo2 = new OsgiBeanRepository(bundleContext); 072 setRegistry(new DefaultRegistry(repo1, repo2)); 073 // Need to clean up the OSGi service when camel context is closed. 074 addLifecycleStrategy(repo2); 075 076 setComponentResolver(new BlueprintComponentResolver(bundleContext)); 077 setLanguageResolver(new BlueprintLanguageResolver(bundleContext)); 078 setDataFormatResolver(new BlueprintDataFormatResolver(bundleContext)); 079 setApplicationContextClassLoader(new BundleDelegatingClassLoader(bundleContext.getBundle())); 080 build(); 081 } 082 083 @Override 084 protected ModelJAXBContextFactory createModelJAXBContextFactory() { 085 // must use classloader of the namespace handler 086 return new BlueprintModelJAXBContextFactory(CamelNamespaceHandler.class.getClassLoader()); 087 } 088 089 public BundleContext getBundleContext() { 090 return bundleContext; 091 } 092 093 public void setBundleContext(BundleContext bundleContext) { 094 this.bundleContext = bundleContext; 095 } 096 097 public BlueprintContainer getBlueprintContainer() { 098 return blueprintContainer; 099 } 100 101 public void setBlueprintContainer(BlueprintContainer blueprintContainer) { 102 this.blueprintContainer = blueprintContainer; 103 } 104 105 public BlueprintCamelStateService getBundleStateService() { 106 return bundleStateService; 107 } 108 109 public void setBundleStateService(BlueprintCamelStateService bundleStateService) { 110 this.bundleStateService = bundleStateService; 111 } 112 113 @Override 114 public void doBuild() throws Exception { 115 LOG.trace("build {}", this); 116 // add service listener so we can be notified when blueprint container is done 117 // and we would be ready to start CamelContext 118 bundleContext.addServiceListener(this); 119 // add blueprint listener as service, as we need this for the blueprint container 120 // to support change events when it changes states 121 registration = bundleContext.registerService(BlueprintListener.class, this, null); 122 // call super 123 super.doBuild(); 124 } 125 126 public void destroy() throws Exception { 127 LOG.trace("destroy {}", this); 128 129 // remove listener and stop this CamelContext 130 try { 131 bundleContext.removeServiceListener(this); 132 } catch (Exception e) { 133 LOG.warn("Error removing ServiceListener: " + this + ". This exception is ignored.", e); 134 } 135 if (registration != null) { 136 try { 137 registration.unregister(); 138 } catch (Exception e) { 139 LOG.warn("Error unregistering service registration: " + registration + ". This exception is ignored.", e); 140 } 141 registration = null; 142 } 143 bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), null); 144 145 // must stop Camel 146 stop(); 147 } 148 149 @Override 150 public void blueprintEvent(BlueprintEvent event) { 151 if (LOG.isDebugEnabled()) { 152 String eventTypeString; 153 154 switch (event.getType()) { 155 case BlueprintEvent.CREATING: 156 eventTypeString = "CREATING"; 157 break; 158 case BlueprintEvent.CREATED: 159 eventTypeString = "CREATED"; 160 break; 161 case BlueprintEvent.DESTROYING: 162 eventTypeString = "DESTROYING"; 163 break; 164 case BlueprintEvent.DESTROYED: 165 eventTypeString = "DESTROYED"; 166 break; 167 case BlueprintEvent.GRACE_PERIOD: 168 eventTypeString = "GRACE_PERIOD"; 169 break; 170 case BlueprintEvent.WAITING: 171 eventTypeString = "WAITING"; 172 break; 173 case BlueprintEvent.FAILURE: 174 eventTypeString = "FAILURE"; 175 break; 176 default: 177 eventTypeString = "UNKNOWN"; 178 break; 179 } 180 181 LOG.debug("Received BlueprintEvent[replay={} type={} bundle={}] {}", event.isReplay(), eventTypeString, event.getBundle().getSymbolicName(), event); 182 } 183 184 if (!event.isReplay() && this.getBundleContext().getBundle().getBundleId() == event.getBundle().getBundleId()) { 185 if (event.getType() == BlueprintEvent.CREATED) { 186 try { 187 LOG.info("Attempting to start CamelContext: {}", this.getName()); 188 this.maybeStart(); 189 } catch (Exception startEx) { 190 LOG.error("Error occurred during starting CamelContext: {}", this.getName(), startEx); 191 } 192 } 193 } 194 } 195 196 @Override 197 public void serviceChanged(ServiceEvent event) { 198 if (LOG.isTraceEnabled()) { 199 String eventTypeString; 200 201 switch (event.getType()) { 202 case ServiceEvent.REGISTERED: 203 eventTypeString = "REGISTERED"; 204 break; 205 case ServiceEvent.MODIFIED: 206 eventTypeString = "MODIFIED"; 207 break; 208 case ServiceEvent.UNREGISTERING: 209 eventTypeString = "UNREGISTERING"; 210 break; 211 case ServiceEvent.MODIFIED_ENDMATCH: 212 eventTypeString = "MODIFIED_ENDMATCH"; 213 break; 214 default: 215 eventTypeString = "UNKNOWN"; 216 break; 217 } 218 219 // use trace logging as this is very noisy 220 LOG.trace("Service: {} changed to: {}", event, eventTypeString); 221 } 222 } 223 224 @Override 225 protected TypeConverter createTypeConverter() { 226 // CAMEL-3614: make sure we use a bundle context which imports org.apache.camel.impl.converter package 227 BundleContext ctx = BundleContextUtils.getBundleContext(getClass()); 228 if (ctx == null) { 229 ctx = bundleContext; 230 } 231 return new OsgiTypeConverter(ctx, this, getInjector()); 232 } 233 234 @Override 235 public void start() { 236 final ClassLoader original = Thread.currentThread().getContextClassLoader(); 237 try { 238 // let's set a more suitable TCCL while starting the context 239 Thread.currentThread().setContextClassLoader(getApplicationContextClassLoader()); 240 bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Starting); 241 super.start(); 242 bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Active); 243 } catch (Exception e) { 244 bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Failure, 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}