001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.dbcp;
019
020 import java.text.SimpleDateFormat;
021 import java.util.ArrayList;
022 import java.util.Date;
023 import java.util.Iterator;
024 import java.util.List;
025
026 /**
027 * Tracks db connection usage for recovering and reporting
028 * abandoned db connections.
029 *
030 * The JDBC Connection, Statement, and ResultSet classes
031 * extend this class.
032 *
033 * @author Glenn L. Nielsen
034 * @version $Revision: 899987 $ $Date: 2010-01-16 11:51:16 -0500 (Sat, 16 Jan 2010) $
035 */
036 public class AbandonedTrace {
037
038 /** DBCP AbandonedConfig */
039 private final AbandonedConfig config;
040 /** A stack trace of the code that created me (if in debug mode) */
041 private volatile Exception createdBy;
042 /** A list of objects created by children of this object */
043 private final List traceList = new ArrayList();
044 /** Last time this connection was used */
045 private volatile long lastUsed = 0;
046
047 /**
048 * Create a new AbandonedTrace without config and
049 * without doing abandoned tracing.
050 */
051 public AbandonedTrace() {
052 this.config = null;
053 init(null);
054 }
055
056 /**
057 * Construct a new AbandonedTrace with no parent object.
058 *
059 * @param config AbandonedConfig
060 */
061 public AbandonedTrace(AbandonedConfig config) {
062 this.config = config;
063 init(null);
064 }
065
066 /**
067 * Construct a new AbandonedTrace with a parent object.
068 *
069 * @param parent AbandonedTrace parent object
070 */
071 public AbandonedTrace(AbandonedTrace parent) {
072 this.config = parent.getConfig();
073 init(parent);
074 }
075
076 /**
077 * Initialize abandoned tracing for this object.
078 *
079 * @param parent AbandonedTrace parent object
080 */
081 private void init(AbandonedTrace parent) {
082 if (parent != null) {
083 parent.addTrace(this);
084 }
085
086 if (config == null) {
087 return;
088 }
089 if (config.getLogAbandoned()) {
090 createdBy = new AbandonedObjectException();
091 }
092 }
093
094 /**
095 * Get the abandoned config for this object.
096 *
097 * @return AbandonedConfig for this object
098 */
099 protected AbandonedConfig getConfig() {
100 return config;
101 }
102
103 /**
104 * Get the last time this object was used in ms.
105 *
106 * @return long time in ms
107 */
108 protected long getLastUsed() {
109 return lastUsed;
110 }
111
112 /**
113 * Set the time this object was last used to the
114 * current time in ms.
115 */
116 protected void setLastUsed() {
117 lastUsed = System.currentTimeMillis();
118 }
119
120 /**
121 * Set the time in ms this object was last used.
122 *
123 * @param time time in ms
124 */
125 protected void setLastUsed(long time) {
126 lastUsed = time;
127 }
128
129 /**
130 * If logAbandoned=true generate a stack trace
131 * for this object then add this object to the parent
132 * object trace list.
133 */
134 protected void setStackTrace() {
135 if (config == null) {
136 return;
137 }
138 if (config.getLogAbandoned()) {
139 createdBy = new AbandonedObjectException();
140 }
141 }
142
143 /**
144 * Add an object to the list of objects being
145 * traced.
146 *
147 * @param trace AbandonedTrace object to add
148 */
149 protected void addTrace(AbandonedTrace trace) {
150 synchronized (this.traceList) {
151 this.traceList.add(trace);
152 }
153 setLastUsed();
154 }
155
156 /**
157 * Clear the list of objects being traced by this
158 * object.
159 */
160 protected void clearTrace() {
161 synchronized(this.traceList) {
162 this.traceList.clear();
163 }
164 }
165
166 /**
167 * Get a list of objects being traced by this object.
168 *
169 * @return List of objects
170 */
171 protected List getTrace() {
172 synchronized (this.traceList) {
173 return new ArrayList(traceList);
174 }
175 }
176
177 /**
178 * Prints a stack trace of the code that
179 * created this object.
180 */
181 public void printStackTrace() {
182 if (createdBy != null && config != null) {
183 createdBy.printStackTrace(config.getLogWriter());
184 }
185 synchronized(this.traceList) {
186 Iterator it = this.traceList.iterator();
187 while (it.hasNext()) {
188 AbandonedTrace at = (AbandonedTrace)it.next();
189 at.printStackTrace();
190 }
191 }
192 }
193
194 /**
195 * Remove a child object this object is tracing.
196 *
197 * @param trace AbandonedTrace object to remove
198 */
199 protected void removeTrace(AbandonedTrace trace) {
200 synchronized(this.traceList) {
201 this.traceList.remove(trace);
202 }
203 }
204
205 static class AbandonedObjectException extends Exception {
206
207 private static final long serialVersionUID = 7398692158058772916L;
208
209 /** Date format */
210 //@GuardedBy("this")
211 private static final SimpleDateFormat format = new SimpleDateFormat
212 ("'DBCP object created' yyyy-MM-dd HH:mm:ss " +
213 "'by the following code was never closed:'");
214
215 private final long _createdTime;
216
217 public AbandonedObjectException() {
218 _createdTime = System.currentTimeMillis();
219 }
220
221 // Override getMessage to avoid creating objects and formatting
222 // dates unless the log message will actually be used.
223 public String getMessage() {
224 String msg;
225 synchronized(format) {
226 msg = format.format(new Date(_createdTime));
227 }
228 return msg;
229 }
230 }
231 }