Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
PerfectSlayer committed Jul 11, 2024
1 parent 8b00e3e commit d00aec5
Show file tree
Hide file tree
Showing 14 changed files with 442 additions and 0 deletions.
1 change: 1 addition & 0 deletions components/context/context-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
apply(from = "$rootDir/gradle/java.gradle")
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);
}
}
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);
}
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;
}
}
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
}
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);
}
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;
}
}
}
10 changes: 10 additions & 0 deletions components/context/context-core/build.gradle.kts
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)
}
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);
}
}
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;
}
}
}
Loading

0 comments on commit d00aec5

Please sign in to comment.