ContextLocal.java

/*
** Module   : ContextLocal.java
** Abstract : Container for a context local variable, using the ThreadLocal idiom
**
** Copyright (c) 2006-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 ECF 20060120   @24009 Created initial version. Contains a context
**                           local variable and exposes access to it using
**                           the ThreadLocal idiom.
** 002 NVS 20060305   @24904 Made modes more reliable and complete. Now
**                           there is STANDALONE mode (no security at all)
**                           and distinct CLIENT and SERVER modes. CLIENT
**                           mode utilizes a static map for storing tokens
**                           so that all client threads share a single
**                           virtual context.
** 003 ECF 20060718   @28091 Fixed memory leak. Storage was not being
**                           cleaned up properly at end of context life
**                           in server and client modes.
** 004 NVS 20070328   @32636 Fixed a bug in CLIENT mode implementation.
**                           The static map for the tokens should be
**                           allocated only once.
** 005 ECF 20070708   @34417 Optimized get() and set(). Only retrieve the
**                           Wrapper object once, rather than checking for
**                           its existence first and then retrieving.
** 006 ECF 20070921   @35244 Reimplemented as generic.
** 007 ECF 20071025   @35525 Made setting of mode threadsafe. Changes
**                           prevent a potential race condition in
**                           setMode().
** 008 ECF 20080506   @38221 Fixed setMode(). Race condition was still
**                           possible, because mode was being set before
**                           all backing support was initialized. Mode is
**                           now set as the last action of setMode().
** 009 CA  20101217          Allow multiple direct session on client nodes - 
**                           the client-side context storage access must be 
**                           synchronized.
** 010 OM  20121016          Added comment description about how to avoid 
**                           issue #1577.
** 011 CA  20130628          Added possibility to reset a context (needed by appservers running in
**                           State-reset operating mode).
** 012 ECF 20131113          Added a check during context cleanup in server and client modes,
**                           which throws ContextLocalCleanupException if the get or set methods
**                           are invoked during context cleanup, as this is a potentially
**                           dangerous action (particularly in server mode, since the security
**                           context will have been popped by that time, leading to unpredictable
**                           results). Also did some minor cleanup of generics.
** 013 CA  20140513          Added some debug helper APIs, to collect dependencies between 
**                           context-local vars.  Added get(create), which conditionally 
**                           initializes the context-local var, if not already set. 
** 014 HC  20141005          Added the possibility to split the context local value
**                           initialization from its instantiation.
** 015 HC  20141106          Javadocs fixed
** 016 CA  20150131          Clean the security caches when client restarts.
** 017 CA  20150228          Ignore the UI Proxy frame classes during debug.
** 018 ECF 20151111          Performance fix: minimize native calls from frequently invoked
**                           methods.
** 019 EVL 20160224          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 020 IAS 20160408          Use synthetic key as context key.
** 021 EVL 20160411          Javadoc fixes.
** 022 GES 20190322          On conversation threads, we know that the security context will
**                           never change. This means it is safe to use a ThreadLocal cache for
**                           get() which is MUCH faster than the SecurityManager lookup. In all
**                           initialization, termination or set operations, the ThreadLocal will
**                           be updated.  The reference is still kept inside the SecurityManager
**                           so that other (non-conversation) threads can still access this state
**                           as before AND so that the proper session cleanup can still occur.
**                           We are trading a small amount of memory and extra processing for
**                           init/term/set to make get much faster.  It is worth it.
** 023 ECF 20220515          Make get(boolean create) work consistently in MODE_STANDALONE, if value
**                           previously was set to null; that is, ensure initialValue() is invoked instead of
**                           returning null, when the 'create' parameter is true. The difference was caused
**                           by the fact that a null value in the fallback implementation is different than
**                           an uninitialized thread-local variable. initialValue() is only invoked once for
**                           a thread-local, unless remove() is called.
**     CA  20221031          Added JMX instrumentation for the 'get()' method.
** 024 AL2 20230216          Avoid using an array to store the return value of the JMX instrumentation
**     CA  20230224          The JMX instrumentation was removed in the 'ContextLocal.get()' as this gets  
**                           invoked millions of times during conversion (TRPL processing) of dynamic queries.
**                           The cause is assumed to be the lambda expression capturing the local variable, 
**                           and gets re-built on each invocation.
** 025 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 026 GBB 20230825          SecurityManager context methods calls updated.
** 027 HC  20240222          Enabled JMX on FWD Client.
** 028 CA  20240829          'fallback' field needs to be a ThreadLocal instance which always exists.  
**                           Otherwise, as a certain ContextLocal instance is global per JVM, race conditions
**                           can appear between i.e. Dispatcher and Conversation threads, where 'fallback' can 
**                           be set to null by Dispatcher while Conversation has just assigned a value.
** 029 CA  20240903          For standalone mode, 'fallback' thread-local var must be assigned on first usage
**                           in get/set.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.security;

import java.io.*;
import java.util.*;

import com.goldencode.p2j.jmx.*;
import com.goldencode.p2j.net.SessionManager;  // avoid name conflicts
import com.goldencode.p2j.util.logging.*;

/**
 * An implementation of a container for an arbitrary object reference, stored
 * context locally.  Uses the same idiom used by the <code>ThreadLocal</code>
 * class in J2SE, in terms of setting and accessing the reference, and
 * generating an initial value for the container.  This implementation adds
 * an additional feature:  context-specific finalization.  Subclasses can be
 * hooked at the end of a context's life by overriding the {@link #cleanup}
 * method to perform any necessary housekeeping.
 * <p>
 * This class serves a dual role, in that it falls back to a mode which uses
 * a <code>ThreadLocal</code> as backing storage if not running within a
 * security context.  When in server mode, it uses the context-specific
 * token-storing services of <code>SecurityManager</code>.  More recently, a
 * client mode has been added (see below).
 * <p>
 * <b>Server Mode</b><br>
 * This is the mode used in production server use cases (i.e., when a security
 * environment is present).  When running in this mode, the implementation
 * relies upon the following <code>SecurityManager</code> methods:
 * <ul>
 *   <li>{@link SecurityManager.ContextSecurityManager#getToken(ContextKey)}
 *   <li>{@link SecurityManager.ContextSecurityManager#addToken(ContextKey, Object)}
 *   <li>{@link SecurityManager.ContextSecurityManager#removeToken(ContextKey)}
 * </ul>
 * <p>
 * An instance of this class uses itself as the lookup key for these methods.
 * We thus rely upon object identity to guarantee a lookup key is unique.
 * Because of this, this class' {@link #equals(Object)} and {@link
 * #hashCode()} methods are identity-based implementations which may not be
 * overridden.
 * <p>
 * <b>Fallback Mode</b><br>
 * This mode, which stores the object reference in a <code>ThreadLocal</code>,
 * is useful in cases where subsystem code which uses a context local
 * variable is executed in a standalone mode, where no security environment
 * has been created.  In this mode, since there is no security context
 * present, there is no context finalization.  In this mode, the
 * <code>cleanup</code> method will NOT be executed when the local thread
 * expires.  This mode is <i>only</i> used if no <code>SecurityManager</code>
 * instance exists.
 * <p>
 * <b>Client Mode</b><br>
 * This mode is used in a production, client environment (i.e., a security
 * manager is available and it reports that it is a client).  This mode
 * assumes a single context and uses a simple map as backing storage.  As
 * with server mode, an instance of this class uses itself as the lookup key.
 *
 * <p>
 * <strong>Note</strong><br>
 * Do not use <code>ContextLocal</code> object references in the 
 * <code>cleanup()</code> method of <code>Cleanable</code> objects as, at the 
 * time of this call, the context-local variables are already deleted from the 
 * SecurityContext (inside the <code>SecurityContext.cleanup()</code> method).
 * <p>
 * <strong>Value initialization</strong>
 * Optionally, the stored object may implement {@link Initializable} to separate
 * instantiation from object initialization. This may be useful in cases
 * the initialization logic of the stored object causes a recursive call to
 * {@linkplain #get()} which would end up in multiple instances to be registered
 * in the context. With the implemented interface the initialization life cycle
 * is as follows: {@linkplain #get()} is called, {@linkplain #initialValue()} is 
 * invoked to retrieve the initial value, the value is registered in the context,
 * {@linkplain Initializable#init()} is invoked on the initial value.
 * When exception is thrown during the call to {@linkplain Initializable#init()}
 * the value is removed from the context and the exception is re-raised.
 * @param   <T>
 *          Type of object stored context locally.
 */
