[camel] branch camel-2.21.x updated: [CAMEL-12980] Interact with Karaf's BundleStateService about Blueprint Camel Context problems

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[camel] branch camel-2.21.x updated: [CAMEL-12980] Interact with Karaf's BundleStateService about Blueprint Camel Context problems

ggrzybek
This is an automated email from the ASF dual-hosted git repository.

ggrzybek pushed a commit to branch camel-2.21.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-2.21.x by this push:
     new 7cf3a5e  [CAMEL-12980] Interact with Karaf's BundleStateService about Blueprint Camel Context problems
7cf3a5e is described below

commit 7cf3a5ec63d2557102f03b4d923d407aa48e2970
Author: Grzegorz Grzybek <[hidden email]>
AuthorDate: Fri Jan 11 10:43:53 2019 +0100

    [CAMEL-12980] Interact with Karaf's BundleStateService about Blueprint Camel Context problems
   
    (cherry picked from commit aadb0f83c6502219b058510487b1106508fd29ae)
---
 components/camel-blueprint/pom.xml                 |   9 ++
 .../camel/blueprint/BlueprintCamelContext.java     |  16 +-
 .../blueprint/BlueprintCamelStateService.java      | 164 +++++++++++++++++++++
 .../camel/blueprint/KarafBundleStateService.java   |  95 ++++++++++++
 .../blueprint/handler/CamelNamespaceHandler.java   |  26 ++++
 5 files changed, 309 insertions(+), 1 deletion(-)

diff --git a/components/camel-blueprint/pom.xml b/components/camel-blueprint/pom.xml
index b3c72aa..600ce87 100644
--- a/components/camel-blueprint/pom.xml
+++ b/components/camel-blueprint/pom.xml
@@ -43,6 +43,7 @@
       !org.apache.camel.core.xml.*,
       org.apache.camel.*;${camel.osgi.import.strict.version},
       org.osgi.service.event*;resolution:=optional,
+      org.apache.karaf.bundle.core;version="[4,5)";resolution:=optional,
       org.apache.aries*;version="[1.0,2)",
       ${camel.osgi.import.defaults},
       *
@@ -92,6 +93,14 @@
       <scope>provided</scope>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.karaf.bundle</groupId>
+      <artifactId>org.apache.karaf.bundle.core</artifactId>
+      <version>${karaf4-version}</version>
+      <optional>true</optional>
+      <scope>provided</scope>
+    </dependency>
+
     <!-- for testing -->
     <dependency>
       <groupId>org.apache.camel</groupId>
diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintCamelContext.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintCamelContext.java
index fe3e3ad..3d64464 100644
--- a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintCamelContext.java
+++ b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintCamelContext.java
@@ -58,6 +58,8 @@ public class BlueprintCamelContext extends DefaultCamelContext implements Servic
     private BlueprintContainer blueprintContainer;
     private ServiceRegistration<?> registration;
 
+    private BlueprintCamelStateService bundleStateService;
+
     public BlueprintCamelContext() {
     }
 
@@ -96,7 +98,15 @@ public class BlueprintCamelContext extends DefaultCamelContext implements Servic
     public void setBlueprintContainer(BlueprintContainer blueprintContainer) {
         this.blueprintContainer = blueprintContainer;
     }
-  
+
+    public BlueprintCamelStateService getBundleStateService() {
+        return bundleStateService;
+    }
+
+    public void setBundleStateService(BlueprintCamelStateService bundleStateService) {
+        this.bundleStateService = bundleStateService;
+    }
+
     public void init() throws Exception {
         LOG.trace("init {}", this);
 
@@ -125,6 +135,7 @@ public class BlueprintCamelContext extends DefaultCamelContext implements Servic
             }
             registration = null;
         }
+        bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), null);
 
         // must stop Camel
         stop();
@@ -240,8 +251,11 @@ public class BlueprintCamelContext extends DefaultCamelContext implements Servic
         try {
             // let's set a more suitable TCCL while starting the context
             Thread.currentThread().setContextClassLoader(getApplicationContextClassLoader());
+            bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Starting);
             super.start();
