--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.iapi.services.context.ContextService\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.iapi.services.context;\r
+\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.services.stream.HeaderPrintWriter;\r
+\r
+import java.security.AccessController;\r
+import java.security.PrivilegedAction;\r
+import java.util.Hashtable;\r
+import java.util.Enumeration;\r
+\r
+import java.util.HashSet;\r
+import java.util.Iterator;\r
+\r
+/**\r
+ A set of static methods to supply easier access to contexts.\r
+*/\r
+public final class ContextService //OLD extends Hashtable\r
+{\r
+\r
+ private static ContextService factory;\r
+ private HeaderPrintWriter errorStream;\r
+\r
+ /**\r
+ Maintains a list of all the contexts that this thread has created\r
+ and/or used. The object stored in the thread local varys according\r
+ how this thread has been used and will be one of:\r
+\r
+ <UL>\r
+ <LI> null - the thread no affiliation with a context manager.\r
+\r
+ <LI> ContextManager - the current thread has used or is using\r
+ this context manager. If ContextManager.activeThread equals\r
+ the current thread then the thread is currently active with\r
+ the ContextManager. In this case ContextManager.activeCount\r
+ will be greater than zero and represent the level of nested\r
+ setCurrentContextmanager calls.\r
+ If ContextManager.activeThread is null then no other thread\r
+ is using the Contextmanager, if ContextManager.activeThread\r
+ is not-null and not equal to the current thread then some\r
+ other thread is using the context. It is assumed that\r
+ only a single thread can be using a ContextManager at any time\r
+ and this is enforced by synchronization outside the ContextManager.\r
+ E.g for JDBC connections, synchronization at the JDBC level.\r
+\r
+ <LI> java.util.Stack containing ContextManagers - the current\r
+ thread is actively using multiple different ContextManagers,\r
+ with nesting. All ContextManagers in the stack will have\r
+ activeThread set to the current thread, and their activeCount\r
+ set to -1. This is beacause nesting is soley represented by\r
+ the stack, with the current context manager on top of the stack.\r
+ This supports multiple levels of nesting across two stacks, e.g.\r
+ C1->C2->C2->C1->C2.\r
+ </UL>\r
+\r
+ This thread local is used to find the current context manager. Basically it provides\r
+ fast access to a list of candidate contexts. If one of the contexts has its activeThread\r
+ equal to the current thread then it is the current context manager.\r
+\r
+ If the thread has pushed multiple contexts (e.g. open a new non-nested Derby connection\r
+ from a server side method) then threadContextList will contain a Stack. The value for each cm\r
+ will be a push order, with higher numbers being more recently pushed.\r
+\r
+ To support the case where a single context manager is pushed twice (nested connection),\r
+ the context manager keeps track of the number of times it has been pushed (set). Note that\r
+ our synchronization requires that a single context can only be accessed by a single thread at a time.\r
+ In the JDBC layer this is enforced by the synchronization on the connection object.\r
+\r
+ <P>\r
+ There are two cases we are trying to optimise.\r
+ <UL>\r
+ <LI> Typical JDBC client program where there a Connection is always executed using a single thread.\r
+ In this case this variable will contain the Connection's context manager \r
+ <LI> Typical application server pooled connection where a single thread may use a connection from a pool\r
+ for the lifetime of the request. In this case this variable will contain a WeakReference.\r
+ </UL>\r
+ <BR>\r
+ Single thread for Connection exection.\r
+ <pre>\r
+ threadContextList.get() == cm\r
+ // while in JDBC engine code\r
+ cm.activeThread == Thread.currentThread();\r
+ cm.activeCount = 1;\r
+ </pre>\r
+ \r
+ <BR>\r
+ J2EE single thread for lifetime of execution.\r
+ <pre>\r
+ // thread executing request\r
+ threadContextList.get() == cm\r
+ // while in JDBC engine code\r
+ cm.activeThread == Thread.currentThread();\r
+ cm.activeCount = 1;\r
+ \r
+ // other threads that have recently executed\r
+ // the same connection can have\r
+ threadContextList.get() == cm\r
+ cm.activeThread != Thread.currentThread();\r
+ </pre>\r
+ \r
+ <BR>\r
+ Nested routine calls within single connection\r
+ <pre>\r
+ threadContextList.get() == cm\r
+ // Within server-side JDBC code in a\r
+ // function called from another function/procedure\r
+ // called from an applications's statement\r
+ // (three levels of nesting)\r
+ cm.activeThread == Thread.currentThread();\r
+ cm.activeCount = 3; \r
+ </pre>\r
+ \r
+ <BR>\r
+ Nested routine calls with the inner routine\r
+ using a different connection to access a Derby database.\r
+ Note nesting of orignal Contextmanager cm is changed\r
+ from an activeCount of 2 to nesting within the stack\r
+ once multiple ContextManagers are involved.\r
+ <pre>\r
+ threadContextList.get() == stack {cm2,cm,cm}\r
+ cm.activeThread == Thread.currentThread();\r
+ cm.activeCount = -1; // nesting in stack\r
+ cm2.activeThread == Thread.currentThread();\r
+ cm2.activeCount = -1; // nesting in stack\r
+ </pre> \r
+ \r
+ <BR>\r
+ Nested multiple ContextManagers, the code supports\r
+ this, though it may not be possible currently\r
+ to have a stack like this from SQL/JDBC.\r
+ <pre>\r
+ threadContextList.get() == stack {cm3,cm2,cm,cm2,cm,cm}\r
+ cm.activeThread == Thread.currentThread();\r
+ cm.activeCount = -1; // nesting in stack\r
+ cm2.activeThread == Thread.currentThread();\r
+ cm2.activeCount = -1; // nesting in stack\r
+ cm3.activeThread == Thread.currentThread();\r
+ cm3.activeCount = -1; // nesting in stack\r
+ </pre> \r
+ */\r
+ private ThreadLocal threadContextList = new ThreadLocal();\r
+\r
+ /**\r
+ * Collection of all ContextManagers that are open\r
+ * in the complete Derby system. A ContextManager is\r
+ * added when it is created with newContextManager and\r
+ * removed when the session is closed.\r
+ * \r
+ * @see #newContextManager()\r
+ * @see SystemContext#cleanupOnError(Throwable)\r
+ */\r
+ private HashSet allContexts;\r
+\r
+ /**\r
+ * Create a new ContextService for a Derby system.\r
+ * Only a single system is active at any time.\r
+ *\r
+ */\r
+ public ContextService() {\r
+\r
+ // find the error stream\r
+ errorStream = Monitor.getStream(); \r
+\r
+ ContextService.factory = this;\r
+\r
+ allContexts = new HashSet();\r
+\r
+ }\r
+\r
+ /**\r
+ So it can be given to us and taken away...\r
+ */\r
+ public static void stop() {\r
+ // For some unknown reason, the ContextManager and\r
+ // ContextService objects will not be garbage collected\r
+ // without the next two lines.\r
+ ContextService fact = ContextService.factory;\r
+ if (fact != null) {\r
+ synchronized (fact) {\r
+ fact.allContexts = null;\r
+ fact.threadContextList = null;\r
+ ContextService.factory = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ public static ContextService getFactory() {\r
+ ContextService csf = factory;\r
+\r
+ if (csf == null)\r
+ throw new ShutdownException();\r
+ return csf;\r
+ }\r
+ /**\r
+ Find the context with the given name in the context service factory\r
+ loaded for the system.\r
+\r
+ @return The requested context, null if it doesn't exist.\r
+ */\r
+ public static Context getContext(String contextId) {\r
+\r
+ ContextManager cm = getFactory().getCurrentContextManager();\r
+\r
+ if( cm == null)\r
+ return null;\r
+ \r
+ return cm.getContext(contextId);\r
+ }\r
+\r
+ /**\r
+ Find the context with the given name in the context service factory\r
+ loaded for the system.\r
+\r
+ This version will not do any debug checking, but return null\r
+ quietly if it runs into any problems.\r
+\r
+ @return The requested context, null if it doesn't exist.\r
+ */\r
+ public static Context getContextOrNull(String contextId) {\r
+ ContextService csf = factory;\r
+\r
+ if (csf == null)\r
+ return null;\r
+ \r
+ ContextManager cm = csf.getCurrentContextManager();\r
+\r
+ if (cm == null)\r
+ return null;\r
+\r
+ return cm.getContext(contextId);\r
+ }\r
+\r
+\r
+ /**\r
+ * Get current Context Manager linked to the current Thread.\r
+ * See setCurrentContextManager for details.\r
+ * Note that this call can be expensive and is only\r
+ * intended to be used in "stateless" situations.\r
+ * Ideally code has a reference to the correct\r
+ * ContextManager from another Object, such as a pushed Context.\r
+ * \r
+ * @return ContextManager current Context Manager\r
+ */\r
+ public ContextManager getCurrentContextManager() {\r
+\r
+ ThreadLocal tcl = threadContextList;\r
+ if (tcl == null) {\r
+ // The context service is already stopped.\r
+ return null;\r
+ }\r
+\r
+ Object list = tcl.get();\r
+\r
+ if (list instanceof ContextManager) {\r
+ \r
+ Thread me = Thread.currentThread();\r
+ \r
+ ContextManager cm = (ContextManager) list;\r
+ if (cm.activeThread == me)\r
+ return cm;\r
+ return null;\r
+ }\r
+\r
+ if (list == null)\r
+ return null;\r
+\r
+ java.util.Stack stack = (java.util.Stack) list;\r
+ return (ContextManager) (stack.peek());\r
+\r
+ }\r
+\r
+ /**\r
+ * Break the link between the current Thread and the passed\r
+ * in ContextManager. Called in a pair with setCurrentContextManager,\r
+ * see that method for details.\r
+ */\r
+ public void resetCurrentContextManager(ContextManager cm) {\r
+ ThreadLocal tcl = threadContextList;\r
+\r
+ if (tcl == null) {\r
+ // The context service is already stopped.\r
+ return;\r
+ }\r
+\r
+ if (SanityManager.DEBUG) {\r
+\r
+ if (Thread.currentThread() != cm.activeThread) {\r
+ SanityManager.THROWASSERT("resetCurrentContextManager - mismatch threads - current" + Thread.currentThread() + " - cm's " + cm.activeThread);\r
+ }\r
+\r
+ if (getCurrentContextManager() != cm) {\r
+ SanityManager.THROWASSERT("resetCurrentContextManager - mismatch contexts - " + Thread.currentThread());\r
+ }\r
+\r
+ if (cm.activeCount < -1) {\r
+ SanityManager.THROWASSERT("resetCurrentContextManager - invalid count - current" + Thread.currentThread() + " - count " + cm.activeCount);\r
+ }\r
+\r
+ if (cm.activeCount == 0) {\r
+ SanityManager.THROWASSERT("resetCurrentContextManager - invalid count - current" + Thread.currentThread() + " - count " + cm.activeCount);\r
+ }\r
+\r
+ if (cm.activeCount > 0) {\r
+ if (tcl.get() != cm)\r
+ SanityManager.THROWASSERT("resetCurrentContextManager - invalid thread local " + Thread.currentThread() + " - object " + tcl.get());\r
+\r
+ }\r
+ }\r
+\r
+ if (cm.activeCount != -1) {\r
+ if (--cm.activeCount == 0) {\r
+ cm.activeThread = null;\r
+ \r
+ // If the ContextManager is empty\r
+ // then don't keep a reference to it\r
+ // when it is not in use. The ContextManager\r
+ // has been closed (most likely) and this\r
+ // is now unwanted. Keeping the reference\r
+ // would hold onto memory and increase the\r
+ // chance of holding onto a another reference\r
+ // will could cause issues for future operations.\r
+ if (cm.isEmpty())\r
+ tcl.set(null);\r
+ \r
+ }\r
+ return;\r
+ }\r
+\r
+ java.util.Stack stack = (java.util.Stack) tcl.get();\r
+\r
+ Object oldCM = stack.pop();\r
+\r
+ ContextManager nextCM = (ContextManager) stack.peek();\r
+\r
+ boolean seenMultipleCM = false;\r
+ boolean seenCM = false;\r
+ for (int i = 0; i < stack.size(); i++) {\r
+\r
+ Object stackCM = stack.elementAt(i);\r
+ if (stackCM != nextCM)\r
+ seenMultipleCM = true;\r
+\r
+ if (stackCM == cm)\r
+ seenCM = true;\r
+ }\r
+\r
+ if (!seenCM) {\r
+ cm.activeThread = null;\r
+ cm.activeCount = 0;\r
+ }\r
+\r
+ if (!seenMultipleCM)\r
+ {\r
+ // all the context managers on the stack\r
+ // are the same so reduce to a simple count.\r
+ nextCM.activeCount = stack.size();\r
+ tcl.set(nextCM);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * The current thread (passed in a me) is setting associateCM\r
+ * to be its current context manager. Sets the thread local\r
+ * variable threadContextList to reflect associateCM being\r
+ * the current ContextManager.\r
+ * \r
+ * @return True if the nesting level is to be represented in\r
+ * the ContextManager.activeCount field. False if not.\r
+ * \r
+ * @see ContextManager#activeCount\r
+ * @see ContextManager#activeThread\r
+ */\r
+ private boolean addToThreadList(Thread me, ContextManager associateCM) {\r
+\r
+ ThreadLocal tcl = threadContextList;\r
+\r
+ if (tcl == null) {\r
+ // The context service is already stopped.\r
+ return false;\r
+ }\r
+\r
+ Object list = tcl.get();\r
+\r
+ // Already set up to reflect associateCM ContextManager\r
+ if (associateCM == list)\r
+ return true;\r
+\r
+ // Not currently using any ContextManager\r
+ if (list == null)\r
+ {\r
+ tcl.set(associateCM);\r
+ return true;\r
+ }\r
+\r
+ java.util.Stack stack;\r
+ if (list instanceof ContextManager) {\r
+ \r
+ // Could be two situations:\r
+ // 1. Single ContextManager not in use by this thread\r
+ // 2. Single ContextManager in use by this thread (nested call)\r
+ \r
+ ContextManager threadsCM = (ContextManager) list;\r
+ if (me == null)\r
+ me = Thread.currentThread();\r
+ \r
+ if (threadsCM.activeThread != me) {\r
+ // Not nested, just a CM left over\r
+ // from a previous execution.\r
+ tcl.set(associateCM);\r
+ return true;\r
+ }\r
+ \r
+ // Nested, need to create a Stack of ContextManagers,\r
+ // the top of the stack will be the active one.\r
+ stack = new java.util.Stack();\r
+ tcl.set(stack);\r
+ \r
+ // The stack represents the true nesting\r
+ // of ContextManagers, splitting out nesting\r
+ // of a single ContextManager into multiple\r
+ // entries in the stack.\r
+ for (int i = 0; i < threadsCM.activeCount; i++)\r
+ {\r
+ stack.push(threadsCM);\r
+ }\r
+ threadsCM.activeCount = -1;\r
+ }\r
+ else\r
+ {\r
+ // existing stack, nesting represented\r
+ // by stack entries, not activeCount.\r
+ stack = (java.util.Stack) list;\r
+ }\r
+\r
+ stack.push(associateCM);\r
+ associateCM.activeCount = -1;\r
+\r
+ if (SanityManager.DEBUG) {\r
+\r
+ if (SanityManager.DEBUG_ON("memoryLeakTrace")) {\r
+\r
+ if (stack.size() > 10)\r
+ System.out.println("memoryLeakTrace:ContextService:threadLocal " + stack.size());\r
+ }\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Link the current thread to the passed in Contextmanager\r
+ * so that a subsequent call to getCurrentContextManager by\r
+ * the current Thread will return cm.\r
+ * ContextManagers are tied to a Thread while the thread\r
+ * is executing Derby code. For example on most JDBC method\r
+ * calls the ContextManager backing the Connection object\r
+ * is tied to the current Thread at the start of the method\r
+ * and reset at the end of the method. Once the Thread\r
+ * has completed its Derby work the method resetCurrentContextManager\r
+ * must be called with the same ContextManager to break the link.\r
+ * Note that a subsquent use of the ContextManager may be on\r
+ * a separate Thread, the Thread is only linked to the ContextManager\r
+ * between the setCurrentContextManager and resetCurrentContextManager calls.\r
+ * <BR>\r
+ * ContextService supports nesting of calls by a single Thread, either\r
+ * with the same ContextManager or a different ContextManager.\r
+ * <UL>\r
+ * <LI>The same ContextManager would be pushed during a nested JDBC call in\r
+ * a procedure or function.\r
+ * <LI>A different ContextManager would be pushed during a call on\r
+ * a different embedded JDBC Connection in a procedure or function.\r
+ * </UL>\r
+ */\r
+ public void setCurrentContextManager(ContextManager cm) {\r
+\r
+\r
+ if (SanityManager.DEBUG) {\r
+ Thread me = Thread.currentThread();\r
+\r
+ if (cm.activeThread != null && me != cm.activeThread) {\r
+ SanityManager.THROWASSERT("setCurrentContextManager - mismatch threads - current " + me + " - cm's " + cm.activeThread);\r
+ }\r
+\r
+ }\r
+\r
+ Thread me = null;\r
+\r
+ if (cm.activeThread == null) {\r
+ cm.activeThread = (me = Thread.currentThread());\r
+ }\r
+ if (addToThreadList(me, cm))\r
+ cm.activeCount++;\r
+ }\r
+\r
+ /**\r
+ * It's up to the caller to track this context manager and set it\r
+ * in the context manager list using setCurrentContextManager.\r
+ * We don't keep track of it due to this call being made.\r
+ */\r
+ public ContextManager newContextManager()\r
+ {\r
+ ContextManager cm = new ContextManager(this, errorStream);\r
+\r
+ // push a context that will shut down the system on\r
+ // a severe error.\r
+ new SystemContext(cm);\r
+\r
+ synchronized (this) {\r
+ allContexts.add(cm);\r
+ \r
+ if (SanityManager.DEBUG) {\r
+\r
+ if (SanityManager.DEBUG_ON("memoryLeakTrace")) {\r
+\r
+ if (allContexts.size() > 50)\r
+ System.out.println("memoryLeakTrace:ContextService:allContexts " + allContexts.size());\r
+ }\r
+ }\r
+ }\r
+\r
+ return cm;\r
+ }\r
+\r
+ public void notifyAllActiveThreads(Context c) {\r
+ Thread me = Thread.currentThread();\r
+\r
+ synchronized (this) {\r
+ for (Iterator i = allContexts.iterator(); i.hasNext(); ) {\r
+\r
+ ContextManager cm = (ContextManager) i.next();\r
+\r
+ Thread active = cm.activeThread;\r
+\r
+ if (active == me)\r
+ continue;\r
+\r
+ if (active == null)\r
+ continue;\r
+\r
+ final Thread fActive = active;\r
+ if (cm.setInterrupted(c))\r
+ {\r
+ AccessController.doPrivileged(\r
+ new PrivilegedAction() {\r
+ public Object run() {\r
+ fActive.interrupt();\r
+ return null;\r
+ }\r
+ });\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Remove a ContextManager from the list of all active\r
+ * contexts managers.\r
+ */\r
+ synchronized void removeContext(ContextManager cm)\r
+ {\r
+ if (allContexts != null)\r
+ allContexts.remove( cm);\r
+ }\r
+}\r