public class ContextLocal<T>
extends ContextKey
implements WeightedToken
{
   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(ContextLocal.class);
   
   /** Instrumentation for {@link #get(boolean)}. */
   private static final NanoTimer GET = NanoTimer.getInstance(FwdServerJMX.TimeStat.ContextLocalGet);

   /** Constant indicating storage mode has not been set */
   private static final int MODE_UNSET = 0;
   
   /** Constant indicating security context based storage mode */
   private static final int MODE_SERVER = 1;
   
   /** Constant indicating client side storage mode */
   private static final int MODE_CLIENT = 2;
   
   /** Constant indicating thread local based storage mode */
   private static final int MODE_STANDALONE = 3;
   
   /** Client side storage for security context tokens */
   private static Map contextStorage = null;
   
   /** Flag indicating if dependencies should be collected or not, by {@link #checkCaller}. */
   private static boolean debug = false;
   
   /** Map holding dependencies.  Used only if {@link #debug} is enabled. */
   private static final Map<String, Set<String>> dependencies = new TreeMap<>();
   
   /** Security manager instance (null if no security environment exists) */
   private SecurityManager security = null;
   
   /** An instance of our fallback (ThreadLocal-based) storage mechanism */
   private final ThreadLocal<Fallback> fallback = new ThreadLocal<>();
   
   /** Storage mode */
   private volatile int mode = MODE_UNSET;
   
   /**
    * Default constructor.
    */
   public ContextLocal()
   {
      // check who calls this
      if (debug)
      {
         checkCaller(this.getClass());
      }
   }
   
   /**
    * Clean the storage caches. 
    */
   protected static synchronized void clearInstance()
   {
      dependencies.clear();
      if (contextStorage != null)
      {
         contextStorage.clear();
      }
   }
   
   /**
    * If {@link #debug} mode is enabled, dump the dependencies to STDOUT.
    */
   protected static void dumpDependencies()
   {
      if (!debug)
      {
         return;
      }
      
      try
      {
         String prefix = "   ";
         PrintWriter bw = new PrintWriter(new FileWriter("context-local.gml"));
         bw.write("graph\n");
         bw.write("[\n");
         bw.write(prefix + "directed 1\n");
         
         Map<String, Integer> ids = new HashMap<>();
         int id = 0;
         
         // write nodes
         for (String node : dependencies.keySet())
         {
            id = id + 1;
            ids.put(node, id);
            
            bw.write(String.format("%snode\n", prefix));
            bw.write(String.format("%s[\n", prefix));
            bw.write(String.format("%s%sid %d\n", prefix, prefix, id));
            bw.write(String.format("%s%slabel \"%s\"\n", prefix, prefix, node));
            bw.write(String.format("%s]\n", prefix));
            
            Set<String> parents = dependencies.get(node);
            for (String parent : parents)
            {
               if (ids.containsKey(parent))
                  continue;
               
               id = id + 1;
               ids.put(parent, id);
               
               bw.write(String.format("%snode\n", prefix));
               bw.write(String.format("%s[\n", prefix));
               bw.write(String.format("%s%sid %d\n", prefix, prefix, id));
               bw.write(String.format("%s%slabel \"%s\"\n", prefix, prefix, parent));
               bw.write(String.format("%s]\n", prefix));
            }
         }
         
         // write edges
         for (String child : dependencies.keySet())
         {
            int idch = ids.get(child);
            Set<String> parents = dependencies.get(child);
            
            for (String parent : parents)
            {
               int idp = ids.get(parent);
               
               bw.write(String.format("%sedge\n", prefix));
               bw.write(String.format("%s[\n", prefix));
               bw.write(String.format("%s%ssource %d\n", prefix, prefix, idp));
               bw.write(String.format("%s%starget %d\n", prefix, prefix, idch));
               bw.write(String.format("%s]\n", prefix));
            }
         }
         
         bw.write("]\n");
         // open the file with yEd
         
         bw.close();
      }
      catch (Exception e)
      {
         LOG.severe("", e);
      }
   }
   
   /**
    * Set the debug mode, to enable or stop caller dependency checks.
    * <p>
    * It will clear all existing dependencies.
    * <p>
    * WARNING! Do not enable debugging in a production environment, as the {@link #checkCaller}
    * API is very time expensive!
    * 
    * @param    state
    *           The new debug state.
    */
   protected static void setDebug(boolean state)
   {
      debug = state;
      dependencies.clear();
   }
   
   /**
    * Debug aid method to check the dependencies between context-local vars.
    * <p>
    * For each variable, it will load the back-dependencies (i.e. context-local variables which 
    * access this context-local var).
    * 
    * @param    localVar
    *           The type of the context local variable being accessed.
    */
   private static void checkCaller(Class<? extends ContextLocal> localVar)
   {
      String thisClsName = localVar.getName();
      StackTraceElement[] stack = Thread.currentThread().getStackTrace();
      for (StackTraceElement e : stack)
      {
         try
         {
            String clsName = e.getClassName();
            
            if (!clsName.startsWith("com.goldencode")                     ||
                clsName.startsWith("com.goldencode.p2j.persist.$__Proxy") ||
                clsName.startsWith("com.goldencode.p2j.ui.$__Proxy")      ||
                clsName.equals(thisClsName)                               ||
                clsName.equals(ContextLocal.class.getName()))
            {
               continue;
            }
            
            Class<?> cls = Class.forName(e.getClassName());
            
            if (ContextLocal.class.isAssignableFrom(cls))
            {
               synchronized (ContextLocal.class)
               {
                  String key = thisClsName.substring("com.goldencode.p2j.".length());
                  Set<String> parents = dependencies.get(key);
                  
                  if (parents == null)
                  {
                     dependencies.put(key, parents = new TreeSet<>());
                  }
                  
                  String val = clsName.substring("com.goldencode.p2j.".length());
                  parents.add(val);
               }
            }
         }
         catch (ClassNotFoundException e1)
         {
            LOG.severe("", e1);
         }
      }
   }
   
   /**
    * Get the weight of this context-local var.  The variables will be ordered by their 
    * {@link WeightFactor weight}, when security context is being reset.  Note that the security 
    * context reset can't ensure a specific order among vars with the same weight.
    * 
    * @return   The weight of this context-local var.  By default, it returns 
    *           {@link WeightFactor#NO_DEPENDENCY} - thus, unless overwritten, the context-local 
    *           var will be cleaned up first.
    */
   @Override
   public WeightFactor getWeight()
   {
      return WeightFactor.NO_DEPENDENCY;
   }
   
   /**
    * Get the value of the current context's copy of this context local
    * variable.  The first time this method is invoked, if no value has yet
    * been set into this container, {@link #initialValue} will be invoked.
    *
    * @return  Context local variable.
    */
   public final T get()
   {
      return getImpl(true);
   }
   
   /**
    * Get the value of the current context's copy of this context local
    * variable.  The first time this method is invoked, if no value has yet
    * been set into this container, {@link #initialValue} will be invoked.
    *
    * @param   create
    *          Flag indicating if the context local variable needs to be created, if it was not
    *          already set.
    *
    * @return  Context local variable.
    */
   public final T get(boolean create)
   {  
      // CA: the JMX instrumentation was removed as the 'ContextLocal.get()' gets invoked millions of times 
      // during conversion (TRPL processing) of dynamic queries.  The cause is assumed to be the lambda 
      // expression capturing the local variable, and gets re-built on each invocation.
      // return GET.timerWithReturn(() -> getImpl(create));
      return getImpl(create);
   }
   
   /**
    * Get the value of the current context's copy of this context local
    * variable.  The first time this method is invoked, if no value has yet
    * been set into this container, {@link #initialValue} will be invoked.
    *
    * @param   create
    *          Flag indicating if the context local variable needs to be created, if it was not
    *          already set.
    *
    * @return  Context local variable.
    */
   private T getImpl(boolean create)
   {
      // check who calls this
      if (debug)
      {
         checkCaller(this.getClass());
      }
      
      if (mode == MODE_UNSET)
      {
         setMode();
      }
      
      T value = null;
      
      if (mode == MODE_STANDALONE)
      {
        Fallback f = fallback.get();
        if (f == null)
        {
           f = new Fallback();
           fallback.set(f);
        }
        value = f.get();
        
        if (value == null && create)
        {
           value = initialValue();
           set(value);
           initializeValue(value);
        }
      }
      else if (mode == MODE_SERVER)
      {
         // use the optimized get from ThreadLocal if it exists
         Fallback f = fallback.get();
         if (f != null)
         {
            value = f.get();
         }
         
         if (value == null)
         {
            // slower SecurityManager approach
            Wrapper w = (Wrapper) security.contextSm.getToken(this);
            
            if (w != null)
            {
               value = w.get();
            }
         }
         
         if (f == null && value != null && hasReadCache())
         {
            // re-create the cache if someone invalidated it
            f = new ReadCache();
            f.set(value);
            fallback.set(f);
         }
         
         if (value == null && create)
         {
            value = initialValue();
            set(value);
            initializeValue(value);
         }
      }
      else
      {
         synchronized (contextStorage)
         {
            Wrapper w = (Wrapper) contextStorage.get(this);
            if (w != null)
            {
               value = w.get();
            }
            else if (create)
            {
               value = initialValue();
               set(value);
               initializeValue(value);
            }
         }
      }
      
      return value;
   }
   
   /**
    * Set the current value of this context local variable.
    *
    * @param   value
    *          Current value.
    */
   public final void set(T value)
   {
      // check who calls this
      if (debug)
      {
         checkCaller(this.getClass());
      }
      
      if (mode == MODE_UNSET)
      {
         setMode();
      }
      
      if (mode == MODE_STANDALONE)
      {
         Fallback f = fallback.get();
         if (f == null)
         {
            f = new Fallback();
            fallback.set(f);
         }
         f.set(value);
         return;
      }
      
      if (mode == MODE_SERVER)
      {
         Wrapper w = (Wrapper) security.contextSm.getToken(this);
         
         Fallback f = fallback.get();

         if (w != null)
         {
            w.set(value);
            
            // if we have a ThreadLocal cache (for get()), propagate changes there
            if (f != null)
            {
               if (hasReadCache())
               {
                  f.set(value);
               }
               else
               {
                  // let the conversation thread re-create it when it reads it next.
                  fallback.set(null);
                  
                  // release local var, too
                  f = null;
               }
            }
         }
         else
         {
            security.contextSm.addToken(this, new Wrapper(value));
            
            // if we have a ThreadLocal cache (for get()), propagate changes there
            if (f != null)
            {
               if (hasReadCache())
               {
                  f.set(value);
               }
               else
               {
                  // let the conversation thread re-create it when it reads it next.
                  fallback.set(null);

                  // release local var, too
                  f = null;
               }
            }
         }
      }
      else
      {
         synchronized (contextStorage)
         {
            Wrapper w = (Wrapper) contextStorage.get(this);
            if (w != null)
            {
               w.set(value);
            }
            else
            {
               contextStorage.put(this, new Wrapper(value));
            }
         }
      }
   }
   
   /**
    * This method will be invoked to retrieve the initial value of this
    * context local variable, the first time {@link #get} is invoked (but
    * only if {@link #set} was not previously invoked.  The returned value
    * will be stored as the variable's initial value.
    * <p>
    * This default implementation simply returns <code>null</code>.
    * Subclasses which require a different initial value must override this
    * method.
    *
    * @return  <code>null</code> by default.
    */
   protected T initialValue()
   {
      return null;
   }
   
   /**
    * This method is invoked at the end of the security context's life.  It
    * should be overridden by subclasses which need to perform cleanup at
    * this point.  Implementors should not use security specific services at
    * this point.
    *
    * @param   value
    *          The last value this context local variable contained.
    */
   protected void cleanup(T value)
   {
   }
   
   /**
    * Check if this instance is allowed to be reset.
    * 
    * @return   <code>true</code> by default.
    */
   protected boolean isResetAllowed()
   {
      return true;
   }
   
   /**
    * Performs value initialization. When <code>value</code> implements {@link Initializable}
    * the method calls its {@link Initializable#init()}. When an exception is thrown inside
    * the init routine, the value is removed from the context and the exception is re-raised.
    * 
    * @param   value
    *          The value to be initialized.
    */
   private void initializeValue(T value)
   {
      if (value instanceof Initializable)
      {
         try
         {
            ((Initializable) value).init();
         }
         catch (Exception ex)
         {
            if (mode == MODE_STANDALONE)
            {
               fallback.remove();
            }
            else if (mode == MODE_SERVER)
            {
               security.contextSm.removeToken(this);
               
               // if we have a ThreadLocal cache (for get()), cleanup
               fallback.remove();
            }
            else
            {
               synchronized (contextStorage)
               {
                  contextStorage.remove(this);
               }
            }
            
            throw ex;
         }
      }
   }
   
   /**
    * Set the mode used to store the local copy of the variable.  If no
    * security manager has been created, thread local storage is used, else
    * security context storage is used.
    */
   private synchronized void setMode()
   {
      // Resolve possible race condition.
      if (mode != MODE_UNSET)
      {
         return;
      }
      
      security = SecurityManager.getInstance();
      
      if (security == null)
      {
         fallback.set(new Fallback());
         mode = MODE_STANDALONE;
         return;
      }
      
      if (security.isServer())
      {
         // if conversation mode, this is a long running thread and we can cache
         // data in the thread local, this will optimize get()
         if (hasReadCache())
         {
            fallback.set(new ReadCache());
         }
         
         mode = MODE_SERVER;
      }
      else
      {
         if (contextStorage == null)
         {
            contextStorage = new HashMap();
         }
         mode = MODE_CLIENT;
      }
   }
   
   /**
    * Check if the current thread can use the read cache of the context's value.
    * 
    * @return   See above.
    */
   private static boolean hasReadCache()
   {
      return SessionManager.isConversationThread();
   }
   
   /**
    * An instance of this class is used to wrap the variable value when it is
    * stored in the security context.  Its primary purpose is to implement
    * the {@link #cleanup} method, which the current security context will
    * invoke at the end of its life.  Only one instance of this class is
    * created per context, per variable, the first time a value needs to be
    * stored.  Thereafter, each time the variable's value is updated, the
    * contents of this wrapper are merely reset.
    */
   private class Wrapper
   implements Cleanable
   {
      /** Data value stored by this context local variable */
      private T contents = null;
      
      /** Flag indicating we are in cleanup mode */
      private boolean cleaning = false;
      
      /**
       * Convenience constructor which assigns contents.
       *
       * @param   contents
       *          Data value stored by this context local variable
       */
      Wrapper(T contents)
      {
         set(contents);
      }
      
      /**
       * Invoked at security context end of life.  Removes this token from
       * storage and invokes the enclosing class' <code>cleanup</code>
       * method, to give subclasses a chance to perform housekeeping.
       */
      public void cleanup()
      {
         cleaning = true;
         
         try
         {
            ContextLocal.this.cleanup(contents);
         }
         finally
         {
            cleaning = false;
            switch (mode)
            {
               case MODE_SERVER:
                  security.contextSm.removeToken(ContextLocal.this);
                  
                  // if we have a ThreadLocal cache, cleanup
                  fallback.remove();
                  break;
               case MODE_CLIENT:
                  contextStorage.remove(ContextLocal.this);
                  break;
            }
         }
      }
      
      /**
       * Get the contents of the context local variable.
       * 
       * @return  Current contents of the variable.
       * 
       * @throws  ContextLocalCleanupException
       *          if this method is invoked during context cleanup.
       */
      T get()
      {
         checkCleaning();
         
         return contents;
      }
      
      /**
       * Set the contents of the context local variable.
       * 
       * @param   contents
       *          Contents to be stored in the variable.
       * 
       * @throws  ContextLocalCleanupException
       *          if this method is invoked during context cleanup.
       */
      void set(T contents)
      {
         checkCleaning();
         
         this.contents = contents;
      }
      
      /**
       * Throw an exception indicating a get or set attempt was made during context cleanup.
       * 
       * @throws  ContextLocalCleanupException
       *          if and only if we are in cleanup mode.
       */
      private void checkCleaning()
      {
         if (cleaning)
         {
            throw new ContextLocalCleanupException(
               "Cannot get or set context local value during context cleanup");
         }
      }
   }
   
   /**
    * A wrapper of the context-local value kept in the {@link ContextLocal#fallback} thread-local storage.
    * This is used as backing storage for this context local variable when no security environment is present.
    * This implementation is necessary to allow the fallback thread local variable's <code>initialValue</code>
    * method to invoke the enclosing class' implementation (which may have been overridden by a subclass).
    */
   private class Fallback
   {
      /** Cached value for this context-local instance. */
      private T value = null;
      
      /**
       * Retrieved the cached {@link #value}.  If not set, call {@link #initialValue()}.
       */
      public T get()
      {
         if (value == null)
         {
            value = initialValue();
         }
         return value;
      }
      
      /**
       * Set the {@link #value}.
       * 
       * @param    value
       *           The current value for this context-local instance.
       */
      public void set(T value)
      {
         this.value = value;
      }
      
      /**
       * Reroute an invocation of thread local's <code>initialValue</code>
       * implementation to the enclosing class' method of the same name.
       *
       * @return  The initial value returned by the enclosing class'
       *          <code>initialValue</code> implementation.
       */
      protected T initialValue()
      {
         T value = ContextLocal.this.initialValue();
         initializeValue(value);
         return value;
      }
   }
   
   /**
    * An extension of {@link Fallback} which is used as the backing storage for a read cache to optimize 
    * {@link ContextLocal#get()}.
    */
   private class ReadCache
   extends Fallback
   {
      /**
       * No <code>initialValue</code> is needed since it will be done in the normal
       * server-side implementation.  This is a NOP.
       *
       * @return  Always <code>null</code>.
       */
      @Override
      protected T initialValue()
      {
         return null;
      }
   }
}