From ad103dec763639f6fc95ef35901d9f2511c24747 Mon Sep 17 00:00:00 2001 From: Koki Kosaka <90018361+kosakak@users.noreply.github.com> Date: Mon, 5 Jun 2023 19:16:35 +0900 Subject: [PATCH] Add MP Telemetry (#66) Add microprofile telemetry tracing feature. --------- Co-authored-by: Koki Kosaka --- launcher-dist/pom.xml | 10 +- .../jersey/server/ApplicationHandler.java | 754 ++++++++++++++++++ .../server/ResourceBagConfigurator.java | 102 +++ .../microprofile/opentracing/pom.xml | 43 - .../opentracing/cdi/OpenTracingExtension.java | 31 - .../opentracing/cdi/TracerProducer.java | 55 -- ...auncherClientTracingRegistrarProvider.java | 82 -- .../rs/LauncherRestClientListener.java | 47 -- .../rs/LauncherTracingDynamicFeature.java | 77 -- .../rs/SpanContextPropagationFilter.java | 42 - .../OpenTracingServletContainerProvider.java | 54 -- .../jakarta.enterprise.inject.spi.Extension | 1 - ...opentracing.ClientTracingRegistrarProvider | 1 - ...profile.rest.client.spi.RestClientListener | 1 - ...sfish.jersey.internal.spi.AutoDiscoverable | 1 - ...vlet.internal.spi.ServletContainerProvider | 1 - launcher-impl/microprofile/pom.xml | 2 +- .../microprofile/telemetry-tracing/pom.xml | 61 ++ .../telemetry/tracing/cdi/MethodRequest.java | 35 + .../cdi/OpenTelemetryConfigProducer.java | 54 ++ .../tracing/cdi/OpenTelemetryExtension.java | 167 ++++ .../tracing/cdi/OpenTelemetryProducer.java | 89 +++ .../tracing/cdi/WithSpanInterceptor.java | 129 +++ .../tracing/cdi/WithSpanInterceptorBean.java | 136 ++++ .../tracing/config/OpenTelemetryConfig.java | 27 + .../OpenTelemetryClientAutoDiscoverable.java | 29 + .../client/OpenTelemetryClientFeature.java | 34 + .../client/OpenTelemetryClientFilter.java | 151 ++++ ...OpenTelemetryPreInvocationInterceptor.java | 22 + .../OpenTelemetryServerAutoDiscoverable.java} | 20 +- .../OpenTelemetryServerDynamicFeature.java | 37 + .../server/OpenTelemetryServerFilter.java | 239 ++++++ .../jakarta.enterprise.inject.spi.Extension | 1 + ...sfish.jersey.internal.spi.AutoDiscoverable | 2 + pom.xml | 48 +- 35 files changed, 2097 insertions(+), 488 deletions(-) create mode 100644 launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java create mode 100644 launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ResourceBagConfigurator.java delete mode 100644 launcher-impl/microprofile/opentracing/pom.xml delete mode 100644 launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/OpenTracingExtension.java delete mode 100644 launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/TracerProducer.java delete mode 100644 launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherClientTracingRegistrarProvider.java delete mode 100644 launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherRestClientListener.java delete mode 100644 launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherTracingDynamicFeature.java delete mode 100644 launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/SpanContextPropagationFilter.java delete mode 100644 launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/servlet/OpenTracingServletContainerProvider.java delete mode 100644 launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension delete mode 100644 launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider delete mode 100644 launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener delete mode 100644 launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable delete mode 100644 launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider create mode 100644 launcher-impl/microprofile/telemetry-tracing/pom.xml create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/MethodRequest.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryConfigProducer.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryExtension.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryProducer.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptor.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptorBean.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/config/OpenTelemetryConfig.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientAutoDiscoverable.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFeature.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFilter.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryPreInvocationInterceptor.java rename launcher-impl/microprofile/{opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/OpenTracingAutoDiscoverable.java => telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerAutoDiscoverable.java} (51%) create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerDynamicFeature.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerFilter.java create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension create mode 100644 launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable diff --git a/launcher-dist/pom.xml b/launcher-dist/pom.xml index 79ffcf5..2afa99a 100644 --- a/launcher-dist/pom.xml +++ b/launcher-dist/pom.xml @@ -65,11 +65,11 @@ com.fujitsu.launcher - microprofile-opentracing + microprofile-rest-client com.fujitsu.launcher - microprofile-rest-client + microprofile-telemetry-tracing com.fujitsu.launcher @@ -138,12 +138,6 @@ org/glassfish/web/embed/default-web.xml - - io.smallrye:smallrye-opentracing - - META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener - - diff --git a/launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java b/launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java new file mode 100644 index 0000000..5b2ca5d --- /dev/null +++ b/launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ApplicationHandler.java @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 Payara Foundation and/or its affiliates. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.server; + +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.security.Principal; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.internal.AutoDiscoverableConfigurator; +import org.glassfish.jersey.internal.BootstrapBag; +import org.glassfish.jersey.internal.BootstrapConfigurator; +import org.glassfish.jersey.internal.ContextResolverFactory; +import org.glassfish.jersey.internal.DynamicFeatureConfigurator; +import org.glassfish.jersey.internal.Errors; +import org.glassfish.jersey.internal.ExceptionMapperFactory; +import org.glassfish.jersey.internal.FeatureConfigurator; +import org.glassfish.jersey.internal.JaxrsProviders; +import org.glassfish.jersey.internal.Version; +import org.glassfish.jersey.internal.inject.Binder; +import org.glassfish.jersey.internal.inject.Bindings; +import org.glassfish.jersey.internal.inject.CompositeBinder; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.Injections; +import org.glassfish.jersey.internal.inject.InstanceBinding; +import org.glassfish.jersey.internal.inject.Providers; +import org.glassfish.jersey.message.MessageBodyWorkers; +import org.glassfish.jersey.message.internal.MessageBodyFactory; +import org.glassfish.jersey.message.internal.MessagingBinders; +import org.glassfish.jersey.message.internal.NullOutputStream; +import org.glassfish.jersey.model.internal.ComponentBag; +import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer; +import org.glassfish.jersey.model.internal.RankedComparator; +import org.glassfish.jersey.model.internal.RankedProvider; +import org.glassfish.jersey.process.internal.ChainableStage; +import org.glassfish.jersey.process.internal.RequestScope; +import org.glassfish.jersey.process.internal.Stage; +import org.glassfish.jersey.process.internal.Stages; +import org.glassfish.jersey.server.internal.JerseyRequestTimeoutHandler; +import org.glassfish.jersey.server.internal.LocalizationMessages; +import org.glassfish.jersey.server.internal.ProcessingProviders; +import org.glassfish.jersey.internal.inject.ParamConverterConfigurator; +import org.glassfish.jersey.server.internal.inject.ParamExtractorConfigurator; +import org.glassfish.jersey.server.internal.inject.ValueParamProviderConfigurator; +import org.glassfish.jersey.server.internal.monitoring.ApplicationEventImpl; +import org.glassfish.jersey.server.internal.monitoring.CompositeApplicationEventListener; +import org.glassfish.jersey.server.internal.monitoring.MonitoringContainerListener; +import org.glassfish.jersey.server.internal.process.ReferencesInitializer; +import org.glassfish.jersey.server.internal.process.RequestProcessingConfigurator; +import org.glassfish.jersey.server.internal.process.RequestProcessingContext; +import org.glassfish.jersey.server.internal.process.RequestProcessingContextReference; +import org.glassfish.jersey.server.internal.routing.Routing; +import org.glassfish.jersey.server.model.ComponentModelValidator; +import org.glassfish.jersey.server.model.ModelProcessor; +import org.glassfish.jersey.server.model.ModelValidationException; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.server.model.internal.ModelErrors; +import org.glassfish.jersey.server.model.internal.ResourceMethodInvokerConfigurator; +import org.glassfish.jersey.server.monitoring.ApplicationEvent; +import org.glassfish.jersey.server.monitoring.ApplicationEventListener; +import org.glassfish.jersey.server.spi.Container; +import org.glassfish.jersey.server.spi.ContainerLifecycleListener; +import org.glassfish.jersey.server.spi.ContainerResponseWriter; + +/** + * Jersey server-side application handler. + *

+ * Container implementations use the {@code ApplicationHandler} API to process requests + * by invoking the {@link #handle(ContainerRequest) handle(request)} + * method on a configured application handler instance. + *

+ *

+ * {@code ApplicationHandler} provides two implementations of {@link jakarta.ws.rs.core.Configuration config} that can be injected + * into the application classes. The first is {@link ResourceConfig resource config} which implements {@code Configuration} + * itself and is configured by the user. The resource config is not modified by this application handler so the future reloads of + * the application is not disrupted by providers found on a classpath. This config can + * be injected only as {@code ResourceConfig} or {@code Application}. The second one can be injected into the + * {@code Configuration} parameters / fields and contains info about all the properties / provider classes / provider instances + * from the resource config and also about all the providers found during processing classes registered under + * {@link ServerProperties server properties}. After the application handler is initialized both configurations are marked as + * read-only. + *

+ *

+ * Application handler instance also acts as an aggregate {@link ContainerLifecycleListener} instance + * for the associated application. It aggregates all the registered container lifecycle listeners + * under a single, umbrella listener, represented by this application handler instance, that delegates all container lifecycle + * listener method calls to all the registered listeners. Jersey {@link Container containers} are expected to invoke + * the container lifecycle methods directly on the active {@code ApplicationHandler} instance. The application handler will then + * make sure to delegate the lifecycle listener calls further to all the container lifecycle listeners registered within the + * application. Additionally, invoking the {@link ContainerLifecycleListener#onShutdown(Container)} method on this application + * handler instance will release all the resources associated with the underlying application instance as well as close the + * application-specific {@link InjectionManager injection manager}. + *

+ * + * @author Pavel Bucek + * @author Jakub Podlesak + * @author Marek Potociar + * @author Libor Kramolis + * @see ResourceConfig + * @see jakarta.ws.rs.core.Configuration + * @see org.glassfish.jersey.server.spi.ContainerProvider + */ +public final class ApplicationHandler implements ContainerLifecycleListener { + + private static final Logger LOGGER = Logger.getLogger(ApplicationHandler.class.getName()); + + /** + * Default dummy security context. + */ + private static final SecurityContext DEFAULT_SECURITY_CONTEXT = new SecurityContext() { + + @Override + public boolean isUserInRole(final String role) { + return false; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public String getAuthenticationScheme() { + return null; + } + }; + + /** + * Configurator which initializes and register {@link ApplicationHandler} and {@link Configuration} instances into + * {@link InjectionManager} and {@link BootstrapBag}. + * + * @author Petr Bouda + */ + private class RuntimeConfigConfigurator implements BootstrapConfigurator { + + @Override + public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { + ServerBootstrapBag serverBag = (ServerBootstrapBag) bootstrapBag; + serverBag.setApplicationHandler(ApplicationHandler.this); + serverBag.setConfiguration(ResourceConfig.createRuntimeConfig(serverBag.getApplication())); + + // TODO: Do we really need these three bindings in DI provider? What JAX-RS specification says? + InstanceBinding handlerBinding = + Bindings.service(ApplicationHandler.this) + .to(ApplicationHandler.class); + + InstanceBinding configBinding = + Bindings.service(serverBag.getRuntimeConfig()) + .to(Configuration.class) + .to(ServerConfig.class); + + injectionManager.register(handlerBinding); + injectionManager.register(configBinding); + } + } + + private Application application; + private ResourceConfig runtimeConfig; + private ServerRuntime runtime; + private Iterable containerLifecycleListeners; + private InjectionManager injectionManager; + private MessageBodyWorkers msgBodyWorkers; + private ManagedObjectsFinalizer managedObjectsFinalizer; + + /** + * Create a new Jersey application handler using a default configuration. + */ + public ApplicationHandler() { + this(new Application()); + } + + /** + * Create a new Jersey server-side application handler configured by a + * {@link Application JAX-RS Application (sub-)class}. + * + * @param jaxrsApplicationClass JAX-RS {@code Application} (sub-)class that will be + * instantiated and used to configure the new Jersey + * application handler. + */ + public ApplicationHandler(final Class jaxrsApplicationClass) { + this(jaxrsApplicationClass, null); + } + + /** + * Create a new Jersey server-side application handler configured by a + * {@link Application JAX-RS Application (sub-)class}. + * + * @param applicationClass JAX-RS {@code Application} (sub-)class that will be + * instantiated and used to configure the new Jersey + * application handler. + * @param customBinder additional custom bindings used to configure the application's. + */ + public ApplicationHandler(final Class applicationClass, final Binder customBinder) { + initialize(new ApplicationConfigurator(applicationClass), Injections.createInjectionManager(), customBinder); + } + + /** + * Create a new Jersey server-side application handler configured by an instance + * of a {@link Application JAX-RS Application sub-class}. + * + * @param application an instance of a JAX-RS {@code Application} (sub-)class that + * will be used to configure the new Jersey application handler. + */ + public ApplicationHandler(final Application application) { + this(application, null, null); + } + + /** + * Create a new Jersey server-side application handler configured by an instance + * of a {@link ResourceConfig} and a custom {@link Binder}. + * + * @param application an instance of a JAX-RS {@code Application} (sub-)class that + * will be used to configure the new Jersey application handler. + * @param customBinder additional custom bindings used to configure the application's. + */ + public ApplicationHandler(final Application application, final Binder customBinder) { + this(application, customBinder, null); + } + + /** + * Create a new Jersey server-side application handler configured by an instance + * of a {@link ResourceConfig}, custom {@link Binder} and a parent used by {@link InjectionManager}. + * + * @param application an instance of a JAX-RS {@code Application} (sub-)class that + * will be used to configure the new Jersey application handler. + * @param customBinder additional custom bindings used during {@link InjectionManager} creation. + * @param parentManager parent used in {@link InjectionManager} for a specific DI provider. + */ + public ApplicationHandler(final Application application, final Binder customBinder, final Object parentManager) { + initialize(new ApplicationConfigurator(application), Injections.createInjectionManager(parentManager), customBinder); + } + + private void initialize(ApplicationConfigurator applicationConfigurator, InjectionManager injectionManager, + Binder customBinder) { + LOGGER.config(LocalizationMessages.INIT_MSG(Version.getBuildId())); + this.injectionManager = injectionManager; + this.injectionManager.register(CompositeBinder.wrap(new ServerBinder(), customBinder)); + this.managedObjectsFinalizer = new ManagedObjectsFinalizer(injectionManager); + + ServerBootstrapBag bootstrapBag = new ServerBootstrapBag(); + bootstrapBag.setManagedObjectsFinalizer(managedObjectsFinalizer); + List bootstrapConfigurators = Arrays.asList( + new RequestProcessingConfigurator(), + new RequestScope.RequestScopeConfigurator(), + new ParamConverterConfigurator(), + new ParamExtractorConfigurator(), + new ValueParamProviderConfigurator(), + new JerseyResourceContextConfigurator(), + new ComponentProviderConfigurator(), + new JaxrsProviders.ProvidersConfigurator(), + applicationConfigurator, + new RuntimeConfigConfigurator(), + new ContextResolverFactory.ContextResolversConfigurator(), + new MessageBodyFactory.MessageBodyWorkersConfigurator(), + new ExceptionMapperFactory.ExceptionMappersConfigurator(), + new ResourceMethodInvokerConfigurator(), + new ProcessingProvidersConfigurator(), + new ContainerProviderConfigurator(RuntimeType.SERVER), + new AutoDiscoverableConfigurator(RuntimeType.SERVER), + new DynamicFeatureConfigurator(), + new FeatureConfigurator(RuntimeType.SERVER)); + + bootstrapConfigurators.forEach(configurator -> configurator.init(injectionManager, bootstrapBag)); + + this.runtime = Errors.processWithException( + () -> initialize(injectionManager, bootstrapConfigurators, bootstrapBag)); + this.containerLifecycleListeners = Providers.getAllProviders(injectionManager, ContainerLifecycleListener.class); + } + + /** + * Assumes the configuration field is initialized with a valid ResourceConfig. + */ + private ServerRuntime initialize(InjectionManager injectionManager, List bootstrapConfigurators, + ServerBootstrapBag bootstrapBag) { + + this.application = bootstrapBag.getApplication(); + this.runtimeConfig = bootstrapBag.getRuntimeConfig(); + + // Register the binders which are dependent on "Application.properties()" + injectionManager.register(new MessagingBinders.MessageBodyProviders(application.getProperties(), RuntimeType.SERVER)); + + // Lock original ResourceConfig. + if (application instanceof ResourceConfig) { + ((ResourceConfig) application).lock(); + } + + CompositeApplicationEventListener compositeListener = null; + + Errors.mark(); // mark begin of validation phase + try { + // TODO: Create as a configurator? / The same code in ClientConfig. + // AutoDiscoverable. + if (!CommonProperties.getValue(runtimeConfig.getProperties(), RuntimeType.SERVER, + CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE, Boolean.FALSE, Boolean.class)) { + runtimeConfig.configureAutoDiscoverableProviders(injectionManager, bootstrapBag.getAutoDiscoverables()); + } else { + runtimeConfig.configureForcedAutoDiscoverableProviders(injectionManager); + } + + // Configure binders and features. + runtimeConfig.configureMetaProviders(injectionManager, bootstrapBag.getManagedObjectsFinalizer()); + + ResourceBagConfigurator resourceBagConfigurator = new ResourceBagConfigurator(); + resourceBagConfigurator.init(injectionManager, bootstrapBag); + + runtimeConfig.lock(); + + ExternalRequestScopeConfigurator externalRequestScopeConfigurator = new ExternalRequestScopeConfigurator(); + externalRequestScopeConfigurator.init(injectionManager, bootstrapBag); + + ModelProcessorConfigurator modelProcessorConfigurator = new ModelProcessorConfigurator(); + modelProcessorConfigurator.init(injectionManager, bootstrapBag); + + ResourceModelConfigurator resourceModelConfigurator = new ResourceModelConfigurator(); + resourceModelConfigurator.init(injectionManager, bootstrapBag); + + ServerExecutorProvidersConfigurator executorProvidersConfigurator = new ServerExecutorProvidersConfigurator(); + executorProvidersConfigurator.init(injectionManager, bootstrapBag); + + injectionManager.completeRegistration(); + + bootstrapConfigurators.forEach(configurator -> configurator.postInit(injectionManager, bootstrapBag)); + resourceModelConfigurator.postInit(injectionManager, bootstrapBag); + + Iterable appEventListeners = + Providers.getAllProviders(injectionManager, ApplicationEventListener.class, new RankedComparator<>()); + + if (appEventListeners.iterator().hasNext()) { + ResourceBag resourceBag = bootstrapBag.getResourceBag(); + compositeListener = new CompositeApplicationEventListener(appEventListeners); + compositeListener.onEvent(new ApplicationEventImpl(ApplicationEvent.Type.INITIALIZATION_START, + this.runtimeConfig, runtimeConfig.getComponentBag().getRegistrations(), + resourceBag.classes, resourceBag.instances, null)); + } + + if (!disableValidation()) { + ComponentModelValidator validator = new ComponentModelValidator( + bootstrapBag.getValueParamProviders(), bootstrapBag.getMessageBodyWorkers()); + validator.validate(bootstrapBag.getResourceModel()); + } + + if (Errors.fatalIssuesFound() && !ignoreValidationError()) { + throw new ModelValidationException(LocalizationMessages.RESOURCE_MODEL_VALIDATION_FAILED_AT_INIT(), + ModelErrors.getErrorsAsResourceModelIssues(true)); + } + } finally { + if (ignoreValidationError()) { + Errors.logErrors(true); + Errors.reset(); // reset errors to the state before validation phase + } else { + Errors.unmark(); + } + } + + this.msgBodyWorkers = bootstrapBag.getMessageBodyWorkers(); + + // assembly request processing chain + ProcessingProviders processingProviders = bootstrapBag.getProcessingProviders(); + final ContainerFilteringStage preMatchRequestFilteringStage = new ContainerFilteringStage( + processingProviders.getPreMatchFilters(), + processingProviders.getGlobalResponseFilters()); + final ChainableStage routingStage = + Routing.forModel(bootstrapBag.getResourceModel().getRuntimeResourceModel()) + .resourceContext(bootstrapBag.getResourceContext()) + .configuration(runtimeConfig) + .entityProviders(msgBodyWorkers) + .valueSupplierProviders(bootstrapBag.getValueParamProviders()) + .modelProcessors(Providers.getAllRankedSortedProviders(injectionManager, ModelProcessor.class)) + .createService(serviceType -> Injections.getOrCreate(injectionManager, serviceType)) + .processingProviders(processingProviders) + .resourceMethodInvokerBuilder(bootstrapBag.getResourceMethodInvokerBuilder()) + .buildStage(); + /* + * Root linear request acceptor. This is the main entry point for the whole request processing. + */ + final ContainerFilteringStage resourceFilteringStage = + new ContainerFilteringStage(processingProviders.getGlobalRequestFilters(), null); + + final ReferencesInitializer referencesInitializer = new ReferencesInitializer(injectionManager, + () -> injectionManager.getInstance(RequestProcessingContextReference.class)); + + final Stage rootStage = Stages + .chain(referencesInitializer) + .to(preMatchRequestFilteringStage) + .to(routingStage) + .to(resourceFilteringStage) + .build(Routing.matchedEndpointExtractor()); + + ServerRuntime serverRuntime = ServerRuntime.createServerRuntime( + injectionManager, bootstrapBag, rootStage, compositeListener, processingProviders); + + // Inject instances. + ComponentBag componentBag = runtimeConfig.getComponentBag(); + ResourceBag resourceBag = bootstrapBag.getResourceBag(); + for (final Object instance : componentBag.getInstances(ComponentBag.excludeMetaProviders(injectionManager))) { + injectionManager.inject(instance); + } + for (final Object instance : resourceBag.instances) { + injectionManager.inject(instance); + } + + logApplicationInitConfiguration(injectionManager, resourceBag, processingProviders); + + if (compositeListener != null) { + ApplicationEvent initFinishedEvent = new ApplicationEventImpl( + ApplicationEvent.Type.INITIALIZATION_APP_FINISHED, runtimeConfig, + componentBag.getRegistrations(), resourceBag.classes, resourceBag.instances, + bootstrapBag.getResourceModel()); + compositeListener.onEvent(initFinishedEvent); + + MonitoringContainerListener containerListener = injectionManager.getInstance(MonitoringContainerListener.class); + containerListener.init(compositeListener, initFinishedEvent); + } + + return serverRuntime; + } + + private boolean ignoreValidationError() { + Boolean ignoreErrorByDefault = Boolean.getBoolean(ServerProperties.RESOURCE_VALIDATION_IGNORE_ERRORS); + return ServerProperties.getValue(runtimeConfig.getProperties(), + ServerProperties.RESOURCE_VALIDATION_IGNORE_ERRORS, + ignoreErrorByDefault, + Boolean.class); + } + + private boolean disableValidation() { + return ServerProperties.getValue(runtimeConfig.getProperties(), + ServerProperties.RESOURCE_VALIDATION_DISABLE, + Boolean.FALSE, + Boolean.class); + } + + private static void logApplicationInitConfiguration(final InjectionManager injectionManager, + final ResourceBag resourceBag, + final ProcessingProviders processingProviders) { + if (!LOGGER.isLoggable(Level.CONFIG)) { + return; + } + + final StringBuilder sb = new StringBuilder(LocalizationMessages.LOGGING_APPLICATION_INITIALIZED()).append('\n'); + + final List rootResourceClasses = resourceBag.getRootResources(); + + if (!rootResourceClasses.isEmpty()) { + sb.append(LocalizationMessages.LOGGING_ROOT_RESOURCE_CLASSES()).append(":"); + for (final Resource r : rootResourceClasses) { + for (final Class clazz : r.getHandlerClasses()) { + sb.append('\n').append(" ").append(clazz.getName()); + } + } + } + + sb.append('\n'); + + final Set messageBodyReaders; + final Set messageBodyWriters; + + if (LOGGER.isLoggable(Level.FINE)) { + Spliterator mbrSpliterator = + Providers.getAllProviders(injectionManager, MessageBodyReader.class).spliterator(); + messageBodyReaders = StreamSupport.stream(mbrSpliterator, false).collect(Collectors.toSet()); + + Spliterator mbwSpliterator = + Providers.getAllProviders(injectionManager, MessageBodyWriter.class).spliterator(); + messageBodyWriters = StreamSupport.stream(mbwSpliterator, false).collect(Collectors.toSet()); + } else { + messageBodyReaders = Providers.getCustomProviders(injectionManager, MessageBodyReader.class); + messageBodyWriters = Providers.getCustomProviders(injectionManager, MessageBodyWriter.class); + } + + printProviders(LocalizationMessages.LOGGING_PRE_MATCH_FILTERS(), + processingProviders.getPreMatchFilters(), sb); + printProviders(LocalizationMessages.LOGGING_GLOBAL_REQUEST_FILTERS(), + processingProviders.getGlobalRequestFilters(), sb); + printProviders(LocalizationMessages.LOGGING_GLOBAL_RESPONSE_FILTERS(), + processingProviders.getGlobalResponseFilters(), sb); + printProviders(LocalizationMessages.LOGGING_GLOBAL_READER_INTERCEPTORS(), + processingProviders.getGlobalReaderInterceptors(), sb); + printProviders(LocalizationMessages.LOGGING_GLOBAL_WRITER_INTERCEPTORS(), + processingProviders.getGlobalWriterInterceptors(), sb); + printNameBoundProviders(LocalizationMessages.LOGGING_NAME_BOUND_REQUEST_FILTERS(), + processingProviders.getNameBoundRequestFilters(), sb); + printNameBoundProviders(LocalizationMessages.LOGGING_NAME_BOUND_RESPONSE_FILTERS(), + processingProviders.getNameBoundResponseFilters(), sb); + printNameBoundProviders(LocalizationMessages.LOGGING_NAME_BOUND_READER_INTERCEPTORS(), + processingProviders.getNameBoundReaderInterceptors(), sb); + printNameBoundProviders(LocalizationMessages.LOGGING_NAME_BOUND_WRITER_INTERCEPTORS(), + processingProviders.getNameBoundWriterInterceptors(), sb); + printProviders(LocalizationMessages.LOGGING_DYNAMIC_FEATURES(), + processingProviders.getDynamicFeatures(), sb); + printProviders(LocalizationMessages.LOGGING_MESSAGE_BODY_READERS(), + messageBodyReaders.stream().map(new WorkersToStringTransform<>()).collect(Collectors.toList()), sb); + printProviders(LocalizationMessages.LOGGING_MESSAGE_BODY_WRITERS(), + messageBodyWriters.stream().map(new WorkersToStringTransform<>()).collect(Collectors.toList()), sb); + + LOGGER.log(Level.CONFIG, sb.toString()); + } + + private static class WorkersToStringTransform implements Function { + + @Override + public String apply(final T t) { + if (t != null) { + return t.getClass().getName(); + } + return null; + } + } + + private static void printNameBoundProviders(final String title, + final Map, List>> providers, + final StringBuilder sb) { + if (!providers.isEmpty()) { + sb.append(title).append(":").append('\n'); + + for (final Map.Entry, List>> entry : providers.entrySet()) { + for (final RankedProvider rankedProvider : entry.getValue()) { + sb.append(" ") + .append(LocalizationMessages.LOGGING_PROVIDER_BOUND(rankedProvider, entry.getKey())) + .append('\n'); + } + } + } + } + + private static void printProviders(final String title, final Iterable providers, final StringBuilder sb) { + final Iterator iterator = providers.iterator(); + boolean first = true; + while (iterator.hasNext()) { + if (first) { + sb.append(title).append(":").append('\n'); + first = false; + } + final T provider = iterator.next(); + sb.append(" ").append(provider).append('\n'); + } + } + + /** + * Invokes a request and returns the {@link Future response future}. + * + * @param requestContext request data. + * @return response future. + */ + public Future apply(final ContainerRequest requestContext) { + return apply(requestContext, new NullOutputStream()); + } + + /** + * Invokes a request and returns the {@link Future response future}. + * + * @param request request data. + * @param outputStream response output stream. + * @return response future. + */ + public Future apply(final ContainerRequest request, + final OutputStream outputStream) { + final FutureResponseWriter responseFuture = + new FutureResponseWriter(request.getMethod(), outputStream, runtime.getBackgroundScheduler()); + + if (request.getSecurityContext() == null) { + request.setSecurityContext(DEFAULT_SECURITY_CONTEXT); + } + request.setWriter(responseFuture); + + handle(request); + + return responseFuture; + } + + private static class FutureResponseWriter extends CompletableFuture implements ContainerResponseWriter { + + private ContainerResponse response = null; + + private final String requestMethodName; + private final OutputStream outputStream; + + private final JerseyRequestTimeoutHandler requestTimeoutHandler; + + private FutureResponseWriter(final String requestMethodName, + final OutputStream outputStream, + final ScheduledExecutorService backgroundScheduler) { + this.requestMethodName = requestMethodName; + this.outputStream = outputStream; + this.requestTimeoutHandler = new JerseyRequestTimeoutHandler(this, backgroundScheduler); + } + + @Override + public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse response) { + this.response = response; + + if (contentLength >= 0) { + response.getHeaders().putSingle(HttpHeaders.CONTENT_LENGTH, Long.toString(contentLength)); + } + + return outputStream; + } + + @Override + public boolean suspend(final long time, final TimeUnit unit, final TimeoutHandler handler) { + return requestTimeoutHandler.suspend(time, unit, handler); + } + + @Override + public void setSuspendTimeout(final long time, final TimeUnit unit) { + requestTimeoutHandler.setSuspendTimeout(time, unit); + } + + @Override + public void commit() { + final ContainerResponse current = response; + if (current != null) { + if (HttpMethod.HEAD.equals(requestMethodName) && current.hasEntity()) { + // for testing purposes: + // need to also strip the object entity as it was stripped when writing to output + current.setEntity(null); + } + requestTimeoutHandler.close(); + super.complete(current); + } + } + + @Override + public void failure(final Throwable error) { + requestTimeoutHandler.close(); + super.completeExceptionally(error); + } + + @Override + public boolean enableResponseBuffering() { + return true; + } + } + + /** + * The main request/response processing entry point for Jersey container implementations. + *

+ * The method invokes the request processing of the provided + * {@link ContainerRequest container request context} and uses the + * {@link ContainerResponseWriter container response writer} to suspend & resume the processing + * as well as write the response back to the container. + *

+ *

+ * The the {@link SecurityContext security context} stored in the container request context + * is bound as an injectable instance in the scope of the processed request context. + * Also, any {@link org.glassfish.jersey.server.spi.RequestScopedInitializer custom scope injections} + * are initialized in the current request scope. + *

+ * + * @param request container request context of the current request. + */ + public void handle(final ContainerRequest request) { + request.setWorkers(msgBodyWorkers); + runtime.process(request); + } + + /** + * Returns {@link InjectionManager} relevant to current application. + * + * @return {@link InjectionManager} instance. + * @since 2.26 + */ + public InjectionManager getInjectionManager() { + return injectionManager; + } + + /** + * Get the application configuration. + * + * @return application configuration. + */ + public ResourceConfig getConfiguration() { + return runtimeConfig; + } + + // Aggregate container lifecycle listener implementation + + @Override + public void onStartup(final Container container) { + for (final ContainerLifecycleListener listener : containerLifecycleListeners) { + listener.onStartup(container); + } + } + + @Override + public void onReload(final Container container) { + for (final ContainerLifecycleListener listener : containerLifecycleListeners) { + listener.onReload(container); + } + } + + @Override + public void onShutdown(final Container container) { + try { + for (final ContainerLifecycleListener listener : containerLifecycleListeners) { + listener.onShutdown(container); + } + } finally { + try { + // Call @PreDestroy method on Application. + injectionManager.preDestroy(ResourceConfig.unwrapApplication(application)); + } finally { + // Shutdown ServiceLocator. + // Takes care of the injected executors & schedulers shut-down too. + managedObjectsFinalizer.preDestroy(); + injectionManager.shutdown(); + } + } + } +} diff --git a/launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ResourceBagConfigurator.java b/launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ResourceBagConfigurator.java new file mode 100644 index 0000000..d6a435d --- /dev/null +++ b/launcher-impl/glassfish/src/main/java/org/glassfish/jersey/server/ResourceBagConfigurator.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.server; + +import java.util.Comparator; +import java.util.logging.Logger; + +import org.glassfish.jersey.internal.BootstrapBag; +import org.glassfish.jersey.internal.BootstrapConfigurator; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.server.model.Resource; + +/** + * Configurator which initializes and register {@link ResourceBag} instance into {@link BootstrapBag}. + * + * @author Petr Bouda + */ +class ResourceBagConfigurator implements BootstrapConfigurator { + + private static final Logger LOGGER = Logger.getLogger(ResourceBagConfigurator.class.getName()); + + @Override + public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { + ServerBootstrapBag serverBag = (ServerBootstrapBag) bootstrapBag; + ResourceConfig runtimeConfig = serverBag.getRuntimeConfig(); + + final boolean disableValidation = ServerProperties.getValue(runtimeConfig.getProperties(), + ServerProperties.RESOURCE_VALIDATION_DISABLE, + Boolean.FALSE, + Boolean.class); + + final ResourceBag.Builder resourceBagBuilder = new ResourceBag.Builder(); + + // Adding programmatic resource models + for (final Resource programmaticResource : runtimeConfig.getResources()) { + resourceBagBuilder.registerProgrammaticResource(programmaticResource); + } + + // Introspecting classes & instances + if (Boolean.getBoolean(ServerProperties.RESOURCE_VALIDATION_IGNORE_ERRORS)) { + runtimeConfig.getClasses().stream().sorted(new Comparator>() { + @Override + public int compare(Class o1, Class o2) { + if (o1.isInterface() && !o2.isInterface()) { + return 1; + } else if (!o1.isInterface() && o2.isInterface()) { + return -1; + } else { + return 0; + } + } + }).forEach( c -> { + try { + final Resource resource = Resource.from(c, disableValidation); + if (resource != null) { + resourceBagBuilder.registerResource(c, resource); + } + } catch (final IllegalArgumentException ex) { + LOGGER.warning(ex.getMessage()); + } + }); + } else { + for (final Class c : runtimeConfig.getClasses()) { + try { + final Resource resource = Resource.from(c, disableValidation); + if (resource != null) { + resourceBagBuilder.registerResource(c, resource); + } + } catch (final IllegalArgumentException ex) { + LOGGER.warning(ex.getMessage()); + } + } + } + + for (final Object o : runtimeConfig.getSingletons()) { + try { + final Resource resource = Resource.from(o.getClass(), disableValidation); + if (resource != null) { + resourceBagBuilder.registerResource(o, resource); + } + } catch (final IllegalArgumentException ex) { + LOGGER.warning(ex.getMessage()); + } + } + + serverBag.setResourceBag(resourceBagBuilder.build()); + } +} diff --git a/launcher-impl/microprofile/opentracing/pom.xml b/launcher-impl/microprofile/opentracing/pom.xml deleted file mode 100644 index 8814ad1..0000000 --- a/launcher-impl/microprofile/opentracing/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - 4.0.0 - - - com.fujitsu.launcher - microprofile - 5.0-SNAPSHOT - - microprofile-opentracing - jar - - - - com.fujitsu.launcher - patched-glassfish - - - org.eclipse.microprofile.opentracing - microprofile-opentracing-api - - - io.smallrye - smallrye-opentracing - - - io.opentracing.contrib - opentracing-tracerresolver - - - diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/OpenTracingExtension.java b/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/OpenTracingExtension.java deleted file mode 100644 index 76c44b0..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/OpenTracingExtension.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2018-2022 Fujitsu Limited and/or its affiliates. All rights - * reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ -package com.fujitsu.launcher.microprofile.opentracing.cdi; - -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.spi.BeanManager; -import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; -import jakarta.enterprise.inject.spi.Extension; - -import io.smallrye.opentracing.contrib.interceptor.OpenTracingInterceptor; - -/** - * A CDI extension for registering producers and interceptors for opentracing. - * - * @author Tsuyoshi Yoshitomi - */ -public class OpenTracingExtension implements Extension { - - public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm) { - bbd.addAnnotatedType(bm.createAnnotatedType(TracerProducer.class), TracerProducer.class.getName()); - bbd.addAnnotatedType(bm.createAnnotatedType(OpenTracingInterceptor.class), OpenTracingInterceptor.class.getName()); - } -} diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/TracerProducer.java b/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/TracerProducer.java deleted file mode 100644 index c3ab2c4..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/cdi/TracerProducer.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2018-2022 Fujitsu Limited and/or its affiliates. All rights - * reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ -package com.fujitsu.launcher.microprofile.opentracing.cdi; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Singleton; - -import io.opentracing.Tracer; -import io.opentracing.contrib.tracerresolver.TracerResolver; -import io.opentracing.util.GlobalTracer; - -/** - * A producer of the singleton {@link Tracer} for CDI. - * The tracer is obtained via {@link TracerResolver}. - * - * @author Tsuyoshi Yoshitomi - */ -@ApplicationScoped -public class TracerProducer { - - private static Tracer tracer; - - @Produces - @Singleton - public static Tracer getTracer() { - - if (tracer != null) { - return tracer; - } - - tracer = TracerResolver.resolveTracer(); - - if (tracer == null) { - tracer = GlobalTracer.get(); // defaults to noop tracer - } - - Logger.getLogger(TracerProducer.class.getName()).log(Level.INFO, "Registering tracer {0}", - tracer.getClass().getName()); - GlobalTracer.registerIfAbsent(tracer); - - return tracer; - } -} diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherClientTracingRegistrarProvider.java b/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherClientTracingRegistrarProvider.java deleted file mode 100644 index 737519a..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherClientTracingRegistrarProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2019-2022 Fujitsu Limited and/or its affiliates. All rights - * reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ -package com.fujitsu.launcher.microprofile.opentracing.rs; - -import java.util.concurrent.ExecutorService; - -import jakarta.enterprise.inject.spi.CDI; -import jakarta.ws.rs.Priorities; -import jakarta.ws.rs.client.ClientBuilder; - -import org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider; -import org.glassfish.jersey.client.JerseyClientBuilder; - -import io.opentracing.Span; -import io.opentracing.SpanContext; -import io.opentracing.Tracer; -import io.opentracing.contrib.concurrent.TracedExecutorService; -import io.smallrye.opentracing.SmallRyeClientTracingFeature; - -/** - * Configures {@link JerseyClientBuilder} to enable client-side tracing. - * - * @author Takahiro Nagao - */ -public class LauncherClientTracingRegistrarProvider implements ClientTracingRegistrarProvider { - - static final int CLIENT_TRACING_FILTER_PRIORITY = Priorities.HEADER_DECORATOR; - static final int CONTEXT_PROPAGATION_FILTER_PRIORITY = Priorities.HEADER_DECORATOR - 1; - - @Override - public ClientBuilder configure(ClientBuilder clientBuilder) { - return configure(clientBuilder, null); - } - - @Override - public ClientBuilder configure(ClientBuilder clientBuilder, ExecutorService executorService) { - if (clientBuilder instanceof JerseyClientBuilder) { - JerseyClientBuilder jerseyClientBuilder = (JerseyClientBuilder) clientBuilder; - Tracer tracer = CDI.current().select(Tracer.class).get(); - - registerClientFeatures(jerseyClientBuilder::register, tracer); - - if (executorService != null) { - jerseyClientBuilder.executorService(new TracedExecutorService(executorService, tracer)); - } - - return jerseyClientBuilder; - } else { - return clientBuilder; - } - } - - public static void registerClientFeatures(Registrar registrar, Tracer tracer) { - Span activeSpan = tracer.activeSpan(); - - registrar.register(new SmallRyeClientTracingFeature(tracer), CLIENT_TRACING_FILTER_PRIORITY); - - if (activeSpan != null) { - // Register SpanContextPropagationFilter with priority higher than ClientTracingFilter - // so that it is called before ClientTracingFilter. - // Without this filter, asynchronous client requests cannot retrieve parent span context information, - // since active span sources based on thread-local storage (e.g. ThreadLocalActiveSpanSource) - // cannot properly provide active spans to asynchronous client requests, - // which are executed on threads different from ones where the active spans are registered. - SpanContext parentSpanContext = activeSpan.context(); - registrar.register(new SpanContextPropagationFilter(parentSpanContext), - CONTEXT_PROPAGATION_FILTER_PRIORITY); - } - } - - public static interface Registrar { - T register(Object component, int priority); - } -} diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherRestClientListener.java b/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherRestClientListener.java deleted file mode 100644 index 97d3518..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherRestClientListener.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2022 Fujitsu Limited and/or its affiliates. All rights - * reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - * - * This file incorporates work authored by SmallRye OpenTracing, - * licensed under the Apache License, Version 2.0, which is available at - * http://www.apache.org/licenses/LICENSE-2.0. - */ -package com.fujitsu.launcher.microprofile.opentracing.rs; - -import org.eclipse.microprofile.opentracing.Traced; -import org.eclipse.microprofile.rest.client.RestClientBuilder; -import org.eclipse.microprofile.rest.client.spi.RestClientListener; - -import com.fujitsu.launcher.microprofile.opentracing.cdi.TracerProducer; - -import io.opentracing.Tracer; -import io.smallrye.opentracing.OpenTracingAsyncInterceptorFactory; -import io.smallrye.opentracing.SmallRyeClientTracingFeature; - -/** - * Patched version of {@link io.smallrye.opentracing.SmallRyeRestClientListener}. - * - * @author Pavol Loffay (original) - * @author Takahiro Nagao (patched) - */ -public class LauncherRestClientListener implements RestClientListener { - - @Override - public void onNewClient(Class clientInterface, RestClientBuilder restClientBuilder) { - Traced traced = clientInterface.getAnnotation(Traced.class); - if (traced != null && !traced.value()) { - // tracing is disabled - return; - } - - Tracer tracer = TracerProducer.getTracer(); - restClientBuilder.register(new SmallRyeClientTracingFeature(tracer)); - restClientBuilder.register(new OpenTracingAsyncInterceptorFactory(tracer)); - } -} diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherTracingDynamicFeature.java b/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherTracingDynamicFeature.java deleted file mode 100644 index 334003e..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/LauncherTracingDynamicFeature.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2019-2022 Fujitsu Limited and/or its affiliates. All rights - * reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - - * This file incorporates work authored by SmallRye OpenTracing, - * licensed under the Apache License, Version 2.0, which is available at - * http://www.apache.org/licenses/LICENSE-2.0. - */ -package com.fujitsu.launcher.microprofile.opentracing.rs; - -import java.util.Optional; -import java.util.logging.Logger; - -import io.smallrye.opentracing.contrib.jaxrs2.server.OperationNameProvider.ClassNameOperationName; -import io.smallrye.opentracing.contrib.jaxrs2.server.OperationNameProvider.WildcardOperationName; -import io.smallrye.opentracing.contrib.jaxrs2.server.ServerTracingDynamicFeature; -import io.smallrye.opentracing.contrib.jaxrs2.server.ServerTracingDynamicFeature.Builder; -import jakarta.ws.rs.container.DynamicFeature; -import jakarta.ws.rs.container.ResourceInfo; -import jakarta.ws.rs.core.FeatureContext; -import jakarta.ws.rs.ext.Provider; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; - -import com.fujitsu.launcher.microprofile.opentracing.cdi.TracerProducer; - -import io.opentracing.Tracer; - - -/** - * Patched version of {@link io.smallrye.opentracing.SmallRyeTracingDynamicFeature}. - * - * @author Pavol Loffay (original) - * @author Takahiro Nagao (patched) - */ -@Provider -public class LauncherTracingDynamicFeature implements DynamicFeature { - - private static final Logger logger = Logger.getLogger(LauncherTracingDynamicFeature.class.getName()); - - private final ServerTracingDynamicFeature delegate; - - public LauncherTracingDynamicFeature() { - Tracer tracer = TracerProducer.getTracer(); - Config config = ConfigProvider.getConfig(); - Optional skipPattern = config.getOptionalValue("mp.opentracing.server.skip-pattern", String.class); - Optional operationNameProvider = config.getOptionalValue("mp.opentracing.server.operation-name-provider", - String.class); - - Builder builder = new Builder(tracer) - .withOperationNameProvider(ClassNameOperationName.newBuilder()) - .withTraceSerialization(false); - if (skipPattern.isPresent()) { - builder.withSkipPattern(skipPattern.get()); - } - if (operationNameProvider.isPresent()) { - if ("http-path".equalsIgnoreCase(operationNameProvider.get())) { - builder.withOperationNameProvider(WildcardOperationName.newBuilder()); - } else if (!"class-method".equalsIgnoreCase(operationNameProvider.get())) { - logger.warning("Provided operation name does not match http-path or class-method. Using default class-method."); - } - } - this.delegate = builder.build(); - } - - @Override - public void configure(ResourceInfo resourceInfo, FeatureContext context) { - this.delegate.configure(resourceInfo, context); - } -} diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/SpanContextPropagationFilter.java b/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/SpanContextPropagationFilter.java deleted file mode 100644 index 20712df..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/SpanContextPropagationFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2018-2022 Fujitsu Limited and/or its affiliates. All rights - * reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ -package com.fujitsu.launcher.microprofile.opentracing.rs; - -import io.smallrye.opentracing.contrib.jaxrs2.client.ClientTracingFilter; -import io.smallrye.opentracing.contrib.jaxrs2.client.TracingProperties; -import jakarta.ws.rs.client.ClientRequestContext; -import jakarta.ws.rs.client.ClientRequestFilter; - -import io.opentracing.SpanContext; - -/** - * Client request filter to propagate a span context to child spans. - * Required when handling asynchronous client requests. - * - *

This filter should be registered with a priority higher than that of {@link ClientTracingFilter}. - * - * @author Takahiro Nagao - */ -public class SpanContextPropagationFilter implements ClientRequestFilter { - - private SpanContext parentSpanContext; - - public SpanContextPropagationFilter(SpanContext parentSpanContext) { - this.parentSpanContext = parentSpanContext; - } - - @Override - public void filter(ClientRequestContext requestContext) { - if (requestContext.getProperty(TracingProperties.CHILD_OF) == null) { - requestContext.setProperty(TracingProperties.CHILD_OF, parentSpanContext); - } - } -} diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/servlet/OpenTracingServletContainerProvider.java b/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/servlet/OpenTracingServletContainerProvider.java deleted file mode 100644 index 165a3af..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/servlet/OpenTracingServletContainerProvider.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2018-2022 Fujitsu Limited and/or its affiliates. All rights - * reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ -package com.fujitsu.launcher.microprofile.opentracing.servlet; - -import java.util.Set; - -import io.smallrye.opentracing.contrib.jaxrs2.server.SpanFinishingFilter; -import jakarta.servlet.FilterRegistration.Dynamic; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; - -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider; - - -/** - * Servlet container provider for adding {@link SpanFinishingFilter}. - * - * @author Takahiro Nagao - */ -public class OpenTracingServletContainerProvider implements ServletContainerProvider { - - @Override - public void preInit(final ServletContext servletContext, final Set> classes) throws ServletException { - } - - @Override - public void postInit(final ServletContext servletContext, final Set> classes, - final Set servletNames) throws ServletException { - } - - @Override - public void onRegister(final ServletContext servletContext, final Set servletNames) - throws ServletException { - Dynamic dynamic = servletContext.addFilter(SpanFinishingFilter.class.getName(), SpanFinishingFilter.class); - - if (dynamic != null) { - dynamic.addMappingForUrlPatterns(null, false, "/*"); - dynamic.setAsyncSupported(true); - } - } - - @Override - public void configure(final ResourceConfig resourceConfig) throws ServletException { - } -} diff --git a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension b/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension deleted file mode 100644 index 50ad325..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension +++ /dev/null @@ -1 +0,0 @@ -com.fujitsu.launcher.microprofile.opentracing.cdi.OpenTracingExtension diff --git a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider b/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider deleted file mode 100644 index ed37a7f..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider +++ /dev/null @@ -1 +0,0 @@ -com.fujitsu.launcher.microprofile.opentracing.rs.LauncherClientTracingRegistrarProvider diff --git a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener b/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener deleted file mode 100644 index 5a8f61e..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener +++ /dev/null @@ -1 +0,0 @@ -com.fujitsu.launcher.microprofile.opentracing.rs.LauncherRestClientListener diff --git a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable deleted file mode 100644 index 4df7b13..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable +++ /dev/null @@ -1 +0,0 @@ -com.fujitsu.launcher.microprofile.opentracing.rs.OpenTracingAutoDiscoverable diff --git a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider b/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider deleted file mode 100644 index 4d14a48..0000000 --- a/launcher-impl/microprofile/opentracing/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider +++ /dev/null @@ -1 +0,0 @@ -com.fujitsu.launcher.microprofile.opentracing.servlet.OpenTracingServletContainerProvider diff --git a/launcher-impl/microprofile/pom.xml b/launcher-impl/microprofile/pom.xml index 931e0ff..a5c295d 100644 --- a/launcher-impl/microprofile/pom.xml +++ b/launcher-impl/microprofile/pom.xml @@ -27,7 +27,7 @@ jwt-auth metrics openapi - opentracing rest-client + telemetry-tracing diff --git a/launcher-impl/microprofile/telemetry-tracing/pom.xml b/launcher-impl/microprofile/telemetry-tracing/pom.xml new file mode 100644 index 0000000..5412655 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/pom.xml @@ -0,0 +1,61 @@ + + + + 4.0.0 + + + com.fujitsu.launcher + microprofile + 5.0-SNAPSHOT + + microprofile-telemetry-tracing + jar + + + + com.fujitsu.launcher + patched-glassfish + + + io.opentelemetry + opentelemetry-exporter-otlp + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api-semconv + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-annotations + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-annotations-support + + + org.eclipse.microprofile.config + microprofile-config-api + + + diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/MethodRequest.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/MethodRequest.java new file mode 100644 index 0000000..9e52673 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/MethodRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.cdi; + +import java.lang.reflect.Method; + +final class MethodRequest { + private final Method method; + private final Object[] args; + + public MethodRequest(final Method method, final Object[] args) { + this.method = method; + this.args = args; + } + + public Method getMethod() { + return method; + } + + public Object[] getArgs() { + return args; + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryConfigProducer.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryConfigProducer.java new file mode 100644 index 0000000..8543d59 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryConfigProducer.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.cdi; + +import java.util.HashMap; +import java.util.Map; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.Config; + +import com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig; + +@Singleton +public class OpenTelemetryConfigProducer { + @Inject + Config config; + + static final Map defaultProperties = Map.of( + "otel.sdk.disabled", "true", + "otel.metrics.exporter", "none"); + + @Produces + @Singleton + public OpenTelemetryConfig produces() { + return new OpenTelemetryConfig() { + @Override + public Map properties() { + Map properties = new HashMap<>(defaultProperties); + for (String propertyName : config.getPropertyNames()) { + if (propertyName.startsWith("otel.") || propertyName.startsWith("OTEL_")) { + config.getOptionalValue(propertyName, String.class).ifPresent( + value -> properties.put(propertyName, value)); + } + } + return properties; + } + }; + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryExtension.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryExtension.java new file mode 100644 index 0000000..d80666a --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryExtension.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fujitsu.launcher.microprofile.telemetry.tracing.rest.client.OpenTelemetryClientFilter; +import com.fujitsu.launcher.microprofile.telemetry.tracing.rest.server.OpenTelemetryServerFilter; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.AnnotatedConstructor; +import jakarta.enterprise.inject.spi.AnnotatedField; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.AnnotatedParameter; +import jakarta.enterprise.inject.spi.AnnotatedType; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.util.Nonbinding; + +import io.opentelemetry.instrumentation.annotations.WithSpan; + +public class OpenTelemetryExtension implements Extension { + public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) { + beforeBeanDiscovery.addInterceptorBinding( + new WithSpanAnnotatedType(beanManager.createAnnotatedType(WithSpan.class))); + + beforeBeanDiscovery.addAnnotatedType(OpenTelemetryProducer.class, OpenTelemetryProducer.class.getName()); + beforeBeanDiscovery.addAnnotatedType(OpenTelemetryConfigProducer.class, OpenTelemetryConfigProducer.class.getName()); + beforeBeanDiscovery.addAnnotatedType(OpenTelemetryServerFilter.class, OpenTelemetryServerFilter.class.getName()); + beforeBeanDiscovery.addAnnotatedType(OpenTelemetryClientFilter.class, OpenTelemetryClientFilter.class.getName()); + } + + public void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { + afterBeanDiscovery.addBean(new WithSpanInterceptorBean(beanManager)); + } + + // To add Nonbinding to @WithSpan members + @SuppressWarnings("unchecked") + static class WithSpanAnnotatedType implements AnnotatedType { + private final AnnotatedType delegate; + private final Set> methods; + + WithSpanAnnotatedType(final AnnotatedType delegate) { + this.delegate = delegate; + this.methods = new HashSet<>(); + + for (AnnotatedMethod method : delegate.getMethods()) { + methods.add(new AnnotatedMethod() { + private final AnnotatedMethod delegate = (AnnotatedMethod) method; + private final Set annotations = Collections.singleton(Nonbinding.Literal.INSTANCE); + + @Override + public Method getJavaMember() { + return delegate.getJavaMember(); + } + + @Override + public List> getParameters() { + return delegate.getParameters(); + } + + @Override + public boolean isStatic() { + return delegate.isStatic(); + } + + @Override + public AnnotatedType getDeclaringType() { + return delegate.getDeclaringType(); + } + + @Override + public Type getBaseType() { + return delegate.getBaseType(); + } + + @Override + public Set getTypeClosure() { + return delegate.getTypeClosure(); + } + + @Override + public T getAnnotation(final Class annotationType) { + if (annotationType.equals(Nonbinding.class)) { + return (T) annotations.iterator().next(); + } + return null; + } + + @Override + public Set getAnnotations() { + return annotations; + } + + @Override + public boolean isAnnotationPresent(final Class annotationType) { + return annotationType.equals(Nonbinding.class); + } + }); + } + } + + @Override + public Class getJavaClass() { + return delegate.getJavaClass(); + } + + @Override + public Set> getConstructors() { + return delegate.getConstructors(); + } + + @Override + public Set> getMethods() { + return this.methods; + } + + @Override + public Set> getFields() { + return delegate.getFields(); + } + + @Override + public Type getBaseType() { + return delegate.getBaseType(); + } + + @Override + public Set getTypeClosure() { + return delegate.getTypeClosure(); + } + + @Override + public T getAnnotation(final Class annotationType) { + return delegate.getAnnotation(annotationType); + } + + @Override + public Set getAnnotations() { + return delegate.getAnnotations(); + } + + @Override + public boolean isAnnotationPresent(final Class annotationType) { + return delegate.isAnnotationPresent(annotationType); + } + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryProducer.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryProducer.java new file mode 100644 index 0000000..db1a87f --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/OpenTelemetryProducer.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.cdi; + +import static com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig.INSTRUMENTATION_NAME; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.CDI; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.common.CompletableResultCode; + +@Singleton +public class OpenTelemetryProducer { + @Inject + OpenTelemetryConfig config; + + @Produces + @Singleton + public OpenTelemetry getOpenTelemetry() { + AutoConfiguredOpenTelemetrySdkBuilder builder = AutoConfiguredOpenTelemetrySdk.builder(); + + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + builder.setServiceClassLoader(contextClassLoader); + } + + return builder + .setResultAsGlobal(false) + .registerShutdownHook(false) + .addPropertiesSupplier(() -> config.properties()) + .build() + .getOpenTelemetrySdk(); + } + + @Produces + @Singleton + public Tracer getTracer() { + return CDI.current().select(OpenTelemetry.class).get().getTracer(INSTRUMENTATION_NAME); + } + + @Produces + @RequestScoped + public Span getSpan() { + return Span.current(); + } + + @Produces + @RequestScoped + public Baggage getBaggage() { + return Baggage.current(); + } + + void close(@Disposes final OpenTelemetry openTelemetry) { + OpenTelemetrySdk openTelemetrySdk = (OpenTelemetrySdk) openTelemetry; + List shutdown = new ArrayList<>(); + shutdown.add(openTelemetrySdk.getSdkTracerProvider().shutdown()); + shutdown.add(openTelemetrySdk.getSdkMeterProvider().shutdown()); + shutdown.add(openTelemetrySdk.getSdkLoggerProvider().shutdown()); + CompletableResultCode.ofAll(shutdown).join(10, TimeUnit.SECONDS); + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptor.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptor.java new file mode 100644 index 0000000..1ec74fb --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptor.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.cdi; + +import static com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig.INSTRUMENTATION_NAME; +import static com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig.INSTRUMENTATION_VERSION; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.annotations.SpanAttribute; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.instrumentation.api.annotation.support.MethodSpanAttributesExtractor; +import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.util.SpanNames; + +public class WithSpanInterceptor { + private final Instrumenter instrumenter; + + public WithSpanInterceptor(final OpenTelemetry openTelemetry) { + InstrumenterBuilder builder = Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, + new MethodRequestSpanNameExtractor()); + builder.setInstrumentationVersion(INSTRUMENTATION_VERSION); + + MethodSpanAttributesExtractor attributesExtractor = MethodSpanAttributesExtractor.newInstance( + MethodRequest::getMethod, + new WithSpanParameterAttributeNamesExtractor(), + MethodRequest::getArgs); + + this.instrumenter = builder.addAttributesExtractor(attributesExtractor) + .buildInstrumenter(methodRequest -> spanKindFromMethod(methodRequest.getMethod())); + } + + private static SpanKind spanKindFromMethod(Method method) { + WithSpan annotation = method.getDeclaredAnnotation(WithSpan.class); + if (annotation == null) { + return SpanKind.INTERNAL; + } + return annotation.kind(); + } + + @AroundInvoke + public Object span(final InvocationContext invocationContext) throws Exception { + MethodRequest methodRequest = new MethodRequest(invocationContext.getMethod(), invocationContext.getParameters()); + + Context parentContext = Context.current(); + Context spanContext = null; + Scope scope = null; + boolean shouldStart = instrumenter.shouldStart(parentContext, methodRequest); + if (shouldStart) { + spanContext = instrumenter.start(parentContext, methodRequest); + scope = spanContext.makeCurrent(); + } + + try { + Object result = invocationContext.proceed(); + + if (shouldStart) { + instrumenter.end(spanContext, methodRequest, null, null); + } + + return result; + } finally { + if (scope != null) { + scope.close(); + } + } + } + + private static final class MethodRequestSpanNameExtractor implements SpanNameExtractor { + @Override + public String extract(final MethodRequest methodRequest) { + WithSpan annotation = methodRequest.getMethod().getDeclaredAnnotation(WithSpan.class); + String spanName = annotation.value(); + if (spanName.isEmpty()) { + spanName = SpanNames.fromMethod(methodRequest.getMethod()); + } + return spanName; + } + } + + private static final class WithSpanParameterAttributeNamesExtractor implements ParameterAttributeNamesExtractor { + private static String attributeName(Parameter parameter) { + SpanAttribute spanAttribute = parameter.getDeclaredAnnotation(SpanAttribute.class); + if (spanAttribute == null) { + return null; + } + String value = spanAttribute.value(); + if (!value.isEmpty()) { + return value; + } else if (parameter.isNamePresent()) { + return parameter.getName(); + } else { + return null; + } + } + + @Override + public String[] extract(final Method method, final Parameter[] parameters) { + String[] attributeNames = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + attributeNames[i] = attributeName(parameters[i]); + } + return attributeNames; + } + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptorBean.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptorBean.java new file mode 100644 index 0000000..0018fab --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/cdi/WithSpanInterceptorBean.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Set; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.spi.InterceptionType; +import jakarta.enterprise.inject.spi.Interceptor; +import jakarta.enterprise.inject.spi.Prioritized; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.interceptor.InvocationContext; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.annotations.WithSpan; + +public class WithSpanInterceptorBean implements Interceptor, Prioritized { + private final BeanManager beanManager; + + public WithSpanInterceptorBean(final BeanManager beanManager) { + this.beanManager = beanManager; + } + + @Override + public Set getInterceptorBindings() { + return Collections.singleton(WithSpanLiteral.INSTANCE); + } + + @Override + public boolean intercepts(final InterceptionType type) { + return InterceptionType.AROUND_INVOKE.equals(type); + } + + @Override + public Object intercept( + final InterceptionType type, + final WithSpanInterceptor instance, + final InvocationContext invocationContext) + throws Exception { + + return instance.span(invocationContext); + } + + @Override + public Class getBeanClass() { + return WithSpanInterceptorBean.class; + } + + @Override + public Set getInjectionPoints() { + return Collections.emptySet(); + } + + @Override + public WithSpanInterceptor create(final CreationalContext creationalContext) { + Bean bean = beanManager.resolve(beanManager.getBeans(OpenTelemetry.class)); + OpenTelemetry openTelemetry = (OpenTelemetry) beanManager.getReference(bean, OpenTelemetry.class, creationalContext); + return new WithSpanInterceptor(openTelemetry); + } + + @Override + public void destroy( + final WithSpanInterceptor instance, + final CreationalContext creationalContext) { + + } + + @Override + public Set getTypes() { + return Collections.singleton(this.getBeanClass()); + } + + @Override + public Set getQualifiers() { + return Collections.emptySet(); + } + + @Override + public Class getScope() { + return Dependent.class; + } + + @Override + public String getName() { + return getBeanClass().getName(); + } + + @Override + public Set> getStereotypes() { + return Collections.emptySet(); + } + + @Override + public boolean isAlternative() { + return false; + } + + @Override + public int getPriority() { + return 100; + } + + public static class WithSpanLiteral extends AnnotationLiteral implements WithSpan { + public static final WithSpanLiteral INSTANCE = new WithSpanLiteral(); + + @Override + public String value() { + return null; + } + + @Override + public SpanKind kind() { + return null; + } + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/config/OpenTelemetryConfig.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/config/OpenTelemetryConfig.java new file mode 100644 index 0000000..eb438e6 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/config/OpenTelemetryConfig.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.config; + +import java.util.Map; +import java.util.Optional; + +public interface OpenTelemetryConfig { + // TODO Get from glassfish-version.properties + static String INSTRUMENTATION_NAME = "Launcher"; + + String INSTRUMENTATION_VERSION = Optional.of("5.0").orElse("SNAPSHOT"); + + Map properties(); +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientAutoDiscoverable.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientAutoDiscoverable.java new file mode 100644 index 0000000..8476dcc --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientAutoDiscoverable.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.rest.client; + +import jakarta.ws.rs.ConstrainedTo; +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.FeatureContext; +import org.glassfish.jersey.internal.spi.AutoDiscoverable; + +@ConstrainedTo(RuntimeType.CLIENT) +public class OpenTelemetryClientAutoDiscoverable implements AutoDiscoverable { + @Override + public void configure(FeatureContext featureContext) { + if (!featureContext.getConfiguration().isRegistered(OpenTelemetryPreInvocationInterceptor.class)) { + featureContext.register(OpenTelemetryPreInvocationInterceptor.class); + } + if(!featureContext.getConfiguration().isRegistered(OpenTelemetryClientFeature.class)) { + featureContext.register(OpenTelemetryClientFeature.class); + } + } +} diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFeature.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFeature.java new file mode 100644 index 0000000..19cba65 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFeature.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.rest.client; + +import jakarta.enterprise.inject.spi.CDI; +import jakarta.ws.rs.core.Feature; +import jakarta.ws.rs.core.FeatureContext; + +public class OpenTelemetryClientFeature implements Feature { + @Override + public boolean configure(FeatureContext featureContext) { + if (featureContext.getConfiguration().isRegistered(OpenTelemetryClientFilter.class)) { + return false; + } + try { + var clientFilter = CDI.current().select(OpenTelemetryClientFilter.class).get(); + if (clientFilter != null) { + featureContext.register(clientFilter); + return true; + } + return false; + } catch (IllegalStateException e) { + return false; + } + } +} diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFilter.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFilter.java new file mode 100644 index 0000000..10e1595 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryClientFilter.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.rest.client; + +import static com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig.INSTRUMENTATION_NAME; +import static com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig.INSTRUMENTATION_VERSION; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.util.List; + +import jakarta.inject.Inject; +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 io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; + +@Provider +public class OpenTelemetryClientFilter implements ClientRequestFilter, ClientResponseFilter { + private Instrumenter instrumenter; + + // RESTEasy requires no-arg constructor for CDI injection: https://issues.redhat.com/browse/RESTEASY-1538 + public OpenTelemetryClientFilter() { + } + + @Inject + public OpenTelemetryClientFilter(final OpenTelemetry openTelemetry) { + ClientAttributesExtractor clientAttributesExtractor = new ClientAttributesExtractor(); + + // TODO - The Client Span name is only "HTTP {METHOD_NAME}": https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name + InstrumenterBuilder builder = Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.create(clientAttributesExtractor)); + builder.setInstrumentationVersion(INSTRUMENTATION_VERSION); + + this.instrumenter = builder + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(clientAttributesExtractor)) + .addAttributesExtractor(HttpClientAttributesExtractor.create(clientAttributesExtractor)) + .buildClientInstrumenter(new ClientRequestContextTextMapSetter()); + } + + @Override + public void filter(final ClientRequestContext request) { + // CDI is not available in some contexts even if this library is available on the CP + if (instrumenter != null) { + Context parentContext = (Context) request.getProperty("otel.span.client.parentContext"); + if (parentContext == null) { + parentContext = Context.current(); + } + if (instrumenter.shouldStart(parentContext, request)) { + Context spanContext = instrumenter.start(parentContext, request); + Scope scope = spanContext.makeCurrent(); + request.setProperty("otel.span.client.context", spanContext); + request.setProperty("otel.span.client.parentContext", parentContext); + request.setProperty("otel.span.client.scope", scope); + } + } + } + + @Override + public void filter(final ClientRequestContext request, final ClientResponseContext response) { + // CDI is not available in some contexts even if this library is available on the CP + if (instrumenter != null) { + Scope scope = (Scope) request.getProperty("otel.span.client.scope"); + if (scope == null) { + return; + } + + Context spanContext = (Context) request.getProperty("otel.span.client.context"); + try { + instrumenter.end(spanContext, request, response, null); + } finally { + scope.close(); + + request.removeProperty("otel.span.client.context"); + request.removeProperty("otel.span.client.parentContext"); + request.removeProperty("otel.span.client.scope"); + } + } + } + + private static class ClientRequestContextTextMapSetter implements TextMapSetter { + @Override + public void set(final ClientRequestContext carrier, final String key, final String value) { + if (carrier != null) { + carrier.getHeaders().put(key, singletonList(value)); + } + } + } + + private static class ClientAttributesExtractor + implements HttpClientAttributesGetter { + + @Override + public String url(final ClientRequestContext request) { + return request.getUri().toString(); + } + + @Override + public String flavor(final ClientRequestContext request, final ClientResponseContext response) { + return null; + } + + @Override + public String method(final ClientRequestContext request) { + return request.getMethod(); + } + + @Override + public List requestHeader(final ClientRequestContext request, final String name) { + return request.getStringHeaders().getOrDefault(name, emptyList()); + } + + @Override + public Integer statusCode(final ClientRequestContext request, final ClientResponseContext response, + final Throwable throwable) { + return response.getStatus(); + } + + @Override + public List responseHeader(final ClientRequestContext request, final ClientResponseContext response, + final String name) { + return response.getHeaders().getOrDefault(name, emptyList()); + } + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryPreInvocationInterceptor.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryPreInvocationInterceptor.java new file mode 100644 index 0000000..301c671 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/client/OpenTelemetryPreInvocationInterceptor.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.rest.client; + +import io.opentelemetry.context.Context; +import jakarta.ws.rs.client.ClientRequestContext; +import org.glassfish.jersey.client.spi.PreInvocationInterceptor; + +public class OpenTelemetryPreInvocationInterceptor implements PreInvocationInterceptor { + @Override + public void beforeRequest(ClientRequestContext clientRequestContext) { + clientRequestContext.setProperty("otel.span.client.parentContext", Context.current()); + } +} diff --git a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/OpenTracingAutoDiscoverable.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerAutoDiscoverable.java similarity index 51% rename from launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/OpenTracingAutoDiscoverable.java rename to launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerAutoDiscoverable.java index 6b9355f..4bb417f 100644 --- a/launcher-impl/microprofile/opentracing/src/main/java/com/fujitsu/launcher/microprofile/opentracing/rs/OpenTracingAutoDiscoverable.java +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerAutoDiscoverable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2022 Fujitsu Limited and/or its affiliates. All rights + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights * reserved. * * This program and the accompanying materials are made available under the @@ -8,25 +8,21 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package com.fujitsu.launcher.microprofile.opentracing.rs; +package com.fujitsu.launcher.microprofile.telemetry.tracing.rest.server; import jakarta.annotation.Priority; +import jakarta.ws.rs.ConstrainedTo; +import jakarta.ws.rs.RuntimeType; import jakarta.ws.rs.core.FeatureContext; - import org.glassfish.jersey.internal.spi.AutoDiscoverable; -/** - * Registers JAX-RS features for tracing. - * - * @author Takahiro Nagao - */ @Priority(AutoDiscoverable.DEFAULT_PRIORITY) -public class OpenTracingAutoDiscoverable implements AutoDiscoverable { - +@ConstrainedTo(RuntimeType.SERVER) +public class OpenTelemetryServerAutoDiscoverable implements AutoDiscoverable { @Override public void configure(final FeatureContext context) { - if (!context.getConfiguration().isRegistered(LauncherTracingDynamicFeature.class)) { - context.register(LauncherTracingDynamicFeature.class); + if (!context.getConfiguration().isRegistered(OpenTelemetryServerDynamicFeature.class)) { + context.register(OpenTelemetryServerDynamicFeature.class); } } } diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerDynamicFeature.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerDynamicFeature.java new file mode 100644 index 0000000..76bd0cb --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerDynamicFeature.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.rest.server; + +import jakarta.enterprise.inject.spi.CDI; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class OpenTelemetryServerDynamicFeature implements DynamicFeature { + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) { + if (featureContext.getConfiguration().isRegistered(OpenTelemetryServerFilter.class)) { + return; + } + + try { + var serverFilter = CDI.current().select(OpenTelemetryServerFilter.class).get(); + if (serverFilter != null) { + featureContext.register(serverFilter); + } + } catch (IllegalStateException e) { + // noop + } + } +} diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerFilter.java b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerFilter.java new file mode 100644 index 0000000..8d5d9f6 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/java/com/fujitsu/launcher/microprofile/telemetry/tracing/rest/server/OpenTelemetryServerFilter.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2023 Fujitsu Limited and/or its affiliates. All rights + * reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * This file incorporates work authored by SmallRye OpenTelemetry, + * licensed under the Apache License, Version 2.0, which is available at + * http://www.apache.org/licenses/LICENSE-2.0. + */ +package com.fujitsu.launcher.microprofile.telemetry.tracing.rest.server; + +import static com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig.INSTRUMENTATION_NAME; +import static com.fujitsu.launcher.microprofile.telemetry.tracing.config.OpenTelemetryConfig.INSTRUMENTATION_VERSION; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.List; + +import jakarta.inject.Inject; +import jakarta.ws.rs.Path; +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.core.UriBuilder; +import jakarta.ws.rs.ext.Provider; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.net.InetSocketAddressNetServerAttributesGetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + +@Provider +public class OpenTelemetryServerFilter implements ContainerRequestFilter, ContainerResponseFilter { + private Instrumenter instrumenter; + + @jakarta.ws.rs.core.Context + ResourceInfo resourceInfo; + + // RESTEasy requires no-arg constructor for CDI injection: https://issues.redhat.com/browse/RESTEASY-1538 + public OpenTelemetryServerFilter() { + } + + @Inject + public OpenTelemetryServerFilter(final OpenTelemetry openTelemetry) { + HttpServerAttributesExtractor serverAttributesExtractor = new HttpServerAttributesExtractor(); + + InstrumenterBuilder builder = Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.create(serverAttributesExtractor)); + builder.setInstrumentationVersion(INSTRUMENTATION_VERSION); + + this.instrumenter = builder + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(serverAttributesExtractor)) + .addAttributesExtractor(io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor + .create(serverAttributesExtractor, new NetServerAttributesExtractor())) + .buildServerInstrumenter(new ContainerRequestContextTextMapGetter()); + } + + @Override + public void filter(final ContainerRequestContext request) { + // CDI is not available in some contexts even if this library is available on the CP + if (instrumenter != null) { + Context parentContext = Context.current(); + if (instrumenter.shouldStart(parentContext, request)) { + request.setProperty("rest.resource.class", resourceInfo.getResourceClass()); + request.setProperty("rest.resource.method", resourceInfo.getResourceMethod()); + + Context spanContext = instrumenter.start(parentContext, request); + Scope scope = spanContext.makeCurrent(); + request.setProperty("otel.span.server.context", spanContext); + request.setProperty("otel.span.server.parentContext", parentContext); + request.setProperty("otel.span.server.scope", scope); + } + } + } + + @Override + public void filter(final ContainerRequestContext request, final ContainerResponseContext response) { + if (instrumenter != null) { + Scope scope = (Scope) request.getProperty("otel.span.server.scope"); + if (scope == null) { + return; + } + + Context spanContext = (Context) request.getProperty("otel.span.server.context"); + try { + instrumenter.end(spanContext, request, response, null); + } finally { + scope.close(); + + request.removeProperty("rest.resource.class"); + request.removeProperty("rest.resource.method"); + request.removeProperty("otel.span.server.context"); + request.removeProperty("otel.span.server.parentContext"); + request.removeProperty("otel.span.server.scope"); + } + } + } + + private static class ContainerRequestContextTextMapGetter implements TextMapGetter { + @Override + public Iterable keys(final ContainerRequestContext carrier) { + return carrier.getHeaders().keySet(); + } + + @Override + public String get(final ContainerRequestContext carrier, final String key) { + if (carrier == null) { + return null; + } + + return carrier.getHeaders().getOrDefault(key, singletonList(null)).get(0); + } + } + + private static class NetServerAttributesExtractor + extends InetSocketAddressNetServerAttributesGetter { + @Override + public String transport(final ContainerRequestContext request) { + return null; + } + + @Override + public String hostName(final ContainerRequestContext request) { + return request.getUriInfo().getRequestUri().getHost(); + } + + @Override + public Integer hostPort(final ContainerRequestContext request) { + URI uri = request.getUriInfo().getRequestUri(); + if (uri.getPort() > 0) { + return uri.getPort(); + } + try { + return uri.toURL().getDefaultPort(); + } catch (MalformedURLException ex) { + return -1; + } + } + + @Override + protected InetSocketAddress getPeerSocketAddress(final ContainerRequestContext request) { + return null; + } + + @Override + protected InetSocketAddress getHostSocketAddress(final ContainerRequestContext request) { + return new InetSocketAddress(hostName(request), hostPort(request)); + } + } + + private static class HttpServerAttributesExtractor + implements HttpServerAttributesGetter { + @Override + public String flavor(final ContainerRequestContext request) { + return (String) request.getProperty(SemanticAttributes.HTTP_FLAVOR.getKey()); + } + + @Override + public String target(final ContainerRequestContext request) { + URI requestUri = request.getUriInfo().getRequestUri(); + String path = requestUri.getPath(); + String query = requestUri.getQuery(); + if (path != null && query != null && !query.isEmpty()) { + return path + "?" + query; + } + return path; + } + + @Override + public String route(final ContainerRequestContext request) { + try { + // This can throw an IllegalArgumentException when determining the route for a subresource + Class resourceClass = (Class) request.getProperty("rest.resource.class"); + Method method = (Method) request.getProperty("rest.resource.method"); + + UriBuilder template = UriBuilder.fromResource(resourceClass); + String contextRoot = request.getUriInfo().getBaseUri().getPath(); + if (contextRoot != null) { + template.path(contextRoot); + } + + if (method.isAnnotationPresent(Path.class)) { + template.path(method); + } + + return template.toTemplate(); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public String scheme(final ContainerRequestContext request) { + return request.getUriInfo().getRequestUri().getScheme(); + } + + @Override + public String method(final ContainerRequestContext request) { + return request.getMethod(); + } + + @Override + public List requestHeader(final ContainerRequestContext request, final String name) { + return request.getHeaders().getOrDefault(name, emptyList()); + } + + @Override + public Integer statusCode(final ContainerRequestContext request, final ContainerResponseContext response, + final Throwable throwable) { + return response.getStatus(); + } + + @Override + public List responseHeader(final ContainerRequestContext request, final ContainerResponseContext response, + final String name) { + return response.getStringHeaders().getOrDefault(name, emptyList()); + } + } +} \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension b/launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension new file mode 100644 index 0000000..2f40b5e --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +com.fujitsu.launcher.microprofile.telemetry.tracing.cdi.OpenTelemetryExtension \ No newline at end of file diff --git a/launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable new file mode 100644 index 0000000..268dfb0 --- /dev/null +++ b/launcher-impl/microprofile/telemetry-tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable @@ -0,0 +1,2 @@ +com.fujitsu.launcher.microprofile.telemetry.tracing.rest.server.OpenTelemetryServerAutoDiscoverable +com.fujitsu.launcher.microprofile.telemetry.tracing.rest.client.OpenTelemetryClientAutoDiscoverable diff --git a/pom.xml b/pom.xml index fdf1384..14fb74f 100644 --- a/pom.xml +++ b/pom.xml @@ -69,8 +69,8 @@ 2.1 5.0.0 3.1 - 3.0 3.0.1 + 1.0 7.0.1 1.9 4.2.4 @@ -86,10 +86,9 @@ 4.2.1 5.0.0 3.3.2 - 3.0.0-RC1 + 1.22.1 2.33 1.7.21 - 0.1.8 3.0.0 2.8.2 3.0.1 @@ -154,12 +153,12 @@ com.fujitsu.launcher - microprofile-opentracing + microprofile-rest-client ${project.version} com.fujitsu.launcher - microprofile-rest-client + microprofile-telemetry-tracing ${project.version} @@ -235,21 +234,6 @@ microprofile-openapi-tck ${mp.openapi.version} - - org.eclipse.microprofile.opentracing - microprofile-opentracing-api - ${mp.opentracing.version} - - - org.eclipse.microprofile.opentracing - microprofile-opentracing-tck - ${mp.opentracing.version} - - - org.eclipse.microprofile.opentracing - microprofile-opentracing-tck-rest-client - ${mp.opentracing.version} - org.eclipse.microprofile.rest.client microprofile-rest-client-api @@ -260,6 +244,11 @@ microprofile-rest-client-tck ${mp.rest-client.version} + + org.eclipse.microprofile.telemetry.tracing + microprofile-telemetry-tracing-tck + ${mp.telemetry.version} + org.glassfish.main.extras @@ -362,17 +351,20 @@ smallrye-open-api-jaxrs ${smallrye.openapi.version} - - io.smallrye - smallrye-opentracing - ${smallrye.opentracing.version} - io.micrometer micrometer-registry-prometheus ${micrometer.version} + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${opentelemetry.java.version}-alpha + pom + import + args4j @@ -385,12 +377,6 @@ slf4j-jdk14 ${slf4j.version} - - - io.opentracing.contrib - opentracing-tracerresolver - ${opentracing-tracerresolver.version} - jakarta.enterprise.concurrent