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.sql.SQLException;
021import java.time.Instant;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.Iterator;
025import java.util.List;
026import java.util.function.Consumer;
027
028import org.apache.commons.pool2.TrackedUse;
029
030/**
031 * Tracks connection usage for recovering and reporting abandoned connections.
032 * <p>
033 * The JDBC Connection, Statement, and ResultSet classes extend this class.
034 * </p>
035 *
036 * @since 2.0
037 */
038public class AbandonedTrace implements TrackedUse, AutoCloseable {
039
040    static void add(final AbandonedTrace receiver, final AbandonedTrace trace) {
041        if (receiver != null) {
042            receiver.addTrace(trace);
043        }
044    }
045
046    /** A list of objects created by children of this object. */
047    private final List<WeakReference<AbandonedTrace>> traceList = new ArrayList<>();
048
049    /** Last time this connection was used. */
050    private volatile Instant lastUsedInstant = Instant.EPOCH;
051
052    /**
053     * Creates a new AbandonedTrace without config and without doing abandoned tracing.
054     */
055    public AbandonedTrace() {
056        init(null);
057    }
058
059    /**
060     * Constructs a new AbandonedTrace with a parent object.
061     *
062     * @param parent
063     *            AbandonedTrace parent object.
064     */
065    public AbandonedTrace(final AbandonedTrace parent) {
066        init(parent);
067    }
068
069    /**
070     * Adds an object to the list of objects being traced.
071     *
072     * @param trace
073     *            AbandonedTrace object to add.
074     */
075    protected void addTrace(final AbandonedTrace trace) {
076        synchronized (this.traceList) {
077            this.traceList.add(new WeakReference<>(trace));
078        }
079        setLastUsed();
080    }
081
082    /**
083     * Clears the list of objects being traced by this object.
084     */
085    protected void clearTrace() {
086        synchronized (this.traceList) {
087            this.traceList.clear();
088        }
089    }
090
091    /**
092     * Subclasses can implement this nop.
093     *
094     * @throws SQLException Ignored here, for subclasses.
095     * @since 2.10.0
096     */
097    @Override
098    public void close() throws SQLException {
099        // nop
100    }
101
102    /**
103     * Closes this resource and if an exception is caught, then calls {@code exceptionHandler}.
104     *
105     * @param exceptionHandler Consumes exception thrown closing this resource.
106     * @since 2.10.0
107     */
108    protected void close(final Consumer<Exception> exceptionHandler) {
109        Utils.close(this, exceptionHandler);
110    }
111
112    /**
113     * Gets the last time this object was used in milliseconds.
114     *
115     * @return long time in milliseconds.
116     */
117    @Override
118    @Deprecated
119    public long getLastUsed() {
120        return lastUsedInstant.toEpochMilli();
121    }
122
123    @Override
124    public Instant getLastUsedInstant() {
125        return lastUsedInstant;
126    }
127
128    /**
129     * Gets a list of objects being traced by this object.
130     *
131     * @return List of objects.
132     */
133    protected List<AbandonedTrace> getTrace() {
134        final int size = traceList.size();
135        if (size == 0) {
136            return Collections.emptyList();
137        }
138        final ArrayList<AbandonedTrace> result = new ArrayList<>(size);
139        synchronized (this.traceList) {
140            final Iterator<WeakReference<AbandonedTrace>> iter = traceList.iterator();
141            while (iter.hasNext()) {
142                final AbandonedTrace trace = iter.next().get();
143                if (trace == null) {
144                    // Clean-up since we are here anyway
145                    iter.remove();
146                } else {
147                    result.add(trace);
148                }
149            }
150        }
151        return result;
152    }
153
154    /**
155     * Initializes abandoned tracing for this object.
156     *
157     * @param parent
158     *            AbandonedTrace parent object.
159     */
160    private void init(final AbandonedTrace parent) {
161        AbandonedTrace.add(parent, this);
162    }
163
164    /**
165     * Removes this object the source object is tracing.
166     *
167     * @param source The object tracing
168     * @since 2.7.0
169     */
170    protected void removeThisTrace(final Object source) {
171        if (source instanceof AbandonedTrace) {
172            AbandonedTrace.class.cast(source).removeTrace(this);
173        }
174    }
175
176    /**
177     * Removes a child object this object is tracing.
178     *
179     * @param trace
180     *            AbandonedTrace object to remove.
181     */
182    protected void removeTrace(final AbandonedTrace trace) {
183        synchronized (this.traceList) {
184            final Iterator<WeakReference<AbandonedTrace>> iter = traceList.iterator();
185            while (iter.hasNext()) {
186                final AbandonedTrace traceInList = iter.next().get();
187                if (trace != null && trace.equals(traceInList)) {
188                    iter.remove();
189                    break;
190                }
191                if (traceInList == null) {
192                    // Clean-up since we are here anyway
193                    iter.remove();
194                }
195            }
196        }
197    }
198
199    /**
200     * Sets the time this object was last used to the current time in milliseconds.
201     */
202    protected void setLastUsed() {
203        lastUsedInstant = Instant.now();
204    }
205
206    /**
207     * Sets the instant this object was last used.
208     *
209     * @param lastUsedInstant
210     *            instant.
211     * @since 2.10.0
212     */
213    protected void setLastUsed(final Instant lastUsedInstant) {
214        this.lastUsedInstant = lastUsedInstant;
215    }
216
217    /**
218     * Sets the time in milliseconds this object was last used.
219     *
220     * @param lastUsedMillis
221     *            time in milliseconds.
222     * @deprecated Use {@link #setLastUsed(Instant)}
223     */
224    @Deprecated
225    protected void setLastUsed(final long lastUsedMillis) {
226        this.lastUsedInstant = Instant.ofEpochMilli(lastUsedMillis);
227    }
228}