+            bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Active);
         } catch (Exception e) {
+            bundleStateService.setBundleState(bundleContext.getBundle(), this.getName(), BlueprintCamelStateService.State.Failure, e);
             routeDefinitionValid.set(false);
             throw e;
         } finally {
diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintCamelStateService.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintCamelStateService.java
new file mode 100644
index 0000000..1df75cc
--- /dev/null
+++ b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintCamelStateService.java
@@ -0,0 +1,164 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.blueprint;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Used by {@link BlueprintCamelContext} to inform about state of Camel context. If running inside Karaf
+ * and Karaf's BundleStateService is accessible, Camel context state will propagate as <em>extended
+ * bundle state</em>.
+ */
+public class BlueprintCamelStateService {
+
+    public static final Logger LOG = LoggerFactory.getLogger(BlueprintCamelStateService.class);
+
+    public enum State {
+        Starting,
+        Active,
+        Failure
+    }
+
+    private Map<String, State> states;
+    private Map<String, Throwable> exceptions;
+
+    private BundleContext bundleContext;
+
+    private ServiceRegistration<?> registration;
+    public BundleContext getBundleContext() {
+        return bundleContext;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    /**
+     * One of four {@link State states} is set for given {@link org.osgi.framework.Bundle} and context Id.
+     * One (blueprint) bundle may declare one or more Camel context.
+     * @param contextId
+     * @param state
+     */
+    public void setBundleState(Bundle bundle, String contextId, State state) {
+        setBundleState(bundle, contextId, state, null);
+    }
+
+    /**
+     * One of four {@link State states} is set for given {@link org.osgi.framework.Bundle} and context Id.
+     * One (blueprint) bundle may declare one or more Camel context.
+     * @param contextId
+     * @param state
+     * @param t
+     */
+    public void setBundleState(Bundle bundle, String contextId, State state, Throwable t) {
+        if (state == State.Failure) {
+            LOG.warn("Changing Camel state for bundle {} to {}", bundle.getBundleId(), state);
+        } else if (LOG.isDebugEnabled()) {
+            LOG.debug("Changing Camel state for bundle {} to {}", bundle.getBundleId(), state);
+        }
+
+        String key = String.format("%d:%s", bundle.getBundleId(), contextId);
+        if (state != null) {
+            states.put(key, state);
+        } else {
+            states.remove(key);
+        }
+        if (t != null) {
+            exceptions.put(key, t);
+        } else {
+            exceptions.remove(key);
+        }
+    }
+
+    /**
+     * Get states for all context registered for given {@link Bundle}
+     * @param bundle
+     * @return
+     */
+    public List<State> getStates(Bundle bundle) {
+        List<State> result = new LinkedList<>();
+        for (Map.Entry<String, State> e : states.entrySet()) {
+            if (e.getKey().startsWith(bundle.getBundleId() + ":")) {
+                result.add(e.getValue());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Get exceptions for all camel contexts for given bundle
+     * @param bundle
+     * @return
+     */
+    public Map<String, Throwable> getExceptions(Bundle bundle) {
+        Map<String, Throwable> result = new LinkedHashMap<>();
+        for (Map.Entry<String, Throwable> e : exceptions.entrySet()) {
+            if (e.getKey().startsWith(bundle.getBundleId() + ":")) {
+                result.put(e.getKey().substring(e.getKey().indexOf(":") + 1), e.getValue());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Attempts to register Karaf-specific BundleStateService - if possible
+     */
+    public void init() {
+        try {
+            states = new ConcurrentHashMap<>();
+            exceptions = new ConcurrentHashMap<>();
+
+            registration = new KarafBundleStateServiceCreator().create(bundleContext, this);
+        } catch (NoClassDefFoundError e) {
+            LOG.info("Karaf BundleStateService not accessible. Bundle state won't reflect Camel context state");
+        }
+    }
+
+    /**
+     * Unregisters any OSGi service registered
+     */
+    public void destroy() {
+        if (registration != null) {
+            registration.unregister();
+        }
+        states.clear();
+        states = null;
+        exceptions.clear();
+        exceptions = null;
+    }
+
+    /**
+     * Static creator to decouple from optional Karaf classes.
+     */
+    private static class KarafBundleStateServiceCreator {
+        public ServiceRegistration<?> create(BundleContext context, BlueprintCamelStateService camelStateService) {
+            KarafBundleStateService karafBundleStateService = new KarafBundleStateService(camelStateService);
+            return karafBundleStateService.register(context);
+        }
+    }
+
+}
diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/KarafBundleStateService.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/KarafBundleStateService.java
new file mode 100644
index 0000000..6e84278
--- /dev/null
+++ b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/KarafBundleStateService.java
@@ -0,0 +1,95 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.blueprint;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map;
+
+import org.apache.karaf.bundle.core.BundleState;
+import org.apache.karaf.bundle.core.BundleStateService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * A service for Karaf to get extended Bundle information related to Camel Context(s) declared in Blueprint
+ * container.
+ */
+public class KarafBundleStateService implements BundleStateService {
+
+    BlueprintCamelStateService camelStateService;
+
+    public KarafBundleStateService(BlueprintCamelStateService camelStateService) {
+        this.camelStateService = camelStateService;
+    }
+
+    @Override
+    public String getName() {
+        return "Camel Blueprint";
+    }
+
+    @Override
+    public String getDiag(Bundle bundle) {
+        if (getState(bundle) == BundleState.Failure) {
+            // return stacktraces for failed camel contexts
+            Map<String, Throwable> exceptions = camelStateService.getExceptions(bundle);
+            StringWriter sw = new StringWriter();
+            for (String contextId : exceptions.keySet()) {
+                sw.append("Camel context \"").append(contextId).append("\"\n");
+                Throwable t = exceptions.get(contextId);
+                if (t instanceof NullPointerException) {
+                    sw.append("Exception: NullPointerException\n");
+                } else if (t.getMessage() != null) {
+                    sw.append("Exception: ").append(t.getMessage()).append("\n");
+                }
+                t.printStackTrace(new PrintWriter(sw));
+                sw.append("\n");
+            }
+            return sw.toString();
+        }
+        return null;
+    }
+
+    @Override
+    public BundleState getState(Bundle bundle) {
+        BundleState effective = BundleState.Unknown;
+        for (BlueprintCamelStateService.State s : camelStateService.getStates(bundle)) {
+            if (effective == BundleState.Unknown || s == BlueprintCamelStateService.State.Failure) {
+                switch (s) {
+                case Starting:
+                    effective = BundleState.Starting;
+                    break;
+                case Active:
+                    effective = BundleState.Active;
+                    break;
+                case Failure:
+                    effective = BundleState.Failure;
+                    break;
+                default:
+                    break;
+                }
+            }
+        }
+        return effective;
+    }
+
+    public ServiceRegistration<?> register(BundleContext context) {
+        return context.registerService(BundleStateService.class, this, null);
+    }
+
+}
diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/handler/CamelNamespaceHandler.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/handler/CamelNamespaceHandler.java
index d810f05..2c44268 100644
--- a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/handler/CamelNamespaceHandler.java
+++ b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/handler/CamelNamespaceHandler.java
@@ -56,6 +56,7 @@ import org.apache.camel.EndpointInject;
 import org.apache.camel.Produce;
 import org.apache.camel.PropertyInject;
 import org.apache.camel.blueprint.BlueprintCamelContext;
+import org.apache.camel.blueprint.BlueprintCamelStateService;
 import org.apache.camel.blueprint.BlueprintModelJAXBContextFactory;
 import org.apache.camel.blueprint.CamelContextFactoryBean;
 import org.apache.camel.blueprint.CamelEndpointFactoryBean;
@@ -279,6 +280,7 @@ public class CamelNamespaceHandler implements NamespaceHandler {
         ctx.setRuntimeClass(BlueprintCamelContext.class);
         ctx.setFactoryComponent(factory2);
         ctx.setFactoryMethod("getContext");
+        ctx.addProperty("bundleStateService", createRef(context, ".camelBlueprint.bundleStateService"));
         ctx.setInitMethod("init");
         ctx.setDestroyMethod("destroy");
 
@@ -288,6 +290,9 @@ public class CamelNamespaceHandler implements NamespaceHandler {
         registerBeans(context, contextId, ccfb.getRedeliveryPolicies());
         registerBeans(context, contextId, ccfb.getBeansFactory());
 
+        // Register single CamelBundleStateService - shared for all bundles and all Blueprint Camel contexts
+        registerBundleStateService(context);
+
         // Register processors
         MutablePassThroughMetadata beanProcessorFactory = context.createMetadata(MutablePassThroughMetadata.class);
         beanProcessorFactory.setId(".camelBlueprint.processor.bean.passThrough." + contextId);
@@ -628,6 +633,27 @@ public class CamelNamespaceHandler implements NamespaceHandler {
         context.getComponentDefinitionRegistry().registerComponentDefinition(e);
     }
 
+    /**
+     * There's single instance of {@link BlueprintCamelStateService} that's used by all Blueprint Camel contexts
+     * to inform about state of Camel contexts. If Karaf is available, this information will propagate to
+     * <em>extended bundle info</em>.
+     * See CAMEL-12980
+     * @param context
+     */
+    private void registerBundleStateService(ParserContext context) {
+        ComponentDefinitionRegistry componentDefinitionRegistry = context.getComponentDefinitionRegistry();
+        ComponentMetadata cm = componentDefinitionRegistry.getComponentDefinition(".camelBlueprint.bundleStateService");
+        if (cm == null) {
+            MutableBeanMetadata ssm = context.createMetadata(MutableBeanMetadata.class);
+            ssm.setId(".camelBlueprint.bundleStateService");
+            ssm.setRuntimeClass(BlueprintCamelStateService.class);
+            ssm.addProperty("bundleContext", createRef(context, "blueprintBundleContext"));
+            ssm.setInitMethod("init");
+            ssm.setDestroyMethod("destroy");
+            componentDefinitionRegistry.registerComponentDefinition(ssm);
+        }
+    }
+
     protected BlueprintContainer getBlueprintContainer(ParserContext context) {
         PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintContainer");
         return (BlueprintContainer) ptm.getObject();