Skip to content

Commit 516aed9

Browse files
[Maven Extension] Migrate from Plexus to JSR 330 dependency injection APIs (#1320)
1 parent 14c385e commit 516aed9

File tree

14 files changed

+137
-191
lines changed

14 files changed

+137
-191
lines changed

maven-extension/build.gradle.kts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,31 @@ plugins {
66
}
77

88
// NOTE
9-
// `META-INF/plexus/components.xml` is manually handled under src/main/resources because there is no Gradle
10-
// equivalent to the Maven plugin `plexus-component-metadata:generate-metadata`
9+
// `META-INF/sis/javax.inject.Named` is manually handled under src/main/resources because there is
10+
// no Gradle equivalent to the Maven plugin `org.eclipse.sisu:sisu-maven-plugin`
1111

1212
description = "Maven extension to observe Maven builds with distributed traces using OpenTelemetry SDK"
1313
otelJava.moduleName.set("io.opentelemetry.maven")
1414

1515
dependencies {
16-
implementation("org.codehaus.plexus:plexus-component-annotations:2.1.1")
16+
compileOnly("javax.inject:javax.inject:1")
1717

1818
implementation("io.opentelemetry:opentelemetry-api")
1919
implementation("io.opentelemetry:opentelemetry-sdk")
2020
implementation("io.opentelemetry:opentelemetry-sdk-trace")
21+
implementation("io.opentelemetry:opentelemetry-sdk-metrics")
22+
implementation("io.opentelemetry:opentelemetry-sdk-logs")
2123
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
2224
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
25+
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
2326
implementation("io.opentelemetry.semconv:opentelemetry-semconv")
2427
implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating:1.25.0-alpha")
25-
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
2628

2729
annotationProcessor("com.google.auto.value:auto-value")
2830
compileOnly("com.google.auto.value:auto-value-annotations")
2931

30-
compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update this version
32+
compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update, support older mvn versions
3133
compileOnly("org.slf4j:slf4j-api")
32-
compileOnly("org.sonatype.aether:aether-api:1.13.1")
3334

3435
testImplementation("org.apache.maven:maven-core:3.5.0")
3536
testImplementation("org.slf4j:slf4j-simple")

maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java

Lines changed: 41 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,44 @@
88
import io.opentelemetry.api.OpenTelemetry;
99
import io.opentelemetry.api.trace.Tracer;
1010
import io.opentelemetry.context.propagation.ContextPropagators;
11+
import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes;
1112
import io.opentelemetry.sdk.OpenTelemetrySdk;
1213
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
1314
import io.opentelemetry.sdk.common.CompletableResultCode;
15+
import java.io.Closeable;
1416
import java.util.HashMap;
1517
import java.util.Locale;
1618
import java.util.Map;
1719
import java.util.concurrent.TimeUnit;
1820
import javax.annotation.Nullable;
19-
import org.codehaus.plexus.component.annotations.Component;
20-
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
21-
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
21+
import javax.annotation.PreDestroy;
22+
import javax.inject.Named;
23+
import javax.inject.Singleton;
2224
import org.slf4j.Logger;
2325
import org.slf4j.LoggerFactory;
2426

2527
/** Service to configure the {@link OpenTelemetry} instance. */
26-
@Component(role = OpenTelemetrySdkService.class, hint = "opentelemetry-service")
27-
public final class OpenTelemetrySdkService implements Initializable, Disposable {
28+
@Named
29+
@Singleton
30+
public final class OpenTelemetrySdkService implements Closeable {
2831

2932
static final String VERSION =
3033
OpenTelemetrySdkService.class.getPackage().getImplementationVersion();
3134

3235
private static final Logger logger = LoggerFactory.getLogger(OpenTelemetrySdkService.class);
3336

34-
private OpenTelemetry openTelemetry = OpenTelemetry.noop();
35-
@Nullable private OpenTelemetrySdk openTelemetrySdk;
37+
private final OpenTelemetrySdk openTelemetrySdk;
3638

37-
@Nullable private Tracer tracer;
39+
private final Tracer tracer;
3840

39-
private boolean mojosInstrumentationEnabled;
41+
private final boolean mojosInstrumentationEnabled;
4042

41-
/** Visible for testing */
42-
@Nullable AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk;
43+
private boolean disposed;
4344

44-
@Override
45-
public synchronized void dispose() {
46-
logger.debug("OpenTelemetry: dispose OpenTelemetrySdkService...");
47-
OpenTelemetrySdk openTelemetrySdk = this.openTelemetrySdk;
48-
if (openTelemetrySdk != null) {
49-
logger.debug("OpenTelemetry: Shutdown SDK Trace Provider...");
50-
CompletableResultCode sdkProviderShutdown =
51-
openTelemetrySdk.getSdkTracerProvider().shutdown();
52-
sdkProviderShutdown.join(10, TimeUnit.SECONDS);
53-
if (sdkProviderShutdown.isSuccess()) {
54-
logger.debug("OpenTelemetry: SDK Trace Provider shut down");
55-
} else {
56-
logger.warn(
57-
"OpenTelemetry: Failure to shutdown SDK Trace Provider (done: {})",
58-
sdkProviderShutdown.isDone());
59-
}
60-
this.openTelemetrySdk = null;
61-
}
62-
this.openTelemetry = OpenTelemetry.noop();
63-
64-
this.autoConfiguredOpenTelemetrySdk = null;
65-
logger.debug("OpenTelemetry: OpenTelemetrySdkService disposed");
66-
}
67-
68-
@Override
69-
public void initialize() {
70-
logger.debug("OpenTelemetry: Initialize OpenTelemetrySdkService v{}...", VERSION);
45+
public OpenTelemetrySdkService() {
46+
logger.debug(
47+
"OpenTelemetry: Initialize OpenTelemetrySdkService v{}...",
48+
MavenOtelSemanticAttributes.TELEMETRY_DISTRO_VERSION_VALUE);
7149

7250
// Change default of "otel.[traces,metrics,logs].exporter" from "otlp" to "none"
7351
// The impacts are
@@ -80,36 +58,48 @@ public void initialize() {
8058
properties.put("otel.metrics.exporter", "none");
8159
properties.put("otel.logs.exporter", "none");
8260

83-
this.autoConfiguredOpenTelemetrySdk =
61+
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
8462
AutoConfiguredOpenTelemetrySdk.builder()
8563
.setServiceClassLoader(getClass().getClassLoader())
8664
.addPropertiesSupplier(() -> properties)
8765
.disableShutdownHook()
8866
.build();
8967

90-
if (logger.isDebugEnabled()) {
91-
logger.debug("OpenTelemetry: OpenTelemetry SDK initialized");
92-
}
9368
this.openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
94-
this.openTelemetry = this.openTelemetrySdk;
9569

9670
Boolean mojoSpansEnabled = getBooleanConfig("otel.instrumentation.maven.mojo.enabled");
97-
this.mojosInstrumentationEnabled = mojoSpansEnabled == null ? true : mojoSpansEnabled;
71+
this.mojosInstrumentationEnabled = mojoSpansEnabled == null || mojoSpansEnabled;
9872

99-
this.tracer = openTelemetry.getTracer("io.opentelemetry.contrib.maven", VERSION);
73+
this.tracer = openTelemetrySdk.getTracer("io.opentelemetry.contrib.maven", VERSION);
10074
}
10175

102-
public Tracer getTracer() {
103-
Tracer tracer = this.tracer;
104-
if (tracer == null) {
105-
throw new IllegalStateException("Not initialized");
76+
@PreDestroy
77+
@Override
78+
public synchronized void close() {
79+
if (disposed) {
80+
logger.debug("OpenTelemetry: OpenTelemetry SDK already shut down, ignore");
81+
} else {
82+
logger.debug("OpenTelemetry: Shutdown OpenTelemetry SDK...");
83+
CompletableResultCode openTelemetrySdkShutdownResult =
84+
this.openTelemetrySdk.shutdown().join(10, TimeUnit.SECONDS);
85+
if (openTelemetrySdkShutdownResult.isSuccess()) {
86+
logger.debug("OpenTelemetry: OpenTelemetry SDK successfully shut down");
87+
} else {
88+
logger.warn(
89+
"OpenTelemetry: Failure to shutdown OpenTelemetry SDK (done: {})",
90+
openTelemetrySdkShutdownResult.isDone());
91+
}
92+
this.disposed = true;
10693
}
107-
return tracer;
94+
}
95+
96+
public Tracer getTracer() {
97+
return this.tracer;
10898
}
10999

110100
/** Returns the {@link ContextPropagators} for this {@link OpenTelemetry}. */
111101
public ContextPropagators getPropagators() {
112-
return openTelemetry.getPropagators();
102+
return this.openTelemetrySdk.getPropagators();
113103
}
114104

115105
public boolean isMojosInstrumentationEnabled() {

maven-extension/src/main/java/io/opentelemetry/maven/OtelExecutionListener.java

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map;
2222
import java.util.Optional;
2323
import java.util.stream.Collectors;
24+
import javax.annotation.Nonnull;
2425
import javax.annotation.Nullable;
2526
import org.apache.maven.execution.AbstractExecutionListener;
2627
import org.apache.maven.execution.ExecutionEvent;
@@ -29,20 +30,15 @@
2930
import org.apache.maven.lifecycle.LifecycleExecutionException;
3031
import org.apache.maven.plugin.MojoExecution;
3132
import org.apache.maven.project.MavenProject;
32-
import org.codehaus.plexus.component.annotations.Component;
33-
import org.codehaus.plexus.component.annotations.Requirement;
3433
import org.slf4j.Logger;
3534
import org.slf4j.LoggerFactory;
3635

3736
/**
38-
* Close the OpenTelemetry SDK (see {@link OpenTelemetrySdkService#dispose()}) on the end of
39-
* execution of the last project ({@link #projectSucceeded(ExecutionEvent)} and {@link
40-
* #projectFailed(ExecutionEvent)}) rather than on the end of the Maven session {@link
41-
* #sessionEnded(ExecutionEvent)} because OpenTelemetry and GRPC classes are unloaded by the Maven
42-
* classloader before {@link #sessionEnded(ExecutionEvent)} causing {@link NoClassDefFoundError}
43-
* messages in the logs.
37+
* Don't mark this class as {@link javax.inject.Named} and {@link javax.inject.Singleton} because
38+
* Maven Sisu doesn't automatically load instance of {@link ExecutionListener} as Maven Extension
39+
* hooks the same way Maven Plexus did so we manually hook this instance of {@link
40+
* ExecutionListener} through the {@link OtelLifecycleParticipant#afterProjectsRead(MavenSession)}.
4441
*/
45-
@Component(role = ExecutionListener.class, hint = "otel-execution-listener")
4642
public final class OtelExecutionListener extends AbstractExecutionListener {
4743

4844
private static final Logger logger = LoggerFactory.getLogger(OtelExecutionListener.class);
@@ -56,17 +52,16 @@ public final class OtelExecutionListener extends AbstractExecutionListener {
5652
*/
5753
private static final ThreadLocal<Scope> MOJO_EXECUTION_SCOPE = new ThreadLocal<>();
5854

59-
@SuppressWarnings("NullAway") // Automatically initialized by DI
60-
@Requirement
61-
private SpanRegistry spanRegistry;
55+
private final SpanRegistry spanRegistry;
6256

63-
@SuppressWarnings("NullAway") // Automatically initialized by DI
64-
@Requirement
65-
private OpenTelemetrySdkService openTelemetrySdkService;
57+
private final OpenTelemetrySdkService openTelemetrySdkService;
6658

6759
private final Map<MavenGoal, MojoGoalExecutionHandler> mojoGoalExecutionHandlers;
6860

69-
public OtelExecutionListener() {
61+
OtelExecutionListener(
62+
SpanRegistry spanRegistry, OpenTelemetrySdkService openTelemetrySdkService) {
63+
this.spanRegistry = spanRegistry;
64+
this.openTelemetrySdkService = openTelemetrySdkService;
7065
this.mojoGoalExecutionHandlers =
7166
MojoGoalExecutionHandlerConfiguration.loadMojoGoalExecutionHandler(
7267
OtelExecutionListener.class.getClassLoader());
@@ -102,36 +97,6 @@ public OtelExecutionListener() {
10297
}
10398
}
10499

105-
/**
106-
* Register in given {@link OtelExecutionListener} to the lifecycle of the given {@link
107-
* MavenSession}
108-
*
109-
* @see org.apache.maven.execution.MavenExecutionRequest#setExecutionListener(ExecutionListener)
110-
*/
111-
public static void registerOtelExecutionListener(
112-
MavenSession session, OtelExecutionListener otelExecutionListener) {
113-
114-
ExecutionListener initialExecutionListener = session.getRequest().getExecutionListener();
115-
if (initialExecutionListener instanceof ChainedExecutionListener
116-
|| initialExecutionListener instanceof OtelExecutionListener) {
117-
// already initialized
118-
logger.debug(
119-
"OpenTelemetry: OpenTelemetry extension already registered as execution listener, skip.");
120-
} else if (initialExecutionListener == null) {
121-
session.getRequest().setExecutionListener(otelExecutionListener);
122-
logger.debug(
123-
"OpenTelemetry: OpenTelemetry extension registered as execution listener. No execution listener initially defined");
124-
} else {
125-
session
126-
.getRequest()
127-
.setExecutionListener(
128-
new ChainedExecutionListener(otelExecutionListener, initialExecutionListener));
129-
logger.debug(
130-
"OpenTelemetry: OpenTelemetry extension registered as execution listener. InitialExecutionListener: "
131-
+ initialExecutionListener);
132-
}
133-
}
134-
135100
@Override
136101
public void sessionStarted(ExecutionEvent executionEvent) {
137102
MavenProject project = executionEvent.getSession().getTopLevelProject();
@@ -370,7 +335,7 @@ public void mojoFailed(ExecutionEvent executionEvent) {
370335

371336
@Override
372337
public void sessionEnded(ExecutionEvent event) {
373-
logger.debug("OpenTelemetry: Maven session ended");
338+
logger.debug("OpenTelemetry: Maven session ended, end root span");
374339
spanRegistry.removeRootSpan().end();
375340
}
376341

@@ -382,7 +347,7 @@ public Iterable<String> keys(Map<String, String> environmentVariables) {
382347

383348
@Override
384349
@Nullable
385-
public String get(@Nullable Map<String, String> environmentVariables, String key) {
350+
public String get(@Nullable Map<String, String> environmentVariables, @Nonnull String key) {
386351
return environmentVariables == null
387352
? null
388353
: environmentVariables.get(key.toUpperCase(Locale.ROOT));

maven-extension/src/main/java/io/opentelemetry/maven/OtelLifecycleParticipant.java

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,59 @@
55

66
package io.opentelemetry.maven;
77

8+
import javax.inject.Inject;
9+
import javax.inject.Named;
10+
import javax.inject.Singleton;
811
import org.apache.maven.AbstractMavenLifecycleParticipant;
912
import org.apache.maven.execution.ExecutionListener;
1013
import org.apache.maven.execution.MavenSession;
11-
import org.codehaus.plexus.component.annotations.Component;
12-
import org.codehaus.plexus.component.annotations.Requirement;
1314
import org.slf4j.Logger;
1415
import org.slf4j.LoggerFactory;
1516

1617
/** Add the {@link OtelExecutionListener} to the lifecycle of the Maven execution */
17-
@Component(role = AbstractMavenLifecycleParticipant.class)
18+
@Named
19+
@Singleton
1820
public final class OtelLifecycleParticipant extends AbstractMavenLifecycleParticipant {
1921

2022
private static final Logger logger = LoggerFactory.getLogger(OtelLifecycleParticipant.class);
2123

22-
@SuppressWarnings("NullAway") // Automatically initialized by DI
23-
@Requirement(role = ExecutionListener.class, hint = "otel-execution-listener")
24-
private OtelExecutionListener otelExecutionListener;
24+
private final OtelExecutionListener otelExecutionListener;
2525

2626
/**
27-
* For an unknown reason, {@link #afterProjectsRead(MavenSession)} is invoked when the module is
28-
* declared as an extension in pom.xml but {@link #afterSessionStart(MavenSession)} is not invoked
27+
* Manually instantiate {@link OtelExecutionListener} and hook it in the Maven build lifecycle
28+
* because Maven Sisu doesn't load it when Maven Plexus did.
2929
*/
30-
@Override
31-
public void afterProjectsRead(MavenSession session) {
32-
OtelExecutionListener.registerOtelExecutionListener(session, this.otelExecutionListener);
33-
logger.debug("OpenTelemetry: afterProjectsRead");
34-
}
35-
36-
@Override
37-
public void afterSessionStart(MavenSession session) {
38-
OtelExecutionListener.registerOtelExecutionListener(session, this.otelExecutionListener);
39-
logger.debug("OpenTelemetry: afterSessionStart");
30+
@Inject
31+
OtelLifecycleParticipant(
32+
OpenTelemetrySdkService openTelemetrySdkService, SpanRegistry spanRegistry) {
33+
this.otelExecutionListener = new OtelExecutionListener(spanRegistry, openTelemetrySdkService);
4034
}
4135

36+
/**
37+
* For an unknown reason, {@link #afterProjectsRead(MavenSession)} is invoked when the module is
38+
* declared as an extension in pom.xml but {@link #afterSessionStart(MavenSession)} is not
39+
* invoked.
40+
*/
4241
@Override
43-
public void afterSessionEnd(MavenSession session) {
44-
logger.debug("OpenTelemetry: afterSessionEnd");
42+
public void afterProjectsRead(MavenSession session) {
43+
ExecutionListener initialExecutionListener = session.getRequest().getExecutionListener();
44+
if (initialExecutionListener instanceof ChainedExecutionListener
45+
|| initialExecutionListener instanceof OtelExecutionListener) {
46+
// already initialized
47+
logger.debug(
48+
"OpenTelemetry: OpenTelemetry extension already registered as execution listener, skip.");
49+
} else if (initialExecutionListener == null) {
50+
session.getRequest().setExecutionListener(this.otelExecutionListener);
51+
logger.debug(
52+
"OpenTelemetry: OpenTelemetry extension registered as execution listener. No execution listener initially defined");
53+
} else {
54+
session
55+
.getRequest()
56+
.setExecutionListener(
57+
new ChainedExecutionListener(this.otelExecutionListener, initialExecutionListener));
58+
logger.debug(
59+
"OpenTelemetry: OpenTelemetry extension registered as execution listener. InitialExecutionListener: "
60+
+ initialExecutionListener);
61+
}
4562
}
4663
}

maven-extension/src/main/java/io/opentelemetry/maven/SpanRegistry.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
import java.util.stream.Collectors;
1313
import javax.annotation.Nonnull;
1414
import javax.annotation.Nullable;
15+
import javax.inject.Named;
16+
import javax.inject.Singleton;
1517
import org.apache.maven.model.Plugin;
1618
import org.apache.maven.plugin.MojoExecution;
1719
import org.apache.maven.project.MavenProject;
18-
import org.codehaus.plexus.component.annotations.Component;
1920
import org.slf4j.Logger;
2021
import org.slf4j.LoggerFactory;
2122

@@ -26,7 +27,8 @@
2627
* Daemon</a>, can't execute multiple builds concurrently, there is no need to differentiate spans
2728
* per {@link org.apache.maven.execution.MavenSession}.
2829
*/
29-
@Component(role = SpanRegistry.class)
30+
@Singleton
31+
@Named
3032
public final class SpanRegistry {
3133

3234
private static final Logger logger = LoggerFactory.getLogger(SpanRegistry.class);

0 commit comments

Comments
 (0)