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 */
017package org.apache.commons.dbcp2;
018
019import java.lang.ref.WeakReference;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024
025import org.apache.commons.pool2.TrackedUse;
026
027/**
028 * Tracks connection usage for recovering and reporting abandoned connections.
029 * <p>
030 * The JDBC Connection, Statement, and ResultSet classes extend this class.
031 * </p>
032 *
033 * @since 2.0
034 */
035public class AbandonedTrace implements TrackedUse {
036
037    /** A list of objects created by children of this object. */
038    private final List<WeakReference<AbandonedTrace>> traceList = new ArrayList<>();
039
040    /** Last time this connection was used. */
041    private volatile long lastUsedMillis;
042
043    /**
044     * Creates a new AbandonedTrace without config and without doing abandoned tracing.
045     */
046    public AbandonedTrace() {
047        init(null);
048    }
049
050    /**
051     * Constructs a new AbandonedTrace with a parent object.
052     *
053     * @param parent
054     *            AbandonedTrace parent object.
055     */
056    public AbandonedTrace(final AbandonedTrace parent) {
057        init(parent);
058    }
059
060    /**
061     * Adds an object to the list of objects being traced.
062     *
063     * @param trace
064     *            AbandonedTrace object to add.
065     */
066    protected void addTrace(final AbandonedTrace trace) {
067        synchronized (this.traceList) {
068            this.traceList.add(new WeakReference<>(trace));
069        }
070        setLastUsed();
071    }
072
073    /**
074     * Clears the list of objects being traced by this object.
075     */
076    protected void clearTrace() {
077        synchronized (this.traceList) {
078            this.traceList.clear();
079        }
080    }
081
082    /**
083     * Gets the last time this object was used in milliseconds.
084     *
085     * @return long time in milliseconds.
086     */
087    @Override
088    public long getLastUsed() {
089        return lastUsedMillis;
090    }
091
092    /**
093     * Gets a list of objects being traced by this object.
094     *
095     * @return List of objects.
096     */
097    protected List<AbandonedTrace> getTrace() {
098        final int size = traceList.size();
099        if (size == 0) {
100            return Collections.emptyList();
101        }
102        final ArrayList<AbandonedTrace> result = new ArrayList<>(size);
103        synchronized (this.traceList) {
104            final Iterator<WeakReference<AbandonedTrace>> iter = traceList.iterator();
105            while (iter.hasNext()) {
106                final AbandonedTrace trace = iter.next().get();
107                if (trace == null) {
108                    // Clean-up since we are here anyway
109                    iter.remove();
110                } else {
111                    result.add(trace);
112                }
113            }
114        }
115        return result;
116    }
117
118    /**
119     * Initializes abandoned tracing for this object.
120     *
121     * @param parent
122     *            AbandonedTrace parent object.
123     */
124    private void init(final AbandonedTrace parent) {
125        if (parent != null) {
126            parent.addTrace(this);
127        }
128    }
129
130    /**
131     * Removes this object the source object is tracing.
132     *
133     * @param source The object tracing
134     * @since 2.7.0
135     */
136    protected void removeThisTrace(final Object source) {
137        if (source instanceof AbandonedTrace) {
138            AbandonedTrace.class.cast(source).removeTrace(this);
139        }
140    }
141
142    /**
143     * Removes a child object this object is tracing.
144     *
145     * @param trace
146     *            AbandonedTrace object to remove.
147     */
148    protected void removeTrace(final AbandonedTrace trace) {
149        synchronized (this.traceList) {
150            final Iterator<WeakReference<AbandonedTrace>> iter = traceList.iterator();
151            while (iter.hasNext()) {
152                final AbandonedTrace traceInList = iter.next().get();
153                if (trace != null && trace.equals(traceInList)) {
154                    iter.remove();
155                    break;
156                }
157                if (traceInList == null) {
158                    // Clean-up since we are here anyway
159                    iter.remove();
160                }
161            }
162        }
163    }
164
165    /**
166     * Sets the time this object was last used to the current time in milliseconds.
167     */
168    protected void setLastUsed() {
169        lastUsedMillis = System.currentTimeMillis();
170    }
171
172    /**
173     * Sets the time in milliseconds this object was last used.
174     *
175     * @param lastUsedMillis
176     *            time in milliseconds.
177     */
178    protected void setLastUsed(final long lastUsedMillis) {
179        this.lastUsedMillis = lastUsedMillis;
180    }
181}