diff --git a/all/src/test/java/io/opentelemetry/all/NoGuavaTest.java b/all/src/test/java/io/opentelemetry/all/NoGuavaTest.java new file mode 100644 index 00000000000..b8d44a75522 --- /dev/null +++ b/all/src/test/java/io/opentelemetry/all/NoGuavaTest.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.all; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import io.opentelemetry.exporter.sender.grpc.managedchannel.internal.UpstreamGrpcSender; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Prevents Guava API usage in published code, even when Guava is available transitively. + * + *

This replaces the narrower Guava beta-API check, whose annotation inspection fails with javac + * 25 and {@code --release 8}. Only published JARs are scanned: tests may use Guava, and transitive + * dependencies are not prohibited. + */ +class NoGuavaTest { + + @ParameterizedTest + @MethodSource("artifactsAndJars") + void noGuavaReferences(String artifactId, String absolutePath) throws IOException { + try (JarFile jarFile = new JarFile(new File(absolutePath))) { + noClasses() + .that() + // futureUnaryCall exposes ListenableFuture. Retain its existing completion semantics. + .doNotBelongToAnyOf(UpstreamGrpcSender.class) + .and() + // Generated into main alongside the test server API; this unused stub returns + // ListenableFuture. Moving generation to test-only sources is a separate cleanup. + .doNotHaveFullyQualifiedName( + "io.opentelemetry.sdk.extension.trace.jaeger.proto.api_v2." + + "SamplingManagerGrpc$SamplingManagerFutureStub") + .should() + .dependOnClassesThat() + .resideInAPackage("com.google.common..") + .as(artifactId + " should not reference Guava except in gRPC future adapters") + .check(new ClassFileImporter().importJar(jarFile)); + } + } + + private static Stream artifactsAndJars() throws IOException { + return Files.readAllLines(Path.of(System.getenv("ARTIFACTS_AND_JARS"))).stream() + .map( + line -> { + String[] parts = line.split(":", 2); + return Arguments.argumentSet(parts[0], parts[0], parts[1]); + }); + } +} diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 2d66515256b..d4077f065ae 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -37,7 +37,7 @@ tasks.withType().configureEach { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(25)) } withJavadocJar() @@ -298,8 +298,6 @@ dependencies { compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.code.findbugs:jsr305") - annotationProcessor("com.google.guava:guava-beta-checker") - // Workaround for @javax.annotation.Generated // see: https://github.com/grpc/grpc-java/issues/3633 compileOnly("javax.annotation:javax.annotation-api") diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index ade32e5eca6..9d1e8fb0f23 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -77,7 +77,6 @@ val DEPENDENCIES = listOf( "com.github.stefanbirkner:system-rules:1.19.0", "com.google.api.grpc:proto-google-common-protos:2.78.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.guava:guava-beta-checker:1.0", "com.sun.net.httpserver:http:20070405", "com.squareup.okhttp3:okhttp:$okhttpVersion", "com.tngtech.archunit:archunit-junit5:1.5.1", diff --git a/docs/knowledge/build.md b/docs/knowledge/build.md index b8e079fd06c..c309cf2c8bb 100644 --- a/docs/knowledge/build.md +++ b/docs/knowledge/build.md @@ -1,6 +1,6 @@ # Build -Java 21 or higher is required to build. The built artifacts target Java 8 or higher. +Java 25 or higher is required to build. The built artifacts target Java 8 or higher. ## Common tasks diff --git a/exporters/common/build.gradle.kts b/exporters/common/build.gradle.kts index d0f7ce84a3d..149a5633855 100644 --- a/exporters/common/build.gradle.kts +++ b/exporters/common/build.gradle.kts @@ -8,7 +8,7 @@ plugins { description = "OpenTelemetry Exporter Common" otelJava.moduleName.set("io.opentelemetry.exporter.internal") otelJava.requireSuppressWarningsExplanation.set(false) -otelJava.osgiOptionalPackages.set(listOf("com.google.common.io", "io.opentelemetry.api.incubator.config", "io.opentelemetry.sdk.autoconfigure.spi")) +otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.api.incubator.config", "io.opentelemetry.sdk.autoconfigure.spi")) // sun.misc, io.grpc, and org.jspecify are not OSGi bundles and have no package versioning; must use unversioned optional. otelJava.osgiUnversionedOptionalPackages.set(listOf("sun.misc", "io.grpc", "org.jspecify.annotations")) // This bundle's exporters load sender implementations via SPI. diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStream.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStream.java index ea9e98ba0b3..49b3916cc55 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStream.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStream.java @@ -22,7 +22,6 @@ package io.opentelemetry.exporter.internal.grpc; -import com.google.common.io.ByteStreams; import io.grpc.Drainable; import io.grpc.KnownLength; import io.opentelemetry.sdk.common.export.MessageWriter; @@ -59,7 +58,13 @@ public int drainTo(OutputStream target) throws IOException { message.writeMessage(target); message = null; } else if (partial != null) { - written = (int) ByteStreams.copy(partial, target); + byte[] buffer = new byte[8192]; + written = 0; + int read; + while ((read = partial.read(buffer)) != -1) { + target.write(buffer, 0, read); + written += read; + } partial = null; } else { written = 0; diff --git a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStreamTest.java b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStreamTest.java index 0893577a3b8..f70277b783d 100644 --- a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStreamTest.java +++ b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/grpc/MarshalerInputStreamTest.java @@ -14,7 +14,11 @@ import java.io.OutputStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Random; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class MarshalerInputStreamTest { @@ -50,15 +54,32 @@ void drainTo_fromMessage() throws IOException { assertThat(baos.toByteArray()).isEqualTo(CONTENT); } + @ParameterizedTest + @ValueSource(ints = {0, 1, 8192, 8193, 20000}) + void drainTo_fromPartial(int size) throws IOException { + byte[] content = new byte[size]; + new Random(0).nextBytes(content); + MarshalerInputStream stream = streamWithPartial(content); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int written = stream.drainTo(baos); + + assertThat(written).isEqualTo(content.length); + assertThat(baos.toByteArray()).isEqualTo(content); + assertThat(stream.available()).isZero(); + assertThat(stream.read()).isEqualTo(-1); + } + @Test - void drainTo_fromPartial() throws IOException { + void drainTo_afterPartialRead() throws IOException { MarshalerInputStream stream = streamWithPartial(CONTENT); + assertThat(stream.read()).isEqualTo(CONTENT[0] & 0xFF); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int written = stream.drainTo(baos); - assertThat(written).isEqualTo(CONTENT.length); - assertThat(baos.toByteArray()).isEqualTo(CONTENT); + assertThat(written).isEqualTo(CONTENT.length - 1); + assertThat(baos.toByteArray()).isEqualTo(Arrays.copyOfRange(CONTENT, 1, CONTENT.length)); } @Test diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryNoRecordEventsSpanImpl.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryNoRecordEventsSpanImpl.java index 8ec2fc6296e..5c075c212b2 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryNoRecordEventsSpanImpl.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryNoRecordEventsSpanImpl.java @@ -22,7 +22,6 @@ package io.opentelemetry.opencensusshim; -import com.google.common.base.Preconditions; import io.opencensus.trace.Annotation; import io.opencensus.trace.AttributeValue; import io.opencensus.trace.EndSpanOptions; @@ -37,6 +36,7 @@ import io.opentelemetry.api.trace.StatusCode; import java.util.EnumSet; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -55,39 +55,39 @@ static OpenTelemetryNoRecordEventsSpanImpl create(SpanContext context) { @Override public void addAnnotation(String description, Map attributes) { - Preconditions.checkNotNull(description, "description"); - Preconditions.checkNotNull(attributes, "attribute"); + Objects.requireNonNull(description, "description"); + Objects.requireNonNull(attributes, "attribute"); } @Override public void addAnnotation(Annotation annotation) { - Preconditions.checkNotNull(annotation, "annotation"); + Objects.requireNonNull(annotation, "annotation"); } @Override public void putAttribute(String key, AttributeValue value) { - Preconditions.checkNotNull(key, "key"); - Preconditions.checkNotNull(value, "value"); + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); } @Override public void putAttributes(Map attributes) { - Preconditions.checkNotNull(attributes, "attributes"); + Objects.requireNonNull(attributes, "attributes"); } @Override public void addMessageEvent(MessageEvent messageEvent) { - Preconditions.checkNotNull(messageEvent, "messageEvent"); + Objects.requireNonNull(messageEvent, "messageEvent"); } @Override public void addLink(Link link) { - Preconditions.checkNotNull(link, "link"); + Objects.requireNonNull(link, "link"); } @Override public void setStatus(Status status) { - Preconditions.checkNotNull(status, "status"); + Objects.requireNonNull(status, "status"); } @Override @@ -102,7 +102,7 @@ public io.opentelemetry.api.trace.Span setStatus(StatusCode canonicalCode, Strin @Override public void end(EndSpanOptions options) { - Preconditions.checkNotNull(options, "options"); + Objects.requireNonNull(options, "options"); } @Override diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanBuilderImpl.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanBuilderImpl.java index 27f4a19a927..cae916eca8b 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanBuilderImpl.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanBuilderImpl.java @@ -22,8 +22,8 @@ package io.opentelemetry.opencensusshim; -import static com.google.common.base.Preconditions.checkNotNull; import static io.opentelemetry.opencensusshim.SpanConverter.mapKind; +import static java.util.Objects.requireNonNull; import io.opencensus.implcore.trace.internal.RandomHandler; import io.opencensus.trace.Sampler; @@ -73,7 +73,7 @@ private OpenTelemetrySpanBuilderImpl( @Nullable SpanContext ocRemoteParentSpanContext, @Nullable Span ocParent, OpenTelemetrySpanBuilderImpl.Options options) { - this.name = checkNotNull(name, "name"); + this.name = requireNonNull(name, "name"); this.ocParent = ocParent; this.ocRemoteParentSpanContext = ocRemoteParentSpanContext; this.options = options; @@ -93,13 +93,13 @@ static OpenTelemetrySpanBuilderImpl createWithRemoteParent( @Override public SpanBuilder setSampler(Sampler sampler) { - this.ocSampler = checkNotNull(sampler, "sampler"); + this.ocSampler = requireNonNull(sampler, "sampler"); return this; } @Override public SpanBuilder setParentLinks(List parentLinks) { - this.ocParentLinks = checkNotNull(parentLinks, "parentLinks"); + this.ocParentLinks = requireNonNull(parentLinks, "parentLinks"); for (Span parent : parentLinks) { this.otelParentLinks.add(SpanConverter.mapSpanContext(parent.getContext())); } @@ -220,8 +220,8 @@ static final class Options { private final TraceConfig traceConfig; Options(RandomHandler randomHandler, TraceConfig traceConfig) { - this.randomHandler = checkNotNull(randomHandler, "randomHandler"); - this.traceConfig = checkNotNull(traceConfig, "traceConfig"); + this.randomHandler = requireNonNull(randomHandler, "randomHandler"); + this.traceConfig = requireNonNull(traceConfig, "traceConfig"); } } } diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java index 5e4c76a386b..64ea4f1a861 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java @@ -31,7 +31,6 @@ import static io.opentelemetry.opencensusshim.SpanConverter.setLongAttribute; import static io.opentelemetry.opencensusshim.SpanConverter.setStringAttribute; -import com.google.common.base.Preconditions; import io.opencensus.trace.Annotation; import io.opencensus.trace.AttributeValue; import io.opencensus.trace.EndSpanOptions; @@ -48,6 +47,7 @@ import io.opentelemetry.api.trace.StatusCode; import java.util.EnumSet; import java.util.Map; +import java.util.Objects; class OpenTelemetrySpanImpl extends Span implements io.opentelemetry.api.trace.Span, DelegatingSpan { @@ -73,8 +73,8 @@ public io.opentelemetry.api.trace.Span getDelegate() { @Override public void putAttribute(String key, AttributeValue value) { - Preconditions.checkNotNull(key, "key"); - Preconditions.checkNotNull(value, "value"); + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); value.match( arg -> DelegatingSpan.super.setAttribute(key, arg), arg -> DelegatingSpan.super.setAttribute(key, arg), @@ -85,7 +85,7 @@ public void putAttribute(String key, AttributeValue value) { @Override public void putAttributes(Map attributes) { - Preconditions.checkNotNull(attributes, "attributes"); + Objects.requireNonNull(attributes, "attributes"); attributes.forEach(this::putAttribute); } @@ -105,7 +105,7 @@ public void addAnnotation(Annotation annotation) { @Override public void addLink(Link link) { - Preconditions.checkNotNull(link, "link"); + Objects.requireNonNull(link, "link"); AttributesBuilder attributesBuilder = Attributes.builder(); mapAttributes(link.getAttributes(), attributesBuilder); // DelegatingSpan does not override addLink, so DelegatingSpan.super.addLink(..) would call the @@ -136,7 +136,7 @@ public void addMessageEvent(MessageEvent messageEvent) { @Override public void setStatus(Status status) { - Preconditions.checkNotNull(status, "status"); + Objects.requireNonNull(status, "status"); StatusCode code = status.isOk() ? StatusCode.OK : StatusCode.ERROR; String description = status.getDescription(); if (description == null) {