This is an automated email from the ASF dual-hosted git repository. ggrzybek pushed a commit to branch camel-2.22.x in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-2.22.x by this push: new 57b2865 [CAMEL-12980] Interact with Karaf's BundleStateService about Blueprint Camel Context problems 57b2865 is described below commit 57b28652687305ce12e93c89d107926b61cba3ca Author: Grzegorz Grzybek <gr.grzy...@gmail.com> 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 92bd927..b98c665 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 5d862c5..03d8783 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();