Skip to content

Commit b9f09ca

Browse files
authored
Http server builder (#11651)
1 parent d1ba858 commit b9f09ca

File tree

45 files changed

+502
-889
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+502
-889
lines changed

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/builder/internal/DefaultHttpClientInstrumenterBuilder.java

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
3030
import java.util.ArrayList;
3131
import java.util.List;
32+
import java.util.Objects;
3233
import java.util.Set;
3334
import java.util.function.Consumer;
3435
import java.util.function.Function;
@@ -58,21 +59,43 @@ public final class DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE> {
5859
private final HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter;
5960
private final HttpSpanNameExtractorBuilder<REQUEST> httpSpanNameExtractorBuilder;
6061

61-
@Nullable private TextMapSetter<REQUEST> headerSetter;
62+
@Nullable private final TextMapSetter<REQUEST> headerSetter;
6263
private Function<SpanNameExtractor<? super REQUEST>, ? extends SpanNameExtractor<? super REQUEST>>
6364
spanNameExtractorTransformer = Function.identity();
6465
private boolean emitExperimentalHttpClientMetrics = false;
6566
private Consumer<InstrumenterBuilder<REQUEST, RESPONSE>> builderCustomizer = b -> {};
6667

67-
public DefaultHttpClientInstrumenterBuilder(
68+
private DefaultHttpClientInstrumenterBuilder(
6869
String instrumentationName,
6970
OpenTelemetry openTelemetry,
70-
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter) {
71-
this.instrumentationName = instrumentationName;
72-
this.openTelemetry = openTelemetry;
71+
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter,
72+
TextMapSetter<REQUEST> headerSetter) {
73+
this.instrumentationName = Objects.requireNonNull(instrumentationName, "instrumentationName");
74+
this.openTelemetry = Objects.requireNonNull(openTelemetry, "openTelemetry");
75+
this.attributesGetter = Objects.requireNonNull(attributesGetter, "attributesGetter");
7376
httpSpanNameExtractorBuilder = HttpSpanNameExtractor.builder(attributesGetter);
7477
httpAttributesExtractorBuilder = HttpClientAttributesExtractor.builder(attributesGetter);
75-
this.attributesGetter = attributesGetter;
78+
this.headerSetter = headerSetter;
79+
}
80+
81+
public static <REQUEST, RESPONSE> DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE> create(
82+
String instrumentationName,
83+
OpenTelemetry openTelemetry,
84+
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter) {
85+
return new DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE>(
86+
instrumentationName, openTelemetry, attributesGetter, null);
87+
}
88+
89+
public static <REQUEST, RESPONSE> DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE> create(
90+
String instrumentationName,
91+
OpenTelemetry openTelemetry,
92+
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter,
93+
TextMapSetter<REQUEST> headerSetter) {
94+
return new DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE>(
95+
instrumentationName,
96+
openTelemetry,
97+
attributesGetter,
98+
Objects.requireNonNull(headerSetter, "headerSetter"));
7699
}
77100

78101
/**
@@ -141,13 +164,6 @@ public DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE> setKnownMethods(
141164
return this;
142165
}
143166

144-
@CanIgnoreReturnValue
145-
public DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE> setHeaderSetter(
146-
@Nullable TextMapSetter<REQUEST> headerSetter) {
147-
this.headerSetter = headerSetter;
148-
return this;
149-
}
150-
151167
/**
152168
* Configures the instrumentation to emit experimental HTTP client metrics.
153169
*
@@ -193,7 +209,6 @@ public DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE> setBuilderCustomi
193209
}
194210

195211
public Instrumenter<REQUEST, RESPONSE> build() {
196-
197212
SpanNameExtractor<? super REQUEST> spanNameExtractor =
198213
spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build());
199214

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/builder/internal/DefaultHttpServerInstrumenterBuilder.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
2929
import java.util.ArrayList;
3030
import java.util.List;
31+
import java.util.Objects;
3132
import java.util.Set;
3233
import java.util.function.Consumer;
3334
import java.util.function.Function;
@@ -61,20 +62,40 @@ public final class DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> {
6162
private boolean emitExperimentalHttpServerMetrics = false;
6263
private Consumer<InstrumenterBuilder<REQUEST, RESPONSE>> builderCustomizer = b -> {};
6364

64-
public DefaultHttpServerInstrumenterBuilder(
65+
private DefaultHttpServerInstrumenterBuilder(
6566
String instrumentationName,
6667
OpenTelemetry openTelemetry,
6768
HttpServerAttributesGetter<REQUEST, RESPONSE> attributesGetter,
68-
@Nullable TextMapGetter<REQUEST> headerGetter) {
69-
this.instrumentationName = instrumentationName;
70-
this.openTelemetry = openTelemetry;
69+
TextMapGetter<REQUEST> headerGetter) {
70+
this.instrumentationName = Objects.requireNonNull(instrumentationName, "instrumentationName");
71+
this.openTelemetry = Objects.requireNonNull(openTelemetry, "openTelemetry");
72+
this.attributesGetter = Objects.requireNonNull(attributesGetter, "attributesGetter");
7173
httpAttributesExtractorBuilder = HttpServerAttributesExtractor.builder(attributesGetter);
7274
httpSpanNameExtractorBuilder = HttpSpanNameExtractor.builder(attributesGetter);
7375
httpServerRouteBuilder = HttpServerRoute.builder(attributesGetter);
74-
this.attributesGetter = attributesGetter;
7576
this.headerGetter = headerGetter;
7677
}
7778

79+
public static <REQUEST, RESPONSE> DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> create(
80+
String instrumentationName,
81+
OpenTelemetry openTelemetry,
82+
HttpServerAttributesGetter<REQUEST, RESPONSE> attributesGetter) {
83+
return new DefaultHttpServerInstrumenterBuilder<>(
84+
instrumentationName, openTelemetry, attributesGetter, null);
85+
}
86+
87+
public static <REQUEST, RESPONSE> DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> create(
88+
String instrumentationName,
89+
OpenTelemetry openTelemetry,
90+
HttpServerAttributesGetter<REQUEST, RESPONSE> attributesGetter,
91+
TextMapGetter<REQUEST> headerGetter) {
92+
return new DefaultHttpServerInstrumenterBuilder<>(
93+
instrumentationName,
94+
openTelemetry,
95+
attributesGetter,
96+
Objects.requireNonNull(headerGetter, "headerGetter"));
97+
}
98+
7899
/**
79100
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
80101
* items.
@@ -172,6 +193,15 @@ public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> setBuilderCustomi
172193
}
173194

174195
public Instrumenter<REQUEST, RESPONSE> build() {
196+
InstrumenterBuilder<REQUEST, RESPONSE> builder = instrumenterBuilder();
197+
198+
if (headerGetter != null) {
199+
return builder.buildServerInstrumenter(headerGetter);
200+
}
201+
return builder.buildInstrumenter(SpanKindExtractor.alwaysServer());
202+
}
203+
204+
public InstrumenterBuilder<REQUEST, RESPONSE> instrumenterBuilder() {
175205
SpanNameExtractor<? super REQUEST> spanNameExtractor =
176206
spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build());
177207

@@ -190,11 +220,7 @@ public Instrumenter<REQUEST, RESPONSE> build() {
190220
.addOperationMetrics(HttpServerExperimentalMetrics.get());
191221
}
192222
builderCustomizer.accept(builder);
193-
194-
if (headerGetter != null) {
195-
return builder.buildServerInstrumenter(headerGetter);
196-
}
197-
return builder.buildInstrumenter(SpanKindExtractor.alwaysServer());
223+
return builder;
198224
}
199225

200226
@CanIgnoreReturnValue

instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerSingletons.java

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,20 @@
77

88
import akka.http.scaladsl.model.HttpRequest;
99
import akka.http.scaladsl.model.HttpResponse;
10-
import io.opentelemetry.api.GlobalOpenTelemetry;
11-
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
12-
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics;
1310
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
14-
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
15-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor;
16-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
17-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
18-
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
19-
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
20-
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
11+
import io.opentelemetry.javaagent.bootstrap.internal.JavaagentHttpServerInstrumenters;
2112
import io.opentelemetry.javaagent.instrumentation.akkahttp.AkkaHttpUtil;
2213

2314
public final class AkkaHttpServerSingletons {
2415

2516
private static final Instrumenter<HttpRequest, HttpResponse> INSTRUMENTER;
2617

2718
static {
28-
AkkaHttpServerAttributesGetter httpAttributesGetter = new AkkaHttpServerAttributesGetter();
29-
InstrumenterBuilder<HttpRequest, HttpResponse> builder =
30-
Instrumenter.<HttpRequest, HttpResponse>builder(
31-
GlobalOpenTelemetry.get(),
32-
AkkaHttpUtil.instrumentationName(),
33-
HttpSpanNameExtractor.builder(httpAttributesGetter)
34-
.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods())
35-
.build())
36-
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
37-
.addAttributesExtractor(
38-
HttpServerAttributesExtractor.builder(httpAttributesGetter)
39-
.setCapturedRequestHeaders(AgentCommonConfig.get().getServerRequestHeaders())
40-
.setCapturedResponseHeaders(AgentCommonConfig.get().getServerResponseHeaders())
41-
.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods())
42-
.build())
43-
.addOperationMetrics(HttpServerMetrics.get())
44-
.addContextCustomizer(
45-
HttpServerRoute.builder(httpAttributesGetter)
46-
.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods())
47-
.build());
48-
if (AgentCommonConfig.get().shouldEmitExperimentalHttpServerTelemetry()) {
49-
builder
50-
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
51-
.addOperationMetrics(HttpServerExperimentalMetrics.get());
52-
}
53-
INSTRUMENTER = builder.buildServerInstrumenter(AkkaHttpServerHeaders.INSTANCE);
19+
INSTRUMENTER =
20+
JavaagentHttpServerInstrumenters.create(
21+
AkkaHttpUtil.instrumentationName(),
22+
new AkkaHttpServerAttributesGetter(),
23+
AkkaHttpServerHeaders.INSTANCE);
5424
}
5525

5626
public static Instrumenter<HttpRequest, HttpResponse> instrumenter() {

instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientTelemetryBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public final class ApacheHttpClientTelemetryBuilder {
2525

2626
ApacheHttpClientTelemetryBuilder(OpenTelemetry openTelemetry) {
2727
builder =
28-
new DefaultHttpClientInstrumenterBuilder<>(
28+
DefaultHttpClientInstrumenterBuilder.create(
2929
INSTRUMENTATION_NAME, openTelemetry, ApacheHttpClientHttpAttributesGetter.INSTANCE);
3030
this.openTelemetry = openTelemetry;
3131
}

instrumentation/apache-httpclient/apache-httpclient-5.2/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v5_2/ApacheHttpClient5TelemetryBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public final class ApacheHttpClient5TelemetryBuilder {
2626

2727
ApacheHttpClient5TelemetryBuilder(OpenTelemetry openTelemetry) {
2828
builder =
29-
new DefaultHttpClientInstrumenterBuilder<>(
29+
DefaultHttpClientInstrumenterBuilder.create(
3030
INSTRUMENTATION_NAME, openTelemetry, ApacheHttpClient5HttpAttributesGetter.INSTANCE);
3131
this.openTelemetry = openTelemetry;
3232
}

instrumentation/armeria/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/internal/ArmeriaInstrumenterBuilderFactory.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ private ArmeriaInstrumenterBuilderFactory() {}
2323

2424
public static DefaultHttpServerInstrumenterBuilder<ServiceRequestContext, RequestLog>
2525
getServerBuilder(OpenTelemetry openTelemetry) {
26-
return new DefaultHttpServerInstrumenterBuilder<>(
26+
return DefaultHttpServerInstrumenterBuilder.create(
2727
INSTRUMENTATION_NAME,
2828
openTelemetry,
2929
ArmeriaHttpServerAttributesGetter.INSTANCE,
@@ -32,8 +32,10 @@ private ArmeriaInstrumenterBuilderFactory() {}
3232

3333
public static DefaultHttpClientInstrumenterBuilder<ClientRequestContext, RequestLog>
3434
getClientBuilder(OpenTelemetry openTelemetry) {
35-
return new DefaultHttpClientInstrumenterBuilder<>(
36-
INSTRUMENTATION_NAME, openTelemetry, ArmeriaHttpClientAttributesGetter.INSTANCE)
37-
.setHeaderSetter(ClientRequestContextSetter.INSTANCE);
35+
return DefaultHttpClientInstrumenterBuilder.create(
36+
INSTRUMENTATION_NAME,
37+
openTelemetry,
38+
ArmeriaHttpClientAttributesGetter.INSTANCE,
39+
ClientRequestContextSetter.INSTANCE);
3840
}
3941
}

instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlySingletons.java

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,8 @@
55

66
package io.opentelemetry.javaagent.instrumentation.grizzly;
77

8-
import io.opentelemetry.api.GlobalOpenTelemetry;
9-
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
10-
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics;
118
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
12-
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
13-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor;
14-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
15-
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
16-
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
17-
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
18-
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
9+
import io.opentelemetry.javaagent.bootstrap.internal.JavaagentHttpServerInstrumenters;
1910
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
2011
import org.glassfish.grizzly.http.HttpRequestPacket;
2112
import org.glassfish.grizzly.http.HttpResponsePacket;
@@ -25,43 +16,22 @@ public final class GrizzlySingletons {
2516
private static final Instrumenter<HttpRequestPacket, HttpResponsePacket> INSTRUMENTER;
2617

2718
static {
28-
GrizzlyHttpAttributesGetter httpAttributesGetter = new GrizzlyHttpAttributesGetter();
29-
30-
InstrumenterBuilder<HttpRequestPacket, HttpResponsePacket> builder =
31-
Instrumenter.<HttpRequestPacket, HttpResponsePacket>builder(
32-
GlobalOpenTelemetry.get(),
33-
"io.opentelemetry.grizzly-2.3",
34-
HttpSpanNameExtractor.builder(httpAttributesGetter)
35-
.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods())
36-
.build())
37-
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
38-
.addAttributesExtractor(
39-
HttpServerAttributesExtractor.builder(httpAttributesGetter)
40-
.setCapturedRequestHeaders(AgentCommonConfig.get().getServerRequestHeaders())
41-
.setCapturedResponseHeaders(AgentCommonConfig.get().getServerResponseHeaders())
42-
.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods())
43-
.build())
44-
.addOperationMetrics(HttpServerMetrics.get());
45-
if (AgentCommonConfig.get().shouldEmitExperimentalHttpServerTelemetry()) {
46-
builder
47-
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
48-
.addOperationMetrics(HttpServerExperimentalMetrics.get());
49-
}
5019
INSTRUMENTER =
51-
builder
52-
.addContextCustomizer(
53-
(context, request, attributes) ->
54-
new AppServerBridge.Builder()
55-
.captureServletAttributes()
56-
.recordException()
57-
.init(context))
58-
.addContextCustomizer(
59-
(context, httpRequestPacket, startAttributes) -> GrizzlyErrorHolder.init(context))
60-
.addContextCustomizer(
61-
HttpServerRoute.builder(httpAttributesGetter)
62-
.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods())
63-
.build())
64-
.buildServerInstrumenter(HttpRequestHeadersGetter.INSTANCE);
20+
JavaagentHttpServerInstrumenters.create(
21+
"io.opentelemetry.grizzly-2.3",
22+
new GrizzlyHttpAttributesGetter(),
23+
HttpRequestHeadersGetter.INSTANCE,
24+
builder ->
25+
builder
26+
.addContextCustomizer(
27+
(context, request, attributes) ->
28+
new AppServerBridge.Builder()
29+
.captureServletAttributes()
30+
.recordException()
31+
.init(context))
32+
.addContextCustomizer(
33+
(context, httpRequestPacket, startAttributes) ->
34+
GrizzlyErrorHolder.init(context)));
6535
}
6636

6737
public static Instrumenter<HttpRequestPacket, HttpResponsePacket> instrumenter() {

instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/internal/JavaHttpClientInstrumenterBuilderFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ private JavaHttpClientInstrumenterBuilderFactory() {}
2121

2222
public static DefaultHttpClientInstrumenterBuilder<HttpRequest, HttpResponse<?>> create(
2323
OpenTelemetry openTelemetry) {
24-
return new DefaultHttpClientInstrumenterBuilder<>(
24+
return DefaultHttpClientInstrumenterBuilder.create(
2525
INSTRUMENTATION_NAME, openTelemetry, JavaHttpClientAttributesGetter.INSTANCE);
2626
}
2727
}

instrumentation/jetty-httpclient/jetty-httpclient-12.0/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v12_0/internal/JettyHttpClientInstrumenterBuilderFactory.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ private JettyHttpClientInstrumenterBuilderFactory() {}
2121

2222
public static DefaultHttpClientInstrumenterBuilder<Request, Response> create(
2323
OpenTelemetry openTelemetry) {
24-
return new DefaultHttpClientInstrumenterBuilder<>(
25-
INSTRUMENTATION_NAME, openTelemetry, JettyClientHttpAttributesGetter.INSTANCE)
26-
.setHeaderSetter(HttpHeaderSetter.INSTANCE);
24+
return DefaultHttpClientInstrumenterBuilder.create(
25+
INSTRUMENTATION_NAME,
26+
openTelemetry,
27+
JettyClientHttpAttributesGetter.INSTANCE,
28+
HttpHeaderSetter.INSTANCE);
2729
}
2830
}

instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyHttpClientInstrumenterBuilderFactory.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ private JettyHttpClientInstrumenterBuilderFactory() {}
2121

2222
public static DefaultHttpClientInstrumenterBuilder<Request, Response> create(
2323
OpenTelemetry openTelemetry) {
24-
return new DefaultHttpClientInstrumenterBuilder<>(
25-
INSTRUMENTATION_NAME, openTelemetry, JettyClientHttpAttributesGetter.INSTANCE)
26-
.setHeaderSetter(HttpHeaderSetter.INSTANCE);
24+
return DefaultHttpClientInstrumenterBuilder.create(
25+
INSTRUMENTATION_NAME,
26+
openTelemetry,
27+
JettyClientHttpAttributesGetter.INSTANCE,
28+
HttpHeaderSetter.INSTANCE);
2729
}
2830
}

0 commit comments

Comments
 (0)