View Javadoc
1   package com.github.valfirst.slf4jtest;
2   
3   import java.util.Collections;
4   import java.util.HashMap;
5   import java.util.Map;
6   import java.util.Objects;
7   import java.util.TreeMap;
8   import org.slf4j.MDC;
9   import org.slf4j.helpers.BasicMDCAdapter;
10  
11  public class TestMDCAdapter extends BasicMDCAdapter {
12  
13      private final ThreadLocal<Map<String, String>> value;
14      private final boolean initialEnable;
15      private final boolean initialInherit;
16      private final boolean initialReturnNullCopyWhenMdcNotSet;
17      private final boolean initialAllowNullValues;
18      private volatile boolean enable;
19      private volatile boolean inherit;
20      private volatile boolean returnNullCopyWhenMdcNotSet;
21      private volatile boolean allowNullValues;
22  
23      public TestMDCAdapter() {
24          this(OverridableProperties.createUnchecked("slf4jtest"));
25      }
26  
27      TestMDCAdapter(OverridableProperties properties) {
28          enable = initialEnable = getBooleanProperty(properties, "mdc.enable", true);
29          inherit = initialInherit = getBooleanProperty(properties, "mdc.inherit", false);
30          returnNullCopyWhenMdcNotSet =
31                  initialReturnNullCopyWhenMdcNotSet =
32                          getBooleanProperty(properties, "mdc.return.null.copy.when.mdc.not.set", false);
33          allowNullValues =
34                  initialAllowNullValues = getBooleanProperty(properties, "mdc.allow.null.values", true);
35  
36          value =
37                  new InheritableThreadLocal<Map<String, String>>() {
38                      @Override
39                      protected Map<String, String> childValue(Map<String, String> parentValue) {
40                          if (enable && inherit && parentValue != null) {
41                              return new HashMap<>(parentValue);
42                          } else {
43                              return null;
44                          }
45                      }
46                  };
47      }
48  
49      static boolean getBooleanProperty(
50              OverridableProperties properties, String propertyKey, boolean defaultValue) {
51          return Boolean.parseBoolean(properties.getProperty(propertyKey, String.valueOf(defaultValue)));
52      }
53  
54      @Override
55      public void put(final String key, final String val) {
56          if (!enable) {
57              return;
58          }
59          if (key == null) {
60              throw new IllegalArgumentException("key cannot be null");
61          }
62          if (val == null && !allowNullValues) {
63              throw new IllegalArgumentException("val cannot be null");
64          }
65          Map<String, String> map = value.get();
66          if (map == null) {
67              map = new HashMap<>();
68              value.set(map);
69          }
70          map.put(key, val);
71      }
72  
73      @Override
74      public String get(final String key) {
75          if (!enable) {
76              return null;
77          }
78          if (key == null) {
79              throw new IllegalArgumentException("key cannot be null");
80          }
81          Map<String, String> map = value.get();
82          if (map != null) {
83              return map.get(key);
84          } else {
85              return null;
86          }
87      }
88  
89      @Override
90      public void remove(final String key) {
91          if (!enable) {
92              return;
93          }
94          if (key == null) {
95              throw new IllegalArgumentException("key cannot be null");
96          }
97          Map<String, String> map = value.get();
98          if (map != null) {
99              map.remove(key);
100         }
101     }
102 
103     @Override
104     public void clear() {
105         if (!enable) {
106             return;
107         }
108         Map<String, String> map = value.get();
109         if (map == null) {
110             return;
111         }
112         map.clear();
113         value.remove();
114     }
115 
116     /**
117      * Return a copy of the current thread's context map. {@code null} is returned if
118      *
119      * <ul>
120      *   <li>The MDC functionality is disabled, c.f. <code>setEnable</code>, or
121      *   <li>"return null when empty" is enabled, c.f. <code>setReturnNullCopyWhenMdcNotSet</code>,
122      *       and <code>put</code> has not been called.
123      * </ul>
124      *
125      * @return A copy of the current thread's context map.
126      */
127     @Override
128     public Map<String, String> getCopyOfContextMap() {
129         if (!enable) {
130             return null;
131         }
132         Map<String, String> map = value.get();
133         if (map == null) {
134             if (returnNullCopyWhenMdcNotSet) {
135                 return null;
136             } else {
137                 return new TreeMap<>();
138             }
139         } else {
140             return new TreeMap<>(map);
141         }
142     }
143 
144     // Internal access
145     Map<String, String> getContextMap() {
146         Map<String, String> map = value.get();
147         return map == null ? Collections.emptySortedMap() : map;
148     }
149 
150     @Override
151     public void setContextMap(final Map<String, String> contextMap) {
152         if (!enable) {
153             return;
154         }
155         clear();
156         if (contextMap == null) {
157             return;
158         }
159         if (contextMap.keySet().stream().anyMatch(Objects::isNull)) {
160             throw new IllegalArgumentException("key cannot be null");
161         }
162         if (!allowNullValues && contextMap.containsValue(null)) {
163             throw new IllegalArgumentException("val cannot be null");
164         }
165         value.set(new HashMap<>(contextMap));
166     }
167 
168     /**
169      * Enable the MDC functionality for all threads.
170      *
171      * @param enable Whether to enable the MDC functionality. The default value is {@code true}.
172      */
173     public void setEnable(boolean enable) {
174         this.enable = enable;
175     }
176 
177     /**
178      * Define whether child threads inherit a copy of the MDC from its parent thread. Note that the
179      * copy is taken when the {@link Thread} is constructed. This affects all threads.
180      *
181      * @param inherit Whether to enable inheritance. The default value is {@code false}.
182      */
183     public void setInherit(boolean inherit) {
184         this.inherit = inherit;
185     }
186 
187     /**
188      * Define whether null values are allowed in the MDC. This affects all threads.
189      *
190      * @param allowNullValues Whether to allow nulls. The default value is {@code true}.
191      */
192     public void setAllowNullValues(boolean allowNullValues) {
193         this.allowNullValues = allowNullValues;
194     }
195 
196     /**
197      * Define whether {@link #getCopyOfContextMap} returns {@code null} when no values have been set.
198      * This affects all threads.
199      *
200      * @param returnNullCopyWhenMdcNotSet Whether to return null. The default value is {@code false}.
201      *     If {@code false}, an empty map is returned instead.
202      */
203     public void setReturnNullCopyWhenMdcNotSet(boolean returnNullCopyWhenMdcNotSet) {
204         this.returnNullCopyWhenMdcNotSet = returnNullCopyWhenMdcNotSet;
205     }
206 
207     /**
208      * Whether the MDC functionality is enabled.
209      *
210      * @return Whether the MDC functionality is enabled.
211      */
212     public boolean getEnable() {
213         return enable;
214     }
215 
216     /**
217      * Whether child threads inherit a copy of the MDC from its parent thread.
218      *
219      * @return Whether inheritance is enabled.
220      */
221     public boolean getInherit() {
222         return inherit;
223     }
224 
225     /**
226      * Whether null values are allowed in the MDC.
227      *
228      * @return Whether nulls are allowed.
229      */
230     public boolean getAllowNullValues() {
231         return allowNullValues;
232     }
233 
234     /**
235      * Whether {@link #getCopyOfContextMap} returns {@code null} when no values have been set.
236      *
237      * @return Whether to return null.
238      */
239     public boolean getReturnNullCopyWhenMdcNotSet() {
240         return returnNullCopyWhenMdcNotSet;
241     }
242 
243     /**
244      * Reset the options to values defined by the static configuration. This undoes to changes made
245      * using {@link #setEnable}, {@link #setInherit}, {@link #setAllowNullValues}, and {@link
246      * #setReturnNullCopyWhenMdcNotSet}.
247      */
248     public void restoreOptions() {
249         enable = initialEnable;
250         inherit = initialInherit;
251         returnNullCopyWhenMdcNotSet = initialReturnNullCopyWhenMdcNotSet;
252         allowNullValues = initialAllowNullValues;
253     }
254 
255     /** Access the current MDC adapter. Used to call the option setting methods. */
256     @SuppressWarnings("unchecked")
257     public static TestMDCAdapter getInstance() {
258         return (TestMDCAdapter) MDC.getMDCAdapter();
259     }
260 }