/* * Copyright 2014-2019 JKOOL, LLC. * * 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 com.jkoolcloud.tnt4j.tracker; import java.util.Collection; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.jkoolcloud.tnt4j.uuid.DefaultUUIDFactory; /** * Helper class that allows sharing of variables within ThreadLocal and across threads within the same JVM. This class * is useful when passing correlators, tags and other key value pairs within and across threads. Use * {@link #getRef(Object, String)} and {@link #clearRef(Object)} to track object references across thread boundaries * within the same JVM. * * @version $Revision: 1 $ * */ public class ContextTracker { public static final String JK_CORR_SESSION_ID = "JK_CORR_SID"; public static final String JK_CORR_REQUEST_ID = "JK_CORR_RID"; private static final ConcurrentMap<String, ContextRef> REF_MAP = new ConcurrentHashMap<>(); private static ThreadLocal<ConcurrentMap<String, String>> CONTEXT = new ThreadLocal<ConcurrentMap<String, String>>() { @Override public ConcurrentMap<String, String> initialValue() { return new ConcurrentHashMap<>(); } }; /** * Obtain a context reference {@link ContextRef} for a specific object. Tracking reference is cached until * {@link #clearRef(Object)} is called. Use this method to track object references across threads within the same * JVM. * * @param obj * object for which context reference is obtained * @return context reference associated with the specified object */ public ContextRef getRef(Object obj) { return getRef(obj, DefaultUUIDFactory.getInstance().newUUID()); } /** * Obtain a context reference {@link ContextRef} for a specific object and associate it with a specified correlation * id. Context reference is cached until {@link #clearRef(Object)} is called. Use this method to track object * references across threads within the same JVM. The thread passing object to another thread should call * {@link #getRef(Object)} and the thread that receives object should call {@link #clearRef(Object)}. * * @param obj * object for which context reference is obtained * @param cid * correlation id to be associated with this object * @return context reference associated with the specified object */ public ContextRef getRef(Object obj, String cid) { String refKey = ContextRef.getObjectRef(obj); ContextRef ref = REF_MAP.get(refKey); if (ref == null) { ref = new ContextRef(obj, cid); ContextRef prev = REF_MAP.putIfAbsent(ref.oid(), ref); ref = prev != null ? prev : ref; } return ref; } /** * Discard a context reference {@link ContextRef} associated with a given object. Context references are created and * cached using {@link #getRef(Object)} and discarded using {@link #clearRef(Object)}. Use this method to track * object references across threads within the same JVM. * * @param obj * object whose reference is discarded * @return context reference associated with the specified object */ public ContextRef clearRef(Object obj) { String refKey = ContextRef.getObjectRef(obj); return REF_MAP.remove(refKey); } /** * Associates the specified value with default key {@code JK_CORR_SESSION_ID} with this map. * * @param value * value to be associated with default key {@code JK_CORR_SESSION_ID} * @return null if no previous value exists, previous value */ public static String set(String value) { return set(JK_CORR_SESSION_ID, value); } /** * Get value associated with default key {@code JK_CORR_SESSION_ID} * * @return value associated with default key {@code JK_CORR_SESSION_ID} */ public static String get() { return get(JK_CORR_SESSION_ID); } /** * Associates the specified value with default key {@code JK_CORR_REQUEST_ID} with this map. * * @param value * value to be associated with default key {@code JK_CORR_REQUEST_ID} * @return null if no previous value exists, previous value */ public static String setRequestId(String value) { return set(JK_CORR_REQUEST_ID, value); } /** * Get value associated with default key {@code JK_CORR_REQUEST_ID} * * @return value associated with default key {@code JK_CORR_REQUEST_ID} */ public static String getRequestId() { return get(JK_CORR_REQUEST_ID); } /** * Get value associated with a given key. * * @param key * key of associated value to get * @return value associated with a given key */ public static String get(String key) { ConcurrentMap<String, String> map = CONTEXT.get(); return map.get(key); } /** * Associates the specified value with default key {@code JK_CORR_ID} with this map. * * @param key * key with which the specified value is to be associated * @param value * value to be associated with the specified key * @return null if no previous value exists, previous value */ public static String set(String key, String value) { ConcurrentMap<String, String> map = CONTEXT.get(); return map.put(key, value); } /** * If the specified key is not already associated with a value, associate it with the given value. * * @param key * key with which the specified value is to be associated * @param value * value to be associated with the specified key * @return null if no previous value exists, previous value */ public static String setIfAbsent(String key, String value) { ConcurrentMap<String, String> map = CONTEXT.get(); return map.putIfAbsent(key, value); } /** * Clear all context keys and values associated with current context * */ public static void clearContext() { ConcurrentMap<String, String> map = CONTEXT.get(); map.clear(); } /** * Clear all context reference key/value associated with current context * */ public static void clearRefs() { REF_MAP.clear(); } /** * Get all value associated with current context * * @return all value associated with current context */ public Collection<String> getValues() { ConcurrentMap<String, String> map = CONTEXT.get(); return map.values(); } /** * Get all keys associated with current context * * @return all keys associated with current context */ public Set<String> getKeys() { ConcurrentMap<String, String> map = CONTEXT.get(); return map.keySet(); } /** * Get a set of all key/value pairs * * @return a set of all key/value pairs */ public Set<Entry<String, String>> entrySet() { ConcurrentMap<String, String> map = CONTEXT.get(); return map.entrySet(); } }