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}