001: /*
002:
003: Derby - Class org.apache.derby.iapi.services.context.ContextManager
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.services.context;
023:
024: import org.apache.derby.iapi.services.sanity.SanityManager;
025: import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
026:
027: import org.apache.derby.iapi.error.PassThroughException;
028:
029: import org.apache.derby.iapi.error.StandardException;
030: import org.apache.derby.iapi.services.monitor.Monitor;
031:
032: import org.apache.derby.iapi.reference.Property;
033: import org.apache.derby.iapi.services.property.PropertyUtil;
034:
035: import org.apache.derby.iapi.error.ExceptionSeverity;
036: import org.apache.derby.iapi.services.i18n.LocaleFinder;
037:
038: import java.util.HashMap;
039: import java.util.ArrayList;
040: import java.util.List;
041: import java.util.Collections;
042: import java.util.Locale;
043:
044: /**
045: *
046: * The ContextManager collects contexts as they are
047: * created. It maintains stacks of contexts by
048: * named ids, so that the top context of a given
049: * type can be returned. It also maintains a global
050: * stack so that contexts can be traversed in the
051: * order they were created.
052: * <p>
053: * The first implementation of the context manager
054: * assumes there is only one thread to worry about
055: * and that the user(s) of the class only create one
056: * instance of ContextManager.
057: */
058:
059: public class ContextManager {
060: /**
061: * The CtxStack implement a stack on top of an ArrayList (to avoid
062: * the inherent overhead associated with java.util.Stack which is
063: * built on top of java.util.Vector, which is fully
064: * synchronized).
065: */
066: private static final class CtxStack {
067: private ArrayList stack_ = new ArrayList();
068:
069: // Keeping a reference to the top element on the stack
070: // optimizes the frequent accesses to this element. The
071: // tradeoff is that pushing and popping becomes more
072: // expensive, but those operations are infrequent.
073: private Context top_ = null;
074:
075: void push(Context context) {
076: stack_.add(context);
077: top_ = context;
078: }
079:
080: void pop() {
081: stack_.remove(stack_.size() - 1);
082: top_ = stack_.isEmpty() ? null : (Context) stack_
083: .get(stack_.size() - 1);
084: }
085:
086: void remove(Context context) {
087: if (context == top_) {
088: pop();
089: return;
090: }
091: stack_.remove(stack_.lastIndexOf(context));
092: }
093:
094: Context top() {
095: return top_;
096: }
097:
098: boolean isEmpty() {
099: return stack_.isEmpty();
100: }
101:
102: List getUnmodifiableList() {
103: return Collections.unmodifiableList(stack_);
104: }
105: }
106:
107: /**
108: * Empty ArrayList to use as void value
109: */
110: //private final ArrayList voidArrayList_ = new ArrayList(0);
111: /**
112: * HashMap that holds the Context objects. The Contexts are stored
113: * with a String key.
114: * @see ContextManager#pushContext(Context)
115: */
116: private final HashMap ctxTable = new HashMap();
117:
118: /**
119: * List of all Contexts
120: */
121: private final ArrayList holder = new ArrayList();
122:
123: /**
124: * Add a Context object to the ContextManager. The object is added
125: * both to the holder list and to a stack for the specific type of
126: * Context.
127: * @param newContext the new Context object
128: */
129: public void pushContext(Context newContext) {
130: checkInterrupt();
131: final String contextId = newContext.getIdName();
132: CtxStack idStack = (CtxStack) ctxTable.get(contextId);
133:
134: // if the stack is null, create a new one.
135: if (idStack == null) {
136: idStack = new CtxStack();
137: ctxTable.put(contextId, idStack);
138: }
139:
140: // add to top of id's stack
141: idStack.push(newContext);
142:
143: // add to top of global stack too
144: holder.add(newContext);
145: }
146:
147: /**
148: * Obtain the last pushed Context object of the type indicated by
149: * the contextId argument.
150: * @param contextId a String identifying the type of Context
151: * @return The Context object with the corresponding contextId, or null if not found
152: */
153: public Context getContext(String contextId) {
154: checkInterrupt();
155:
156: final CtxStack idStack = (CtxStack) ctxTable.get(contextId);
157: if (SanityManager.DEBUG)
158: SanityManager.ASSERT(idStack == null || idStack.isEmpty()
159: || idStack.top().getIdName() == contextId);
160: return (idStack == null ? null : idStack.top());
161: }
162:
163: /**
164: * Remove the last pushed Context object, regardless of type. If
165: * there are no Context objects, no action is taken.
166: */
167: public void popContext() {
168: checkInterrupt();
169: // no contexts to remove, so we're done.
170: if (holder.isEmpty()) {
171: return;
172: }
173:
174: // remove the top context from the global stack
175: Context theContext = (Context) holder.remove(holder.size() - 1);
176:
177: // now find its id and remove it from there, too
178: final String contextId = theContext.getIdName();
179: final CtxStack idStack = (CtxStack) ctxTable.get(contextId);
180:
181: if (SanityManager.DEBUG) {
182: SanityManager.ASSERT(idStack != null
183: && (!idStack.isEmpty())
184: && idStack.top().getIdName() == contextId);
185: }
186: idStack.pop();
187: }
188:
189: /**
190: * Removes the specified Context object. If
191: * the specified Context object does not exist, the call will fail.
192: * @param theContext the Context object to remove.
193: */
194: void popContext(Context theContext) {
195: checkInterrupt();
196: if (SanityManager.DEBUG)
197: SanityManager.ASSERT(!holder.isEmpty());
198:
199: // first, remove it from the global stack.
200: holder.remove(holder.lastIndexOf(theContext));
201:
202: final String contextId = theContext.getIdName();
203: final CtxStack idStack = (CtxStack) ctxTable.get(contextId);
204:
205: // now remove it from its id's stack.
206: idStack.remove(theContext);
207: }
208:
209: /**
210: * Is the ContextManager empty containing no Contexts.
211: */
212: final boolean isEmpty() {
213: return holder.isEmpty();
214: }
215:
216: /**
217: * Return an unmodifiable list reference to the ArrayList backing
218: * CtxStack object for this type of Contexts. This method allows
219: * fast traversal of all Contexts on that stack. The first element
220: * in the List corresponds to the bottom of the stack. The
221: * assumption is that the Stack will not be modified while it is
222: * being traversed.
223: * @param contextId the type of Context stack to return.
224: * @return an unmodifiable "view" of the ArrayList backing the stack
225: * @see org.apache.derby.impl.sql.conn.GenericLanguageConnectionContext#resetSavepoints()
226: * @see org.apache.derby.iapi.sql.conn.StatementContext#resetSavePoint()
227: */
228: public final List getContextStack(String contextId) {
229: final CtxStack cs = (CtxStack) ctxTable.get(contextId);
230: return (cs == null ? Collections.EMPTY_LIST : cs
231: .getUnmodifiableList());
232: }
233:
234: /**
235: @return true if the context manager is shutdown, false otherwise.
236: */
237: public boolean cleanupOnError(Throwable error) {
238: if (shutdown)
239: return true;
240:
241: if (errorStringBuilder == null)
242: errorStringBuilder = new ErrorStringBuilder(errorStream
243: .getHeader());
244:
245: ThreadDeath seenThreadDeath = null;
246: if (error instanceof ThreadDeath)
247: seenThreadDeath = (ThreadDeath) error;
248:
249: if (error instanceof PassThroughException)
250: error = ((PassThroughException) error).getException();
251:
252: boolean reportError = reportError(error);
253:
254: if (reportError) {
255: ContextImpl lcc = null;
256: StringBuffer sb = null;
257: if (!shutdown) {
258: // report an id for the message if possible
259: lcc = (ContextImpl) getContext(org.apache.derby.iapi.reference.ContextId.LANG_CONNECTION);
260: if (lcc != null) {
261: sb = lcc.appendErrorInfo();
262: }
263: }
264:
265: String cleanup = "Cleanup action starting";
266:
267: if (sb != null) {
268: sb.append(cleanup);
269: cleanup = sb.toString();
270: }
271:
272: errorStringBuilder.appendln(cleanup);
273:
274: if (!shutdown) // Do this only during normal processing.
275: {
276: ContextImpl sc = (ContextImpl) getContext(org.apache.derby.iapi.reference.ContextId.LANG_STATEMENT);
277: // Output the SQL statement that failed in the log file.
278: if (sc != null) {
279: sb = sc.appendErrorInfo();
280: if (sb != null)
281: errorStringBuilder.appendln(sb.toString());
282: }
283: }
284: }
285:
286: /*
287: REVISIT RESOLVE
288: Ensure that the traversal of the stack works in all
289: cases where contexts can pop themselves *and*
290: contexts can pop other contexts off the stack.
291: */
292:
293: forever: for (;;) {
294:
295: int errorSeverity = error instanceof StandardException ? ((StandardException) error)
296: .getSeverity()
297: : ExceptionSeverity.NO_APPLICABLE_SEVERITY;
298: if (reportError) {
299: errorStringBuilder.stackTrace(error);
300: flushErrorString();
301: }
302:
303: boolean lastHandler = false;
304:
305: /*
306: Walk down the stack, calling
307: cleanup on each context. We use
308: the vector interface to do this.
309: */
310: cleanup: for (int index = holder.size() - 1; index >= 0; index--) {
311:
312: try {
313: if (lastHandler) {
314: break;
315: }
316:
317: Context ctx = ((Context) holder.get(index));
318: lastHandler = ctx.isLastHandler(errorSeverity);
319:
320: ctx.cleanupOnError(error);
321: } catch (StandardException se) {
322:
323: if (error instanceof StandardException) {
324:
325: if (se.getSeverity() > ((StandardException) error)
326: .getSeverity()) {
327: // Ok, error handling raised a more severe error,
328: // restart with the more severe error
329: error = se;
330: reportError = reportError(se);
331: if (reportError) {
332: errorStream
333: .println("New exception raised during cleanup "
334: + error.getMessage());
335: errorStream.flush();
336: }
337: continue forever;
338: }
339: }
340:
341: if (reportError(se)) {
342: errorStringBuilder
343: .appendln("Less severe exception raised during cleanup (ignored) "
344: + se.getMessage());
345: errorStringBuilder.stackTrace(se);
346: flushErrorString();
347: }
348:
349: /*
350: For a less severe error, keep with the last error
351: */
352: continue cleanup;
353: } catch (Throwable t) {
354: reportError = reportError(t);
355:
356: if (error instanceof StandardException) {
357: /*
358: Ok, error handling raised a more severe error,
359: restart with the more severe error
360: A Throwable after a StandardException is always
361: more severe.
362: */
363: error = t;
364: if (reportError) {
365: errorStream
366: .println("New exception raised during cleanup "
367: + error.getMessage());
368: errorStream.flush();
369: }
370: continue forever;
371: }
372:
373: if (reportError) {
374: errorStringBuilder
375: .appendln("Equally severe exception raised during cleanup (ignored) "
376: + t.getMessage());
377: errorStringBuilder.stackTrace(t);
378: flushErrorString();
379: }
380:
381: if (t instanceof ThreadDeath) {
382: if (seenThreadDeath != null)
383: throw seenThreadDeath;
384:
385: seenThreadDeath = (ThreadDeath) t;
386: }
387:
388: /*
389: For a less severe error, just continue with the last
390: error
391: */
392: continue cleanup;
393: }
394: }
395:
396: if (reportError) {
397: errorStream.println("Cleanup action completed");
398: errorStream.flush();
399: }
400:
401: if (seenThreadDeath != null)
402: throw seenThreadDeath;
403:
404: return false;
405: }
406:
407: }
408:
409: synchronized boolean setInterrupted(Context c) {
410:
411: boolean interruptMe = (c == null) || holder.contains(c);
412:
413: if (interruptMe) {
414: this .shutdown = true;
415: }
416: return interruptMe;
417: }
418:
419: /**
420: Check to see if we have been interrupted. If we have then
421: a ShutdownException will be thrown. This will be either the
422: one passed to interrupt or a generic one if some outside
423: source interrupted the thread.
424: */
425: private void checkInterrupt() {
426: if (shutdown) {
427: // system must have changed underneath us
428: throw new ShutdownException();
429: }
430: }
431:
432: /**
433: Set the locale for this context.
434: */
435: public void setLocaleFinder(LocaleFinder finder) {
436: this .finder = finder;
437: }
438:
439: private Locale messageLocale;
440:
441: public void setMessageLocale(String localeID)
442: throws StandardException {
443: this .messageLocale = Monitor.getLocaleFromString(localeID);
444: }
445:
446: public Locale getMessageLocale() {
447: if (messageLocale != null)
448: return messageLocale;
449: else if (finder != null) {
450: try {
451: return finder.getCurrentLocale();
452: } catch (StandardException se) {
453:
454: }
455: }
456: return Locale.getDefault();
457: }
458:
459: /**
460: * Flush the built up error string to whereever
461: * it is supposed to go, and reset the error string
462: */
463: private void flushErrorString() {
464: errorStream.print(errorStringBuilder.get().toString());
465: errorStream.flush();
466: errorStringBuilder.reset();
467: }
468:
469: /*
470: ** Class methods
471: */
472:
473: private boolean reportError(Throwable t) {
474:
475: if (t instanceof StandardException) {
476:
477: StandardException se = (StandardException) t;
478:
479: switch (se.report()) {
480: case StandardException.REPORT_DEFAULT:
481: int level = se.getSeverity();
482: return (level >= logSeverityLevel)
483: || (level == ExceptionSeverity.NO_APPLICABLE_SEVERITY);
484:
485: case StandardException.REPORT_NEVER:
486: return false;
487:
488: case StandardException.REPORT_ALWAYS:
489: default:
490: return true;
491: }
492: }
493:
494: return !(t instanceof ShutdownException);
495:
496: }
497:
498: /**
499: * Constructs a new instance. No CtxStacks are inserted into the
500: * hashMap as they will be allocated on demand.
501: * @param csf the ContextService owning this ContextManager
502: * @param stream error stream for reporting errors
503: */
504: ContextManager(ContextService csf, HeaderPrintWriter stream) {
505: errorStream = stream;
506: owningCsf = csf;
507:
508: logSeverityLevel = PropertyUtil.getSystemInt(
509: Property.LOG_SEVERITY_LEVEL, SanityManager.DEBUG ? 0
510: : ExceptionSeverity.SESSION_SEVERITY);
511: }
512:
513: final ContextService owningCsf;
514:
515: private int logSeverityLevel;
516:
517: private HeaderPrintWriter errorStream;
518: private ErrorStringBuilder errorStringBuilder;
519:
520: private boolean shutdown;
521: private LocaleFinder finder;
522:
523: /**
524: * The thread that owns this ContextManager, set by
525: * ContextService.setCurrentContextManager and reset
526: * by resetCurrentContextManager. Only a single
527: * thread can be active in a ContextManager at any time,
528: * and the thread only "owns" the ContextManager while
529: * it is executing code within Derby. In the JDBC case
530: * setCurrentContextManager is called at the start of
531: * a JBDC method and resetCurrentContextManager on completion.
532: * Nesting within the same thread is supported, such as server-side
533: * JDBC calls in a Java routine or procedure. In that case
534: * the activeCount will represent the level of nesting, in
535: * some situations.
536: * <BR>
537:
538: * @see ContextService#setCurrentContextManager(ContextManager)
539: * @see ContextService#resetCurrentContextManager(ContextManager)
540: * @see #activeCount
541: */
542: Thread activeThread;
543:
544: /**
545: * Count of the number of setCurrentContextManager calls
546: * by a single thread, for nesting situations with a single
547: * active Contextmanager. If nesting is occuring with multiple
548: * different ContextManagers then this value is set to -1
549: * and nesting is represented by entries in a stack in the
550: * ThreadLocal variable, threadContextList.
551: *
552: * @see ContextService#threadContextList
553: */
554: int activeCount;
555: }
|