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.LinkedHashMap;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.osgi.framework.Bundle;
026import org.osgi.framework.BundleContext;
027import org.osgi.framework.ServiceRegistration;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * Used by {@link BlueprintCamelContext} to inform about state of Camel context. If running inside Karaf
033 * and Karaf's BundleStateService is accessible, Camel context state will propagate as <em>extended
034 * bundle state</em>.
035 */
036public class BlueprintCamelStateService {
037
038    public static final Logger LOG = LoggerFactory.getLogger(BlueprintCamelStateService.class);
039
040    public enum State {
041        Starting,
042        Active,
043        Failure
044    }
045
046    private Map<String, State> states;
047    private Map<String, Throwable> exceptions;
048
049    private BundleContext bundleContext;
050
051    private ServiceRegistration<?> registration;
052    public BundleContext getBundleContext() {
053        return bundleContext;
054    }
055
056    public void setBundleContext(BundleContext bundleContext) {
057        this.bundleContext = bundleContext;
058    }
059
060    /**
061     * One of four {@link State states} is set for given {@link org.osgi.framework.Bundle} and context Id.
062     * One (blueprint) bundle may declare one or more Camel context.
063     * @param contextId
064     * @param state
065     */
066    public void setBundleState(Bundle bundle, String contextId, State state) {
067        setBundleState(bundle, contextId, state, null);
068    }
069
070    /**
071     * One of four {@link State states} is set for given {@link org.osgi.framework.Bundle} and context Id.
072     * One (blueprint) bundle may declare one or more Camel context.
073     * @param contextId
074     * @param state
075     * @param t
076     */
077    public void setBundleState(Bundle bundle, String contextId, State state, Throwable t) {
078        if (state == State.Failure) {
079            LOG.warn("Changing Camel state for bundle {} to {}", bundle.getBundleId(), state);
080        } else if (LOG.isDebugEnabled()) {
081            LOG.debug("Changing Camel state for bundle {} to {}", bundle.getBundleId(), state);
082        }
083
084        String key = String.format("%d:%s", bundle.getBundleId(), contextId);
085        if (state != null) {
086            states.put(key, state);
087        } else {
088            states.remove(key);
089        }
090        if (t != null) {
091            exceptions.put(key, t);
092        } else {
093            exceptions.remove(key);
094        }
095    }
096
097    /**
098     * Get states for all context registered for given {@link Bundle}
099     * @param bundle
100     * @return
101     */
102    public List<State> getStates(Bundle bundle) {
103        List<State> result = new LinkedList<>();
104        for (Map.Entry<String, State> e : states.entrySet()) {
105            if (e.getKey().startsWith(bundle.getBundleId() + ":")) {
106                result.add(e.getValue());
107            }
108        }
109        return result;
110    }
111
112    /**
113     * Get exceptions for all camel contexts for given bundle
114     * @param bundle
115     * @return
116     */
117    public Map<String, Throwable> getExceptions(Bundle bundle) {
118        Map<String, Throwable> result = new LinkedHashMap<>();
119        for (Map.Entry<String, Throwable> e : exceptions.entrySet()) {
120            if (e.getKey().startsWith(bundle.getBundleId() + ":")) {
121                result.put(e.getKey().substring(e.getKey().indexOf(":") + 1), e.getValue());
122            }
123        }
124        return result;
125    }
126
127    /**
128     * Attempts to register Karaf-specific BundleStateService - if possible
129     */
130    public void init() {
131        try {
132            states = new ConcurrentHashMap<>();
133            exceptions = new ConcurrentHashMap<>();
134
135            registration = new KarafBundleStateServiceCreator().create(bundleContext, this);
136        } catch (NoClassDefFoundError e) {
137            LOG.info("Karaf BundleStateService not accessible. Bundle state won't reflect Camel context state");
138        }
139    }
140
141    /**
142     * Unregisters any OSGi service registered
143     */
144    public void destroy() {
145        if (registration != null) {
146            registration.unregister();
147        }
148        states.clear();
149        states = null;
150        exceptions.clear();
151        exceptions = null;
152    }
153
154    /**
155     * Static creator to decouple from optional Karaf classes.
156     */
157    private static class KarafBundleStateServiceCreator {
158        public ServiceRegistration<?> create(BundleContext context, BlueprintCamelStateService camelStateService) {
159            KarafBundleStateService karafBundleStateService = new KarafBundleStateService(camelStateService);
160            return karafBundleStateService.register(context);
161        }
162    }
163
164}