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;
}
}
}