1 /* 2 * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 27 package com.sun.jmx.snmp; 28 29 import java.util.Stack; 30 import java.util.EmptyStackException; 31 32 /** 33 * <p><b>Warning: The interface of this class is subject to change. 34 * Use at your own risk.</b></p> 35 * 36 * <p>This class associates a context with each thread that 37 * references it. The context is a set of mappings between Strings 38 * and Objects. It is managed as a stack, typically with code like 39 * this:</p> 40 * 41 * <pre> 42 * ThreadContext oldContext = ThreadContext.push(myKey, myObject); 43 * // plus possibly further calls to ThreadContext.push... 44 * try { 45 * doSomeOperation(); 46 * } finally { 47 * ThreadContext.restore(oldContext); 48 * } 49 * </pre> 50 * 51 * <p>The <code>try</code>...<code>finally</code> block ensures that 52 * the <code>restore</code> is done even if 53 * <code>doSomeOperation</code> terminates abnormally (with an 54 * exception).</p> 55 * 56 * <p>A thread can consult its own context using 57 * <code>ThreadContext.get(myKey)</code>. The result is the 58 * value that was most recently pushed with the given key.</p> 59 * 60 * <p>A thread cannot read or modify the context of another thread.</p> 61 * 62 * <p><b>This API is a Sun Microsystems internal API and is subject 63 * to change without notice.</b></p> 64 */ 65 public class ThreadContext implements Cloneable { 66 67 /* The context of a thread is stored as a linked list. At the 68 head of the list is the value returned by localContext.get(). 69 At the tail of the list is a sentinel ThreadContext value with 70 "previous" and "key" both null. There is a different sentinel 71 object for each thread. 72 73 Because a null key indicates the sentinel, we reject attempts to 74 push context entries with a null key. 75 76 The reason for using a sentinel rather than just terminating 77 the list with a null reference is to protect against incorrect 78 or even malicious code. If you have a reference to the 79 sentinel value, you can erase the context stack. Only the 80 caller of the first "push" that put something on the stack can 81 get such a reference, so if that caller does not give this 82 reference away, no one else can erase the stack. 83 84 If the restore method took a null reference to mean an empty 85 stack, anyone could erase the stack, since anyone can make a 86 null reference. 87 88 When the stack is empty, we discard the sentinel object and 89 have localContext.get() return null. Then we recreate the 90 sentinel object on the first subsequent push. 91 92 ThreadContext objects are immutable. As a consequence, you can 93 give a ThreadContext object to setInitialContext that is no 94 longer current. But the interface says this can be rejected, 95 in case we remove immutability later. */ 96 97 /* We have to comment out "final" here because of a bug in the JDK1.1 98 compiler. Uncomment it when we discard 1.1 compatibility. */ 99 private /*final*/ ThreadContext previous; 100 private /*final*/ String key; 101 private /*final*/ Object value; 102 103 private ThreadContext(ThreadContext previous, String key, Object value) { 104 this.previous = previous; 105 this.key = key; 106 this.value = value; 107 } 108 109 /** 110 * <p>Get the Object that was most recently pushed with the given key.</p> 111 * 112 * @param key the key of interest. 113 * 114 * @return the last Object that was pushed (using 115 * <code>push</code>) with that key and not subsequently canceled 116 * by a <code>restore</code>; or null if there is no such object. 117 * A null return value may also indicate that the last Object 118 * pushed was the value <code>null</code>. Use the 119 * <code>contains</code> method to distinguish this case from the 120 * case where there is no Object. 121 * 122 * @exception IllegalArgumentException if <code>key</code> is null. 123 */ 124 public static Object get(String key) throws IllegalArgumentException { 125 ThreadContext context = contextContaining(key); 126 if (context == null) 127 return null; 128 else 129 return context.value; 130 } 131 132 /** 133 * <p>Check whether a value with the given key exists in the stack. 134 * This means that the <code>push</code> method was called with 135 * this key and it was not cancelled by a subsequent 136 * <code>restore</code>. This method is useful when the 137 * <code>get</code> method returns null, to distinguish between 138 * the case where the key exists in the stack but is associated 139 * with a null value, and the case where the key does not exist in 140 * the stack.</p> 141 * 142 * @return true if the key exists in the stack. 143 * 144 * @exception IllegalArgumentException if <code>key</code> is null. 145 */ 146 public static boolean contains(String key) 147 throws IllegalArgumentException { 148 return (contextContaining(key) != null); 149 } 150 151 /** 152 * <p>Find the ThreadContext in the stack that contains the given key, 153 * or return null if there is none.</p> 154 * 155 * @exception IllegalArgumentException if <code>key</code> is null. 156 */ 157 private static ThreadContext contextContaining(String key) 158 throws IllegalArgumentException { 159 if (key == null) 160 throw new IllegalArgumentException("null key"); 161 for (ThreadContext context = getContext(); 162 context != null; 163 context = context.previous) { 164 if (key.equals(context.key)) 165 return context; 166 /* Note that "context.key" may be null if "context" is the 167 sentinel, so don't write "if (context.key.equals(key))"! */ 168 } 169 return null; 170 } 171 172 // /** 173 // * Change the value that was most recently associated with the given key 174 // * in a <code>push</code> operation not cancelled by a subsequent 175 // * <code>restore</code>. If there is no such association, nothing happens 176 // * and the return value is null. 177 // * 178 // * @param key the key of interest. 179 // * @param value the new value to associate with that key. 180 // * 181 // * @return the value that was previously associated with the key, or null 182 // * if the key does not exist in the stack. 183 // * 184 // * @exception IllegalArgumentException if <code>key</code> is null. 185 // */ 186 // public static Object set(String key, Object value) 187 // throws IllegalArgumentException { 188 // ThreadContext context = contextContaining(key); 189 // if (context == null) 190 // return null; 191 // Object old = context.value; 192 // context.value = value; 193 // return old; 194 // } 195 196 /** 197 * <p>Push an object on the context stack with the given key. 198 * This operation can subsequently be undone by calling 199 * <code>restore</code> with the ThreadContext value returned 200 * here.</p> 201 * 202 * @param key the key that will be used to find the object while it is 203 * on the stack. 204 * @param value the value to be associated with that key. It may be null. 205 * 206 * @return a ThreadContext that can be given to <code>restore</code> to 207 * restore the stack to its state before the <code>push</code>. 208 * 209 * @exception IllegalArgumentException if <code>key</code> is null. 210 */ 211 public static ThreadContext push(String key, Object value) 212 throws IllegalArgumentException { 213 if (key == null) 214 throw new IllegalArgumentException("null key"); 215 216 ThreadContext oldContext = getContext(); 217 if (oldContext == null) 218 oldContext = new ThreadContext(null, null, null); // make sentinel 219 ThreadContext newContext = new ThreadContext(oldContext, key, value); 220 setContext(newContext); 221 return oldContext; 222 } 223 224 /** 225 * <p>Return an object that can later be supplied to <code>restore</code> 226 * to restore the context stack to its current state. The object can 227 * also be given to <code>setInitialContext</code>.</p> 228 * 229 * @return a ThreadContext that represents the current context stack. 230 */ 231 public static ThreadContext getThreadContext() { 232 return getContext(); 233 } 234 235 /** 236 * <p>Restore the context stack to an earlier state. This typically 237 * undoes the effect of one or more <code>push</code> calls.</p> 238 * 239 * @param oldContext the state to return. This is usually the return 240 * value of an earlier <code>push</code> operation. 241 * 242 * @exception NullPointerException if <code>oldContext</code> is null. 243 * @exception IllegalArgumentException if <code>oldContext</code> 244 * does not represent a context from this thread, or if that 245 * context was undone by an earlier <code>restore</code>. 246 */ 247 public static void restore(ThreadContext oldContext) 248 throws NullPointerException, IllegalArgumentException { 249 /* The following test is not strictly necessary in the code as it 250 stands today, since the reference to "oldContext.key" would 251 generate a NullPointerException anyway. But if someone 252 didn't notice that during subsequent changes, they could 253 accidentally permit restore(null) with the semantics of 254 trashing the context stack. */ 255 if (oldContext == null) 256 throw new NullPointerException(); 257 258 /* Check that the restored context is in the stack. */ 259 for (ThreadContext context = getContext(); 260 context != oldContext; 261 context = context.previous) { 262 if (context == null) { 263 throw new IllegalArgumentException("Restored context is not " + 264 "contained in current " + 265 "context"); 266 } 267 } 268 269 /* Discard the sentinel if the stack is empty. This means that it 270 is an error to call "restore" a second time with the 271 ThreadContext value that means an empty stack. That's why we 272 don't say that it is all right to restore the stack to the 273 state it was already in. */ 274 if (oldContext.key == null) 275 oldContext = null; 276 277 setContext(oldContext); 278 } 279 280 /** 281 * <p>Set the initial context of the calling thread to a context obtained 282 * from another thread. After this call, the calling thread will see 283 * the same results from the <code>get</code> method as the thread 284 * from which the <code>context</code> argument was obtained, at the 285 * time it was obtained.</p> 286 * 287 * <p>The <code>context</code> argument must be the result of an earlier 288 * <code>push</code> or <code>getThreadContext</code> call. It is an 289 * error (which may or may not be detected) if this context has been 290 * undone by a <code>restore</code>.</p> 291 * 292 * <p>The context stack of the calling thread must be empty before this 293 * call, i.e., there must not have been a <code>push</code> not undone 294 * by a subsequent <code>restore</code>.</p> 295 * 296 * @exception IllegalArgumentException if the context stack was 297 * not empty before the call. An implementation may also throw this 298 * exception if <code>context</code> is no longer current in the 299 * thread from which it was obtained. 300 */ 301 /* We rely on the fact that ThreadContext objects are immutable. 302 This means that we don't have to check that the "context" 303 argument is valid. It necessarily represents the head of a 304 valid chain of ThreadContext objects, even if the thread from 305 which it was obtained has subsequently been set to a point 306 later in that chain using "restore". */ 307 public void setInitialContext(ThreadContext context) 308 throws IllegalArgumentException { 309 /* The following test assumes that we discard sentinels when the 310 stack is empty. */ 311 if (getContext() != null) 312 throw new IllegalArgumentException("previous context not empty"); 313 setContext(context); 314 } 315 316 private static ThreadContext getContext() { 317 return localContext.get(); 318 } 319 320 private static void setContext(ThreadContext context) { 321 localContext.set(context); 322 } 323 324 private static ThreadLocal<ThreadContext> localContext = 325 new ThreadLocal<ThreadContext>(); 326 }