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    private BlueprintCamelStateService bundleStateService;
062
063    public BlueprintCamelContext() {
064    }
065
066    public BlueprintCamelContext(BundleContext bundleContext, BlueprintContainer blueprintContainer) {
067        this.bundleContext = bundleContext;
068        this.blueprintContainer = blueprintContainer;
069
070        // inject common osgi
071        OsgiCamelContextHelper.osgiUpdate(this, bundleContext);
072
073        // and these are blueprint specific
074        setComponentResolver(new BlueprintComponentResolver(bundleContext));
075        setLanguageResolver(new BlueprintLanguageResolver(bundleContext));
076        setDataFormatResolver(new BlueprintDataFormatResolver(bundleContext));
077        setApplicationContextClassLoader(new BundleDelegatingClassLoader(bundleContext.getBundle()));
078    }
079
080    @Override
081    protected ModelJAXBContextFactory createModelJAXBContextFactory() {
082        // must use classloader of the namespace handler
083        return new BlueprintModelJAXBContextFactory(CamelNamespaceHandler.class.getClassLoader());
084    }
085
086    public BundleContext getBundleContext() {
087        return bundleContext;
088    }
089
090    public void setBundleContext(BundleContext bundleContext) {
091        this.bundleContext = bundleContext;
092    }
093
094    public BlueprintContainer getBlueprintContainer() {
095        return blueprintContainer;
096    }
097
098    public void setBlueprintContainer(BlueprintContainer blueprintContainer) {
099        this.blueprintContainer = blueprintContainer;
100    }
101
102    public BlueprintCamelStateService getBundleStateService() {
103        return bundleStateService;
104    }
105
106    public void setBundleStateService(BlueprintCamelStateService bundleStateService) {
107        this.bundleStateService = bundleStateService;
108    }
109
110    public void init() throws Exception {
111        LOG.trace("init {}", this);
112
113        // add service listener so we can be notified when blueprint container is done
114        // and we would be ready to start CamelContext
115        bundleContext.addServiceListener(this);
116        // add blueprint listener as service, as we need this for the blueprint container
117        // to support change events when it changes states
118        registration = bundleContext.registerService(BlueprintListener.class, this, null);
119    }
120
121    public void destroy() throws Exception {
122        LOG.trace("destroy {}", this);
123
124        // remove listener and stop this CamelContext
125        try {
126            bundleContext.removeServiceListener(this);
127        } catch (Exception e) {
128            LOG.warn("Error removing ServiceListener: " + this + ". This exception is ignored.", e);
129        }
130        if (registration != null) {
131            try {
132                registration.unregister();
133            } catch (Exception e) {
134                LOG.warn("Error unregistering service registration: " + registration + ". This exception is ignored.", e);
135            }
136            registration = null;
137        }
138        bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), null);
139
140        // must stop Camel
141        stop();
142    }
143
144    @Override
145    public Map<String, Properties> findComponents() throws LoadPropertiesException, IOException {
146        return BundleContextUtils.findComponents(bundleContext, this);
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={}] %s", event.isReplay(), eventTypeString, event.getBundle().getSymbolicName(), event.toString());
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            } else if (event.getType() == BlueprintEvent.DESTROYING) {
193                try {
194                    LOG.info("Stopping CamelContext: {}", this.getName());
195                    this.stop();
196                } catch (Exception stopEx) {
197                    LOG.error("Error occurred during stopping CamelContext: " + this.getName(), stopEx);
198                }
199            }
200        }
201    }
202
203    @Override
204    public void serviceChanged(ServiceEvent event) {
205        if (LOG.isTraceEnabled()) {
206            String eventTypeString;
207
208            switch (event.getType()) {
209            case ServiceEvent.REGISTERED:
210                eventTypeString = "REGISTERED";
211                break;
212            case ServiceEvent.MODIFIED:
213                eventTypeString = "MODIFIED";
214                break;
215            case ServiceEvent.UNREGISTERING:
216                eventTypeString = "UNREGISTERING";
217                break;
218            case ServiceEvent.MODIFIED_ENDMATCH:
219                eventTypeString = "MODIFIED_ENDMATCH";
220                break;
221            default:
222                eventTypeString = "UNKNOWN";
223                break;
224            }
225
226            // use trace logging as this is very noisy
227            LOG.trace("Service: {} changed to: {}", event.toString(), eventTypeString);
228        }
229    }
230
231    @Override
232    protected TypeConverter createTypeConverter() {
233        // CAMEL-3614: make sure we use a bundle context which imports org.apache.camel.impl.converter package
234        BundleContext ctx = BundleContextUtils.getBundleContext(getClass());
235        if (ctx == null) {
236            ctx = bundleContext;
237        }
238        FactoryFinder finder = new OsgiFactoryFinderResolver(bundleContext).resolveDefaultFactoryFinder(getClassResolver());
239        return new OsgiTypeConverter(ctx, this, getInjector(), finder);
240    }
241
242    @Override
243    protected Registry createRegistry() {
244        Registry reg = new BlueprintContainerRegistry(getBlueprintContainer());
245        return OsgiCamelContextHelper.wrapRegistry(this, reg, bundleContext);
246    }
247    
248    @Override
249    public void start() throws Exception {
250        final ClassLoader original = Thread.currentThread().getContextClassLoader();
251        try {
252            // let's set a more suitable TCCL while starting the context
253            Thread.currentThread().setContextClassLoader(getApplicationContextClassLoader());
254            bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Starting);
255            super.start();
256            bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Active);
257        } catch (Exception e) {
258            bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Failure, e);
259            routeDefinitionValid.set(false);
260            throw e;
261        } finally {
262            Thread.currentThread().setContextClassLoader(original);
263        }
264    }
265
266    private void maybeStart() throws Exception {
267        LOG.trace("maybeStart: {}", this);
268
269        if (!routeDefinitionValid.get()) {
270            LOG.trace("maybeStart: {} is skipping since CamelRoute definition is not correct.", this);
271            return;
272        }
273
274        // allow to register the BluerintCamelContext eager in the OSGi Service Registry, which ex is needed
275        // for unit testing with camel-test-blueprint
276        boolean eager = "true".equalsIgnoreCase(System.getProperty("registerBlueprintCamelContextEager"));
277        if (eager) {
278            for (EventNotifier notifier : getManagementStrategy().getEventNotifiers()) {
279                if (notifier instanceof OsgiCamelContextPublisher) {
280                    OsgiCamelContextPublisher publisher = (OsgiCamelContextPublisher) notifier;
281                    publisher.registerCamelContext(this);
282                    break;
283                }
284            }
285        }
286
287        // for example from unit testing we want to start Camel later and not
288        // when blueprint loading the bundle
289        boolean skip = "true".equalsIgnoreCase(System.getProperty("skipStartingCamelContext"));
290        if (skip) {
291            LOG.trace("maybeStart: {} is skipping as System property skipStartingCamelContext is set", this);
292            return;
293        }
294
295        if (!isStarted() && !isStarting()) {
296            LOG.debug("Starting {}", this);
297            start();
298        } else {
299            // ignore as Camel is already started
300            LOG.trace("Ignoring maybeStart() as {} is already started", this);
301        }
302    }
303
304}