From fe9d7c59a4ebe283072ff2ce526036c30369940f Mon Sep 17 00:00:00 2001 From: Benjamin Confino Date: Thu, 13 Jun 2024 14:22:49 +0100 Subject: [PATCH] provide executor via porting package (#183) --- tck/pom.xml | 2 +- tck/tracing/README.adoc | 9 +- .../tck/async/JaxRsServerAsyncTest.java | 6 +- .../async/JaxRsServerAsyncTestEndpoint.java | 19 +- .../PropertiesBasedConfigurationBuilder.java | 226 ++++++++++++++++++ .../tck/porting/api/Configuration.java | 37 +++ .../porting/api/ConfigurationAccessor.java | 53 ++++ 7 files changed, 339 insertions(+), 13 deletions(-) create mode 100644 tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/PropertiesBasedConfigurationBuilder.java create mode 100644 tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/Configuration.java create mode 100644 tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/ConfigurationAccessor.java diff --git a/tck/pom.xml b/tck/pom.xml index b3421973..3f1728c4 100644 --- a/tck/pom.xml +++ b/tck/pom.xml @@ -84,4 +84,4 @@ - \ No newline at end of file + diff --git a/tck/tracing/README.adoc b/tck/tracing/README.adoc index d77f2011..02f4c65e 100644 --- a/tck/tracing/README.adoc +++ b/tck/tracing/README.adoc @@ -58,7 +58,7 @@ These tests test the B3 and Jaeger propagation formats which are not required. I * `optional-jaxrs-tests` JAX-RS server async programming models + -Although support for JAX-RS server async programming models is not optional, these tests depend on Jakarta Concurrency because they use `ManagedExecutorService`. +Although support for JAX-RS server async programming models is not optional, these tests require you to provide an executor. <> + If you are testing in an environment which does not provide Jakarta Concurrency, you should exclude the `optional-jaxrs-tests` group. @@ -121,6 +121,13 @@ If you use Apache Maven then the tests are run via the `maven-surefire-plugin` ---- +== Providing an Executor [[Executor]] + +When the tests are running, ensure that the classpath contains the following: + +- A class that implements the interface `java.util.concurrent.Executor` in whatever way is most appropriate for your server. +- A file under META-INF/microprofile-telemetry-tck.properties. This file must contain a line `telemetry.tck.executor=.` referring to the previous class. + == Running as a Scanned Dependency You can also run the TCK as a scanned dependency. diff --git a/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java index 82bcebf3..991716b3 100644 --- a/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java +++ b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java @@ -37,6 +37,7 @@ import org.eclipse.microprofile.telemetry.tracing.tck.TestLibraries; import org.eclipse.microprofile.telemetry.tracing.tck.exporter.InMemorySpanExporter; import org.eclipse.microprofile.telemetry.tracing.tck.exporter.InMemorySpanExporterProvider; +import org.eclipse.microprofile.telemetry.tracing.tck.porting.PropertiesBasedConfigurationBuilder; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.testng.Arquillian; @@ -70,6 +71,7 @@ public static WebArchive createDeployment() { .addClasses(InMemorySpanExporter.class, InMemorySpanExporterProvider.class, JaxRsServerAsyncTestEndpointClient.class, JaxRsServerAsyncTestEndpoint.class) .addAsLibrary(TestLibraries.AWAITILITY_LIB) + .addPackages(true, PropertiesBasedConfigurationBuilder.class.getPackage()) .addAsServiceProvider(ConfigurableSpanExporterProvider.class, InMemorySpanExporterProvider.class) .addAsResource(config, "META-INF/microprofile-config.properties") .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); @@ -171,7 +173,9 @@ private void doErrorAsyncTest(Function BAGGAGE_VALUE_ATTR = AttributeKey.stringKey("test.baggage"); - @Resource - private ManagedExecutorService managedExecutor; + private Executor executor = ConfigurationAccessor.get().getExecutor(); @Inject private Tracer tracer; @@ -90,7 +89,7 @@ public CompletionStage getCompletionStage(@QueryParam(value = "baggageVa } // Call a subtask, propagating the context - ExecutorService contextExecutor = Context.taskWrapping(managedExecutor); + Executor contextExecutor = Context.taskWrapping(executor); CompletableFuture result = CompletableFuture.supplyAsync(this::subtask, contextExecutor); // Return the async result @@ -109,7 +108,7 @@ public CompletionStage getCompletionStageError(@QueryParam(value = "ba } // Call a subtask, propagating the context - ExecutorService contextExecutor = Context.taskWrapping(managedExecutor); + Executor contextExecutor = Context.taskWrapping(executor); CompletableFuture result = CompletableFuture.supplyAsync(this::subtaskError, contextExecutor); // Return the async result return result; @@ -127,7 +126,7 @@ public void getSuspend(@Suspended AsyncResponse async, @QueryParam(value = "bagg } // Call a subtask, propagating the context - ExecutorService contextExecutor = Context.taskWrapping(managedExecutor); + Executor contextExecutor = Context.taskWrapping(executor); contextExecutor.execute(() -> { // Ensure we call resume, either with the result or a thrown exception try { @@ -150,7 +149,7 @@ public void getSuspendError(@Suspended AsyncResponse async, @QueryParam(value = } // Call a subtask, propagating the context - ExecutorService contextExecutor = Context.taskWrapping(managedExecutor); + Executor contextExecutor = Context.taskWrapping(executor); contextExecutor.execute(() -> { // Ensure we call resume, either with the result or a thrown exception try { diff --git a/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/PropertiesBasedConfigurationBuilder.java b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/PropertiesBasedConfigurationBuilder.java new file mode 100644 index 00000000..64802b83 --- /dev/null +++ b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/PropertiesBasedConfigurationBuilder.java @@ -0,0 +1,226 @@ +/* + * Copyright 2010, Red Hat, Inc., and individual contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//This code comes from +//https://github.com/jakartaee/cdi-tck/blob/master/impl/src/main/java/org/jboss/cdi/tck/impl/PropertiesBasedConfigurationBuilder.java +//with minor changes. Full credit to the original authors. +package org.eclipse.microprofile.telemetry.tracing.tck.porting; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.Executor; + +import org.eclipse.microprofile.telemetry.tracing.tck.porting.api.Configuration; + +public class PropertiesBasedConfigurationBuilder { + + public static final String RESOURCE_BUNDLE = "META-INF/microprofile-telemetry-tck.properties"; + + @SuppressWarnings("unchecked") + public Configuration build(boolean deploymentPhase) { + + Configuration configuration = new Configuration(); + + configuration.setExecutor( + (Executor) getInstanceValue(Configuration.EXECUTOR_PROPERTY_NAME, Executor.class, !deploymentPhase)); + + return configuration; + } + /** + * + * @param + * @param propertyName + * @param expectedType + * @param required + * @return + */ + @SuppressWarnings("unchecked") + protected Class getClassValue(String propertyName, Class expectedType, boolean required) { + + Set> classes = new HashSet>(); + + for (String className : getPropertyValues(propertyName)) { + ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader(); + try { + if (currentThreadClassLoader != null) { + classes.add((Class) currentThreadClassLoader.loadClass(className)); + } else { + classes.add((Class) Class.forName(className)); + } + + } catch (ClassNotFoundException | LinkageError e) { + throw new IllegalArgumentException("Implementation class with name " + className + + " not found using classloader " + + (currentThreadClassLoader != null + ? currentThreadClassLoader + : this.getClass().getClassLoader()), + e); + } + } + + if (classes.size() == 0) { + if (required) { + throw new IllegalArgumentException( + "Cannot find any implementations of " + expectedType.getSimpleName() + ", check that " + + propertyName + + " is specified"); + } else { + return null; + } + } else if (classes.size() > 1) { + throw new IllegalArgumentException( + "More than one implementation of " + expectedType.getSimpleName() + " specified by " + propertyName + + ", not sure which one to use!"); + } else { + return classes.iterator().next(); + } + } + + /** + * + * @param + * @param propertyName + * @param expectedType + * @param required + * @return + */ + protected T getInstanceValue(String propertyName, Class expectedType, boolean required) { + + T instance = null; + + Class clazz = getClassValue(propertyName, expectedType, required); + if (clazz != null) { + try { + instance = clazz.newInstance(); + } catch (InstantiationException e) { + throw new IllegalStateException("Error instantiating " + clazz + " specified by " + propertyName, e); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Error instantiating " + clazz + " specified by " + propertyName, e); + } + } + return instance; + } + /** + * Get a list of possible values for a given key. + * + * First, System properties are tried, followed by the specified resource bundle (first in classpath only). + * + * @param key + * The key to search for + * @return A list of possible values. An empty list is returned if there are no matches. + */ + public Set getPropertyValues(String key) { + Set values = new HashSet(); + addPropertiesFromSystem(key, values); + addPropertiesFromResourceBundle(key, values); + return values; + } + + /** + * Adds matches from system properties + * + * @param key + * The key to match + * @param values + * The currently found values + */ + private void addPropertiesFromSystem(String key, Set values) { + addProperty(key, System.getProperty(key), values); + } + + /** + * Adds matches from detected resource bundles. + * + * @param key + * The key to match + * @param values + * The currently found values + */ + private void addPropertiesFromResourceBundle(String key, Set values) { + addPropertiesFromResourceBundle(key, values, new StringBuilder()); + } + + /** + * Adds matches from detected resource bundles + * + * @param key + * The key to match + * @param values + * The currently found values + * @param info + * a StringBuilder to append information about the found property, useful for debugging duplicates + */ + private void addPropertiesFromResourceBundle(String key, Set values, StringBuilder info) { + try { + int count = 0; + for (Enumeration e = getResources(RESOURCE_BUNDLE); e.hasMoreElements();) { + + URL url = e.nextElement(); + Properties properties = new Properties(); + InputStream propertyStream = url.openStream(); + + try { + properties.load(propertyStream); + String value = properties.getProperty(key); + if (value != null) { + values.add(value); + info.append(String.format("\t%d: %s=%s\n", count++, url.toExternalForm(), value)); + } + } finally { + if (propertyStream != null) { + propertyStream.close(); + } + } + } + + } catch (IOException e) { + // No-op, file is optional + } + } + + /** + * Add the property to the set of properties only if it hasn't already been added + * + * @param key + * The key searched for + * @param value + * The value of the property + * @param values + * The currently found values + */ + private void addProperty(String key, String value, Set values) { + if (value != null) { + values.add(value); + } + } + + /** + * + * @param name + * @return + * @throws IOException + */ + public Enumeration getResources(String name) throws IOException { + + if (Thread.currentThread().getContextClassLoader() != null) { + return Thread.currentThread().getContextClassLoader().getResources(name); + } else { + return getClass().getClassLoader().getResources(name); + } + } +} diff --git a/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/Configuration.java b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/Configuration.java new file mode 100644 index 00000000..b436e45d --- /dev/null +++ b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/Configuration.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.eclipse.microprofile.telemetry.tracing.tck.porting.api; + +import java.util.concurrent.Executor; + +public class Configuration { + + public static final String EXECUTOR_PROPERTY_NAME = "telemetry.tck.executor"; + private Executor executor; + + public Executor getExecutor() { + return executor; + } + + public void setExecutor(Executor executor) { + this.executor = executor; + } + +} diff --git a/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/ConfigurationAccessor.java b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/ConfigurationAccessor.java new file mode 100644 index 00000000..e5118ea3 --- /dev/null +++ b/tck/tracing/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/porting/api/ConfigurationAccessor.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.eclipse.microprofile.telemetry.tracing.tck.porting.api; + +import org.eclipse.microprofile.telemetry.tracing.tck.porting.PropertiesBasedConfigurationBuilder; + +public class ConfigurationAccessor { + private static Configuration current; + + private ConfigurationAccessor() { + } + + /** + * @param deploymentPhase + * Deployment phase (building test archive) initialization includes deployment specific properties + * @return current JSR 365 TCK configuration + */ + public static Configuration get(boolean deploymentPhase) { + + if (current == null) { + try { + current = new PropertiesBasedConfigurationBuilder().build(deploymentPhase); + } catch (Exception e) { + throw new IllegalStateException("Unable to get configuration", e); + } + } + return current; + } + + /** + * @return current JSR 365 TCK configuration + */ + public static Configuration get() { + return get(false); + } +}