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