-
Notifications
You must be signed in to change notification settings - Fork 278
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8b00e3e
commit d00aec5
Showing
14 changed files
with
442 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
apply(from = "$rootDir/gradle/java.gradle") |
77 changes: 77 additions & 0 deletions
77
components/context/context-api/src/main/java/datadog/context/Context.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package datadog.context; | ||
|
||
import javax.annotation.Nonnull; | ||
import javax.annotation.Nullable; | ||
|
||
// TODO Javadoc | ||
/** | ||
* An immutable context key-value store. It can store any values but keys are unique. Setting a new | ||
* value to an existing key will replace the existing value. | ||
*/ | ||
public interface Context { | ||
/** Retrieves the current context. */ | ||
static @Nonnull Context current() { | ||
return ContextStorage.get().current(); | ||
} | ||
|
||
/** | ||
* Retrieves the context bound to the given object. | ||
* | ||
* @param bearer The object to retrieve context from. | ||
* @return The bound context, {@code null} if no context bound to the object. | ||
*/ | ||
static @Nullable Context from(@Nonnull Object bearer) { | ||
return ContextBinder.get().retrieveFrom(bearer); | ||
} | ||
|
||
/** | ||
* Creates an empty context. | ||
* | ||
* @return An empty context. | ||
*/ | ||
static @Nonnull Context empty() { | ||
return ContextStorage.get().empty(); | ||
} | ||
|
||
/** | ||
* Retrieves the value associated to the given key, {@code null} if there is no value for the key. | ||
* | ||
* @param key The key to get the associated value. | ||
* @param <V> The value type. | ||
* @return The value associated to the given key, {@code null} if there is no value for the key. | ||
*/ | ||
<V> @Nullable V get(@Nonnull ContextKey<V> key); | ||
|
||
/** | ||
* Create a new context with given key value set, in addition to any existing values. | ||
* | ||
* @param key The key associated to the value. | ||
* @param value The value to store, or {@code null} to remove the value associated to the given | ||
* key if any./ | ||
* @param <V> The value type. | ||
* @return A new context with the given key value set, in addition to any existing values. | ||
*/ | ||
<V> @Nonnull Context with(@Nonnull ContextKey<V> key, @Nullable V value); | ||
|
||
// default <V> Context without(ContextKey<V> key) { | ||
// return with(key, null); | ||
// } | ||
|
||
/** | ||
* Makes the context current. | ||
* | ||
* @return The scope associated to the current execution. | ||
*/ | ||
default @Nonnull ContextScope makeCurrent() { | ||
return ContextStorage.get().attach(this); | ||
} | ||
|
||
/** | ||
* Binds a context to an object. | ||
* | ||
* @param bearer The object to bear the given context. | ||
*/ | ||
default void attachTo(@Nonnull Object bearer) { | ||
ContextBinder.get().attach(this, bearer); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
components/context/context-api/src/main/java/datadog/context/ContextBinder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package datadog.context; | ||
|
||
import static datadog.context.Loaders.CONTEXT_BINDER; | ||
|
||
import javax.annotation.Nonnull; | ||
import javax.annotation.Nullable; | ||
|
||
public interface ContextBinder { | ||
static @Nonnull ContextBinder get() { | ||
return CONTEXT_BINDER; | ||
} | ||
|
||
boolean attach(@Nonnull Context context, @Nonnull Object bearer); | ||
|
||
@Nullable | ||
Context retrieveFrom(@Nonnull Object bearer); | ||
} |
44 changes: 44 additions & 0 deletions
44
components/context/context-api/src/main/java/datadog/context/ContextKey.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package datadog.context; | ||
|
||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
/** | ||
* The key to store and retrieve values from {@link Context} key-value storage. | ||
* | ||
* @param <T> The type of the value to store. | ||
*/ | ||
public class ContextKey<T> { | ||
private static final AtomicInteger INDEX_GENERATOR = new AtomicInteger(0); | ||
private final String name; | ||
private final int index; | ||
|
||
private ContextKey(String name) { | ||
this.name = name; | ||
this.index = INDEX_GENERATOR.getAndIncrement(); | ||
} | ||
|
||
/** | ||
* Create a key for ScopedContext key-value store. | ||
* | ||
* @param name A display name for the key (debug/log purpose only). | ||
* @param <T> The type of the value to store. | ||
* @return The key instance to store and retrieve value from ScopedContext key-value storage. | ||
*/ | ||
public static <T> ContextKey<T> named(String name) { | ||
return new ContextKey<T>(name); | ||
} | ||
|
||
/** | ||
* Get the context store index for this key. | ||
* | ||
* @return The context store index for this key. | ||
*/ | ||
int index() { | ||
return this.index; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return this.name; | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
components/context/context-api/src/main/java/datadog/context/ContextScope.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package datadog.context; | ||
|
||
/** | ||
* A scope representing an attached context for an execution unit. Closing the scope will detach the | ||
* context. | ||
* | ||
* <p>* Scopes are intended to be used with {@code try-with-resources} block: | ||
* | ||
* <pre>{@code | ||
* try (Scope ignored = span.makeCurrent()) { | ||
* // Execution unit | ||
* } | ||
* }</pre> | ||
*/ | ||
@FunctionalInterface | ||
public interface ContextScope extends AutoCloseable { | ||
@Override | ||
void close(); // Should not throw exception | ||
} |
15 changes: 15 additions & 0 deletions
15
components/context/context-api/src/main/java/datadog/context/ContextStorage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package datadog.context; | ||
|
||
import static datadog.context.Loaders.CONTEXT_STORAGE; | ||
|
||
public interface ContextStorage { | ||
static ContextStorage get() { | ||
return CONTEXT_STORAGE; | ||
} | ||
|
||
Context empty(); | ||
|
||
Context current(); | ||
|
||
ContextScope attach(Context context); | ||
} |
85 changes: 85 additions & 0 deletions
85
components/context/context-api/src/main/java/datadog/context/Loaders.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package datadog.context; | ||
|
||
import static java.util.Comparator.comparingInt; | ||
|
||
import java.util.Iterator; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.ServiceLoader; | ||
import javax.annotation.Nonnull; | ||
|
||
final class Loaders { | ||
static final ContextStorage CONTEXT_STORAGE = loadContextStorage(); | ||
static final ContextBinder CONTEXT_BINDER = loadContextBinder(); | ||
|
||
private static ContextBinder loadContextBinder() { | ||
// Load all context binder providers | ||
ServiceLoader<ContextBinderProvider> serviceLoader = | ||
ServiceLoader.load(ContextBinderProvider.class); | ||
List<ContextBinderProvider> providers = new LinkedList<>(); | ||
for (ContextBinderProvider provider : serviceLoader) { | ||
providers.add(provider); | ||
} | ||
// Check found providers | ||
if (providers.isEmpty()) { | ||
throw new IllegalStateException("No ContextBinder implementation found"); | ||
} else if (providers.size() == 1) { | ||
// Directly return the only binder available | ||
return providers.get(0).getContextBinder(); | ||
} else { | ||
// Return a compound provider respecting their priority | ||
providers.sort(comparingInt(ContextBinderProvider::priority)); | ||
ContextBinder[] binders = | ||
providers.stream() | ||
.map(ContextBinderProvider::getContextBinder) | ||
.toArray(ContextBinder[]::new); | ||
return new CompoundContextBinder(binders); | ||
} | ||
} | ||
|
||
static ContextStorage loadContextStorage() { | ||
ServiceLoader<ContextStorage> serviceLoader = ServiceLoader.load(ContextStorage.class); | ||
Iterator<ContextStorage> iterator = serviceLoader.iterator(); | ||
if (iterator.hasNext()) { | ||
return iterator.next(); | ||
} else { | ||
throw new IllegalStateException("No ContextStorage implementation found"); | ||
} | ||
} | ||
|
||
public interface ContextBinderProvider { | ||
ContextBinder getContextBinder(); | ||
|
||
int priority(); | ||
} | ||
|
||
private static class CompoundContextBinder implements ContextBinder { | ||
private final ContextBinder[] contextBinders; | ||
|
||
private CompoundContextBinder(ContextBinder[] contextBinders) { | ||
this.contextBinders = contextBinders; | ||
} | ||
|
||
@Override | ||
public boolean attach(@Nonnull Context context, @Nonnull Object bearer) { | ||
for (ContextBinder contextBinder : this.contextBinders) { | ||
if (contextBinder.attach(context, bearer)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public Context retrieveFrom(@Nonnull Object bearer) { | ||
Context value = null; | ||
for (ContextBinder contextBinder : this.contextBinders) { | ||
value = contextBinder.retrieveFrom(bearer); | ||
if (value != null) { | ||
return value; | ||
} | ||
} | ||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
plugins { | ||
`java-library` | ||
} | ||
|
||
apply(from = "$rootDir/gradle/java.gradle") | ||
|
||
dependencies { | ||
api(project(":components:context:context-api")) | ||
implementation(libs.slf4j) | ||
} |
70 changes: 70 additions & 0 deletions
70
components/context/context-core/src/main/java/datadog/context/ArrayBasedContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package datadog.context; | ||
|
||
import static java.lang.Math.max; | ||
import static java.util.Arrays.copyOfRange; | ||
|
||
import java.util.Arrays; | ||
import java.util.Map; | ||
import javax.annotation.Nonnull; | ||
|
||
/** An array-based {@link Context} implementation. */ | ||
public class ArrayBasedContext implements Context { | ||
private static final ArrayBasedContext EMPTY = new ArrayBasedContext(new Object[0]); | ||
/** The generic store. Values are indexed by their {@link ContextKey#index()}. */ | ||
private final Object[] store; | ||
|
||
private ArrayBasedContext(Object[] store) { | ||
this.store = store; | ||
} | ||
|
||
/** | ||
* Get an empty context. | ||
* | ||
* @return An empty context. | ||
*/ | ||
public static ArrayBasedContext empty() { | ||
return EMPTY; | ||
} | ||
|
||
// TODO DOCUMENT | ||
public static ArrayBasedContext fromMap(Map<ContextKey<?>, Object> content) { | ||
if (content.isEmpty()) { | ||
return empty(); | ||
} | ||
int length = content.keySet().stream().mapToInt(ContextKey::index).max().orElse(0) + 1; | ||
Object[] store = new Object[length]; | ||
content.forEach((key, value) -> store[key.index()] = value); | ||
return new ArrayBasedContext(store); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public <T> T get(@Nonnull ContextKey<T> key) { | ||
return key != null && key.index() < this.store.length ? (T) this.store[key.index()] : null; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public <T> ArrayBasedContext with(@Nonnull ContextKey<T> key, T value) { | ||
if (key == null) { | ||
return this; | ||
} | ||
Object[] newStore = copyOfRange(this.store, 0, max(this.store.length, key.index() + 1)); | ||
newStore[key.index()] = value; | ||
return new ArrayBasedContext(newStore); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
|
||
ArrayBasedContext that = (ArrayBasedContext) o; | ||
return Arrays.equals(this.store, that.store); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Arrays.hashCode(this.store); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
components/context/context-core/src/main/java/datadog/context/DefaultContextBinder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package datadog.context; | ||
|
||
import java.util.Map; | ||
import java.util.WeakHashMap; | ||
import javax.annotation.Nonnull; | ||
|
||
public class DefaultContextBinder implements ContextBinder { | ||
private final Map<Object, Context> bindings = new WeakHashMap<>(); | ||
|
||
@Override | ||
public boolean attach(@Nonnull Context context, @Nonnull Object bearer) { | ||
this.bindings.put(bearer, context); | ||
return true; | ||
} | ||
|
||
@Override | ||
public Context retrieveFrom(@Nonnull Object bearer) { | ||
return this.bindings.get(bearer); | ||
} | ||
|
||
public static class Provider implements Loaders.ContextBinderProvider { | ||
@Override | ||
public ContextBinder getContextBinder() { | ||
return new DefaultContextBinder(); | ||
} | ||
|
||
@Override | ||
public int priority() { | ||
return 0; | ||
} | ||
} | ||
} |
Oops, something went wrong.