You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2023/08/03 00:16:22 UTC
[cxf] branch main updated: CXF-8457 Provide OpenTelemetry support (#1340)
This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new fa641eaf4a CXF-8457 Provide OpenTelemetry support (#1340)
fa641eaf4a is described below
commit fa641eaf4a8b60789c9396ab3d77d8d1daab6550
Author: Emile de Weerd <em...@deweerd.fr>
AuthorDate: Thu Aug 3 02:16:15 2023 +0200
CXF-8457 Provide OpenTelemetry support (#1340)
A new module under integration/tracing is introduced to add support for tracing with OpenTelemetry (short OTel).
A few additional changes have been done:
* Add the OTel BOMs in the parent/pom.xml dependency management (too much? should keep only the dependencies that are used?)
* The new module is strongly inspired by the integration/tracing/tracing-opentracing module
* The tracing systest was enriched with tests dedicated to OpenTelemetry in the JaxRS and JaxWS contexts
* The necessary OTel dependencies are added to distribution/javadoc to be able to produce the documentation
* A new Jax-RS sample showcases span creation on client call and server call
---
.gitignore | 1 +
bom/pom.xml | 5 +
distribution/javadoc/pom.xml | 8 +
.../jax_rs/tracing_opentelemetry/README.txt | 34 ++
.../samples/jax_rs/tracing_opentelemetry/pom.xml | 167 ++++++++
.../java/demo/jaxrs/tracing/GoogleBooksApi.java | 31 ++
.../demo/jaxrs/tracing/OpenTelemetrySetup.java | 52 +++
.../java/demo/jaxrs/tracing/client/Client.java | 49 +++
.../java/demo/jaxrs/tracing/server/Catalog.java | 158 +++++++
.../jaxrs/tracing/server/CatalogApplication.java | 45 ++
.../demo/jaxrs/tracing/server/CatalogStore.java | 71 ++++
.../java/demo/jaxrs/tracing/server/Server.java | 55 +++
distribution/src/main/release/samples/pom.xml | 1 +
integration/pom.xml | 1 +
integration/tracing/tracing-opentelemetry/pom.xml | 101 +++++
.../AbstractOpenTelemetryClientInterceptor.java | 63 +++
.../AbstractOpenTelemetryClientProvider.java | 132 ++++++
.../AbstractOpenTelemetryInterceptor.java | 69 +++
.../AbstractOpenTelemetryProvider.java | 120 ++++++
.../opentelemetry/OpenTelemetryClientFeature.java | 61 +++
.../OpenTelemetryClientStartInterceptor.java | 62 +++
.../OpenTelemetryClientStopInterceptor.java | 50 +++
.../opentelemetry/OpenTelemetryContext.java | 121 ++++++
.../opentelemetry/OpenTelemetryFeature.java | 81 ++++
.../OpenTelemetryStartInterceptor.java | 51 +++
.../OpenTelemetryStopInterceptor.java | 66 +++
.../cxf/tracing/opentelemetry/ScopedSpan.java | 42 ++
.../cxf/tracing/opentelemetry/TraceScope.java | 47 +++
.../internal/TextMapInjectAdapter.java | 54 +++
.../jaxrs/OpenTelemetryClientProvider.java | 60 +++
.../jaxrs/OpenTelemetryContextProvider.java | 49 +++
.../opentelemetry/jaxrs/OpenTelemetryFeature.java | 56 +++
.../opentelemetry/jaxrs/OpenTelemetryProvider.java | 80 ++++
.../OpenTelemetryTracerContextClassProvider.java | 29 ++
.../org.apache.cxf.jaxrs.ext.ContextClassProvider | 1 +
.../opentelemetry/OpenTelemetryContextTest.java | 54 +++
parent/pom.xml | 19 +
systests/tracing/pom.xml | 15 +
.../cxf/systest/jaxrs/tracing/BookStore.java | 4 +-
.../jaxrs/tracing/opentelemetry/HasAttribute.java | 53 +++
.../jaxrs/tracing/opentelemetry/HasSpan.java | 67 +++
.../tracing/opentelemetry/IsLogContaining.java | 44 ++
.../opentelemetry/OpenTelemetryTracingTest.java | 464 +++++++++++++++++++++
.../jaxws/tracing/opentelemetry/BookStore.java | 69 +++
.../opentelemetry/OpenTelemetryTracingTest.java | 277 ++++++++++++
45 files changed, 3137 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index bb6bd9fc49..149e4b9468 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ derby.log
.sts4-cache/
**/.factorypath
.vscode/
+*.xml.bak
diff --git a/bom/pom.xml b/bom/pom.xml
index 471b884506..c61d460a66 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -69,6 +69,11 @@
<artifactId>cxf-integration-tracing-opentracing</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-integration-tracing-opentelemetry</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-bindings-coloc</artifactId>
diff --git a/distribution/javadoc/pom.xml b/distribution/javadoc/pom.xml
index 43fe3a95ef..8ad65581c9 100644
--- a/distribution/javadoc/pom.xml
+++ b/distribution/javadoc/pom.xml
@@ -394,6 +394,14 @@
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-jmx</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-semconv</artifactId>
+ </dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-util</artifactId>
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/README.txt b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/README.txt
new file mode 100644
index 0000000000..0de4a4f3a2
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/README.txt
@@ -0,0 +1,34 @@
+JAX-RS OpenTelemetry Demo
+=================
+
+The demo shows a basic usage of OpenTelemetry API distributed tracer
+with REST based Web Services using JAX-RS 2.0 (JSR-339). The REST server provides the
+following services at URL http://localhost:9000/catalog:
+
+ - GET to http://localhost:9000/catalog
+ - POST to http://localhost:9000/catalog
+ - GET to http://localhost:9000/catalog/<id>
+ - DELETE to URL http://localhost:9000/catalog/<id>
+ - GET to URL http://localhost:9000/catalog/search?q=<query>
+
+The last endpoint calls public Google Books API in order to search the books by
+query criteria. It demonstrates the integration with native OpenTelemetry instrumentation.
+
+Building and running the demo using Maven
+---------------------------------------
+
+From the base directory of this sample (i.e., where this README file is
+located), the Maven pom.xml file can be used to build and run the demo.
+
+
+Using either UNIX or Windows:
+
+ mvn install
+ mvn -Pserver (from one command line window)
+ mvn -Pclient (from a second command line window)
+
+
+To remove the target dir, run mvn clean".
+
+
+
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/pom.xml b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/pom.xml
new file mode 100644
index 0000000000..a0327a8c2b
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/pom.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>jax_rs_tracing_opentelelemtry</artifactId>
+ <name>JAX-RS Demo Using Distributed Tracing with OpenTelemetry</name>
+ <description>JAX-RS Demo Using Distributed Tracing with OpenTelemetry</description>
+ <parent>
+ <groupId>org.apache.cxf.samples</groupId>
+ <artifactId>cxf-samples</artifactId>
+ <version>4.0.3-SNAPSHOT</version>
+ <relativePath>../..</relativePath>
+ </parent>
+ <properties>
+ <cxf.version>${project.version}</cxf.version>
+ </properties>
+ <profiles>
+ <profile>
+ <id>server</id>
+ <build>
+ <defaultGoal>test</defaultGoal>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>test</phase>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ <configuration>
+ <mainClass>demo.jaxrs.tracing.server.Server</mainClass>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>client</id>
+ <build>
+ <defaultGoal>test</defaultGoal>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>test</phase>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ <configuration>
+ <mainClass>demo.jaxrs.tracing.client.Client</mainClass>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-transports-http</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
+ <!-- This dependency is needed if you're using the Jetty container -->
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-transports-http-jetty</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-client</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-integration-tracing-opentelemetry</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-extension-providers</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.json</groupId>
+ <artifactId>jakarta.json-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>jakarta.json</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.ws.rs</groupId>
+ <artifactId>jakarta.ws.rs-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-webapp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.github.openfeign</groupId>
+ <artifactId>feign-core</artifactId>
+ <version>12.4</version>
+ </dependency>
+ <dependency>
+ <groupId>io.github.openfeign</groupId>
+ <artifactId>feign-httpclient</artifactId>
+ <version>12.4</version>
+ </dependency>
+ <dependency>
+ <groupId>io.github.openfeign.opentracing</groupId>
+ <artifactId>feign-opentracing</artifactId>
+ <version>0.4.1</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-sdk</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-semconv</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-exporter-logging</artifactId>
+ </dependency>
+ <!-- We need the Opentracing SHIM because feign does not have an Opentelemetry client wrapper -->
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-opentracing-shim</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/GoogleBooksApi.java b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/GoogleBooksApi.java
new file mode 100644
index 0000000000..f5e0080cef
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/GoogleBooksApi.java
@@ -0,0 +1,31 @@
+/**
+ * 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 demo.jaxrs.tracing;
+
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
+import feign.Response;
+
+public interface GoogleBooksApi {
+ @RequestLine("GET /books/v1/volumes?q={q}")
+ @Headers("Accept: application/json")
+ Response search(@Param("q") String query);
+}
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/OpenTelemetrySetup.java b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/OpenTelemetrySetup.java
new file mode 100644
index 0000000000..dd5a04c851
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/OpenTelemetrySetup.java
@@ -0,0 +1,52 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 demo.jaxrs.tracing;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.exporter.logging.LoggingSpanExporter;
+import io.opentelemetry.opentracingshim.OpenTracingShim;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
+import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
+import io.opentracing.util.GlobalTracer;
+
+public class OpenTelemetrySetup {
+
+ public static OpenTelemetrySdk setup(String serviceName) {
+ Resource resource = Resource.getDefault()
+ .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, serviceName)));
+
+ SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(BatchSpanProcessor.builder(LoggingSpanExporter.create()).build())
+ .setResource(resource).build();
+
+ OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider)
+ .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
+ .buildAndRegisterGlobal();
+
+ // injecting OTel OpenTracing SHIM for feign opentracing to work
+ GlobalTracer.registerIfAbsent(OpenTracingShim.createTracerShim(openTelemetrySdk));
+
+ return openTelemetrySdk;
+ }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/client/Client.java b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/client/Client.java
new file mode 100644
index 0000000000..f574378213
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/client/Client.java
@@ -0,0 +1,49 @@
+/**
+ * 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 demo.jaxrs.tracing.client;
+
+import java.util.Arrays;
+
+import demo.jaxrs.tracing.OpenTelemetrySetup;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.tracing.opentelemetry.OpenTelemetryFeature;
+import org.apache.cxf.tracing.opentelemetry.jaxrs.OpenTelemetryClientProvider;
+
+public final class Client {
+ private Client() {
+ }
+
+ public static void main(final String[] args) throws Exception {
+ try (OpenTelemetrySdk openTelemetry = OpenTelemetrySetup.setup(Client.class.getName())) {
+ final OpenTelemetryClientProvider provider = new OpenTelemetryClientProvider(openTelemetry,
+ OpenTelemetryFeature.DEFAULT_INSTRUMENTATION_NAME);
+
+ final Response response = WebClient
+ .create("http://localhost:9000/catalog", Arrays.asList(provider))
+ .accept(MediaType.APPLICATION_JSON).get();
+
+ System.out.println(response.readEntity(String.class));
+ response.close();
+ }
+ }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/Catalog.java b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/Catalog.java
new file mode 100644
index 0000000000..210fea66b4
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/Catalog.java
@@ -0,0 +1,158 @@
+/**
+ * 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 demo.jaxrs.tracing.server;
+
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.container.AsyncResponse;
+import jakarta.ws.rs.container.Suspended;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+
+import org.apache.cxf.tracing.Traceable;
+import org.apache.cxf.tracing.TracerContext;
+
+import demo.jaxrs.tracing.GoogleBooksApi;
+import feign.Feign;
+import feign.httpclient.ApacheHttpClient;
+import feign.opentracing.TracingClient;
+import io.opentracing.Tracer;
+
+@Path("/catalog")
+public class Catalog {
+ private final ExecutorService executor = Executors.newFixedThreadPool(2);
+ private final CatalogStore store;
+
+ public Catalog() {
+ store = new CatalogStore();
+ }
+
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addBook(@Context final UriInfo uriInfo, @Context final TracerContext tracing,
+ @FormParam("title") final String title) {
+ try {
+ final String id = UUID.randomUUID().toString();
+
+ executor.submit(
+ tracing.wrap("Inserting New Book",
+ new Traceable<Void>() {
+ public Void call(final TracerContext context) throws Exception {
+ store.put(id, title);
+ return null;
+ }
+ }
+ )
+ ).get(10, TimeUnit.SECONDS);
+
+ return Response
+ .created(uriInfo.getRequestUriBuilder().path(id).build())
+ .build();
+ } catch (final Exception ex) {
+ return Response
+ .serverError()
+ .entity(Json
+ .createObjectBuilder()
+ .add("error", ex.getMessage())
+ .build())
+ .build();
+ }
+ }
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public void getBooks(@Suspended final AsyncResponse response,
+ @Context final TracerContext tracing) throws Exception {
+ tracing.continueSpan(new Traceable<Void>() {
+ @Override
+ public Void call(final TracerContext context) throws Exception {
+ executor.submit(tracing.wrap("Looking for books", new Traceable<Void>() {
+ @Override
+ public Void call(final TracerContext context) throws Exception {
+ response.resume(store.scan());
+ return null;
+ }
+ }));
+
+ return null;
+ }
+ });
+ }
+
+ @GET
+ @Path("/{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonObject getBook(@PathParam("id") final String id) throws IOException {
+ final JsonObject book = store.get(id);
+
+ if (book == null) {
+ throw new NotFoundException("Book with does not exists: " + id);
+ }
+
+ return book;
+ }
+
+ @DELETE
+ @Path("/{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response delete(@PathParam("id") final String id) throws IOException {
+ if (!store.remove(id)) {
+ throw new NotFoundException("Book with does not exists: " + id);
+ }
+
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/search")
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonObject search(@QueryParam("q") final String query, @Context final TracerContext tracing) throws Exception {
+ final GoogleBooksApi api = Feign
+ .builder()
+ .client(new TracingClient(new ApacheHttpClient(), tracing.unwrap(Tracer.class)))
+ .target(GoogleBooksApi.class, "https://www.googleapis.com");
+
+ final feign.Response response = api.search(query);
+ try (final Reader reader = response.body().asReader()) {
+ return Json.createReader(reader).readObject();
+ }
+ }
+}
+
+
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/CatalogApplication.java b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/CatalogApplication.java
new file mode 100644
index 0000000000..91736dfe5a
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/CatalogApplication.java
@@ -0,0 +1,45 @@
+/**
+ * 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 demo.jaxrs.tracing.server;
+
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
+
+import org.apache.cxf.jaxrs.provider.jsrjsonp.JsrJsonpProvider;
+import org.apache.cxf.tracing.opentelemetry.jaxrs.OpenTelemetryFeature;
+
+@ApplicationPath("/")
+public class CatalogApplication extends Application {
+ @Override
+ public Set<Object> getSingletons() {
+ return new HashSet<>(
+ Arrays.asList(
+ new Catalog(),
+ new OpenTelemetryFeature(),
+ new JsrJsonpProvider()
+ )
+ );
+ }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/CatalogStore.java b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/CatalogStore.java
new file mode 100644
index 0000000000..5184012bf5
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/CatalogStore.java
@@ -0,0 +1,71 @@
+/**
+ * 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 demo.jaxrs.tracing.server;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonObject;
+
+public class CatalogStore {
+ private final Map<String, String> books = new ConcurrentHashMap<>();
+
+ public CatalogStore() {
+ }
+
+ public boolean remove(final String key) throws IOException {
+ return books.remove(key) != null;
+ }
+
+ public JsonObject get(final String key) throws IOException {
+ final String title = books.get(key);
+
+ if (title != null) {
+ return Json.createObjectBuilder()
+ .add("id", key)
+ .add("title", title)
+ .build();
+ }
+
+ return null;
+ }
+
+ public void put(final String key, final String title) throws IOException {
+ books.put(key, title);
+ }
+
+ public JsonArray scan() throws IOException {
+ final JsonArrayBuilder builder = Json.createArrayBuilder();
+
+ for (Map.Entry<String, String> entry: books.entrySet()) {
+ builder.add(Json.createObjectBuilder()
+ .add("id", entry.getKey())
+ .add("title", entry.getValue())
+ );
+ }
+
+ return builder.build();
+ }
+
+}
diff --git a/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/Server.java b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/Server.java
new file mode 100644
index 0000000000..126b2ab340
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/tracing_opentelemetry/src/main/java/demo/jaxrs/tracing/server/Server.java
@@ -0,0 +1,55 @@
+/**
+ * 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 demo.jaxrs.tracing.server;
+
+import demo.jaxrs.tracing.OpenTelemetrySetup;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+public class Server {
+ protected Server() throws Exception {
+ org.eclipse.jetty.server.Server server = new org.eclipse.jetty.server.Server(9000);
+
+ // Register and map the dispatcher servlet
+ final ServletHolder servletHolder = new ServletHolder(new CXFNonSpringJaxrsServlet());
+ final ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/");
+ context.addServlet(servletHolder, "/*");
+
+ servletHolder.setInitParameter("jakarta.ws.rs.Application", CatalogApplication.class.getName());
+
+ try (OpenTelemetrySdk ignored = OpenTelemetrySetup.setup(CatalogApplication.class.getName())) {
+ server.setHandler(context);
+ server.start();
+ server.join();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ new Server();
+ System.out.println("Server ready...");
+
+ Thread.sleep(5 * 6000 * 1000);
+ System.out.println("Server exiting");
+ System.exit(0);
+ }
+}
diff --git a/distribution/src/main/release/samples/pom.xml b/distribution/src/main/release/samples/pom.xml
index 6b592068d9..347f180984 100644
--- a/distribution/src/main/release/samples/pom.xml
+++ b/distribution/src/main/release/samples/pom.xml
@@ -101,6 +101,7 @@
<module>jax_rs/sse_tomcat</module>
<module>jax_rs/tracing_brave</module>
<module>jax_rs/tracing_opentracing</module>
+ <module>jax_rs/tracing_opentelemetry</module>
<module>jax_rs/websocket</module>
<module>jax_rs/websocket_web</module>
<module>jax_server_aegis_client</module>
diff --git a/integration/pom.xml b/integration/pom.xml
index 4e2a626cff..fedd856b1e 100644
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -34,6 +34,7 @@
<module>cdi</module>
<module>tracing/tracing-brave</module>
<module>tracing/tracing-opentracing</module>
+ <module>tracing/tracing-opentelemetry</module>
<module>spring-boot</module>
</modules>
</project>
diff --git a/integration/tracing/tracing-opentelemetry/pom.xml b/integration/tracing/tracing-opentelemetry/pom.xml
new file mode 100644
index 0000000000..731b467d65
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>cxf-integration-tracing-opentelemetry</artifactId>
+ <packaging>bundle</packaging>
+ <name>Apache CXF Distributed Tracing using OpenTelemetry</name>
+ <description>Apache CXF Distributed using OpenTelemetry</description>
+ <url>https://cxf.apache.org</url>
+ <parent>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-parent</artifactId>
+ <version>4.0.3-SNAPSHOT</version>
+ <relativePath>../../../parent/pom.xml</relativePath>
+ </parent>
+
+ <properties>
+ <cxf.module.name>org.apache.cxf.tracing.opentelemetry</cxf.module.name>
+ <cxf.osgi.export>
+ org.apache.cxf.tracing,
+ org.apache.cxf.tracing.opentelemetry,
+ org.apache.cxf.tracing.opentelemetry.jaxrs
+ </cxf.osgi.export>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>jakarta.xml.ws</groupId>
+ <artifactId>jakarta.xml.ws-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+ <version>${project.version}</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-frontend-jaxws</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-management</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-semconv</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jdk14</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-transports-http-jetty</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-sdk-testing</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryClientInterceptor.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryClientInterceptor.java
new file mode 100644
index 0000000000..d269324b36
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryClientInterceptor.java
@@ -0,0 +1,63 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import io.opentelemetry.api.OpenTelemetry;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptor;
+
+public abstract class AbstractOpenTelemetryClientInterceptor extends AbstractOpenTelemetryClientProvider
+ implements PhaseInterceptor<Message> {
+
+ private String phase;
+
+ protected AbstractOpenTelemetryClientInterceptor(final String phase, final OpenTelemetry openTelemetry,
+ final String instrumentationName) {
+ super(openTelemetry, instrumentationName);
+ this.phase = phase;
+ }
+
+ public Collection<PhaseInterceptor<? extends Message>> getAdditionalInterceptors() {
+ return null;
+ }
+
+ public Set<String> getAfter() {
+ return Collections.emptySet();
+ }
+
+ public Set<String> getBefore() {
+ return Collections.emptySet();
+ }
+
+ public String getId() {
+ return getClass().getName();
+ }
+
+ public String getPhase() {
+ return phase;
+ }
+
+ public void handleFault(Message message) {
+ }
+
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryClientProvider.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryClientProvider.java
new file mode 100644
index 0000000000..bca027396c
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryClientProvider.java
@@ -0,0 +1,132 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.tracing.opentelemetry;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.tracing.AbstractTracingProvider;
+import org.apache.cxf.tracing.opentelemetry.internal.TextMapInjectAdapter;
+
+public abstract class AbstractOpenTelemetryClientProvider extends AbstractTracingProvider {
+ protected static final Logger LOG = LogUtils.getL7dLogger(AbstractOpenTelemetryClientProvider.class);
+ protected static final String TRACE_SPAN = "org.apache.cxf.tracing.client.opentelemetry.span";
+
+ private final Tracer tracer;
+ private final OpenTelemetry openTelemetry;
+
+ public AbstractOpenTelemetryClientProvider(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ this.openTelemetry = openTelemetry;
+ this.tracer = openTelemetry.getTracer(instrumentationName);
+ }
+
+ protected TraceScopeHolder<TraceScope> startTraceSpan(final Map<String, List<String>> requestHeaders,
+ URI uri, String method) {
+ Context parentContext = Context.current();
+ Span activeSpan = tracer.spanBuilder(buildSpanDescription(uri.toString(), method))
+ .setParent(parentContext).setSpanKind(SpanKind.CLIENT)
+ .setAttribute(SemanticAttributes.HTTP_METHOD, method)
+ .setAttribute(SemanticAttributes.HTTP_URL, uri.toString())
+ // TODO: Enhance with semantics from request
+ .startSpan();
+ Scope scope = activeSpan.makeCurrent();
+
+ openTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), requestHeaders,
+ TextMapInjectAdapter.get());
+
+ // In case of asynchronous client invocation, the span should be detached as JAX-RS
+ // client request / response filters are going to be executed in different threads.
+ Span span = null;
+ if (isAsyncInvocation()) {
+ span = activeSpan;
+ scope.close();
+ }
+
+ return new TraceScopeHolder<TraceScope>(new TraceScope(activeSpan, scope, null),
+ span != null /* detached */);
+ }
+
+ private boolean isAsyncInvocation() {
+ return !PhaseInterceptorChain.getCurrentMessage().getExchange().isSynchronous();
+ }
+
+ protected void stopTraceSpan(final TraceScopeHolder<TraceScope> holder, final int responseStatus) {
+ if (holder == null) {
+ return;
+ }
+
+ final TraceScope traceScope = holder.getScope();
+ if (traceScope != null) {
+ Span span = traceScope.getSpan();
+ Scope scope = traceScope.getScope();
+
+ // If the client invocation was asynchronous , the trace span has been created
+ // in another thread and should be re-attached to the current one.
+ if (holder.isDetached()) {
+ scope = span.makeCurrent();
+ }
+
+ span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE.getKey(), responseStatus);
+
+ span.end();
+
+ scope.close();
+ }
+ }
+
+ protected void stopTraceSpan(final TraceScopeHolder<TraceScope> holder, final Throwable ex) {
+ if (holder == null) {
+ return;
+ }
+
+ final TraceScope traceScope = holder.getScope();
+ if (traceScope != null) {
+ Span span = traceScope.getSpan();
+ Scope scope = traceScope.getScope();
+
+ // If the client invocation was asynchronous , the trace span has been created
+ // in another thread and should be re-attached to the current one.
+ if (holder.isDetached()) {
+ scope = span.makeCurrent();
+ }
+
+ span.setStatus(StatusCode.ERROR);
+ if (ex != null) {
+ span.recordException(ex, Attributes.of(SemanticAttributes.EXCEPTION_ESCAPED, true));
+ }
+ span.end();
+
+ scope.close();
+ }
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryInterceptor.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryInterceptor.java
new file mode 100644
index 0000000000..0cc2553b78
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryInterceptor.java
@@ -0,0 +1,69 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptor;
+
+public abstract class AbstractOpenTelemetryInterceptor extends AbstractOpenTelemetryProvider
+ implements PhaseInterceptor<Message> {
+
+ private final String phase;
+
+ protected AbstractOpenTelemetryInterceptor(String phase, OpenTelemetry openTelemetry,
+ String instrumentationName) {
+ super(openTelemetry, instrumentationName);
+ this.phase = phase;
+ }
+
+ @Override
+ public Set<String> getAfter() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getBefore() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public String getId() {
+ return getClass().getName();
+ }
+
+ @Override
+ public String getPhase() {
+ return phase;
+ }
+
+ @Override
+ public Collection<PhaseInterceptor<? extends Message>> getAdditionalInterceptors() {
+ return null;
+ }
+
+ @Override
+ public void handleFault(Message message) {
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryProvider.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryProvider.java
new file mode 100644
index 0000000000..547eafaf1d
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/AbstractOpenTelemetryProvider.java
@@ -0,0 +1,120 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.tracing.opentelemetry;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanBuilder;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.tracing.AbstractTracingProvider;
+import org.apache.cxf.tracing.opentelemetry.internal.TextMapInjectAdapter;
+
+public abstract class AbstractOpenTelemetryProvider extends AbstractTracingProvider {
+ protected static final Logger LOG = LogUtils.getL7dLogger(AbstractOpenTelemetryProvider.class);
+ protected static final String TRACE_SPAN = "org.apache.cxf.tracing.opentelemetry.span";
+
+ protected final OpenTelemetry openTelemetry;
+
+ protected final Tracer tracer;
+
+ protected AbstractOpenTelemetryProvider(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ this.openTelemetry = openTelemetry;
+ this.tracer = openTelemetry.getTracer(instrumentationName);
+ }
+
+ protected TraceScopeHolder<TraceScope> startTraceSpan(final Map<String, List<String>> requestHeaders,
+ URI uri, String method) {
+
+ Context parent = openTelemetry.getPropagators().getTextMapPropagator()
+ .extract(Context.current(), requestHeaders, TextMapInjectAdapter.get());
+ Scope parentScope = parent.makeCurrent();
+
+ SpanBuilder spanBuilder = tracer.spanBuilder(buildSpanDescription(uri.getPath(), method))
+ .setSpanKind(SpanKind.SERVER)
+ .setAttribute(SemanticAttributes.HTTP_METHOD, method)
+ .setAttribute(SemanticAttributes.HTTP_URL, uri.toString());
+ Span activeSpan = spanBuilder.startSpan();
+ Scope scope = activeSpan.makeCurrent();
+
+ // Set additional tags
+
+ // If the service resource is using asynchronous processing mode, the trace
+ // scope will be closed in another thread and as such should be detached.
+ Span span = null;
+ if (isAsyncResponse()) {
+ // Do not modify the current context span
+ span = activeSpan;
+ propagateContinuationSpan(span);
+ scope.close();
+ parentScope.close();
+ }
+
+ return new TraceScopeHolder<TraceScope>(new TraceScope(activeSpan, scope, parentScope), span != null);
+ }
+
+ protected void stopTraceSpan(final Map<String, List<String>> requestHeaders,
+ final Map<String, List<Object>> responseHeaders, final int responseStatus,
+ final TraceScopeHolder<TraceScope> holder) {
+
+ if (holder == null) {
+ return;
+ }
+
+ final TraceScope traceScope = holder.getScope();
+ if (traceScope != null) {
+ Span span = traceScope.getSpan();
+ Scope scope = traceScope.getScope();
+
+ // If the service resource is using asynchronous processing mode, the trace
+ // scope has been created in another thread and should be re-attached to the current
+ // one.
+ if (holder.isDetached()) {
+ scope = span.makeCurrent();
+ }
+
+ span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, responseStatus);
+ span.end();
+
+ scope.close();
+ traceScope.getParentScope().close();
+ }
+ }
+
+ protected boolean isAsyncResponse() {
+ return !PhaseInterceptorChain.getCurrentMessage().getExchange().isSynchronous();
+ }
+
+ private void propagateContinuationSpan(final Span continuation) {
+ PhaseInterceptorChain.getCurrentMessage().put(Span.class, continuation);
+ }
+
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientFeature.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientFeature.java
new file mode 100644
index 0000000000..eabdaca94e
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientFeature.java
@@ -0,0 +1,61 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import org.apache.cxf.Bus;
+import org.apache.cxf.annotations.Provider;
+import org.apache.cxf.annotations.Provider.Scope;
+import org.apache.cxf.annotations.Provider.Type;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.feature.AbstractPortableFeature;
+import org.apache.cxf.feature.DelegatingFeature;
+import org.apache.cxf.interceptor.InterceptorProvider;
+
+@NoJSR250Annotations
+@Provider(value = Type.Feature, scope = Scope.Client)
+public class OpenTelemetryClientFeature extends DelegatingFeature<OpenTelemetryClientFeature.Portable> {
+ public OpenTelemetryClientFeature(OpenTelemetry openTelemetry) {
+ super(new Portable(openTelemetry));
+ }
+
+ public OpenTelemetryClientFeature(OpenTelemetry openTelemetry, String instrumentationName) {
+ super(new Portable(openTelemetry, instrumentationName));
+ }
+
+ public static class Portable implements AbstractPortableFeature {
+ private OpenTelemetryClientStartInterceptor out;
+ private OpenTelemetryClientStopInterceptor in;
+ public Portable(OpenTelemetry openTelemetry) {
+ this(openTelemetry, OpenTelemetryFeature.DEFAULT_INSTRUMENTATION_NAME);
+ }
+
+ public Portable(OpenTelemetry openTelemetry, String instrumentationName) {
+ out = new OpenTelemetryClientStartInterceptor(openTelemetry, instrumentationName);
+ in = new OpenTelemetryClientStopInterceptor(openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void doInitializeProvider(InterceptorProvider provider, Bus bus) {
+ provider.getInInterceptors().add(in);
+ provider.getOutInterceptors().add(out);
+ }
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientStartInterceptor.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientStartInterceptor.java
new file mode 100644
index 0000000000..4068c97f6a
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientStartInterceptor.java
@@ -0,0 +1,62 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import java.util.List;
+import java.util.Map;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.Phase;
+
+public class OpenTelemetryClientStartInterceptor extends AbstractOpenTelemetryClientInterceptor {
+ public OpenTelemetryClientStartInterceptor(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ this(Phase.PRE_STREAM, openTelemetry, instrumentationName);
+ }
+
+ public OpenTelemetryClientStartInterceptor(final String phase, final OpenTelemetry openTelemetry, final String instrumentationName) {
+ super(phase, openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void handleMessage(Message message) throws Fault {
+ final Map<String, List<String>> headers = CastUtils
+ .cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
+ final TraceScopeHolder<TraceScope> holder = super.startTraceSpan(headers, getUri(message),
+ (String)message
+ .get(Message.HTTP_REQUEST_METHOD));
+
+ if (holder != null) {
+ message.getExchange().put(TRACE_SPAN, holder);
+ }
+ }
+
+ @Override
+ public void handleFault(Message message) {
+ @SuppressWarnings("unchecked")
+ final TraceScopeHolder<TraceScope> holder = (TraceScopeHolder<TraceScope>)message.getExchange()
+ .get(TRACE_SPAN);
+
+ final Exception ex = message.getContent(Exception.class);
+ super.stopTraceSpan(holder, ex);
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientStopInterceptor.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientStopInterceptor.java
new file mode 100644
index 0000000000..db69a0973c
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryClientStopInterceptor.java
@@ -0,0 +1,50 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import io.opentelemetry.api.OpenTelemetry;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.Phase;
+
+public class OpenTelemetryClientStopInterceptor extends AbstractOpenTelemetryClientInterceptor {
+ public OpenTelemetryClientStopInterceptor(final OpenTelemetry openTelemetry,
+ final String instrumentationName) {
+ this(Phase.RECEIVE, openTelemetry, instrumentationName);
+ }
+
+ public OpenTelemetryClientStopInterceptor(final String phase, final OpenTelemetry openTelemetry,
+ final String instrumentationName) {
+ super(phase, openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void handleMessage(Message message) throws Fault {
+ @SuppressWarnings("unchecked")
+ final TraceScopeHolder<TraceScope> holder = (TraceScopeHolder<TraceScope>)message.getExchange()
+ .get(TRACE_SPAN);
+
+ Integer responseCode = (Integer)message.get(Message.RESPONSE_CODE);
+ if (responseCode == null) {
+ responseCode = 200;
+ }
+
+ super.stopTraceSpan(holder, responseCode);
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryContext.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryContext.java
new file mode 100644
index 0000000000..fb181ee76d
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryContext.java
@@ -0,0 +1,121 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import java.util.concurrent.Callable;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanBuilder;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import org.apache.cxf.tracing.Traceable;
+import org.apache.cxf.tracing.TracerContext;
+
+public class OpenTelemetryContext implements TracerContext {
+ private final Tracer tracer;
+ private final Span continuation;
+
+ public OpenTelemetryContext(final Tracer tracer) {
+ this(tracer, null);
+ }
+
+ public OpenTelemetryContext(final Tracer tracer, final Span continuation) {
+ this.tracer = tracer;
+ this.continuation = continuation;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Scope startSpan(final String description) {
+ return newOrChildSpan(description, null);
+ }
+
+ @Override
+ public <T> T continueSpan(final Traceable<T> traceable) throws Exception {
+ Scope scope = null;
+
+ if (Span.current() != null && continuation != null && !Span.current().getSpanContext().getSpanId()
+ .equals(continuation.getSpanContext().getSpanId())) {
+ scope = continuation.makeCurrent();
+ }
+
+ try { // NOPMD
+ return traceable.call(new OpenTelemetryContext(tracer));
+ } finally {
+ if (continuation != null && scope != null) {
+ scope.close();
+ }
+ }
+ }
+
+ @Override
+ public <T> Callable<T> wrap(final String description, final Traceable<T> traceable) {
+ final Callable<T> callable = new Callable<T>() {
+ @Override
+ public T call() throws Exception {
+ return traceable.call(new OpenTelemetryContext(tracer));
+ }
+ };
+
+ // Carry over parent from the current thread
+ final Context parent = Context.current();
+ return () -> {
+ try (Scope scope = newOrChildSpan(description, parent)) {
+ return callable.call();
+ }
+ };
+ }
+
+ @Override
+ public void annotate(String key, String value) {
+ final Span current = Span.current();
+ if (current != null) {
+ current.setAttribute(key, value);
+ }
+ }
+
+ @Override
+ public void timeline(String message) {
+ final Span current = Span.current();
+ if (current != null) {
+ current.addEvent(message);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T unwrap(final Class<T> clazz) {
+ if (Tracer.class.equals(clazz)) {
+ return (T)tracer;
+ } else {
+ throw new IllegalArgumentException("The class is '" + clazz
+ + "'not supported and cannot be unwrapped");
+ }
+ }
+
+ private Scope newOrChildSpan(final String description, Context parent) {
+ SpanBuilder spanBuilder = tracer.spanBuilder(description);
+ if (parent != null) {
+ spanBuilder.setParent(parent);
+ }
+ Span span = spanBuilder.startSpan();
+ return new ScopedSpan(span, span.makeCurrent());
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryFeature.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryFeature.java
new file mode 100644
index 0000000000..e7e9ece02a
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryFeature.java
@@ -0,0 +1,81 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import org.apache.cxf.Bus;
+import org.apache.cxf.annotations.Provider;
+import org.apache.cxf.annotations.Provider.Scope;
+import org.apache.cxf.annotations.Provider.Type;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.feature.AbstractPortableFeature;
+import org.apache.cxf.feature.DelegatingFeature;
+import org.apache.cxf.interceptor.InterceptorProvider;
+
+@NoJSR250Annotations
+@Provider(value = Type.Feature, scope = Scope.Server)
+public class OpenTelemetryFeature extends DelegatingFeature<OpenTelemetryFeature.Portable> {
+ public static final String DEFAULT_INSTRUMENTATION_NAME = "org.apache.cxf";
+
+ public OpenTelemetryFeature() {
+ super(new Portable());
+ }
+
+ public OpenTelemetryFeature(final String instrumentationName) { super(new Portable(instrumentationName)); }
+
+ public OpenTelemetryFeature(final OpenTelemetry openTelemetry) {
+ super(new Portable(openTelemetry));
+ }
+
+ public OpenTelemetryFeature(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ super(new Portable(openTelemetry, instrumentationName));
+ }
+
+ public static class Portable implements AbstractPortableFeature {
+ private OpenTelemetryStartInterceptor in;
+ private OpenTelemetryStopInterceptor out;
+
+ public Portable() {
+ this(GlobalOpenTelemetry.get());
+ }
+
+ public Portable(final String instrumentationName) {
+ this(GlobalOpenTelemetry.get(), instrumentationName);
+ }
+
+ public Portable(final OpenTelemetry openTelemetry) {
+ this(openTelemetry, DEFAULT_INSTRUMENTATION_NAME);
+ }
+
+ public Portable(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ in = new OpenTelemetryStartInterceptor(openTelemetry, instrumentationName);
+ out = new OpenTelemetryStopInterceptor(openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void doInitializeProvider(InterceptorProvider provider, Bus bus) {
+ provider.getInInterceptors().add(in);
+ provider.getInFaultInterceptors().add(in);
+
+ provider.getOutInterceptors().add(out);
+ provider.getOutFaultInterceptors().add(out);
+ }
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryStartInterceptor.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryStartInterceptor.java
new file mode 100644
index 0000000000..7795ec7021
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryStartInterceptor.java
@@ -0,0 +1,51 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import java.util.List;
+import java.util.Map;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.Phase;
+
+@NoJSR250Annotations
+public class OpenTelemetryStartInterceptor extends AbstractOpenTelemetryInterceptor {
+ public OpenTelemetryStartInterceptor(OpenTelemetry openTelemetry, String instrumentationName) {
+ super(Phase.PRE_INVOKE, openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void handleMessage(Message message) throws Fault {
+ final Map<String, List<String>> headers = CastUtils
+ .cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
+
+ final TraceScopeHolder<TraceScope> holder = super.startTraceSpan(headers, getUri(message),
+ (String)message
+ .get(Message.HTTP_REQUEST_METHOD));
+
+ if (holder != null) {
+ message.getExchange().put(TRACE_SPAN, holder);
+ }
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryStopInterceptor.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryStopInterceptor.java
new file mode 100644
index 0000000000..aeae145d5d
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryStopInterceptor.java
@@ -0,0 +1,66 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.phase.Phase;
+
+public class OpenTelemetryStopInterceptor extends AbstractOpenTelemetryInterceptor {
+ public OpenTelemetryStopInterceptor(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ super(Phase.POST_MARSHAL, openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void handleMessage(Message message) throws Fault {
+ Map<String, List<Object>> responseHeaders = CastUtils
+ .cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
+
+ if (responseHeaders == null) {
+ responseHeaders = new HashMap<>();
+ message.put(Message.PROTOCOL_HEADERS, responseHeaders);
+ }
+
+ boolean isRequestor = MessageUtils.isRequestor(message);
+ Message requestMessage = isRequestor
+ ? message.getExchange().getOutMessage() : message.getExchange().getInMessage();
+ Map<String, List<String>> requestHeaders = CastUtils
+ .cast((Map<?, ?>)requestMessage.get(Message.PROTOCOL_HEADERS));
+
+ @SuppressWarnings("unchecked")
+ final TraceScopeHolder<TraceScope> holder = (TraceScopeHolder<TraceScope>)message.getExchange()
+ .get(TRACE_SPAN);
+
+ Integer responseCode = (Integer)message.get(Message.RESPONSE_CODE);
+ if (responseCode == null) {
+ responseCode = 200;
+ }
+
+ super.stopTraceSpan(requestHeaders, responseHeaders, responseCode, holder);
+ }
+
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/ScopedSpan.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/ScopedSpan.java
new file mode 100644
index 0000000000..d4fda948a2
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/ScopedSpan.java
@@ -0,0 +1,42 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Scope;
+
+/**
+ * The instance of the Scope which is finishing the Span upon close() call.
+ */
+class ScopedSpan implements Scope {
+ private final Span span;
+ private final Scope scope;
+
+ ScopedSpan(final Span span, final Scope scope) {
+ this.span = span;
+ this.scope = scope;
+ }
+
+ @Override
+ public void close() {
+ span.end();
+ scope.close();
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/TraceScope.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/TraceScope.java
new file mode 100644
index 0000000000..1a641b4ae6
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/TraceScope.java
@@ -0,0 +1,47 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Scope;
+
+public class TraceScope {
+ private final Span span;
+ private final Scope scope;
+
+ private final Scope parentScope;
+
+ TraceScope(final Span span, final Scope scope, final Scope parentScope) {
+ this.span = span;
+ this.scope = scope;
+ this.parentScope = parentScope;
+ }
+
+ public Span getSpan() {
+ return span;
+ }
+
+ public Scope getScope() {
+ return scope;
+ }
+
+ public Scope getParentScope() {
+ return parentScope;
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/internal/TextMapInjectAdapter.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/internal/TextMapInjectAdapter.java
new file mode 100644
index 0000000000..fc1c3a83ad
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/internal/TextMapInjectAdapter.java
@@ -0,0 +1,54 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.tracing.opentelemetry.internal;
+
+import java.util.List;
+import java.util.Map;
+
+import io.opentelemetry.context.propagation.TextMapGetter;
+import io.opentelemetry.context.propagation.TextMapSetter;
+
+public final class TextMapInjectAdapter
+ implements TextMapGetter<Map<String, List<String>>>, TextMapSetter<Map<String, List<String>>> {
+ private static final TextMapInjectAdapter INSTANCE = new TextMapInjectAdapter();
+
+ private TextMapInjectAdapter() {
+
+ }
+
+ public static TextMapInjectAdapter get() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Iterable<String> keys(Map<String, List<String>> carrier) {
+ return carrier.keySet();
+ }
+
+ @Override
+ public String get(Map<String, List<String>> carrier, String key) {
+ List<String> values = carrier.get(key);
+ return values == null || values.isEmpty() ? null : values.get(values.size() - 1);
+ }
+
+ @Override
+ public void set(Map<String, List<String>> carrier, String key, String value) {
+ carrier.put(key, List.of(value));
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryClientProvider.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryClientProvider.java
new file mode 100644
index 0000000000..ddf58987be
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryClientProvider.java
@@ -0,0 +1,60 @@
+/**
+ * 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.cxf.tracing.opentelemetry.jaxrs;
+
+import java.io.IOException;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.client.ClientResponseContext;
+import jakarta.ws.rs.client.ClientResponseFilter;
+import jakarta.ws.rs.ext.Provider;
+import org.apache.cxf.tracing.opentelemetry.AbstractOpenTelemetryClientProvider;
+import org.apache.cxf.tracing.opentelemetry.TraceScope;
+
+@Provider
+public class OpenTelemetryClientProvider extends AbstractOpenTelemetryClientProvider
+ implements ClientRequestFilter, ClientResponseFilter {
+
+ public OpenTelemetryClientProvider(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ super(openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void filter(final ClientRequestContext requestContext) throws IOException {
+ final TraceScopeHolder<TraceScope> holder = super.startTraceSpan(requestContext.getStringHeaders(),
+ requestContext.getUri(),
+ requestContext.getMethod());
+
+ if (holder != null) {
+ requestContext.setProperty(TRACE_SPAN, holder);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext)
+ throws IOException {
+ final TraceScopeHolder<TraceScope> holder = (TraceScopeHolder<TraceScope>)requestContext
+ .getProperty(TRACE_SPAN);
+ super.stopTraceSpan(holder, responseContext.getStatus());
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryContextProvider.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryContextProvider.java
new file mode 100644
index 0000000000..29eddd82d8
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryContextProvider.java
@@ -0,0 +1,49 @@
+/**
+ * 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.cxf.tracing.opentelemetry.jaxrs;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
+import org.apache.cxf.jaxrs.ext.ContextProvider;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.tracing.TracerContext;
+import org.apache.cxf.tracing.opentelemetry.OpenTelemetryContext;
+
+public class OpenTelemetryContextProvider implements ContextProvider<TracerContext> {
+ private final Tracer tracer;
+
+ public OpenTelemetryContextProvider(final Tracer tracer) {
+ this.tracer = tracer;
+ }
+
+ @Override
+ public TracerContext createContext(final Message message) {
+ // Check if there is a server span passed along with the message
+ final Span continuation = message.get(Span.class);
+
+ // If server span is already present, let us check if it is detached
+ // (asynchronous invocation)
+ if (continuation != null) {
+ return new OpenTelemetryContext(tracer, continuation);
+ }
+
+ return new OpenTelemetryContext(tracer);
+ }
+
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryFeature.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryFeature.java
new file mode 100644
index 0000000000..53dc19b19e
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryFeature.java
@@ -0,0 +1,56 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.tracing.opentelemetry.jaxrs;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import jakarta.ws.rs.core.Feature;
+import jakarta.ws.rs.core.FeatureContext;
+import jakarta.ws.rs.ext.Provider;
+
+@Provider
+public class OpenTelemetryFeature implements Feature {
+ private final OpenTelemetry openTelemetry;
+ private final String instrumentationName;
+
+ public OpenTelemetryFeature() {
+ this(GlobalOpenTelemetry.get());
+ }
+
+ public OpenTelemetryFeature(String instrumentationName) {
+ this(GlobalOpenTelemetry.get(), instrumentationName);
+ }
+
+ public OpenTelemetryFeature(final OpenTelemetry openTelemetry) {
+ this(openTelemetry,
+ org.apache.cxf.tracing.opentelemetry.OpenTelemetryFeature.DEFAULT_INSTRUMENTATION_NAME);
+ }
+
+ public OpenTelemetryFeature(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ this.openTelemetry = openTelemetry;
+ this.instrumentationName = instrumentationName;
+ }
+
+ @Override
+ public boolean configure(FeatureContext context) {
+ context.register(new OpenTelemetryProvider(openTelemetry, instrumentationName));
+ context.register(new OpenTelemetryContextProvider(openTelemetry.getTracer(instrumentationName)));
+ return true;
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryProvider.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryProvider.java
new file mode 100644
index 0000000000..f43e2ad244
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryProvider.java
@@ -0,0 +1,80 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.tracing.opentelemetry.jaxrs;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ContainerResponseContext;
+import jakarta.ws.rs.container.ContainerResponseFilter;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.container.Suspended;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.ext.Provider;
+import org.apache.cxf.tracing.opentelemetry.AbstractOpenTelemetryProvider;
+import org.apache.cxf.tracing.opentelemetry.TraceScope;
+
+@Provider
+public class OpenTelemetryProvider extends AbstractOpenTelemetryProvider
+ implements ContainerRequestFilter, ContainerResponseFilter {
+ @Context
+ private ResourceInfo resourceInfo;
+
+ public OpenTelemetryProvider(final OpenTelemetry openTelemetry, final String instrumentationName) {
+ super(openTelemetry, instrumentationName);
+ }
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext) throws IOException {
+ final TraceScopeHolder<TraceScope> holder = super.startTraceSpan(requestContext.getHeaders(),
+ requestContext.getUriInfo()
+ .getRequestUri(),
+ requestContext.getMethod());
+
+ if (holder != null) {
+ requestContext.setProperty(TRACE_SPAN, holder);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void filter(final ContainerRequestContext requestContext,
+ final ContainerResponseContext responseContext)
+ throws IOException {
+ super.stopTraceSpan(requestContext.getHeaders(), responseContext.getHeaders(),
+ responseContext.getStatus(),
+ (TraceScopeHolder<TraceScope>)requestContext.getProperty(TRACE_SPAN));
+ }
+
+ @Override
+ protected boolean isAsyncResponse() {
+ for (final Annotation[] annotations : resourceInfo.getResourceMethod().getParameterAnnotations()) {
+ for (final Annotation annotation : annotations) {
+ if (annotation.annotationType().equals(Suspended.class)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryTracerContextClassProvider.java b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryTracerContextClassProvider.java
new file mode 100644
index 0000000000..788fe9e3f1
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/java/org/apache/cxf/tracing/opentelemetry/jaxrs/OpenTelemetryTracerContextClassProvider.java
@@ -0,0 +1,29 @@
+/**
+ * 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.cxf.tracing.opentelemetry.jaxrs;
+
+import org.apache.cxf.jaxrs.ext.ContextClassProvider;
+import org.apache.cxf.tracing.TracerContext;
+
+public class OpenTelemetryTracerContextClassProvider implements ContextClassProvider {
+ @Override
+ public Class<?> getContextClass() {
+ return TracerContext.class;
+ }
+}
diff --git a/integration/tracing/tracing-opentelemetry/src/main/resources/META-INF/services/org.apache.cxf.jaxrs.ext.ContextClassProvider b/integration/tracing/tracing-opentelemetry/src/main/resources/META-INF/services/org.apache.cxf.jaxrs.ext.ContextClassProvider
new file mode 100644
index 0000000000..2fcb268970
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/main/resources/META-INF/services/org.apache.cxf.jaxrs.ext.ContextClassProvider
@@ -0,0 +1 @@
+org.apache.cxf.tracing.opentelemetry.jaxrs.OpenTelemetryTracerContextClassProvider
\ No newline at end of file
diff --git a/integration/tracing/tracing-opentelemetry/src/test/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryContextTest.java b/integration/tracing/tracing-opentelemetry/src/test/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryContextTest.java
new file mode 100644
index 0000000000..74f4719545
--- /dev/null
+++ b/integration/tracing/tracing-opentelemetry/src/test/java/org/apache/cxf/tracing/opentelemetry/OpenTelemetryContextTest.java
@@ -0,0 +1,54 @@
+/**
+ * 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.cxf.tracing.opentelemetry;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class OpenTelemetryContextTest {
+ @Rule
+ public OpenTelemetryRule otel = OpenTelemetryRule.create();
+
+ private OpenTelemetryContext context;
+ private Tracer tracer;
+
+ @Before
+ public void setUp() {
+ tracer = otel.getOpenTelemetry().getTracer("test");
+ context = new OpenTelemetryContext(tracer);
+ }
+
+ @Test
+ public void testUnwrapTracer() {
+ assertThat(context.unwrap(Tracer.class), sameInstance(tracer));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnwrapUnsupportedClass() {
+ context.unwrap(Scope.class);
+ }
+
+}
diff --git a/parent/pom.xml b/parent/pom.xml
index 10f10ea030..a6c0220e98 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -191,6 +191,8 @@
<cxf.netty.tcnative.version>2.0.46.Final</cxf.netty.tcnative.version>
<cxf.olingo.version>2.0.12</cxf.olingo.version>
<cxf.openjpa.version>3.2.2</cxf.openjpa.version>
+ <cxf.opentelemetry.version>1.28.0</cxf.opentelemetry.version>
+ <cxf.opentelemetry.semconv.version>1.28.0-alpha</cxf.opentelemetry.semconv.version>
<cxf.opentracing.version>0.33.0</cxf.opentracing.version>
<cxf.openwebbeans.version>2.0.27</cxf.openwebbeans.version>
<cxf.persistence-api.version>3.0.0</cxf.persistence-api.version>
@@ -1870,6 +1872,23 @@
<version>${cxf.opentracing.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-bom</artifactId>
+ <version>${cxf.opentelemetry.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-semconv</artifactId>
+ <version>${cxf.opentelemetry.semconv.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-opentracing-shim</artifactId>
+ <version>${cxf.opentelemetry.version}</version>
+ </dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-core</artifactId>
diff --git a/systests/tracing/pom.xml b/systests/tracing/pom.xml
index 977ed98cdb..82ff9035e0 100644
--- a/systests/tracing/pom.xml
+++ b/systests/tracing/pom.xml
@@ -104,6 +104,11 @@
<artifactId>cxf-integration-tracing-opentracing</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-integration-tracing-opentelemetry</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-testutils</artifactId>
@@ -149,6 +154,16 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-sdk-testing</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/BookStore.java b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/BookStore.java
index a6428e1ea6..7649dd7c47 100644
--- a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/BookStore.java
+++ b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/BookStore.java
@@ -43,14 +43,14 @@ import org.apache.cxf.tracing.Traceable;
import org.apache.cxf.tracing.TracerContext;
@Path("/bookstore/")
-public class BookStore<T extends Closeable> {
+public class BookStore<T extends AutoCloseable> {
@Context private TracerContext tracer;
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
@GET
@Path("/books")
@Produces(MediaType.APPLICATION_JSON)
- public Collection<Book> getBooks() throws IOException {
+ public Collection<Book> getBooks() throws Exception {
try (T span = tracer.startSpan("Get Books")) {
return books();
}
diff --git a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/HasAttribute.java b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/HasAttribute.java
new file mode 100644
index 0000000000..ce83cc46d2
--- /dev/null
+++ b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/HasAttribute.java
@@ -0,0 +1,53 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.systest.jaxrs.tracing.opentelemetry;
+
+import static org.hamcrest.Matchers.equalTo;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import org.hamcrest.FeatureMatcher;
+import org.hamcrest.Matcher;
+
+public class HasAttribute<T> extends FeatureMatcher<Attributes, T> {
+ private final AttributeKey<T> key;
+
+ public HasAttribute(AttributeKey<T> key, Matcher<T> valueMatcher) {
+ super(valueMatcher, "attribute map containing key " + key.getKey() + " with value",
+ "attribute value");
+ this.key = key;
+ }
+
+ public static <V> HasAttribute<V> hasAttribute(AttributeKey<V> key, Matcher<V> valueMatcher) {
+ return new HasAttribute<V>(key, valueMatcher);
+ }
+
+ public static <V> HasAttribute<V> hasAttribute(AttributeKey<V> key, V value) {
+ return hasAttribute(key, equalTo(value));
+ }
+
+ public static HasAttribute<String> hasAttribute(String key, String value) {
+ return hasAttribute(AttributeKey.stringKey(key), value);
+ }
+
+ @Override
+ protected T featureValueOf(Attributes attributes) {
+ return attributes.get(key);
+ }
+}
diff --git a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/HasSpan.java b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/HasSpan.java
new file mode 100644
index 0000000000..6ae1220608
--- /dev/null
+++ b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/HasSpan.java
@@ -0,0 +1,67 @@
+/**
+ * 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.cxf.systest.jaxrs.tracing.opentelemetry;
+
+import io.opentelemetry.sdk.trace.data.EventData;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.hamcrest.core.IsIterableContaining;
+
+public class HasSpan extends IsIterableContaining<SpanData> {
+ public HasSpan(final String name) {
+ this(name, null);
+ }
+
+ public HasSpan(final String name, final Matcher<Iterable<? super EventData>> matcher) {
+ super(new TypeSafeMatcher<SpanData>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("span with name ").appendValue(name).appendText(" ");
+
+ if (matcher != null) {
+ description.appendText(" and ");
+ matcher.describeTo(description);
+ }
+ }
+
+ @Override
+ protected boolean matchesSafely(SpanData item) {
+ if (!name.equals(item.getName())) {
+ return false;
+ }
+
+ if (matcher != null) {
+ return matcher.matches(item.getEvents());
+ }
+
+ return true;
+ }
+ });
+ }
+
+ public static HasSpan hasSpan(final String name) {
+ return new HasSpan(name);
+ }
+
+ public static HasSpan hasSpan(final String name, final Matcher<Iterable<? super EventData>> matcher) {
+ return new HasSpan(name, matcher);
+ }
+}
diff --git a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/IsLogContaining.java b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/IsLogContaining.java
new file mode 100644
index 0000000000..37582c2fe0
--- /dev/null
+++ b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/IsLogContaining.java
@@ -0,0 +1,44 @@
+/**
+ * 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.cxf.systest.jaxrs.tracing.opentelemetry;
+
+import io.opentelemetry.sdk.trace.data.EventData;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.hamcrest.core.IsIterableContaining;
+
+public class IsLogContaining extends IsIterableContaining<EventData> {
+ public IsLogContaining(final String value) {
+ super(new TypeSafeMatcher<EventData>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("annotation with name ").appendValue(value);
+ }
+
+ @Override
+ protected boolean matchesSafely(EventData item) {
+ return value.equals(item.getName());
+ }
+ });
+ }
+
+ public static IsLogContaining hasItem(final String value) {
+ return new IsLogContaining(value);
+ }
+}
diff --git a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/OpenTelemetryTracingTest.java b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/OpenTelemetryTracingTest.java
new file mode 100644
index 0000000000..8bd4985f33
--- /dev/null
+++ b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxrs/tracing/opentelemetry/OpenTelemetryTracingTest.java
@@ -0,0 +1,464 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.systest.jaxrs.tracing.opentelemetry;
+
+import io.opentelemetry.sdk.trace.data.SpanData;
+import static org.apache.cxf.systest.jaxrs.tracing.opentelemetry.HasAttribute.hasAttribute;
+import static org.apache.cxf.systest.jaxrs.tracing.opentelemetry.HasSpan.hasSpan;
+import static org.apache.cxf.systest.jaxrs.tracing.opentelemetry.IsLogContaining.hasItem;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.net.MalformedURLException;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.IntStream;
+
+import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.SpanId;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.api.trace.TraceFlags;
+import io.opentelemetry.api.trace.TraceId;
+import io.opentelemetry.api.trace.TraceState;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.context.propagation.TextMapSetter;
+import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.systest.jaxrs.tracing.BookStore;
+import org.apache.cxf.systest.jaxrs.tracing.NullPointerExceptionMapper;
+import org.apache.cxf.testutil.common.AbstractClientServerTestBase;
+import org.apache.cxf.testutil.common.AbstractTestServerBase;
+import org.apache.cxf.tracing.opentelemetry.OpenTelemetryClientFeature;
+import org.apache.cxf.tracing.opentelemetry.jaxrs.OpenTelemetryClientProvider;
+import org.apache.cxf.tracing.opentelemetry.jaxrs.OpenTelemetryFeature;
+import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class OpenTelemetryTracingTest extends AbstractClientServerTestBase {
+ public static final String PORT = allocatePort(OpenTelemetryTracingTest.class);
+
+ private static final AtomicLong RANDOM = new AtomicLong();
+ @ClassRule
+ public static OpenTelemetryRule otelRule = OpenTelemetryRule.create();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @BeforeClass
+ public static void startServers() throws Exception {
+ AbstractResourceInfo.clearAllMaps();
+ // keep out of process due to stack traces testing failures
+ assertTrue("server did not launch correctly", launchServer(OpenTelemetryServer.class, true));
+ }
+
+ private static WebClient createWebClient(final String path, final Object... providers) {
+ return WebClient.create("http://localhost:" + PORT + path, Arrays.asList(providers))
+ .accept(MediaType.APPLICATION_JSON);
+ }
+
+ private static Context fromRandom() {
+ return Context.root()
+ .with(Span.wrap(SpanContext
+ .create(TraceId.fromLongs(RANDOM.getAndIncrement(), RANDOM.getAndIncrement()),
+ SpanId.fromLong(RANDOM.getAndIncrement()), TraceFlags.getSampled(),
+ TraceState.getDefault())));
+ }
+
+ @After
+ public void tearDown() {
+ otelRule.clearSpans();
+ }
+
+ @Test
+ public void testThatNewSpanIsCreatedWhenNotProvided() {
+ final Response r = createWebClient("/bookstore/books").get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books"));
+ assertThat(otelRule.getSpans().get(1).getAttributes(),
+ hasAttribute(SemanticAttributes.HTTP_STATUS_CODE, 200L));
+ assertThat(otelRule.getSpans().get(1).getInstrumentationScopeInfo().getName(),
+ equalTo("jaxrs-server-test"));
+ }
+
+ @Test
+ public void testThatNewInnerSpanIsCreated() {
+ final Context parentContext = fromRandom();
+
+ try (Scope parentScope = parentContext.makeCurrent()) {
+ final Response r = withTrace(createWebClient("/bookstore/books")).get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books"));
+ }
+ }
+
+ @Test
+ public void testThatCurrentSpanIsAnnotatedWithKeyValue() {
+ final Context parentContext = fromRandom();
+
+ try (Scope parentScope = parentContext.makeCurrent()) {
+ final Response r = withTrace(createWebClient("/bookstore/book/1")).get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().size(), equalTo(1));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("GET /bookstore/book/1"));
+ assertThat(otelRule.getSpans().get(0).getAttributes(), hasAttribute("book-id", "1"));
+ }
+ }
+
+ @Test
+ public void testThatParallelSpanIsAnnotatedWithTimeline() {
+ final Context parentContext = fromRandom();
+
+ try (Scope parentScope = parentContext.makeCurrent()) {
+ final Response r = withTrace(createWebClient("/bookstore/process")).put("");
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans(), hasSpan("Processing books", hasItem("Processing started")));
+ assertThat(otelRule.getSpans(), hasSpan("PUT /bookstore/process"));
+ }
+ }
+
+ @Test
+ public void testThatNewChildSpanIsCreatedWhenParentIsProvided() {
+ final Response r = createWebClient("/bookstore/books",
+ new OpenTelemetryClientProvider(otelRule.getOpenTelemetry(), "jaxrs-client-test"))
+ .get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().toString(), otelRule.getSpans().size(), equalTo(3));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(0).getParentSpanContext().isValid(), equalTo(true));
+
+ // let's check that the parent client span has the custom instrumentation name in scope info
+ assertThat(otelRule.getSpans().get(2).getInstrumentationScopeInfo().getName(), equalTo("jaxrs-client-test"));
+ }
+
+ @Test
+ public void testThatNewInnerSpanIsCreatedUsingAsyncInvocation() throws InterruptedException {
+ final Context parentContext = fromRandom();
+
+ try (Scope parentScope = parentContext.makeCurrent()) {
+ final Response r = withTrace(createWebClient("/bookstore/books/async")).get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 2);
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertEquals("Processing books", otelRule.getSpans().get(0).getName());
+ assertEquals("GET /bookstore/books/async", otelRule.getSpans().get(1).getName());
+ assertThat(otelRule.getSpans().get(1).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(1).getParentSpanId(),
+ equalTo(Span.current().getSpanContext().getSpanId()));
+ }
+ }
+
+ @Test
+ public void testThatOuterSpanIsCreatedUsingAsyncInvocation() {
+ final Context parentContext = fromRandom();
+
+ try (Scope parentScope = parentContext.makeCurrent()) {
+ final Response r = withTrace(createWebClient("/bookstore/books/async/notrace")).get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().size(), equalTo(1));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("GET /bookstore/books/async/notrace"));
+ }
+ }
+
+ @Test
+ public void testThatNewSpanIsCreatedUsingAsyncInvocation() throws InterruptedException {
+ final Response r = createWebClient("/bookstore/books/async").get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 2);
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Processing books"));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books/async"));
+ }
+
+ @Test
+ public void testThatNewSpanIsCreatedWhenNotProvidedUsingAsyncClient() throws Exception {
+ final WebClient client = createWebClient("/bookstore/books",
+ new OpenTelemetryClientProvider(otelRule.getOpenTelemetry(), "jaxrs-client-test"));
+
+ final Response r = client.async().get().get(1L, TimeUnit.SECONDS);
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 3);
+
+ assertThat(otelRule.getSpans().size(), equalTo(3));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books"));
+ assertThat(otelRule.getSpans().get(1).getKind(), equalTo(SpanKind.SERVER));
+ assertThat(otelRule.getSpans().get(2).getName(), equalTo("GET " + client.getCurrentURI()));
+ assertThat(otelRule.getSpans().get(2).getKind(), equalTo(SpanKind.CLIENT));
+ }
+
+ @Test
+ public void testThatNewSpansAreCreatedWhenNotProvidedUsingMultipleAsyncClients() throws Exception {
+ final WebClient client = createWebClient("/bookstore/books",
+ new OpenTelemetryClientProvider(otelRule.getOpenTelemetry(), "jaxrs-client-test"));
+
+ // The intention is to make a calls one after another, not in parallel, to ensure the
+ // thread have trace contexts cleared out.
+ IntStream.range(0, 4).mapToObj(index -> client.async().get()).map(this::get)
+ .forEach(r -> assertEquals(Status.OK.getStatusCode(), r.getStatus()));
+
+ assertThat(otelRule.getSpans().size(), equalTo(12));
+
+ IntStream.range(0, 4).map(index -> index * 3).forEach(index -> {
+ assertThat(otelRule.getSpans().get(index).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(index + 1).getName(), equalTo("GET /bookstore/books"));
+ assertThat(otelRule.getSpans().get(index + 2).getName(),
+ equalTo("GET " + client.getCurrentURI()));
+ });
+ }
+
+ @Test
+ public void testThatNewSpansAreCreatedWhenNotProvidedUsingMultipleClients() throws Exception {
+ final WebClient client = createWebClient("/bookstore/books",
+ new OpenTelemetryClientProvider(otelRule.getOpenTelemetry(), "jaxrs-client-test"));
+
+ // The intention is to make a calls one after another, not in parallel, to ensure the
+ // thread have trace contexts cleared out.
+ IntStream.range(0, 4).mapToObj(index -> client.get())
+ .forEach(r -> assertEquals(Status.OK.getStatusCode(), r.getStatus()));
+
+ assertEquals(otelRule.getSpans().toString(), 12, otelRule.getSpans().size());
+
+ IntStream.range(0, 4).map(index -> index * 3).forEach(index -> {
+ assertThat(otelRule.getSpans().get(index).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(index + 1).getName(), equalTo("GET /bookstore/books"));
+ assertThat(otelRule.getSpans().get(index + 2).getName(),
+ equalTo("GET " + client.getCurrentURI()));
+ });
+ }
+
+ @Test
+ public void testThatProvidedSpanIsNotClosedWhenActive() throws MalformedURLException {
+ final WebClient client = createWebClient("/bookstore/books",
+ new OpenTelemetryClientProvider(otelRule.getOpenTelemetry(), "jaxrs-client-test"));
+
+ final Span span = otelRule.getOpenTelemetry().getTracer("test").spanBuilder("test span").startSpan();
+ try (Scope scope = span.makeCurrent()) {
+ final Response r = client.get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertEquals(otelRule.getSpans().toString(), 3, otelRule.getSpans().size());
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(0).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books"));
+ assertThat(otelRule.getSpans().get(1).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(2).getName(), equalTo("GET " + client.getCurrentURI()));
+ assertThat(otelRule.getSpans().get(2).getParentSpanContext().isValid(), equalTo(true));
+ } finally {
+ span.end();
+ }
+
+ // Await till flush happens, usually every second
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 4);
+
+ assertThat(otelRule.getSpans().size(), equalTo(4));
+ assertThat(otelRule.getSpans().get(3).getName(), equalTo("test span"));
+ assertThat(otelRule.getSpans().get(3).getParentSpanContext().isValid(), equalTo(false));
+ }
+
+ @Test
+ public void testThatProvidedSpanIsNotDetachedWhenActiveUsingAsyncClient() throws Exception {
+ final WebClient client = createWebClient("/bookstore/books",
+ new OpenTelemetryClientProvider(otelRule.getOpenTelemetry(), "jaxrs-client-test"));
+
+ final Span span = otelRule.getOpenTelemetry().getTracer("test").spanBuilder("test span").startSpan();
+ try (Scope scope = span.makeCurrent()) {
+ final Response r = client.async().get().get(1L, TimeUnit.MINUTES);
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+ assertThat(Span.current().getSpanContext().getSpanId(),
+ equalTo(span.getSpanContext().getSpanId()));
+
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 3);
+
+ assertThat(otelRule.getSpans().size(), equalTo(3));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(0).getParentSpanContext(), notNullValue());
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books"));
+ assertThat(otelRule.getSpans().get(1).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(2).getName(), equalTo("GET " + client.getCurrentURI()));
+ assertThat(otelRule.getSpans().get(2).getParentSpanContext(), notNullValue());
+ } finally {
+ span.end();
+ }
+
+ // Await till flush happens, usually every second
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 4);
+
+ assertThat(otelRule.getSpans().size(), equalTo(4));
+ assertThat(otelRule.getSpans().get(3).getName(), equalTo("test span"));
+ assertThat(otelRule.getSpans().get(3).getParentSpanContext().isValid(), equalTo(false));
+ }
+
+ @Test
+ public void testThatInnerSpanIsCreatedUsingPseudoAsyncInvocation() {
+ final Context parentContext = fromRandom();
+
+ try (Scope parentScope = parentContext.makeCurrent()) {
+ final Response r = withTrace(createWebClient("/bookstore/books/pseudo-async")).get();
+ assertEquals(Status.OK.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books/pseudo-async"));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Processing books"));
+ }
+ }
+
+ @Test
+ public void testThatNewSpanIsCreatedOnClientTimeout() {
+ final WebClient client = WebClient
+ .create("http://localhost:" + PORT + "/bookstore/books/long", Collections.emptyList(),
+ Arrays.asList(new OpenTelemetryClientFeature(otelRule.getOpenTelemetry())), null)
+ .accept(MediaType.APPLICATION_JSON);
+
+ HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
+ httpClientPolicy.setConnectionTimeout(100);
+ httpClientPolicy.setReceiveTimeout(100);
+ WebClient.getConfig(client).getHttpConduit().setClient(httpClientPolicy);
+
+ expectedException.expect(ProcessingException.class);
+ try {
+ client.get();
+ } finally {
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 2);
+ assertThat(otelRule.getSpans().toString(), otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("GET " + client.getCurrentURI()));
+ assertThat(otelRule.getSpans().get(0).getStatus().getStatusCode(), equalTo(StatusCode.ERROR));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("GET /bookstore/books/long"));
+ }
+ }
+
+ @Test
+ public void testThatErrorSpanIsCreatedOnExceptionWhenNotProvided() {
+ final Response r = createWebClient("/bookstore/books/exception").get();
+ assertEquals(Status.INTERNAL_SERVER_ERROR.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().toString(), otelRule.getSpans().size(), equalTo(1));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("GET /bookstore/books/exception"));
+ assertThat(otelRule.getSpans().get(0).getAttributes(),
+ hasAttribute(SemanticAttributes.HTTP_STATUS_CODE, 500L));
+ }
+
+ @Test
+ public void testThatErrorSpanIsCreatedOnErrorWhenNotProvided() {
+ final Response r = createWebClient("/bookstore/books/error").get();
+ assertEquals(Status.SERVICE_UNAVAILABLE.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().toString(), otelRule.getSpans().size(), equalTo(1));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("GET /bookstore/books/error"));
+ assertThat(otelRule.getSpans().get(0).getAttributes(),
+ hasAttribute(SemanticAttributes.HTTP_STATUS_CODE, 503L));
+ }
+
+ @Test
+ public void testThatErrorSpanIsCreatedOnMappedExceptionWhenNotProvided() {
+ final Response r = createWebClient("/bookstore/books/mapper").get();
+ assertEquals(Status.NOT_FOUND.getStatusCode(), r.getStatus());
+
+ assertThat(otelRule.getSpans().toString(), otelRule.getSpans().size(), equalTo(1));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("GET /bookstore/books/mapper"));
+ assertThat(otelRule.getSpans().get(0).getAttributes(),
+ hasAttribute(SemanticAttributes.HTTP_STATUS_CODE, 404L));
+ }
+
+ private WebClient withTrace(final WebClient client) {
+ GlobalOpenTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), client,
+ new TextMapSetter<WebClient>() {
+ @Override
+ public void set(WebClient carrier,
+ String key,
+ String value) {
+ carrier.header(key, value);
+ }
+ });
+
+ return client;
+ }
+
+ private <T> T get(final Future<T> future) {
+ try {
+ return future.get(1L, TimeUnit.MINUTES);
+ } catch (InterruptedException | TimeoutException | ExecutionException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public static class OpenTelemetryServer extends AbstractTestServerBase {
+
+ private org.apache.cxf.endpoint.Server server;
+
+ @Override
+ protected void run() {
+ final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+ sf.setResourceClasses(BookStore.class);
+ sf.setResourceProvider(BookStore.class, new SingletonResourceProvider(new BookStore<Scope>()));
+ sf.setAddress("http://localhost:" + PORT);
+ sf.setProvider(new JacksonJsonProvider());
+ sf.setProvider(new OpenTelemetryFeature(otelRule.getOpenTelemetry(), "jaxrs-server-test"));
+ sf.setProvider(new NullPointerExceptionMapper());
+ server = sf.create();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ server.destroy();
+ }
+ }
+}
diff --git a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxws/tracing/opentelemetry/BookStore.java b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxws/tracing/opentelemetry/BookStore.java
new file mode 100644
index 0000000000..e1f50ff199
--- /dev/null
+++ b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxws/tracing/opentelemetry/BookStore.java
@@ -0,0 +1,69 @@
+/**
+ * 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.cxf.systest.jaxws.tracing.opentelemetry;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.UUID;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
+import jakarta.annotation.Resource;
+import jakarta.jws.WebMethod;
+import jakarta.jws.WebService;
+import jakarta.xml.ws.WebServiceContext;
+import jakarta.xml.ws.handler.MessageContext;
+import org.apache.cxf.systest.Book;
+import org.apache.cxf.systest.jaxws.tracing.BookStoreService;
+
+@WebService(endpointInterface = "org.apache.cxf.systest.jaxws.tracing.BookStoreService", serviceName = "BookStore")
+public class BookStore implements BookStoreService {
+ private final Tracer tracer;
+
+ @Resource
+ private WebServiceContext context;
+
+ public BookStore() {
+ tracer = GlobalOpenTelemetry.getTracer(BookStore.class.getName());
+ }
+
+ @WebMethod
+ public Collection<Book> getBooks() {
+ final Span span = tracer.spanBuilder("Get Books").startSpan();
+ try (Scope scope = span.makeCurrent()) {
+ return Arrays.asList(new Book("Apache CXF in Action", UUID.randomUUID().toString()),
+ new Book("Mastering Apache CXF", UUID.randomUUID().toString()));
+ } finally {
+ span.end();
+ }
+ }
+
+ @WebMethod
+ public int removeBooks() {
+ throw new RuntimeException("Unable to remove books");
+ }
+
+ @WebMethod
+ public void addBooks() {
+ final MessageContext ctx = context.getMessageContext();
+ ctx.put(MessageContext.HTTP_RESPONSE_CODE, 202);
+ }
+}
diff --git a/systests/tracing/src/test/java/org/apache/cxf/systest/jaxws/tracing/opentelemetry/OpenTelemetryTracingTest.java b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxws/tracing/opentelemetry/OpenTelemetryTracingTest.java
new file mode 100644
index 0000000000..969ce2f088
--- /dev/null
+++ b/systests/tracing/src/test/java/org/apache/cxf/systest/jaxws/tracing/opentelemetry/OpenTelemetryTracingTest.java
@@ -0,0 +1,277 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.cxf.systest.jaxws.tracing.opentelemetry;
+
+import static org.apache.cxf.systest.jaxrs.tracing.opentelemetry.HasAttribute.hasAttribute;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.SpanId;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.TraceFlags;
+import io.opentelemetry.api.trace.TraceId;
+import io.opentelemetry.api.trace.TraceState;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
+import jakarta.xml.ws.soap.SOAPFaultException;
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.ext.logging.LoggingInInterceptor;
+import org.apache.cxf.ext.logging.LoggingOutInterceptor;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.frontend.ClientProxy;
+import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
+import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.systest.jaxws.tracing.BookStoreService;
+import org.apache.cxf.testutil.common.AbstractClientServerTestBase;
+import org.apache.cxf.testutil.common.AbstractTestServerBase;
+import org.apache.cxf.tracing.opentelemetry.OpenTelemetryClientFeature;
+import org.apache.cxf.tracing.opentelemetry.OpenTelemetryFeature;
+import org.apache.cxf.tracing.opentelemetry.internal.TextMapInjectAdapter;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class OpenTelemetryTracingTest extends AbstractClientServerTestBase {
+ public static final String PORT = allocatePort(OpenTelemetryTracingTest.class);
+
+ private static final AtomicLong RANDOM = new AtomicLong();
+
+ @ClassRule
+ public static OpenTelemetryRule otelRule = OpenTelemetryRule.create();
+
+ @BeforeClass
+ public static void startServers() throws Exception {
+ // keep out of process due to stack traces testing failures
+ assertTrue("server did not launch correctly", launchServer(BraveServer.class, true));
+ }
+
+ private static BookStoreService createJaxWsService() {
+ return createJaxWsService(Collections.emptyMap());
+ }
+
+ private static BookStoreService createJaxWsService(final Map<String, List<String>> headers) {
+ return createJaxWsService(headers, null);
+ }
+
+ private static BookStoreService createJaxWsService(final Map<String, List<String>> headers,
+ final Feature feature) {
+
+ JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
+ factory.getOutInterceptors().add(new LoggingOutInterceptor());
+ factory.getInInterceptors().add(new LoggingInInterceptor());
+ factory.setServiceClass(BookStoreService.class);
+ factory.setAddress("http://localhost:" + PORT + "/BookStore");
+
+ if (feature != null) {
+ factory.getFeatures().add(feature);
+ }
+
+ final BookStoreService service = (BookStoreService)factory.create();
+ final Client proxy = ClientProxy.getClient(service);
+ proxy.getRequestContext().put(Message.PROTOCOL_HEADERS, headers);
+
+ return service;
+ }
+
+ private static Context fromRandom() {
+ return Context.root()
+ .with(Span.wrap(SpanContext
+ .create(TraceId.fromLongs(RANDOM.getAndIncrement(), RANDOM.getAndIncrement()),
+ SpanId.fromLong(RANDOM.getAndIncrement()), TraceFlags.getSampled(),
+ TraceState.getDefault())));
+ }
+
+ @After
+ public void tearDown() {
+ otelRule.clearSpans();
+ }
+
+ @Test
+ public void testThatNewSpanIsCreatedWhenNotProvided() throws Exception {
+ final BookStoreService service = createJaxWsService();
+ assertThat(service.getBooks().size(), equalTo(2));
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("POST /BookStore"));
+ }
+
+ @Test
+ public void testThatNewInnerSpanIsCreated() throws Exception {
+ final Context parentContext = fromRandom();
+
+ try (Scope parentScope = parentContext.makeCurrent()) {
+ final Map<String, List<String>> headers = new HashMap<>();
+ GlobalOpenTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), headers,
+ TextMapInjectAdapter.get());
+
+ final BookStoreService service = createJaxWsService(headers);
+ assertThat(service.getBooks().size(), equalTo(2));
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("POST /BookStore"));
+ }
+ }
+
+ @Test
+ public void testThatNewChildSpanIsCreatedWhenParentIsProvided() throws Exception {
+ final BookStoreService service = createJaxWsService(new OpenTelemetryClientFeature(otelRule
+ .getOpenTelemetry(), "jaxws-client-test"));
+ assertThat(service.getBooks().size(), equalTo(2));
+
+ assertThat(otelRule.getSpans().size(), equalTo(3));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(0).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("POST /BookStore"));
+ assertThat(otelRule.getSpans().get(1).getKind(), equalTo(SpanKind.SERVER));
+ assertThat(otelRule.getSpans().get(2).getName(),
+ equalTo("POST http://localhost:" + PORT + "/BookStore"));
+ assertThat(otelRule.getSpans().get(2).getKind(), equalTo(SpanKind.CLIENT));
+ }
+
+ @Test
+ public void testThatProvidedSpanIsNotClosedWhenActive() throws Exception {
+ final BookStoreService service = createJaxWsService(new OpenTelemetryClientFeature(otelRule
+ .getOpenTelemetry(), "jaxws-client-test"));
+
+ try (Scope parentScope = Context.root().makeCurrent()) {
+ final Span span = otelRule.getOpenTelemetry().getTracer("test").spanBuilder("test span")
+ .startSpan();
+ try (Scope scope = span.makeCurrent()) {
+ assertThat(service.getBooks().size(), equalTo(2));
+ assertThat(Span.current(), not(nullValue()));
+
+ assertThat(otelRule.getSpans().size(), equalTo(3));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("Get Books"));
+ assertThat(otelRule.getSpans().get(0).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(0).getInstrumentationScopeInfo().getName(),
+ equalTo(BookStore.class.getName()));
+ assertThat(otelRule.getSpans().get(1).getName(), equalTo("POST /BookStore"));
+ assertThat(otelRule.getSpans().get(1).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(1).getInstrumentationScopeInfo().getName(),
+ equalTo("jaxws-server-test"));
+ assertThat(otelRule.getSpans().get(2).getName(),
+ equalTo("POST http://localhost:" + PORT + "/BookStore"));
+ assertThat(otelRule.getSpans().get(2).getParentSpanContext().isValid(), equalTo(true));
+ assertThat(otelRule.getSpans().get(2).getInstrumentationScopeInfo().getName(),
+ equalTo("jaxws-client-test"));
+ } finally {
+ span.end();
+ }
+
+ // Await till flush happens, usually every second
+ await().atMost(Duration.ofSeconds(1L)).until(() -> otelRule.getSpans().size() == 4);
+
+ assertThat(otelRule.getSpans().size(), equalTo(4));
+ assertThat(otelRule.getSpans().get(3).getName(), equalTo("test span"));
+ assertThat(otelRule.getSpans().get(3).getParentSpanContext().isValid(), equalTo(false));
+ }
+ }
+
+ @Test
+ public void testThatNewSpanIsCreatedInCaseOfFault() throws Exception {
+ final BookStoreService service = createJaxWsService();
+
+ try {
+ service.removeBooks();
+ fail("Expected SOAPFaultException to be raised");
+ } catch (final SOAPFaultException ex) {
+ /* expected exception */
+ }
+
+ assertThat(otelRule.getSpans().size(), equalTo(1));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("POST /BookStore"));
+ }
+
+ @Test
+ public void testThatNewChildSpanIsCreatedWhenParentIsProvidedInCaseOfFault() throws Exception {
+ final BookStoreService service = createJaxWsService(new OpenTelemetryClientFeature(otelRule
+ .getOpenTelemetry(), "jaxws-client-test"));
+
+ try {
+ service.removeBooks();
+ fail("Expected SOAPFaultException to be raised");
+ } catch (final SOAPFaultException ex) {
+ /* expected exception */
+ }
+
+ assertThat(otelRule.getSpans().size(), equalTo(2));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("POST /BookStore"));
+ assertThat(otelRule.getSpans().get(0).getAttributes(),
+ hasAttribute(SemanticAttributes.HTTP_STATUS_CODE, 500L));
+ assertThat(otelRule.getSpans().get(1).getName(),
+ equalTo("POST http://localhost:" + PORT + "/BookStore"));
+ }
+
+ @Test
+ public void testThatNewChildSpanIsCreatedWhenParentIsProvidedAndCustomStatusCodeReturned()
+ throws Exception {
+ final BookStoreService service = createJaxWsService(new OpenTelemetryClientFeature(otelRule
+ .getOpenTelemetry(), "jaxws-client-test"));
+ service.addBooks();
+
+ assertThat(otelRule.getSpans().size(), equalTo(1));
+ assertThat(otelRule.getSpans().get(0).getName(), equalTo("POST /BookStore"));
+ assertThat(otelRule.getSpans().get(0).getAttributes(),
+ hasAttribute(SemanticAttributes.HTTP_STATUS_CODE, 202L));
+ }
+
+ private BookStoreService createJaxWsService(final Feature feature) {
+ return createJaxWsService(Collections.emptyMap(), feature);
+ }
+
+ public static class BraveServer extends AbstractTestServerBase {
+
+ private org.apache.cxf.endpoint.Server server;
+
+ @Override
+ protected void run() {
+ final JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
+ sf.setServiceClass(BookStore.class);
+ sf.setAddress("http://localhost:" + PORT);
+ sf.getFeatures().add(new OpenTelemetryFeature(otelRule.getOpenTelemetry(), "jaxws-server-test"));
+ server = sf.create();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ server.destroy();
+ }
+ }
+}