diff --git a/gradle/spotless.gradle b/gradle/spotless.gradle
index ab659d44af..d1bf85005a 100644
--- a/gradle/spotless.gradle
+++ b/gradle/spotless.gradle
@@ -3,7 +3,7 @@ apply plugin: 'com.diffplug.spotless'
spotless {
java {
googleJavaFormat()
- licenseHeaderFile rootProject.file('gradle/enforcement/spotless.license.java'), '(package|import|public)'
+ licenseHeaderFile rootProject.file('gradle/enforcement/spotless.license.java'), '(package|import|public|// Includes work from:)'
target 'src/**/*.java'
}
groovy {
diff --git a/instrumentation/apache-camel-2.20/apache-camel-2.20.gradle b/instrumentation/apache-camel-2.20/apache-camel-2.20.gradle
new file mode 100644
index 0000000000..c7081b901f
--- /dev/null
+++ b/instrumentation/apache-camel-2.20/apache-camel-2.20.gradle
@@ -0,0 +1,35 @@
+ext {
+ minJavaVersionForTests = JavaVersion.VERSION_1_8
+}
+
+apply from: "$rootDir/gradle/instrumentation.gradle"
+
+muzzle {
+ pass {
+ group = "org.apache.camel"
+ module = "camel-core"
+ versions = "[2.20.1,3)"
+ }
+}
+
+dependencies {
+ library group: 'org.apache.camel', name: 'camel-core', version: '2.20.1'
+
+ testImplementation project(':instrumentation:apache-httpclient:apache-httpclient-2.0')
+ testImplementation project(':instrumentation:servlet:servlet-3.0')
+
+ testImplementation group: 'org.spockframework', name: 'spock-spring', version: "$versions.spock"
+
+ testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '1.5.17.RELEASE'
+ testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter', version: '1.5.17.RELEASE'
+
+ testImplementation group: 'org.apache.camel', name: 'camel-spring-boot-starter', version: '2.20.1'
+ testImplementation group: 'org.apache.camel', name: 'camel-jetty-starter', version: '2.20.1'
+ testImplementation group: 'org.apache.camel', name: 'camel-http-starter', version: '2.20.1'
+ testImplementation group: 'org.apache.camel', name: 'camel-jaxb-starter', version: '2.20.1'
+ testImplementation group: 'org.apache.camel', name: 'camel-undertow', version: '2.20.1'
+
+ testImplementation 'javax.xml.bind:jaxb-api:2.3.1'
+
+ latestDepTestLibrary group: 'org.apache.camel', name: 'camel-core', version: '2.+'
+}
diff --git a/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/ActiveSpanManager.java b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/ActiveSpanManager.java
new file mode 100644
index 0000000000..ed80d8e321
--- /dev/null
+++ b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/ActiveSpanManager.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// Includes work from:
+/*
+ * Apache Camel Opentracing Component
+ *
+ * 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 io.opentelemetry.javaagent.instrumentation.apachecamel;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Scope;
+import org.apache.camel.Exchange;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Utility class for managing active spans as a stack associated with an exchange. */
+class ActiveSpanManager {
+
+ private static final String ACTIVE_SPAN_PROPERTY = "OpenTelemetry.activeSpan";
+
+ private static final Logger LOG = LoggerFactory.getLogger(ActiveSpanManager.class);
+
+ private ActiveSpanManager() {}
+
+ public static Span getSpan(Exchange exchange) {
+ SpanWithScope spanWithScope = exchange.getProperty(ACTIVE_SPAN_PROPERTY, SpanWithScope.class);
+ if (spanWithScope != null) {
+ return spanWithScope.getSpan();
+ }
+ return null;
+ }
+
+ /**
+ * This method activates the supplied span for the supplied exchange. If an existing span is found
+ * for the exchange, this will be pushed onto a stack.
+ *
+ * @param exchange The exchange
+ * @param span The span
+ */
+ public static void activate(Exchange exchange, Span span) {
+
+ SpanWithScope parent = exchange.getProperty(ACTIVE_SPAN_PROPERTY, SpanWithScope.class);
+ SpanWithScope spanWithScope = SpanWithScope.activate(span, parent);
+ exchange.setProperty(ACTIVE_SPAN_PROPERTY, spanWithScope);
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Activated a span: " + spanWithScope);
+ }
+ }
+
+ /**
+ * This method deactivates an existing active span associated with the supplied exchange. Once
+ * deactivated, if a parent span is found associated with the stack for the exchange, it will be
+ * restored as the current span for that exchange.
+ *
+ * @param exchange The exchange
+ */
+ public static void deactivate(Exchange exchange) {
+
+ SpanWithScope spanWithScope = exchange.getProperty(ACTIVE_SPAN_PROPERTY, SpanWithScope.class);
+
+ if (spanWithScope != null) {
+ spanWithScope.deactivate();
+ exchange.setProperty(ACTIVE_SPAN_PROPERTY, spanWithScope.getParent());
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Deactivated span: " + spanWithScope);
+ }
+ }
+ }
+
+ public static class SpanWithScope {
+ @Nullable private final SpanWithScope parent;
+ private final Span span;
+ private final Scope scope;
+
+ public SpanWithScope(SpanWithScope parent, Span span, Scope scope) {
+ this.parent = parent;
+ this.span = span;
+ this.scope = scope;
+ }
+
+ public static SpanWithScope activate(Span span, SpanWithScope parent) {
+ Scope scope = CamelTracer.TRACER.startScope(span);
+ return new SpanWithScope(parent, span, scope);
+ }
+
+ public SpanWithScope getParent() {
+ return parent;
+ }
+
+ public Span getSpan() {
+ return span;
+ }
+
+ public void deactivate() {
+ span.end();
+ scope.close();
+ }
+
+ @Override
+ public String toString() {
+ return "SpanWithScope [span=" + span + ", scope=" + scope + "]";
+ }
+ }
+}
diff --git a/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelContextInstrumentation.java b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelContextInstrumentation.java
new file mode 100644
index 0000000000..c9602ad69d
--- /dev/null
+++ b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelContextInstrumentation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.apachecamel;
+
+import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
+import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.tooling.Instrumenter;
+import java.util.Collections;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.camel.CamelContext;
+
+@AutoService(Instrumenter.class)
+public class CamelContextInstrumentation extends Instrumenter.Default {
+
+ public CamelContextInstrumentation() {
+ super("apachecamel", "apache-camel");
+ }
+
+ @Override
+ public ElementMatcher classLoaderMatcher() {
+ // Optimization for expensive typeMatcher.
+ return hasClassesNamed("org.apache.camel.CamelContext");
+ }
+
+ @Override
+ public ElementMatcher typeMatcher() {
+
+ return not(isAbstract()).and(implementsInterface(named("org.apache.camel.CamelContext")));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelDirection",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.SpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.BaseSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.DbSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.MessagingSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.HttpSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.InternalSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.KafkaSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.LogSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.RestSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.TimerSpanDecorator",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.DecoratorRegistry",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.ActiveSpanManager",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.ActiveSpanManager$SpanWithScope",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelPropagationUtil",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelPropagationUtil$MapGetter",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelPropagationUtil$MapSetter",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelTracer",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelEventNotifier",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelRoutePolicy",
+ "io.opentelemetry.javaagent.instrumentation.apachecamel.CamelTracingService"
+ };
+ }
+
+ @Override
+ public Map extends ElementMatcher super MethodDescription>, String> transformers() {
+
+ return Collections.singletonMap(
+ named("start").and(isPublic()).and(takesArguments(0)),
+ CamelContextInstrumentation.class.getName() + "$ContextAdvice");
+ }
+
+ public static class ContextAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onContextStart(@Advice.This final CamelContext context) throws Exception {
+
+ if (context.hasService(CamelTracingService.class) == null) {
+ // start this service eager so we init before Camel is starting up
+ context.addService(new CamelTracingService(context), true, true);
+ }
+ }
+ }
+}
diff --git a/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelDirection.java b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelDirection.java
new file mode 100644
index 0000000000..a18b1777dc
--- /dev/null
+++ b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelDirection.java
@@ -0,0 +1,11 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.apachecamel;
+
+public enum CamelDirection {
+ INBOUND,
+ OUTBOUND;
+}
diff --git a/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelEventNotifier.java b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelEventNotifier.java
new file mode 100644
index 0000000000..668756d501
--- /dev/null
+++ b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelEventNotifier.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// Includes work from:
+/*
+ * Apache Camel Opentracing Component
+ *
+ * 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 io.opentelemetry.javaagent.instrumentation.apachecamel;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Context;
+import java.util.EventObject;
+import org.apache.camel.management.event.ExchangeSendingEvent;
+import org.apache.camel.management.event.ExchangeSentEvent;
+import org.apache.camel.support.EventNotifierSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class CamelEventNotifier extends EventNotifierSupport {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CamelEventNotifier.class);
+
+ @Override
+ public void notify(EventObject event) {
+
+ try {
+ if (event instanceof ExchangeSendingEvent) {
+ onExchangeSending((ExchangeSendingEvent) event);
+ } else if (event instanceof ExchangeSentEvent) {
+ onExchangeSent((ExchangeSentEvent) event);
+ }
+ } catch (Throwable t) {
+ LOG.warn("Failed to capture tracing data", t);
+ }
+ }
+
+ /** Camel about to send (outbound). */
+ private void onExchangeSending(ExchangeSendingEvent ese) {
+ SpanDecorator sd = CamelTracer.TRACER.getSpanDecorator(ese.getEndpoint());
+ if (!sd.shouldStartNewSpan()) {
+ return;
+ }
+
+ String name =
+ sd.getOperationName(ese.getExchange(), ese.getEndpoint(), CamelDirection.OUTBOUND);
+ Span span = CamelTracer.TRACER.startSpan(name, sd.getInitiatorSpanKind());
+ sd.pre(span, ese.getExchange(), ese.getEndpoint(), CamelDirection.OUTBOUND);
+ CamelPropagationUtil.injectParent(Context.current(), ese.getExchange().getIn().getHeaders());
+ ActiveSpanManager.activate(ese.getExchange(), span);
+
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("[Exchange sending] Initiator span started " + span);
+ }
+ }
+
+ /** Camel finished sending (outbound). Finish span and remove it from CAMEL holder. */
+ private void onExchangeSent(ExchangeSentEvent event) {
+ ExchangeSentEvent ese = event;
+ SpanDecorator sd = CamelTracer.TRACER.getSpanDecorator(ese.getEndpoint());
+ if (!sd.shouldStartNewSpan()) {
+ return;
+ }
+
+ Span span = ActiveSpanManager.getSpan(ese.getExchange());
+ if (span != null) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("[Exchange sent] Initiator span finished " + span);
+ }
+ sd.post(span, ese.getExchange(), ese.getEndpoint());
+ ActiveSpanManager.deactivate(ese.getExchange());
+ } else {
+ LOG.warn("Could not find managed span for exchange " + ese.getExchange());
+ }
+ }
+
+ @Override
+ public boolean isEnabled(EventObject event) {
+ return event instanceof ExchangeSendingEvent || event instanceof ExchangeSentEvent;
+ }
+
+ @Override
+ public String toString() {
+ return "OpenTelemetryCamelEventNotifier";
+ }
+}
diff --git a/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelPropagationUtil.java b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelPropagationUtil.java
new file mode 100644
index 0000000000..90c2de0c07
--- /dev/null
+++ b/instrumentation/apache-camel-2.20/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelPropagationUtil.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.apachecamel;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.propagation.TextMapPropagator.Getter;
+import io.opentelemetry.context.propagation.TextMapPropagator.Setter;
+import java.util.Map;
+
+final class CamelPropagationUtil {
+
+ private CamelPropagationUtil() {}
+
+ static Context extractParent(final Map exchangeHeaders) {
+ return OpenTelemetry.getGlobalPropagators()
+ .getTextMapPropagator()
+ .extract(Context.current(), exchangeHeaders, MapGetter.INSTANCE);
+ }
+
+ static void injectParent(Context context, final Map exchangeHeaders) {
+ OpenTelemetry.getGlobalPropagators()
+ .getTextMapPropagator()
+ .inject(context, exchangeHeaders, MapSetter.INSTANCE);
+ }
+
+ private static class MapGetter implements Getter