Skip to content

Commit

Permalink
Refactoring in ContextContainer and implementation (#10)
Browse files Browse the repository at this point in the history
* Refactoring in ContextContainer

Rename SimpleContextContainer to DefaultContextContainer.

Drop ContextAccessorLoader and ThreadLocalAccessorLoader in favor of
configuring instances of those directly on DefaultContextContainer.
The layers above (e.g. Web Framework, GraphQL transport, etc.) have
the responsibility to know what accessors should be used, and therefore
ContextContainer should be directly configurable. In other words it
cannot be a global mechanism like ServiceLoader.

* Drop NOOP constants and implementations

If a ContextContainerPropagator isn't found, it should be an
IllegalStateException. It means we don't know how to save to a specific
external context, and something is wrong. Or otherwise it would
silently not propagate.

If a ContextContainer isn't present, there are several cases.
- the code expects a ContextContainer and therefore needs to check
  through the isNoop() method anyway to protect itself, but it might
  forget to do so.
- the code expects there may or may not be a ContextContainer, but
  wants to start a new one if not found. Here it also needs to check
  isNoop(), but if it doesn't do so, it could even lead to errors in
  code that expects an actual ContextContainer and not a noop.
- only code that expects there may or may not be a ContextContainer and
  doesn't care either way benefits; arguably though a noop lurking
  always has the potential of causing side effects and surprises.

For ContextContainerPropagator, we now always throw
IllegalStateException if there isn't a compatible one registered.

For ContextContainer, restoreFrom raises ISE while separately there
is a restoreIfPresentFrom that returns null if not found.

* Polishing and minor refactoring

Ordering of methods in DefaultContextContainer consistent with the
order in ContextContainer.

Rename ContextContainerPropagator to ContextContainerAdapter.
  • Loading branch information
rstoyanchev authored Jun 1, 2022
1 parent 1d04f0a commit 306ba5c
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 312 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Glossary
* `Context` - mean of communication. That can be e.g. Reactor `Context`, Reactor Netty `Channel` etc.
* `ContextAccessor` - what to take from the Context and put it to the container and vice versa (e.g. if there's a Reactor Context I want to take out and put back a value for key `foo`)
* `ThreadLocalAccessor` - how to take from thread local and put it to the context container and vice versa. (e.g. `ObservationThreadLocalAccessor` will take the value of the current `Observation`)
* `ContextContainerPropagator` - how to write the `ContextContainer` to a Context and from it (e.g. `ReactorContextContainerPropagator` - how to read and write the container to Reactor Context)
* `ContextContainerAdapter` - how to write the `ContextContainer` to a Context and from it (e.g. `ReactorContextContainerAdapter` - how to read and write the container to Reactor Context)
* `Namespace` logical grouping for `ThreadLocalAccessor`s. Can be used for filtering out thread local accessors.

## Contributing
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,78 +31,6 @@
*/
public interface ContextContainer {

/**
* A No-Op instance that does nothing. To be used instead of {@code null}.
*/
ContextContainer NOOP = new ContextContainer() {
@Override
public void captureValues(Object context) {

}

@Override
public <T> T restoreValues(T context) {
return context;
}

@Override
public <T> T get(String key) {
return null;
}

@Override
public boolean containsKey(String key) {
return false;
}

@Override
public <T> T put(String key, T value) {
return value;
}

@Override
public <T> T remove(String key) {
return null;
}

@Override
public ContextContainer captureThreadLocalValues() {
return this;
}

@Override
public ContextContainer captureThreadLocalValues(Predicate<Namespace> predicate) {
return this;
}

@Override
public Scope restoreThreadLocalValues() {
return () -> {
};
}

@Override
public <T> T saveTo(T context) {
return context;
}

@Override
public boolean isNoOp() {
return true;
}

@Override
public void tryScoped(Runnable action) {
action.run();
}

@Override
public <T> T tryScoped(Supplier<T> action) {
return action.get();
}
};


/**
* Gets an element from the container.
*
Expand Down Expand Up @@ -182,41 +110,53 @@ public <T> T tryScoped(Supplier<T> action) {
* @param context context in which we want to store this container
* @param <T> type of the context
* @return the context with the stored container
* @throws IllegalStateException if a {@link ContextContainerAdapter}
* that supports the given external context is not available
*/
<T> T saveTo(T context);

/**
* Restores the {@link ContextContainer} from the provided context.
* Restores the {@link ContextContainer} from the provided external context.
*
* @param context context from which we want to retrieve from {@link ContextContainer}
* @param <T> type of the context
* @return the container retrieved from the given context
* @throws IllegalStateException if a {@link ContextContainerAdapter}
* that supports the given external context is not available, or if the
* external context does not have a {@link ContextContainer} saved
* @see #restoreIfPresentFrom(Object)
*/
@SuppressWarnings({"unchecked", "rawtypes"})
static <T> ContextContainer restoreFrom(T context) {
ContextContainerPropagator propagator = ContextContainerPropagatorLoader.getPropagatorToRestore(context);
return propagator.restore(context);
ContextContainerAdapter adapter = ContextContainerAdapterLoader.getAdapterToRead(context);
return adapter.restore(context);
}

/**
* Removes the {@link ContextContainer} from the given context.
* Restores the {@link ContextContainer} from the provided external context.
*
* @param context context from which we want to remove the {@link ContextContainer}
* @param context context from which we want to retrieve from {@link ContextContainer}
* @param <T> type of the context
* @return context with {@link ContextContainer} removed
* @return the container retrieved from the external context or {@code null}
*/
@SuppressWarnings({"unchecked", "rawtypes"})
static <T> T removeFrom(T context) {
ContextContainerPropagator propagator = ContextContainerPropagatorLoader.getPropagatorToRestore(context);
return (T) propagator.remove(context);
static <T> ContextContainer restoreIfPresentFrom(T context) {
ContextContainerAdapter adapter = ContextContainerAdapterLoader.getAdapterToRead(context);
return adapter.restoreIfPresent(context);
}

/**
* Is this a noop implementation?
* Removes the {@link ContextContainer} from the given context.
*
* @return {@code true} if this instance is a no-op
* @param context context from which we want to remove the {@link ContextContainer}
* @param <T> type of the context
* @return context with {@link ContextContainer} removed
*/
boolean isNoOp();
@SuppressWarnings({"unchecked", "rawtypes"})
static <T> T removeFrom(T context) {
ContextContainerAdapter adapter = ContextContainerAdapterLoader.getAdapterToWrite(context);
return (T) adapter.remove(context);
}

/**
* Takes the runnable and runs it with thread local values available.
Expand All @@ -237,16 +177,6 @@ static <T> T removeFrom(T context) {
<T> T tryScoped(Supplier<T> action);


/**
* Builds a new {@link ContextContainer}.
*
* @return a new {@link ContextContainer}
*/
static ContextContainer create() {
return new SimpleContextContainer();
}


/**
* Propagates the context over the {@link Runnable}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

/**
* Abstraction to tell context propagation how to read from and write to
* a given context. To register your own {@link ContextContainerPropagator}
* a given context. To register your own {@link ContextContainerAdapter}
* you have to register it using the {@link ServiceLoader} mechanism.
*
* @param <READ> context from which we read
Expand All @@ -28,39 +28,7 @@
* @author Marcin Grzejszczak
* @since 1.0.0
*/
public interface ContextContainerPropagator<READ, WRITE> {

/**
* No-Op instance of the {@link ContextContainerPropagator}. Does nothing.
*/
@SuppressWarnings("rawtypes")
ContextContainerPropagator NOOP = new ContextContainerPropagator<Object, Object>() {
@Override
public Object save(ContextContainer contextContainer, Object context) {
return context;
}

@Override
public ContextContainer restore(Object context) {
return ContextContainer.NOOP;
}

@Override
public Object remove(Object ctx) {
return ctx;
}

@Override
public boolean supportsSaveTo(Class<?> contextType) {
return false;
}

@Override
public boolean supportsRestoreFrom(Class<?> contextType) {
return false;
}

};
public interface ContextContainerAdapter<READ, WRITE> {

/**
* Save the {@link ContextContainer} to the external context.
Expand All @@ -73,13 +41,23 @@ public boolean supportsRestoreFrom(Class<?> contextType) {

/**
* Restore the {@link ContextContainer} from the external context. If the container
* is not there the implementation should return {@link ContextContainer#NOOP}.
* is not there, the implementation should raise {@link IllegalStateException}.
*
* @param context context from which we want to retrieve the {@link ContextContainer}
* @return container
* @return the container instance
* @throws IllegalStateException if no {@link ContextContainer} is found
*/
ContextContainer restore(READ context);

/**
* Restore the {@link ContextContainer} from the external context. If the container
* is not there, the implementation should raise {@link IllegalStateException}.
*
* @param context context from which we want to retrieve the {@link ContextContainer}
* @return the container instance or {@code null}
*/
ContextContainer restoreIfPresent(READ context);

/**
* Removes the {@link ContextContainer} from the context.
*
Expand All @@ -91,14 +69,14 @@ public boolean supportsRestoreFrom(Class<?> contextType) {
/**
* Checks if this implementation can work with the provided context for writing.
*
* @return class type for which this propagator is applicable
* @return class type for which this adapter is applicable
*/
boolean supportsSaveTo(Class<?> contextType);

/**
* Checks if this implementation can work with the provided context for reading.
*
* @return class type for which this propagator is applicable
* @return class type for which this adapter is applicable
*/
boolean supportsRestoreFrom(Class<?> contextType);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* 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
*
* https://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 io.micrometer.contextpropagation;

import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;

/**
* Loads {@link ThreadLocalAccessor} and {@link ContextAccessor}.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
final class ContextContainerAdapterLoader {

private static final ServiceLoader<ContextContainerAdapter> adapters = ServiceLoader.load(ContextContainerAdapter.class);

private static final Map<Class, ContextContainerAdapter> cache = new ConcurrentHashMap<>();

static ContextContainerAdapter getAdapterToWrite(Object context) {
return cache.computeIfAbsent(context.getClass(), aClass -> {
for (ContextContainerAdapter contextContainerAdapter : adapters) {
if (contextContainerAdapter.supportsSaveTo(aClass)) {
return contextContainerAdapter;
}
}
throw new IllegalStateException(
"No ContextContainerAdapter for context type: " + context.getClass());
});
}

static ContextContainerAdapter getAdapterToRead(Object context) {
return cache.computeIfAbsent(context.getClass(), aClass -> {
for (ContextContainerAdapter contextContainerAdapter : adapters) {
if (contextContainerAdapter.supportsRestoreFrom(aClass)) {
return contextContainerAdapter;
}
}
throw new IllegalStateException(
"No ContextContainerAdapter for context type: " + context.getClass());
});
}
}
Loading

0 comments on commit 306ba5c

Please sign in to comment.