package io.pcp.parfait.timing;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import net.jcip.annotations.ThreadSafe;
/**
* <p>
* Map-like functions to keep track of key/value pairs for application threads.
* Keys are Strings, with values of any arbitrary object. Optionally keeps a log
* framework's MDC weakly in sync with changes -- that is, there is no atomicity
* guarantee so it's plausible that this class' context information and the
* logger's will not be in a consistent state; however, log4j and logback's MDC
* expose data <em>only</em> to the current thread via a thread-local so this is
* unlikely to be a problem in practice.
* </p>
* <p>
* Most methods operate on the context of the calling thread; only
* {@link #forThread(Thread)} allows cross-thread information retrieval.
* </p>
*
* @author Cowan
*/
@ThreadSafe
public class ThreadContext {
private static final CacheLoader<Thread, Map<String, Object>> NEW_CONTEXT_CREATOR = new CacheLoader<Thread, Map<String, Object>>() {
@Override
public Map<String, Object> load(Thread thread) throws Exception {
return new ConcurrentHashMap<>();
}
};
private final LoadingCache<Thread, Map<String, Object>> PER_THREAD_CONTEXTS = CacheBuilder.newBuilder().weakKeys().build(NEW_CONTEXT_CREATOR);
private volatile MdcBridge mdcBridge = new NullMdcBridge();
public ThreadContext() {
this(new NullMdcBridge());
}
public ThreadContext(MdcBridge mdcBridge) {
// TODO should that be a static variable..?
this.mdcBridge=mdcBridge;
}
/**
* Adds the given key/value pair to the current thread's context, and updates {@link MdcBridge} with
* same.
*/
public void put(String key, Object value) {
PER_THREAD_CONTEXTS.getUnchecked(Thread.currentThread()).put(key, value);
mdcBridge.put(key, value);
}
/**
* Removes the given key from the current thread's context and {@link MdcBridge}.
*/
public void remove(String key) {
PER_THREAD_CONTEXTS.getUnchecked(Thread.currentThread()).remove(key);
mdcBridge.remove(key);
}
/**
* Retrieves the value corresponding to the supplied key for the current thread (null if no such
* value exists)
*/
public Object get(String key) {
return PER_THREAD_CONTEXTS.getUnchecked(Thread.currentThread()).get(key);
}
/**
* Clears all values for the current thread.
*/
public void clear() {
/**
* Unfortunately log4j's MDC historically never had a mechanism to block remove keys,
* so we're forced to do this one by one.
*/
for (String key : allKeys()) {
mdcBridge.remove(key);
}
PER_THREAD_CONTEXTS.getUnchecked(Thread.currentThread()).clear();
}
/**
* Retrieves a copy of the thread context for the given thread
*/
public Map<String, Object> forThread(Thread t) {
return new HashMap<String, Object>(PER_THREAD_CONTEXTS.getUnchecked(t));
}
public Collection<String> allKeys() {
Set<String> keys = new HashSet<String>();
for (Map<String, Object> threadMdc : PER_THREAD_CONTEXTS.asMap().values()) {
keys.addAll(threadMdc.keySet());
}
return keys;
}
public Object getForThread(Thread thread, String key) {
return PER_THREAD_CONTEXTS.getUnchecked(thread).get(key);
}
/**
* Factory method that creates a new ThreadContext initialized to also update Log4j's MDC.
*/
public static ThreadContext newMDCEnabledContext() {
return new ThreadContext(new Log4jMdcBridge());
}
/**
* Factory method that creates a new ThreadContext initialised to also update SLF4J's MDC
*/
public static ThreadContext newSLF4JEnabledContext() {
return new ThreadContext(new Slf4jMDCBridge());
}
public interface MdcBridge {
void put(String key, Object object);
void remove(String key);
}
public static class NullMdcBridge implements MdcBridge {
@Override
public void put(String key, Object object) {
// no-op
}
@Override
public void remove(String key) {
// no-op
}
}
public static class Log4jMdcBridge implements MdcBridge {
@Override
public void put(String key, Object object) {
org.apache.log4j.MDC.put(key, object);
}
@Override
public void remove(String key) {
org.apache.log4j.MDC.remove(key);
}
}
public static class Slf4jMDCBridge implements MdcBridge {
@Override
public void put(String key, Object object) {
org.slf4j.MDC.put(key, String.valueOf(object));
}
@Override
public void remove(String key) {
org.slf4j.MDC.remove(key);
}
}
}