Skip to content

Commit

Permalink
provide executor via porting package (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamin-confino authored Jun 13, 2024
1 parent a459822 commit fe9d7c5
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 13 deletions.
2 changes: 1 addition & 1 deletion tck/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@
</plugins>
</build>

</project>
</project>
9 changes: 8 additions & 1 deletion tck/tracing/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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. <<Executor, See the section on providing an executor>>
+
If you are testing in an environment which does not provide Jakarta Concurrency, you should exclude the `optional-jaxrs-tests` group.

Expand Down Expand Up @@ -121,6 +121,13 @@ If you use Apache Maven then the tests are run via the `maven-surefire-plugin`
</build>
----

== 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=<packagename>.<classname>` referring to the previous class.

== Running as a Scanned Dependency
You can also run the TCK as a scanned dependency.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -171,7 +173,9 @@ private void doErrorAsyncTest(Function<JaxRsServerAsyncTestEndpointClient, Strin
requestFunction.apply(client);
fail("Client did not throw an exception");
} catch (WebApplicationException e) {
assertEquals(e.getResponse().getStatus(), HttpURLConnection.HTTP_BAD_REQUEST);
assertEquals(e.getResponse().getStatus(), HttpURLConnection.HTTP_BAD_REQUEST,
"expected " + HttpURLConnection.HTTP_BAD_REQUEST + " but got " + e.getResponse().getStatus()
+ " full output: " + e.getResponse().readEntity(String.class));
readErrorSpans();
}
} catch (URISyntaxException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2023 Contributors to the Eclipse Foundation
* Copyright (c) 2016-2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
Expand All @@ -23,16 +23,16 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executor;

import org.eclipse.microprofile.telemetry.tracing.tck.porting.api.ConfigurationAccessor;

import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import jakarta.annotation.Resource;
import jakarta.enterprise.concurrent.ManagedExecutorService;
import jakarta.inject.Inject;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.GET;
Expand Down Expand Up @@ -72,8 +72,7 @@ public class JaxRsServerAsyncTestEndpoint {
public static final String BAGGAGE_KEY = "test.baggage.key";
public static final AttributeKey<String> BAGGAGE_VALUE_ATTR = AttributeKey.stringKey("test.baggage");

@Resource
private ManagedExecutorService managedExecutor;
private Executor executor = ConfigurationAccessor.get().getExecutor();

@Inject
private Tracer tracer;
Expand All @@ -90,7 +89,7 @@ public CompletionStage<String> getCompletionStage(@QueryParam(value = "baggageVa
}

// Call a subtask, propagating the context
ExecutorService contextExecutor = Context.taskWrapping(managedExecutor);
Executor contextExecutor = Context.taskWrapping(executor);
CompletableFuture<String> result = CompletableFuture.supplyAsync(this::subtask, contextExecutor);

// Return the async result
Expand All @@ -109,7 +108,7 @@ public CompletionStage<Response> getCompletionStageError(@QueryParam(value = "ba
}

// Call a subtask, propagating the context
ExecutorService contextExecutor = Context.taskWrapping(managedExecutor);
Executor contextExecutor = Context.taskWrapping(executor);
CompletableFuture<Response> result = CompletableFuture.supplyAsync(this::subtaskError, contextExecutor);
// Return the async result
return result;
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <T>
* @param propertyName
* @param expectedType
* @param required
* @return
*/
@SuppressWarnings("unchecked")
protected <T> Class<T> getClassValue(String propertyName, Class<T> expectedType, boolean required) {

Set<Class<T>> classes = new HashSet<Class<T>>();

for (String className : getPropertyValues(propertyName)) {
ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
try {
if (currentThreadClassLoader != null) {
classes.add((Class<T>) currentThreadClassLoader.loadClass(className));
} else {
classes.add((Class<T>) 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 <T>
* @param propertyName
* @param expectedType
* @param required
* @return
*/
protected <T> T getInstanceValue(String propertyName, Class<T> expectedType, boolean required) {

T instance = null;

Class<T> 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<String> getPropertyValues(String key) {
Set<String> values = new HashSet<String>();
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<String> 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<String> 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<String> values, StringBuilder info) {
try {
int count = 0;
for (Enumeration<URL> 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<String> values) {
if (value != null) {
values.add(value);
}
}

/**
*
* @param name
* @return
* @throws IOException
*/
public Enumeration<URL> getResources(String name) throws IOException {

if (Thread.currentThread().getContextClassLoader() != null) {
return Thread.currentThread().getContextClassLoader().getResources(name);
} else {
return getClass().getClassLoader().getResources(name);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Loading

0 comments on commit fe9d7c5

Please sign in to comment.