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: 892307 $ $Date: 2013-12-31 23:27:28 +0000 (Tue, 31 Dec 2013) $
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    }